【Swift】UITextViewの高さを入力内容に合わせて変える方法
この記事ではUITextViewに入力する内容の長さに合わせてTextViewの高さを変える方法を書いていきます。
完成図は以下です。
1.準備
まずはStoryboardの準備です。
画面下部にUITextViewとそのコンテナとなるUIViewを置きます。それぞれの制約は下記です。
①textViewのコンテナView
②textView
さらにはViewControllerへ3つのoutletを追加します。
- textView(UITextView)
- textViewHeight(UITextViewの高さ制約)
- textViewContainerHeight(コンテナUIViewの高さ制約)
ViewControllerは以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! @IBOutlet weak var textViewHeight: NSLayoutConstraint! @IBOutlet weak var textViewContainerHeight: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() } } |
2.入力内容に合わせてUITextViewの高さを変える
2-1.行が増えたら高さをあげる
まずは単純に入力内容が増えるに連れてUITextViewの高さもあげるところから始めます。
ViewControllerに以下を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
fileprivate var currentTextViewHeight: CGFloat = 38.5 //HiraginoSans Fontサイズ15の初期高さ override func viewDidLoad() { super.viewDidLoad() self.textView.delegate = self } extension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { self.textView.translatesAutoresizingMaskIntoConstraints = true self.textView.sizeToFit() self.textView.isScrollEnabled = false let resizedHeight = self.textView.frame.size.height self.textViewHeight.constant = resizedHeight //@x: 60(左のマージン) //@y: 10(上のマージン) //@width: self.view.frame.width - 120(左右のマージン) //@height: sizeToFit()後の高さ self.textView.frame = CGRect(x: 60, y: 10, width: self.view.frame.width - 120, height: resizedHeight) if resizedHeight > currentTextViewHeight { let addingHeight = resizedHeight - currentTextViewHeight self.textViewContainerHeight.constant += addingHeight currentTextViewHeight = resizedHeight } } } |
順を追って見ていきます。
1 2 3 4 5 6 7 8 9 10 |
self.textView.translatesAutoresizingMaskIntoConstraints = true self.textView.sizeToFit() self.textView.isScrollEnabled = false let resizedHeight = self.textView.frame.size.height self.textViewHeight.constant = resizedHeight //@x: 60(左のマージン) //@y: 10(上のマージン) //@width: self.view.frame.width - 120(左右のマージン) //@height: sizeToFit()後の高さ self.textView.frame = CGRect(x: 60, y: 10, width: self.view.frame.width - 120, height: resizedHeight) |
まずtranslatesAutoresizingMaskIntoConstraintsをtrueにして動的にtextViewのframeを更新できるようにします。その後、sizeToFit()で入力内容のサイズに合わせます。これだけですと横幅も入力内容のサイズにフィットしてしまうため、リサイズ後の高さだけを取得し、textViewのframeを再定義します。
これでtextViewは入力内容に沿ってどんどん高さが増えていきます。
次にtextViewのコンテナとなるUIViewの高さも一緒に増やす必要があります。ViewControllerに以下を追加します。
1 2 3 4 5 |
if resizedHeight > currentTextViewHeight { let addingHeight = resizedHeight - currentTextViewHeight self.textViewContainerHeight.constant += addingHeight currentTextViewHeight = resizedHeight } |
sizeToFit()でリサイズしたtextViewの高さが currentTextViewHeight(初期値はHiraginoSans Fontサイズ15の高さ)を上回る場合はその差分の高さをコンテナViewに付け足します。最後にtextViewの高さも更新されるため、currentTextViewHeightの値もリサイズ後の高さを代入します。
現時点でのViewControllerは以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! @IBOutlet weak var textViewHeight: NSLayoutConstraint! @IBOutlet weak var textViewContainerHeight: NSLayoutConstraint! fileprivate var currentTextViewHeight: CGFloat = 38.5 //HiraginoSans Fontサイズ15の初期高さ override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) self.textView.delegate = self let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) self.view.addGestureRecognizer(tapGesture) } //キーボードが現れたら画面の位置も一緒にあげる @objc func keyboardWillShow(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { if self.view.frame.origin.y == 0 { self.view.frame.origin.y -= keyboardSize.height } else { let suggestionHeight = self.view.frame.origin.y + keyboardSize.height self.view.frame.origin.y -= suggestionHeight } } } //キーボードが隠れたら画面の位置も元に戻す @objc func keyboardWillHide() { if self.view.frame.origin.y != 0 { self.view.frame.origin.y = 0 } } //タップでキーボードを隠す @objc func dismissKeyboard() { self.view.endEditing(true) } } extension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { self.textView.translatesAutoresizingMaskIntoConstraints = true self.textView.sizeToFit() self.textView.isScrollEnabled = false let resizedHeight = self.textView.frame.size.height self.textViewHeight.constant = resizedHeight //@x: 60(左のマージン) //@y: 10(上のマージン) //@width: self.view.frame.width - 120(左右のマージン) //@height: sizeToFit()後の高さ self.textView.frame = CGRect(x: 60, y: 10, width: self.view.frame.width - 120, height: resizedHeight) if resizedHeight > currentTextViewHeight { let addingHeight = resizedHeight - currentTextViewHeight self.textViewContainerHeight.constant += addingHeight currentTextViewHeight = resizedHeight } } } |
2-2.行が減ったら高さを下げる
今のままですと入力内容を削除してもtextViewの高さは増えたままですので、textViewの高さも増減できるようにします。
ViewControllerに以下を追加します。
1 2 3 4 5 6 7 8 9 |
if resizedHeight > currentTextViewHeight { let addingHeight = resizedHeight - currentTextViewHeight self.textViewContainerHeight.constant += addingHeight currentTextViewHeight = resizedHeight } else if resizedHeight < currentTextViewHeight { let subtractingHeight = currentTextViewHeight - resizedHeight self.textViewContainerHeight.constant -= subtractingHeight currentTextViewHeight = resizedHeight } |
else ifの部分が追加分です。
sizeToFit()でリサイズしたtextViewの高さが currentTextViewHeightを下回る場合はその差分の高さをコンテナViewから引きます。最後にtextViewの高さも更新されるため、currentTextViewHeightの値もリサイズ後の高さを代入します。
現時点でのViewControllerは以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! @IBOutlet weak var textViewHeight: NSLayoutConstraint! @IBOutlet weak var textViewContainerHeight: NSLayoutConstraint! fileprivate var currentTextViewHeight: CGFloat = 38.5 //HiraginoSans Fontサイズ15の初期高さ override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) self.textView.delegate = self let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) self.view.addGestureRecognizer(tapGesture) } //キーボードが現れたら画面の位置も一緒にあげる @objc func keyboardWillShow(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { if self.view.frame.origin.y == 0 { self.view.frame.origin.y -= keyboardSize.height } else { let suggestionHeight = self.view.frame.origin.y + keyboardSize.height self.view.frame.origin.y -= suggestionHeight } } } //キーボードが隠れたら画面の位置も元に戻す @objc func keyboardWillHide() { if self.view.frame.origin.y != 0 { self.view.frame.origin.y = 0 } } //タップでキーボードを隠す @objc func dismissKeyboard() { self.view.endEditing(true) } } extension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { self.textView.translatesAutoresizingMaskIntoConstraints = true self.textView.sizeToFit() self.textView.isScrollEnabled = false let resizedHeight = self.textView.frame.size.height self.textViewHeight.constant = resizedHeight //@x: 60(左のマージン) //@y: 10(上のマージン) //@width: self.view.frame.width - 120(左右のマージン) //@height: sizeToFit()後の高さ self.textView.frame = CGRect(x: 60, y: 10, width: self.view.frame.width - 120, height: resizedHeight) if resizedHeight > currentTextViewHeight { let addingHeight = resizedHeight - currentTextViewHeight self.textViewContainerHeight.constant += addingHeight currentTextViewHeight = resizedHeight } else if resizedHeight < currentTextViewHeight { let subtractingHeight = currentTextViewHeight - resizedHeight self.textViewContainerHeight.constant -= subtractingHeight currentTextViewHeight = resizedHeight } } } |
3.高さに制限を加えて、一定の高さに達するとスクロールに切り替える
このままですとtextViewの高さが無限に増えていくので、一定の高さに達したら高さを固定してスクロールに切り替えます。
ViewControllerに以下を加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
func textViewDidChange(_ textView: UITextView) { let contentHeight = self.textView.contentSize.height //@38.0: textViewの高さの最小値 //@80.0: textViewの高さの最大値 if 38.0 <= contentHeight && contentHeight <= 80.0 { self.textView.translatesAutoresizingMaskIntoConstraints = true self.textView.sizeToFit() self.textView.isScrollEnabled = false let resizedHeight = self.textView.frame.size.height self.textViewHeight.constant = resizedHeight //@x: 60(左のマージン) //@y: 10(上のマージン) //@width: self.view.frame.width - 120(左右のマージン) //@height: sizeToFit()後の高さ self.textView.frame = CGRect(x: 60, y: 10, width: self.view.frame.width - 120, height: resizedHeight) if resizedHeight > currentTextViewHeight { let addingHeight = resizedHeight - currentTextViewHeight self.textViewContainerHeight.constant += addingHeight currentTextViewHeight = resizedHeight } else if resizedHeight < currentTextViewHeight { let subtractingHeight = currentTextViewHeight - resizedHeight self.textViewContainerHeight.constant -= subtractingHeight currentTextViewHeight = resizedHeight } } else { self.textView.isScrollEnabled = true self.textViewHeight.constant = currentTextViewHeight self.textView.frame = CGRect(x: 60, y: 10, width: self.view.frame.width - 120, height: currentTextViewHeight) } } |
変更点を見ていきます。
1 2 3 4 5 6 7 8 9 10 11 |
let contentHeight = self.textView.contentSize.height //@38.0: textViewの高さの最小値 //@80.0: textViewの高さの最大値 if 38.0 <= contentHeight && contentHeight <= 80.0 { } else { self.textView.isScrollEnabled = true } |
今までのようにsizeToFit()を使ってしまうと、textViewがリサイズされてから高さを減らすことになってしまい表示がカクつくので使えません。
代わりにcontentSizeを使います。
contentSizeを使い、入力テキストの高さを取得し定義した最大の高さ(この記事では80.0)を超えた場合に isScrollEnabledをtrueにしてスクロール出来るようにします。
最終的にViewControllerは以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! @IBOutlet weak var textViewHeight: NSLayoutConstraint! @IBOutlet weak var textViewContainerHeight: NSLayoutConstraint! fileprivate var currentTextViewHeight: CGFloat = 38.5 //HiraginoSans Fontサイズ15の初期高さ override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) self.textView.delegate = self let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) self.view.addGestureRecognizer(tapGesture) } //キーボードが現れたら画面の位置も一緒にあげる @objc func keyboardWillShow(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { if self.view.frame.origin.y == 0 { self.view.frame.origin.y -= keyboardSize.height } else { let suggestionHeight = self.view.frame.origin.y + keyboardSize.height self.view.frame.origin.y -= suggestionHeight } } } //キーボードが隠れたら画面の位置も元に戻す @objc func keyboardWillHide() { if self.view.frame.origin.y != 0 { self.view.frame.origin.y = 0 } } //タップでキーボードを隠す @objc func dismissKeyboard() { self.view.endEditing(true) } } extension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { let contentHeight = self.textView.contentSize.height //@38.0: textViewの高さの最小値 //@80.0: textViewの高さの最大値 if 38.0 <= contentHeight && contentHeight <= 80.0 { self.textView.translatesAutoresizingMaskIntoConstraints = true self.textView.sizeToFit() self.textView.isScrollEnabled = false let resizedHeight = self.textView.frame.size.height self.textViewHeight.constant = resizedHeight //@x: 60(左のマージン) //@y: 10(上のマージン) //@width: self.view.frame.width - 120(左右のマージン) //@height: sizeToFit()後の高さ self.textView.frame = CGRect(x: 60, y: 10, width: self.view.frame.width - 120, height: resizedHeight) if resizedHeight > currentTextViewHeight { let addingHeight = resizedHeight - currentTextViewHeight self.textViewContainerHeight.constant += addingHeight currentTextViewHeight = resizedHeight } else if resizedHeight < currentTextViewHeight { let subtractingHeight = currentTextViewHeight - resizedHeight self.textViewContainerHeight.constant -= subtractingHeight currentTextViewHeight = resizedHeight } } else { self.textView.isScrollEnabled = true } } } |
以上、UITextViewの高さを入力内容に合わせて変える方法でした。