質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

968閲覧

【Xcode】textFieldがキーボードに隠れる

abc1222

総合スコア24

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2020/10/20 08:01

編集2020/10/21 10:48

前提・実現したいこと

画面の下の方に設置しているtextFieldが、キーボード表示時に隠れてしまいます。
上記問題を解決するため下記のサイトを参考にしたのですが、1つの画面では上手くいき、もう一つの画面では上手くいきません。

https://ayafitpay.com/hidden-ios-keyboard/

上手く行かなかった画面は、OverFullScreenを使ってポップアップのように表示しているという違いがあり、それが原因だとは思うのですが、では実際にどうすれば良いのかが分からず苦戦しています。

イメージ説明

真ん中の画面が上手くいった画面です。(一番下のtextFieldがキーボードに合わせてスクロールされる)
一番右の画面が上手くいかない画面です。(textFieldをタップしてもキーボードに合わせてスクロールしない。なぜか後ろに透けて見える前の画面がスクロールされている様子)

追記:解決策はまだ分かっていませんが、原因は分かったため、質問の一番下に「原因」の見出しで情報を追加しています。

該当のソースコード

textFieldや本件に関係ないコードも多いので、見やすいように一部省略しています。

Swift

1import UIKit 2 3class testController: UIViewController { 4 5 var selectedTextField: UITextField? 6 7 @IBOutlet weak var scrollView: UIScrollView! 8 @IBOutlet weak var testField1: UITextField! 9 @IBOutlet weak var testField2: UITextField! 10 @IBOutlet weak var testField3: UITextField! 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 15 self.textFieldInit() 16 17 } 18 19 override func viewWillAppear(_ animated: Bool) { 20 super.viewWillAppear(animated) 21 // キーボードイベントの監視開始 22 NotificationCenter.default.addObserver(self,selector: #selector(keyboardWillBeShown(notification:)),name: UIResponder.keyboardWillShowNotification,object: nil) 23 24 NotificationCenter.default.addObserver(self,selector: #selector(keyboardWillBeHidden(notification:)),name: UIResponder.keyboardWillHideNotification,object: nil) 25 } 26 27 override func viewWillDisappear(_ animated: Bool) { 28 super.viewWillDisappear(animated) 29 // キーボードイベントの監視解除 30 NotificationCenter.default.removeObserver(self,name: UIResponder.keyboardWillShowNotification,object: nil) 31 32 NotificationCenter.default.removeObserver(self,name: UIResponder.keyboardWillHideNotification,object: nil) 33 } 34 35 36 /* 37 // MARK: - Navigation 38 39 // In a storyboard-based application, you will often want to do a little preparation before navigation 40 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 41 // Get the new view controller using segue.destination. 42 // Pass the selected object to the new view controller. 43 } 44 */ 45 46} 47 48extension testController: UITextFieldDelegate { 49 50 func textFieldInit() { 51 // 最初に選択されているTextFieldをセット 52 self.selectedTextField = self.testField1 53 54 // 各TextFieldのdelegate 色んなイベントが飛んでくるようになる 55 self.testField1.delegate = self 56 self.testField2.delegate = self 57 self.testField3.delegate = self 58 59 } 60 61 // キーボードが表示された時に呼ばれる 62 @objc func keyboardWillBeShown(notification: NSNotification) { 63 if let userInfo = notification.userInfo { 64 if let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue, let animationDuration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue { 65 restoreScrollViewSize() 66 67 let convertedKeyboardFrame = scrollView.convert(keyboardFrame, from: nil) 68 //現在選択中のTextFieldの下部Y座標とキーボードの高さから、スクロール量を決定 69 let offsetY: CGFloat = self.selectedTextField!.frame.maxY - convertedKeyboardFrame.minY 70 if offsetY < 0 { return } 71 updateScrollViewSize(moveSize: offsetY, duration: animationDuration) 72 } 73 } 74 } 75 76 // キーボードが閉じられた時に呼ばれる 77 @objc func keyboardWillBeHidden(notification: NSNotification) { 78 restoreScrollViewSize() 79 } 80 81 // TextFieldが選択された時 82 func textFieldDidBeginEditing(_ textField: UITextField) { 83 // 選択されているTextFieldを更新 84 self.selectedTextField = textField 85 } 86 87 // リターンが押された時 88 func textFieldShouldReturn(_ textField: UITextField) -> Bool { 89 // キーボードを閉じる 90 textField.resignFirstResponder() 91 return true 92 } 93 94 // moveSize分Y方向にスクロールさせる 95 func updateScrollViewSize(moveSize: CGFloat, duration: TimeInterval) { 96 UIView.animate(withDuration: duration, 97 delay: 0.025, 98 options: .curveLinear, 99 animations: { 100 let contentInsets = UIEdgeInsets(top: 0, 101 left: 0, 102 bottom: moveSize, 103 right: 0) 104 self.scrollView.contentInset = contentInsets 105 self.scrollView.scrollIndicatorInsets = contentInsets 106 self.scrollView.contentOffset = CGPoint(x: 0, 107 y: moveSize) 108 }, 109 completion: nil) 110 } 111 112 func restoreScrollViewSize() { 113 // キーボードが閉じられた時に、スクロールした分を戻す 114 self.scrollView.contentInset = UIEdgeInsets.zero 115 self.scrollView.scrollIndicatorInsets = UIEdgeInsets.zero 116 } 117} 118 119

追記

現在のストーリーボード上のViewの階層と、そのほか関係の有りそうな項目を追記いたします
イメージ説明

最初のScrollViewが参考サイトのとおりに作った所です。
SafeAreaいっぱいに広げ、すぐ下のViewはSafeAreaと同じ縦横幅に設定しています。
上記Viewは透過させ、その上に(階層としては下?に)Viewを余白4方向20で設置し、ポップアップのように見せています。
ポップアップ用のViewに見出し用ラベル、textFieldを設置するScrollView、ボタン設置用のStackViewを置いています。

UIViewControllerにOutletさせているのは最初のScrollViewです。
スクロールビューが2つあることが駄目なのでしょうか。

その他気になる所
・Extentionの使い方がよく分からず、前の画面にも同じExtentionを記載しています。
※クラス名やOutletさせるtextFieldは変えています

原因

textFieldをStackViewの中に入れているなど、入れ子が複雑な(深い?)状態になっていました。
そして、勉強中なので間違っているかもしれませんが、self.selectedTextField!.frame.maxYは親Viewからの位置を取得するようですので、スクロールビューからの距離ではなく、StackViewからの距離を取得しているみたいです。
結果、0 − キーボードサイズを計算し、offsetY < 0 { return }によって何も起きないという状況と思われます。

じゃあどうすればスクロールビューからtextFieldまでの距離が取れるのかなどまだわからない点も多いので自己解決には記載できないのですが、取り急ぎご報告です。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TsukubaDepot

2020/10/20 13:24

再現できる範囲で試しましたが、こちらで試した感じだと、UIScrollView はきちんとスクロールしました。 少なくとも、遷移前の画面(前の画面)の方がスクロールするということはありませんでした。 参考にされたサイトと違うのは、Content Layout Guideline を使わないように追加で設定した点ですが、おそらくそれは本質的ではないと思います。
abc1222

2020/10/20 14:17

ご確認いただき、ありがとうございます! ストーリーボードの状態など、もう少し情報を追加してみます。 また、念のため、Content Layout Guidelineを使わないように設定してみます。
abc1222

2020/10/20 14:49

Content Layout Guidelineを使わない方法がわからなかったので、取り急ぎ、現状のストーリーボードの状態や、他に関係ありそうな情報を質問に追記しました。
TsukubaDepot

2020/10/21 00:10

StoryBoard で ScrollView を選び、Show the size inspector を表示させると、 Content Inset の中に Content Layout Guides のチェックボックスが見つかりますので、それを外すとガイドラインを使わない方法に戻すことが可能です。
TsukubaDepot

2020/10/21 00:14

せっかくご提示いただいたのですが、同じ現象を再現させるのはかなり大変そうに見受けられます。 仮に真似して作ってみても、細かい設定が異なっていれば、当然結果は異なってしまいます。 ScrollView を2つ使うことが原因なのかはわかりませんが、それを確かめる唯一の方法は、一気にインタフェースを作り込まず、ひとつづつ動作を検証し、動くことを確認してから次の機能を盛り込むことかと思います。 overFullScreen で表示されたScrollView が反応せず、配下の ScrollView が動いてしまうということは、observer などで指定するターゲット(インスタンス)の設定がおかしくなっている可能性も否定できませんが、そのような状況は考えうる範囲では再現できませんでした(少なくともいただいたコードの範囲では)。 ひとつは、3つ目の遷移画面だけ独立したプロジェクトとして作成し、そこでまずは正しく動くか否かを範dんされてみてはいかがでしょうか。 そのようにすれば、切り分けは少しは可能になるかと思われます。
abc1222

2020/10/21 02:04

ありがとうございます! コード側に問題があると思っていましたが、再現が取れなかったということですので、改めてViewを一つずつ追加していきながらどこに問題があるのか確認してみます!
abc1222

2020/10/21 10:40

回答ありがとうございました。 アドバイス通りストーリーボードで一つずつ組み立てながら都度確認を行ったことで原因がわかりました。 ■原因 textFieldをStackViewの中に入れているなど、入れ子が複雑な(深い?)状態になっていました。 そして、勉強中なので間違っているかもしれませんが、self.selectedTextField!.frame.maxYは親Viewからの位置を取得するようですので、スクロールビューからの距離ではなく、StackViewからの距離を取得しているみたいです。 結果、0 − キーボードサイズを計算し、offsetY < 0 { return }によって何も起きないという状況と思われます。 じゃあどうすればスクロールビューからtextFieldまでの距離が取れるのかなどまだわからない点も多いので自己解決には記載できないのですが、取り急ぎご報告です。
TsukubaDepot

2020/10/21 11:58

オリジナルを見ると let convertedKeyboardFrame = scrollView.convert(keyboardFrame, from: nil) 上記のような行がありますが、ここでkeyboardの座標系をscrollViewの座標系に変換していると思われます。 参考にされた記事だと、ScrollView の中に固定の View を配置し、そこに TextField を配置しますのでこれで解決しますが、さらに ScrollView を入れ粉にしているようだと、その中での座標系についても考慮する必要があるかと思います。そうなると、現在表示している ScrollView における表示上の原点まで考えなければいけないように思えますので、結構複雑になるかもしれません。 ただ、これらの階層関係や座標関係をきちんと整理できれば、キーボード分の移動は可能になるのではないでしょうか。
abc1222

2020/10/21 13:35

ありがとうございます! 改めて整理し直し、convert()を活用することで目的の動きを達成することができました! (もう少し整理すれば一番最初のスクロールビューは無くても実装できそうですが) 今後も困ったときは、一つずつViewを設置しながら原因を探っていきたいと思います。 let convertedTextFieldFrame = scrollView.convert(self.selectedTextField!.frame, from: self.selectedTextField!.superview) let convertedKeyboardFrame = scrollView.convert(keyboardFrame, from: nil) let offsetY: CGFloat = convertedTextFieldFrame.maxY - convertedKeyboardFrame.minY
guest

回答1

0

自己解決

原因は質問に記載の通り

textFieldのスクロールビューからの座標を取得するために、convert()を活用することで目的の動きを達成することができました

let convertedTextFieldFrame = scrollView.convert(self.selectedTextField!.frame, from: self.selectedTextField!.superview)

let convertedKeyboardFrame = scrollView.convert(keyboardFrame, from: nil)

let offsetY: CGFloat = convertedTextFieldFrame.maxY - convertedKeyboardFrame.minY

投稿2020/10/21 13:38

abc1222

総合スコア24

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問