🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Swift

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

Q&A

解決済

1回答

2564閲覧

viewをタップするとレイアウトが崩れてしまう。(キーボード表示について)

ZY.

総合スコア22

Swift

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

0グッド

0クリップ

投稿2021/02/14 15:39

前提・実現したいこと

Twitterの投稿画面のようなものを作っています。
キーボードが表示されたときにはキーボードの上に投稿ボタンなどを配置したいと考えています。
画面遷移時にTextViewにフォーカスを当てるようにしているのですが、TextViewを2回タップするとボタンなどを配置しているViewのレイアウトが変わってしまうのでそれを防ぎたいです。

発生している問題

《ボタンなどを配置しているViewについて》 キーボードの上に表示させる投稿ボタンなどのUIパーツを配置するためのView(keyboardBackView)を画面底辺に設置しています。 画面遷移のタイミングでキーボードが表示されるので(常時)、keyboardBackViewの高さにキーボードの高さを加えて、キーボード上に表示されるように調整しています。 このときにTextViewにフォーカスがあたっている状態なので、TextViewを2回タップするとkeyboardBackViewの高さが変わってしまいます。(上に高さが加わる) タップしてもさらに高さを加えないようにしたいです。

イメージ説明

該当のソースコード

swift

1import UIKit 2 3class PostViewController: UIViewController, UIScrollViewDelegate { 4 5 @IBOutlet weak var textView: UITextView! 6 @IBOutlet weak var wordCountLabel: UILabel! 7 fileprivate let placeholder: String = "テキストを入力" // プレースホルダ 8 fileprivate var maxWordCount: Int = 100 // 最大文字数 9 @IBOutlet weak var keyboardBackViewConstraint: NSLayoutConstraint! 10 @IBOutlet weak var keyboardBackView: UIView! 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 15 self.textView.delegate = self 16 17 if textView.text.isEmpty { 18 textView.textColor = .darkGray 19 textView.text = placeholder 20 self.wordCountLabel.text = "300/300" 21 } 22 23 NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) 24 25 // 遷移時にtextViewにフォーカスをあてる 26 self.textView.becomeFirstResponder() 27 } 28 29 override func viewDidLayoutSubviews() { 30 self.keyboardBackView.addBorder(width: 0.5, color: UIColor.black, position: .top) 31 } 32 33 @objc func keyboardWillShow(notification: NSNotification) { 34 if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { 35 36 let suggestionHeight = self.keyboardBackViewConstraint.constant + keyboardSize.height 37 self.keyboardBackViewConstraint.constant = suggestionHeight 38 } 39 } 40 41 42 @IBAction func cancelButtonTapped(_ sender: Any) { 43 self.dismiss(animated: true, completion: nil) 44 45 } 46 47} 48 49extension PostViewController: UITextViewDelegate { 50 51 func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { 52 let existingLines = textView.text.components(separatedBy: .newlines) 53 let newLines = text.components(separatedBy: .newlines) 54 let linesAfterChange = existingLines.count + newLines.count - 1 55 return linesAfterChange <= 300 && textView.text.count + (text.count - range.length) <= maxWordCount 56 } 57 58 func textViewDidChange(_ textView: UITextView) { 59 let existingLines = textView.text.components(separatedBy: .newlines) 60 if existingLines.count <= 300 { 61 self.wordCountLabel.text = "(maxWordCount - textView.text.count)/300" 62 } 63 } 64 65 func textViewDidBeginEditing(_ textView: UITextView) { 66 if textView.text == placeholder { 67 textView.text = nil 68 textView.textColor = .darkText 69 } 70 } 71 72 func textViewDidEndEditing(_ textView: UITextView) { 73 if textView.text.isEmpty { 74 textView.textColor = .darkGray 75 textView.text = placeholder 76 } 77 } 78} 79 80enum BorderPosition { 81 case top 82 case left 83 case right 84 case bottom 85} 86 87extension UIView { 88 /// 特定の場所にborderをつける 89 /// 90 /// - Parameters: 91 /// - width: 線の幅 92 /// - color: 線の色 93 /// - position: 上下左右どこにborderをつけるか 94 func addBorder(width: CGFloat, color: UIColor, position: BorderPosition) { 95 96 let border = CALayer() 97 98 switch position { 99 case .top: 100 border.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: width) 101 border.backgroundColor = color.cgColor 102 self.layer.addSublayer(border) 103 case .left: 104 border.frame = CGRect(x: 0, y: 0, width: width, height: self.frame.height) 105 border.backgroundColor = color.cgColor 106 self.layer.addSublayer(border) 107 case .right: 108 print(self.frame.width) 109 110 border.frame = CGRect(x: self.frame.width - width, y: 0, width: width, height: self.frame.height) 111 border.backgroundColor = color.cgColor 112 self.layer.addSublayer(border) 113 case .bottom: 114 border.frame = CGRect(x: 0, y: self.frame.height - width, width: self.frame.width, height: width) 115 border.backgroundColor = color.cgColor 116 self.layer.addSublayer(border) 117 } 118 } 119}

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

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

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

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

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

guest

回答1

0

ベストアンサー

UIResponder.keyboardWillChangeFrameNotification はキーボードを出現させたあと、テキストの入力内容に応じて変換予測を出力させるなどする場合にも発生しますので、ひとつの textView を連続してクリックすると、複数回呼び出される可能性があります。

そういう背景を考えた場合、

Swift

1 @objc func keyboardWillShow(notification: NSNotification) { 2 if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { 3 4 // MARK: 現在の View の高さにキーボードの高さを加えている 5 let suggestionHeight = self.keyboardBackViewConstraint.constant + keyboardSize.height 6 self.keyboardBackViewConstraint.constant = suggestionHeight 7 } 8 }

上記の MARK: の部分で、最新の View の高さにキーボードの高さを追加していますので、変換予測を出した際には、(キーボードの高さ * 2 + 変換予測のフレーム高さ)分の制約を算出することになってしまいます。

今回つくられた View の高さは固定だと思いますので、たとえば固定値を30と決めた場合、

Swift

1 @objc func keyboardWillShow(notification: NSNotification) { 2 // あるいは、固定値を使わず viewDidLoad で StoryBoard での設定を代入させる 3 let defaultHeightConstraint: CGFloat = 30 4 5 if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { 6 // MARK: View の固定値 + キーボードの高さ 7 let suggestionHeight = defaultHeightConstraint + keyboardSize.height 8 self.keyboardBackViewConstraint.constant = suggestionHeight 9 } 10 }

にすれば、おそらく期待されている動作になるかと思います。

また、ご提示のコードは View の高さ制約を変更していますが、View の bottom に対する制約を変更した方が良さそうに思えます。

また、この View を常時表示するのでなければ、inputAccessoryView として設定するのが一番よいかもしれません。

The custom accessory view to display when the text view becomes the first responder.

投稿2021/02/14 23:39

TsukubaDepot

総合スコア5086

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

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

ZY.

2021/02/15 14:54 編集

ありがとうございます!!期待する動作になりました。 bottomに対する制約を変更とのことですが、キーボードバックにあるViewをキーボード(予測変換)の上まで持ち上げるようにするということでしょうか。 もしくは以下のようにViewのButtomに対してキーボードの高さを加えるということでしょうか。 (Viewの高さはStoryboardで固定の状態で) let suggestionHeight = keyboardSize.height self.keyboardBackViewBottomConstraint.constant = suggestionHeight
TsukubaDepot

2021/02/18 05:31

すいません、こちらは他の通知に埋もれて気づいていませんでした。 最終的には目的次第なのですが、今回の目的であれば「キーボードバックにあるViewをキーボード(予測変換)の上まで持ち上げるようにする」ということでよいのではないかと思います。 ただ、これも親 View のどこに textField があるのかによって、View 自身を動かさなければならない場合もあると思いますので、それは状況に合わせて判断していただくことになるかと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問