備忘録兼紹介です。
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
}
}
ありがとうございました。
よかったら使ってください。(もっといい実装があれば教えて下さい。)