【swift】複数のUITextField、UITextViewがキーボードで隠れないにする1番簡単な方法

はじめに

まぁ、1番簡単というのは個人的にはなのですが、、、

以前にもこちらで同じような内容を書いたんですけど
今回のやり方のほうが個人的には汎用的であって、今後、このやり方でやるだろうなとういことでメモしておく

実装

今回は、1画面にUITextFieldとUITextViewが複数ある場合を想定します。
ポイントとしては、scrolle.ContentOffsetとView(UITextFieldまたはUITextViewのframe.y)を合わせることかと思う。
で、その際に画面をせりあがらせる前の位置を記録しておいて、キーボードが閉じるときにそこに戻すということでしょうか

class ViewController: UITextFieldDelegate, UITextViewDelegate {

・・・

    // フォーカスが当たっているものをここにセットする(両方セットされることはないようにする)
    var activeTextField: UITextField?
    var activeTextView: UITextView?

    // キーボードを開く前の表示位置を保持する
    var saveContentOffsetY: CGFloat?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        ・・・
    }

    // UITeixtFieldが選択された場合
    func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
        self.activeTextField = textField
        self.activeTextView = nil
        return true
    }

    // UITextViewが選択された場合
    func textViewShouldBeginEditing(textView: UITextView) -> Bool {
        self.activeTextView = textView
        self.activeTextField = nil
        return true
    }

    // キーボードが表示されるときに通知される
    // 表示位置を選択されたViewの位置にくるようにセットする
    func handleKeyboardWillShowNotification(notification: NSNotification) {
        // UITextFieldが選択され場合
        if activeTextField != nil {
            self.saveContentOffsetY = self.scrollView.contentOffset.y
            self.scrollView.contentOffset.y = activeTextField!.frame.origin.y - 50
        // UITextViewが選択された場合
        } else if activeTextView != nil {
            self.saveContentOffsetY = self.scrollView.contentOffset.y
            self.scrollView.contentOffset.y = activeTextView!.frame.origin.y - 50
        }
    }

    // キーボードが隠れるとき、元の位置に戻す
    func handleKeyboardWillHideNotification(notification: NSNotification) {
        self.scrollView.contentOffset.y = self.saveContentOffsetY!
    }

    ・・・
}

個人的にはこのやり方が1番直感的でわかりやすかったです

サンプルコード

※完成例
f:id:yoppy0066:20151107032911p:plain

import UIKit

class ViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate, CustomTextViewDelegate {
    
    var scrollView = UIScrollView()
    
    var activeTextField: UITextField?
    var activeTextView: UITextView?
    
