Swift マテリアルデザイン TextField ライブラリ不使用


マテリアルデザインをライブラリ不使用で利用できるものがどこにも記載がなかったのでまとめました。
MaterialDesignのライブラリを利用すると、既存のアプリの改修の際、継承関係が難しくなると思います。
理由は@IBOutlet@IBActionの2つをを接続して、@IBActionでマテリアルデザインの動きの部分を担当させるからです。
なんとか、デフォルトの機能で実装する必要があり、これを紹介したいと思います。
なお、この説明は、初学者にわかりやすく説明したため、説明がくどいと感じるかもしれません。あらかじめご了承ください。

まず完成形の確認から

入力すると、そのプレースホルダが上部に移動して、枠線の色が変わり、
入力状態で有ることがわかりやすくなります。

まずxibファイルを作成します。

コントローラーは今回作成した下記を指定します。
iPhone8など、カメラのために画面上部が、曲面になっていない長方形の機種を選択します。
そしてサイズをここでは320*56pxに指定しています。
高さの56pxは、マテリアルデザインの基準です。
FilesOwnerに今回作成するクラスを指定します。

その後,StoryBoardと接続できるようにします。

import UIKit

class MaterialTextField: UIView {

    //これを最下部のViewに接続(すべてが乗っているView)
    @IBOutlet var contentView: UIView!

    //viewを配置する、これが外枠線となる
    @IBOutlet var borderView: UIView!

    //通常のテキストフィールド、イベントをgetするために配置する
    @IBOutlet var textField: UITextField!

    //プレースホルダのラベル
    @IBOutlet var placeholderLabel: UILabel!

    //プレースホルダの位置を変更するために接続、この値を変更すると位置が変更される
    @IBOutlet var placeholderLabelTopLayout: NSLayoutConstraint!



    //xibファイルを読み込むときには必ず必要
    override class func awakeFromNib() {
        super.awakeFromNib()
    }

    //メモリとのやり取り等
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        initSubViews()
       }

    //ここのframeにオブジェクトが配置されていく。
    override init(frame: CGRect) {
        super.init(frame: frame)
         //今回の設定を呼び出す
        initSubViews()
    }

    //今回の設定
    private func initSubViews(){
        Bundle.main.loadNibNamed("MaterialTextField", owner: self, options: nil)
        contentView.frame = bounds
        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        addSubview(contentView)
        textField.delegate = self
        addBorderTextField()
    }

    func addBorderTextField(){

        //viewのlayerは外枠線のプロパティを持っている
        borderView.layer.borderColor = UIColor.gray.cgColor
        borderView.layer.borderWidth = 1
        borderView.layer.cornerRadius = 3

        //labelのテキスト等の設定
        placeholderLabel.textColor = UIColor.gray
        placeholderLabel.backgroundColor = .white

    }

xibファイルのオブジェクトを接続していきます。

xibファイルの構造はこんな感じ
順番は

1. viewを配置する (外枠の線を描画するため)
2. その中にtextFieldを配置する。(textFieldのイベントを取得するため)
3.更にプレースホルダ用の文字のlabelを配置する(この文字が移動する)

ポイントが、labelの上部との距離
ここを@IBoutletで接続して,コードの方で値を変化させます。
ちょっとイメージがわかないといけないので、動画を添付しますね。
この動画では、@IBoutletは先に、コードの方に記載してあるので、xib画面にて接続できます。

Material Designの動きの部分

 func addBorderTextField(){

        //viewのlayerは外枠線のプロパティを持っている
        borderView.layer.borderColor = UIColor.gray.cgColor
        borderView.layer.borderWidth = 1
        borderView.layer.cornerRadius = 3

        //labelのテキスト等の設定
        placeholderLabel.textColor = UIColor.gray
        placeholderLabel.backgroundColor = .white

    }



    func movePlaceholderToTop(){
        placeholderLabel.isHidden = false
        //クロージャー内でselfを使うときに循環参照が起きる可能性があるので[weak self]を利用
        UIView.animate(withDuration: 0.3, animations: {[unowned self] in
            //constraintの値を0へ
            self.placeholderLabelTopLayout.constant = 0.0
            //即座にレイアウトをupdateするメソッド
            self.contentView.layoutIfNeeded()

            }){ [unowned self] (completed) in
                self.borderView.layer.borderColor = UIColor.systemIndigo.cgColor
                self.placeholderLabel.textColor = UIColor.systemIndigo
        }
    }

    func movePlaceholderToCenter(){
        placeholderLabel.isHidden = textField.text != ""
        UIView.animate(withDuration: 0.3, animations: {[unowned self] in
            self.placeholderLabelTopLayout.constant = 20.0
            self.contentView.layoutIfNeeded()
            }){ [unowned self] (completed) in
                self.borderView.layer.borderColor = UIColor.gray.cgColor
                self.placeholderLabel.textColor = UIColor.gray
        }
    }


TextFieldのイベント処理の実装

個人的な好みでクラスは分けています。読みやすいので


extension MaterialTextField: UITextFieldDelegate {

//TextFieldの編集が始まったときに呼ばれるメソッド 
    func textFieldDidBeginEditing(_ textField: UITextField) {

//編集が始まると、labelを上に動かす
        movePlaceholderToTop()
    }

//TextFieldの編集が終わったときに呼ばれるメソッド     
    func textFieldDidEndEditing(_ textField: UITextField) {

//編集が終わると元に戻す
        movePlaceholderToCenter()
    }


//TextFieldでリターンキーがクリックされたときに呼ばれるメソッド 
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    } 
}


StoryBoardにて実装

ポイントは

1- viewを配置する(TextFieldではない)
2- クラスを指定しておく(MaterialTextField)
3- 接続時にクラスを指定。(IBOutlet)

コードはこんなイメージ


import UIKit

//UITextFieldDelegateを継承しておく
class ViewController: UIViewController , UITextFieldDelegate{

//上記で接続したview クラスの指定は MaterialTextField
    @IBOutlet weak var materialTextField: MaterialTextField!

    override func viewDidLoad() {
        super.viewDidLoad()

//初期値の文字を設定
        materialTextField.placeholderLabel.text = "Address"

    }
}

間違っている点や、修正点がありましたら、教えていただけますか。
わかりにくい点もありましたら、修正依頼お願いします。