LoginSignup
3
5

More than 3 years have passed since last update.

UIScrollVIewの上でも、キーボードが現れたときにいい感じにスクロールする

Posted at

備忘録兼紹介です。
UIScrollView × 複数のUITextFieldで、キーボードが出たときにスクロールさせる処理を紹介します。

動き

コード

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var scroll: UIScrollView!    // UITextFieldとかを配置するView
    @IBOutlet weak var stack: UIStackView!      // 表示サンプル用

    // ScrollViewのBottomの制約
    @IBOutlet weak var scrollViewBottomConstraints: NSLayoutConstraint!

    private var nowEditingField:UITextField!    // 編集中のUITextField
    private var offsetAdded:CGFloat?            // スクロール戻す時用

    override func viewDidLoad() {
        super.viewDidLoad()

        // 表示サンプルとして、UILabelを20個配置
        let lines = 20
        for count in 1...lines {
            let field = UITextField()
            field.placeholder = "\(count)"
            field.borderStyle = .roundedRect
            field.textAlignment = .center

            field.delegate = self // ← UITextFieldのdelegateを拾いたいので、delegateを設定する

            self.stack.addArrangedSubview(field)
            field.widthAnchor.constraint(equalToConstant: 250.0).isActive = true
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // キーボードが表示されるときと消えるときに動作させるメソッドを登録
        // キーボードの高さがほしかった
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillShow),
            name: UIResponder.keyboardWillShowNotification,
            object: nil
        )
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(UIResponder.keyboardWillShowNotification)
    }
}

extension ViewController: UITextFieldDelegate {

    // テキストフィールドにフォーカスがあたった時に呼ばれる
    func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
        self.nowEditingField = textField
        return true
    }

    // テキストフィールドからフォーカスが外れる時に呼ばれる
    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
        self.nowEditingField = nil
        let offsetAdded = self.offsetAdded
        self.offsetAdded = nil
        var offset:CGPoint = CGPoint(x: 0, y: 0)
        if self.scroll.contentOffset.y - (offsetAdded ?? 0) < 0 {
            // 上端
            offset = CGPoint(x: self.scroll.contentOffset.x, y: 0)
        }else if self.scroll.contentOffset.y > (self.scroll.contentSize.height - self.scroll.frame.size.height - self.scrollViewBottomConstraints.constant) {
            // 下端
            offset = CGPoint(x: self.scroll.contentOffset.x, y: self.scroll.contentOffset.y)
        }else {
            // その他
            offset = CGPoint(x: self.scroll.contentOffset.x, y: self.scroll.contentOffset.y - (offsetAdded ?? 0))
        }

        self.scrollViewBottomConstraints.constant = 0
        self.scroll.setContentOffset(offset, animated: true)

        return true
    }

    // キーボードが表示される直前に呼ばれる ※textFieldShouldBeginEditingより後に呼ばれる
    @objc func keyboardWillShow(_ notification: Notification) {
        guard let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
            return
        }
        guard let field = self.nowEditingField else { return }

        let keyboardHeight = keyboardFrame.cgRectValue.height
        let keyboardTopLine = self.view.frame.height - keyboardHeight

        let bottomLine = field.frame.origin.y + field.frame.height
        let displayBottom = bottomLine - self.scroll.contentOffset.y

        if displayBottom > keyboardTopLine {
            let sub = displayBottom - keyboardTopLine
            self.offsetAdded = sub
            let offset = CGPoint(x: self.scroll.contentOffset.x, y: self.scroll.contentOffset.y + sub)

            self.scroll.setContentOffset(offset, animated: true)
            self.scrollViewBottomConstraints.constant = keyboardHeight
        }
    }

    // キーボードのリターンでキーボードを閉じる
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        self.view.endEditing(true)
        return true
    }
}


ありがとうございました。
よかったら使ってください。(もっといい実装があれば教えて下さい。)

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5