    var saveContentOffsetY: CGFloat?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        self.scrollView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)
        
        let marginLeft: CGFloat = 30
        let labelHeight: CGFloat = 30
        let inputHeight: CGFloat = 50
        
        let labelEmail = CustomLabel(frame: CGRectMake(marginLeft, 50, self.view.frame.size.width - (marginLeft*2), labelHeight))
        labelEmail.text = "メールアドレス"
        self.scrollView.addSubview(labelEmail)
        
        let inputEmail = CustomTextfield(frame: CGRectMake(marginLeft, labelEmail.frame.origin.y + labelEmail.frame.size.height + 5, self.view.frame.size.width - (marginLeft*2), inputHeight))
        inputEmail.delegate = self
        self.scrollView.addSubview(inputEmail)
        
        let labelName = CustomLabel(frame: CGRectMake(marginLeft, inputEmail.frame.origin.y + inputEmail.frame.size.height + 10, self.view.frame.size.width - (marginLeft*2), labelHeight))
        labelName.text = "名前"
        self.scrollView.addSubview(labelName)
        
        let inputName = CustomTextfield(frame: CGRectMake(marginLeft, labelName.frame.origin.y + labelName.frame.size.height + 5, self.view.frame.size.width - (marginLeft*2), inputHeight))
        inputName.delegate = self
        self.scrollView.addSubview(inputName)
        
        let labelAddress = CustomLabel(frame: CGRectMake(marginLeft, inputName.frame.origin.y + inputName.frame.size.height + 10, self.view.frame.size.width - (marginLeft*2), labelHeight))
        labelAddress.text = "住所"
        self.scrollView.addSubview(labelAddress)
        
        let inputAddress = CustomTextfield(frame: CGRectMake(marginLeft, labelAddress.frame.origin.y + labelAddress.frame.size.height + 5, self.view.frame.size.width - (marginLeft*2), inputHeight))
        inputAddress.delegate = self
        self.scrollView.addSubview(inputAddress)
        
        let labelDescription = CustomLabel(frame: CGRectMake(marginLeft, inputAddress.frame.origin.y + inputAddress.frame.size.height + 10, self.view.frame.size.width - (marginLeft*2), labelHeight))
        labelDescription.text = "紹介文"
        self.scrollView.addSubview(labelDescription)
        
        let inputDescription = CustomTextView(frame: CGRectMake(marginLeft, labelDescription.frame.origin.y + labelDescription.frame.size.height + 5, self.view.frame.size.width - (marginLeft*2), 150))
        inputDescription.delegate = self
        inputDescription._delegate = self
        self.scrollView.addSubview(inputDescription)
        
        self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width, inputDescription.frame.origin.y + inputDescription.frame.size.height + 50)
        self.view.addSubview(self.scrollView)
    }

    override func viewWillAppear(animated: Bool) {
        
        super.viewWillAppear(animated)
        
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.addObserver(self, selector: "handleKeyboardWillShowNotification:", name: UIKeyboardWillShowNotification, object: nil)
        notificationCenter.addObserver(self, selector: "handleKeyboardWillHideNotification:", name: UIKeyboardWillHideNotification, object: nil)
    }
    
    func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
        self.activeTextField = textField
        self.activeTextView = nil
        return true
    }

    func textViewShouldBeginEditing(textView: UITextView) -> Bool {
        self.activeTextView = textView
        self.activeTextField = nil
        return true
    }

    func handleKeyboardWillShowNotification(notification: NSNotification) {
        if activeTextField != nil {
            self.saveContentOffsetY = self.scrollView.contentOffset.y
            self.scrollView.contentOffset.y = activeTextField!.frame.origin.y - 50
        } else if activeTextView != nil {
            self.saveContentOffsetY = self.scrollView.contentOffset.y
            self.scrollView.contentOffset.y = activeTextView!.frame.origin.y - 50
        }
    }
    
    func handleKeyboardWillHideNotification(notification: NSNotification) {
        self.scrollView.contentOffset.y = self.saveContentOffsetY!
    }
    
    func textFieldShouldReturn(textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    
    func textViewShouldReturn(textView: UITextView) -> Bool {
        textView.resignFirstResponder()
        return true
    }
    
    func onCloseTextView() {
        self.activeTextView?.resignFirstResponder()
    }
}

class CustomLabel: UILabel {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setUp()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setUp()
    }
    
    func setUp() {
        self.font = UIFont.systemFontOfSize(18)
    }
}

class CustomTextfield: UITextField {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setUp()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setUp()
    }
    
    func setUp() {
        self.font = UIFont.systemFontOfSize(18)
        self.layer.borderWidth = 0.5
        self.layer.borderColor = UIColor.grayColor().CGColor
        self.layer.cornerRadius = 3
    }
}

class CustomTextView: UITextView {
    
    var _delegate: CustomTextViewDelegate! = nil
    
    required internal init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setUp()
    }
    
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer);
        self.setUp()
    }
    
    func setUp() {
        self.font = UIFont.systemFontOfSize(18)
        self.layer.borderWidth = 0.5
        self.layer.borderColor = UIColor.grayColor().CGColor
        self.layer.cornerRadius = 3
        
        let accessoryView = UIView(frame: CGRectMake(0, 0, self.frame.size.width, 44))
        accessoryView.backgroundColor = UIColor.whiteColor()
        let closeButton = UIButton(frame: CGRectMake(self.frame.size.width - 50, 5, 100, 30))
        closeButton.setTitle("完了", forState: UIControlState.Normal)
        closeButton.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)
        closeButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.Right
        closeButton.addTarget(self, action: "onClose:", forControlEvents: .TouchUpInside)
        accessoryView.addSubview(closeButton)
        self.inputAccessoryView = accessoryView
    }
    
    func onClose(sender: UIButton) {
        self._delegate.onCloseTextView()
    }
}

protocol CustomTextViewDelegate {
    func onCloseTextView()->Void
}

とはいえ、やっぱりアプリ開発はいちいち考慮することが多くて難しいと思った今日1日でした

以上です