Help us understand the problem. What is going on with this article?

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

備忘録兼紹介です。
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
    }
}


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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした