Swift4 programaticallyにユーザーインタラクションを実現する

Hello World

エンジニアの佐山です。
今回もiOS開発について書いていきたいと思います。

さて、ウェルクスではアプリの開発にStoryboardを用いないで開発を行なっていると、以下の記事で書きました。

補足情報ですが、コードだけでレイアウトを組むことをprogrammaticallyと言うので、検索する際も上記のワードを使って検索を行うと、ヒットしやすいです。

note.welks.co.jp

note.welks.co.jp

layoutSubview()を使ってレイアウトを組むのを上記でやったかと思うのですが、では、ビューとコントローラーを分けてどうやってタップした際の処理を行うかについて書きたいと思います。

作るもの

今回作るアプリでは、テキストフィールドに入力された状態でボタンを押すと、現在「Hello World」となっている部分が入れ替わるというアプリを作っていきます。

あくまでユーザーインタラクションをコードで実現するためのサンプルです

ベースとなるビューを作っていく

note.welks.co.jp

上記を参考にベースとなるビューを作成していきます。

BaseView.swiftを編集していきます。

レイアウトは適当です

import UIKit

class BaseView: UIView {
    let label = UILabel()
    let btn = UIButton()
    let field = UITextField()
    
    // イニシャライザメソッドのinitをオーバーライドする
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        self.backgroundColor = .white
        
        // label
        label.text = "Hello World!!"
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 30)
        
        // field
        field.placeholder = "Add your text"
        field.layer.borderWidth = 0.5
        
        // btn
        btn.setTitle("Add your text", for: .normal)
        btn.setTitleColor(.white, for: .normal)
        btn.backgroundColor = .black
        btn.layer.cornerRadius = 25.0
        
        self.addSubview(label)
        self.addSubview(field)
        self.addSubview(btn)
        
        
        
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // ラベルのサイズを取得しておく
        let labelSize = self.label.sizeThatFits(self.bounds.size)
        
        let x = (self.bounds.width - labelSize.width) / 2
        let y = (self.bounds.height - labelSize.height) / 3
        let labelOrigin = CGPoint(x: x, y: y)
        
        // frameにラベルをセットしレイアウトに渡す
        self.label.frame = CGRect(origin: labelOrigin, size: labelSize)
        
        self.field.frame = CGRect(x: self.bounds.width / 4, y: self.label.frame.maxY + 20, width: self.bounds.width / 2, height: 30)
        
        self.btn.frame = CGRect(x: self.bounds.width / 4, y: self.field.frame.maxY + 20, width: self.bounds.width / 2, height: 50)
    }

}

以下のような不揃いなビューが完成しました

f:id:sayamaken0402:20180605110854p:plain

addTargetでタップイベントを作成する

btnをタップした時の処理を追加します。

// btn
btn.setTitle("Add your text", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.backgroundColor = .black
btn.layer.cornerRadius = 25.0

の部分を編集していきます。

// btn
btn.setTitle("Add your text", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.backgroundColor = .black
btn.layer.cornerRadius = 25.0
// タップした時のイベントハンドラ
btn.addTarget(self, action: #selector(onTapped), for: .touchUpInside)

btn.addTarget(self, action: #selector(onTapped), for: .touchUpInside)の行を追加しました。

さらに最終行らへんにタップ時に呼ばれる処理を書いていきます。

import UIKit

class BaseView: UIView {
    let label = UILabel()
    let btn = UIButton()
    let field = UITextField()
    
    // イニシャライザメソッドのinitをオーバーライドする
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        // 背景色を白にする
        self.backgroundColor = .white
        
        // label
        label.text = "Hello World!!"
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 30)
        
        // field
        field.placeholder = "Add your text"
        field.layer.borderWidth = 0.5
        
        // btn
        btn.setTitle("Add your text", for: .normal)
        btn.setTitleColor(.white, for: .normal)
        btn.backgroundColor = .black
        btn.layer.cornerRadius = 25.0
        // タップした時のイベントハンドラ
        btn.addTarget(self, action: #selector(onTapped), for: .touchUpInside)
        
        // ラベルを表示させる
        self.addSubview(label)
        self.addSubview(field)
        self.addSubview(btn)
        
        
        
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // ラベルのサイズを取得しておく
        let labelSize = self.label.sizeThatFits(self.bounds.size)
        
        let x = (self.bounds.width - labelSize.width) / 2
        let y = (self.bounds.height - labelSize.height) / 3
        let labelOrigin = CGPoint(x: x, y: y)
        
        // frameにラベルをセットしレイアウトに渡す
        self.label.frame = CGRect(origin: labelOrigin, size: labelSize)
        
        self.field.frame = CGRect(x: self.bounds.width / 4, y: self.label.frame.maxY + 20, width: self.bounds.width / 2, height: 30)
        
        self.btn.frame = CGRect(x: self.bounds.width / 4, y: self.field.frame.maxY + 20, width: self.bounds.width / 2, height: 50)
    }
    
    // 追加
    @objc func onTapped(){
        // ここに処理を書く
    }

}

BaseViewからViewControllerへ処理を委譲する

BaseViewクラスからViewControllerクラスへ処理を分けるので、delegateを使う必要があります。

BaseView.swiftの上部で独自プロトコルを定義します。

import UIKit

protocol BaseViewDelegate: class {
    func baseViewOnTapped()
}

delegateをBaseViewで参照させます。

let label = UILabel()
let btn = UIButton()
let field = UITextField()

weak var delegate: BaseViewDelegate?

先ほど書いた、@objc func...を以下のように編集します。

@objc func onTapped(){
  delegate?.baseViewOnTapped()
}

ViewControllerを編集する

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // BaseView.swiftを作成
        let baseView = BaseView(frame: self.view.bounds)
        
        // 自動でリサイズしてくれる設定
        baseView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        
        // viewにサブクラスとしてBaseViewを追加
        self.view.addSubview(baseView)
    }
}

上記を編集していきます。

まずは、ViewControllerクラスを拡張します。

import UIKit

class ViewController: UIViewController {

    let baseView = BaseView()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // BaseView.swiftを作成
        //let baseView = BaseView(frame: self.view.bounds)
        baseView.frame = view.bounds
        // 追加
        baseView.delegate = self
        
        // 自動でリサイズしてくれる設定
        baseView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        
        // viewにサブクラスとしてBaseViewを追加
        self.view.addSubview(baseView)
    }
}

// 以下を追加
extension ViewController: BaseViewDelegate {
    func baseViewOnTapped() {
        let fieldText = baseView.field.text
        // 空文字判定
        guard (fieldText != "") else {
            return
        }
        // 空文字でなかったら、以下の処理を実行

        baseView.label.text = fieldText
        baseView.field.text = ""
    }
}

追加したのは、viewDidLoad内にbaseView.delegate = selfとViewControllerの拡張を追加しました。

完成

テキストフィールドに入力をする

f:id:sayamaken0402:20180605120007p:plain

ボタンをタップする

f:id:sayamaken0402:20180605120011p:plain

以上です。