見出し画像

【徒然iOS】気ままにUIKit43〜Scroll Viewを使って入力欄とキーボードが被らないようにする方法〜

概要

このマガジンは四十を過ぎたおっさんが、

を参考にStoryboardでiOSアプリを完全に趣味で楽しんでいるだけな記事を気ままに上げてます。

今回

をハイ、レッツゴ🕺

前準備

今回は前回の続きをそのままやるとか書いてるけど、思いっきし、前回の結果を削除して1から作り直すのと変わらないので、いつもどおり、

念の為、

  1. バックアップ

  2. 新しいクラス

  3. ビューコントローラの追加

  4. イニシャルビューの変更

をいつも通りやってから本題へ💃

てな感じかな💦

本題

とりあえず、サイト記事にあるイメージを、

てな感じで、Assetにセットした〜〜〜〜

と、ここでお断り

7年近く前のサイト記事どおりに手順どおり操作してみたんだけど、

結構、

説明が抜けていたり、
手順どおりやってもおかしいところが多かったので、
ここからは、ちょっと自分の操作を中心にやってく〜〜〜〜

⒈スクロールビューを配置して、その中にイメージを配置してAutoLayoutを追加

スクロールビューを適当に配置して
イメージ配置

サイト記事のとおりだと、イメージが左に寄りすぎて、シミュレータで実行しても変なので〜〜〜〜

イメージを目一杯引き伸ばした後に、スクロールビューとの距離から制約を設定🕺
赤のコンフリクトが起こるので、いつものやり方でここは解消
解消できたのを確認して、シミュレータ実行
スクロールできた🕺

⒉編集しやすいように、ビューコントローラーをFreedomに変更

編集しやすくなった🕺

と、スクロールビューの位置が変わってないので、編集しづらい
ので、

てな感じで引き伸ばす
引き伸ばし後も、シミュレータでスクロールできることを確認しとく〜〜〜

⒊テキストフィールドとテキストビューをサイト記事どおり配置

ここは、各パーツを

AutoLayoutの制約の際に、スクロールビューからの距離
幅は指定しないと自動的に変わるのでWidthも今回は設定する

に気をつければ、以外は、サイト記事の設定項目で制約を追加するだけ

これな
全パーツを組み込んで、シミュレートしたとこ
ちゃんとできてるでしょ👀

⒋⒊で組んだパーツをアウトレット接続

接続完了

⒌テキストフィールドのキーボードはReturnキーを押すと閉じられるようにAction接続を追加

アクション接続
組み込み完了

⒍以下のコードを組み込む

//サイト記事のコード
import UIKit
class ViewController: UIViewController,UITextViewDelegate {
    @IBOutlet weak var testTextView2: UITextView!
    @IBOutlet weak var testTextView1: UITextView!
    @IBOutlet weak var testTextField: UITextField!
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //ビューを作成する。
        let testView = UIView()
        testView.frame.size.height = 60
        testView.backgroundColor = UIColor.blueColor()
        //「閉じるボタン」を作成する。
        let closeButton = UIButton(frame:CGRectMake(CGFloat( UIScreen.mainScreen().bounds.size.width)-70, 0, 70, 50))
        closeButton.setTitle("閉じる", forState:UIControlState.Normal)
        closeButton.backgroundColor = UIColor.redColor()
        closeButton.addTarget(self,action:"onClickCloseButton:", forControlEvents: .TouchUpInside)
        //ビューに「閉じるボタン」を追加する。
        testView.addSubview(closeButton)
        //キーボードのアクセサリにビューを設定する。
        testTextView1.inputAccessoryView = testView
        testTextView2.inputAccessoryView = testView
    }
    //テキストフィールドの編集終了時の呼び出しメソッド
    @IBAction func endInputTextField(sender: UITextField) {
    }
    //「閉じるボタン」で呼び出されるメソッド
    func onClickCloseButton(sender: UIButton) {
        //キーボードを閉じる
        testTextView1.resignFirstResponder()
        testTextView2.resignFirstResponder()
    }
}

ここは様々なエラーが起きるので、
改修し終わったコードを載せるね〜〜〜〜

class ScrollKeyboardViewController: UIViewController,UITextViewDelegate {
    @IBOutlet weak var myTextField: UITextField!
    @IBOutlet weak var myText1View: UITextView!
    @IBOutlet weak var myText2View: UITextView!
    override func viewDidLoad() {
        super.viewDidLoad()
        //ビューを作成する。
        let myView = UIView()
        myView.frame.size.height = 60
        myView.backgroundColor = UIColor.blue
        //「閉じるボタン」を作成する。
        let closeButton = UIButton(frame:CGRectMake(CGFloat( UIScreen.main.bounds.size.width)-70, 0, 70, 50))
        closeButton.setTitle("閉じる", for:UIControl.State.normal)
        closeButton.backgroundColor = UIColor.red
        closeButton.addTarget(self,action:#selector(TextViewStandardController.onClickCloseButton(_:)), for: .touchUpInside)
        //ビューに「閉じるボタン」を追加する。
        myView.addSubview(closeButton)
        //キーボードのアクセサリにビューを設定する。
        myText1View.inputAccessoryView = myView
        myText2View.inputAccessoryView = myView
    }
    //テキストフィールドの編集終了時の呼び出しメソッド
    @IBAction func endInputTextField(_ sender: UITextField) {
    }
    //「閉じるボタン」で呼び出されるメソッド
    @objc func onClickCloseButton(_ sender: UIButton) {
        //キーボードを閉じる
        myText1View.resignFirstResponder()
        myText2View.resignFirstResponder()
    }
}
ほい
ほい

て感じで、閉じるボタンでキーボード閉じるまでは実装できたね〜〜〜🕺

⒎スクロールビューの移動を実装する

と、ここまでだとキーボードがスクロールビューに被っちゃうので、直す。
けども、、、
解説がやたら長いので〜〜〜〜

サイト記事の最後にある、コードで一気に直す🕺

import UIKit 
class ViewController: UIViewController,UITextViewDelegate,UITextFieldDelegate {
    @IBOutlet weak var testTextView2: UITextView!
    @IBOutlet weak var testTextView1: UITextView!
    @IBOutlet weak var testTextField: UITextField!
    @IBOutlet weak var testScrollView: UIScrollView!
    var target:UIView! //タップされた部品
    var moveY:CGFloat = 0 //移動距離
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //ビューを作成する。
        let testView = UIView()
        testView.frame.size.height = 60
        testView.backgroundColor = UIColor.blueColor()
        //「閉じるボタン」を作成する。
        let closeButton = UIButton(frame:CGRectMake(CGFloat( UIScreen.mainScreen().bounds.size.width)-70, 0, 70, 50))
        closeButton.setTitle("閉じる", forState:UIControlState.Normal)
        closeButton.backgroundColor = UIColor.redColor()
        closeButton.addTarget(self,action:"onClickCloseButton:", forControlEvents: .TouchUpInside)
        //ビューに「閉じるボタン」を追加する。
        testView.addSubview(closeButton)
        //キーボードのアクセサリにビューを設定する。
        testTextView1.inputAccessoryView = testView
        testTextView2.inputAccessoryView = testView
        //キーボードが現れるときに通知するメソッドを登録する。
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeShown:", name: UIKeyboardWillShowNotification, object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
        //部品のデリゲート先に自分を設定する。
        testTextField.delegate = self
        testTextView1.delegate = self
        testTextView2.delegate = self
    }
   //テキストフィールド編集前の呼び出しメソッド
    func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
        target = textField
        return true
    }
   //テキストビュー編集前の呼び出しメソッド
    func textViewShouldBeginEditing(textView: UITextView) -> Bool {
        target = textView
        return true
    } 
   //キーボードが開くときの呼び出しメソッド
    func keyboardWillBeShown(notification:NSNotification) {
       //キーボードのフレームを取得する。
        if let keyboardFrame = notification.userInfo![UIKeyboardFrameEndUserInfoKey]?.CGRectValue {
            //部品とキーボードがかぶっていかを判定
            let zureY = CGRectGetMaxY(target.frame) - testScrollView.contentOffset.y - CGRectGetMinY(keyboardFrame)
            if (zureY > 0) {
                //スクロールビューをずらす
                testScrollView.contentInset.bottom += zureY
                testScrollView.contentOffset.y += zureY
                moveY += zureY
            }
        }
    }
    //テキストフィールドの編集終了時の呼び出しメソッド
    @IBAction func endInputTextField(sender: UITextField) {
    }
    //「閉じるボタン」で呼び出されるメソッド
    func onClickCloseButton(sender: UIButton) {
        //キーボードを閉じる
        testTextView1.resignFirstResponder()
        testTextView2.resignFirstResponder()
    }
    //キーボードが閉じられるときの呼び出しメソッド
    func keyboardWillBeHidden(notification:NSNotification){
       //スクロールビューの位置を元に戻す。
        UIView.animateWithDuration(0.5, animations: {self.testScrollView.contentOffset.y -= self.moveY})
        testScrollView.contentInset = UIEdgeInsetsZero
        moveY = 0
    }
}

と思ったけど、コードをみると、
ScrollViewのアウトレット接続が思いっきり端折られているので、

てな感じでアウトレット接続をしてから、

不足分のコードをゆっくり追加していく〜〜〜〜
コードを追加して、オブジェクト名だけ直しても

てな感じでエラーがたくさん出てくるので〜〜〜
いつもどおりFixやJumptoDefinitionなんかを駆使しながら直してく
手直しした結果、ちゃんと入力ボックスに被らないように、位置が調整される👀

今回のコード(まとめ)

class ScrollKeyboardViewController: UIViewController,UITextViewDelegate,UITextFieldDelegate{
    @IBOutlet weak var myTextField: UITextField!
    @IBOutlet weak var myText1View: UITextView!
    @IBOutlet weak var myText2View: UITextView!
    @IBOutlet weak var myScrollView: UIScrollView!
    //タップされた部品
    var target:UIView!
    //移動距離
    var moveY:CGFloat = 0
    override func viewDidLoad() {
        super.viewDidLoad()
        //ビューを作成する。
        let myView = UIView()
        myView.frame.size.height = 60
        myView.backgroundColor = UIColor.blue
        //「閉じるボタン」を作成する。
        let closeButton = UIButton(frame:CGRectMake(CGFloat(UIScreen.main.bounds.size.width)-70, 0, 70, 50))
        closeButton.setTitle("閉じる", for:UIControl.State.normal)
        closeButton.backgroundColor = UIColor.red
        closeButton.addTarget(self,action:#selector(TextViewStandardController.onClickCloseButton(_:)), for: .touchUpInside)
        //ビューに「閉じるボタン」を追加する。
        myView.addSubview(closeButton)
        //キーボードのアクセサリにビューを設定する。
        myText1View.inputAccessoryView = myView
        myText2View.inputAccessoryView = myView
        //キーボードが現れるときに通知するメソッドを登録する。
        NotificationCenter.default.addObserver(self, selector: #selector(TextViewStandardLotateRightController.keyboardWillBeShown(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillBeHidden(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
        //部品のデリゲート先に自分を設定する。
        myTextField.delegate = self
        myText1View.delegate = self
        myText2View.delegate = self
    }
    //テキストフィールド編集前の呼び出しメソッド
    @objc func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
        target = textField
        return true
    }
    //テキストビュー編集前の呼び出しメソッド
    @objc func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
        target = textView
        return true
    }
    //キーボードが開くときの呼び出しメソッド
    @objc func keyboardWillBeShown(_ notification:NSNotification) {
        //キーボードのフレームを取得する。
        if let keyboardFrame = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
            //部品とキーボードがかぶっていかを判定
            let zureY = CGRectGetMaxY(target.frame) - myScrollView.contentOffset.y - CGRectGetMinY(keyboardFrame)
            if (zureY > 0) {
                //スクロールビューをずらす
                myScrollView.contentInset.bottom += zureY
                myScrollView.contentOffset.y += zureY
                moveY += zureY
            }
        }
    }
    //テキストフィールドの編集終了時の呼び出しメソッド
    @IBAction func endInputTextField(_ sender: UITextField) {
    }
    //「閉じるボタン」で呼び出されるメソッド
    @objc func onClickCloseButton(_ sender: UIButton) {
        //キーボードを閉じる
        myText1View.resignFirstResponder()
        myText2View.resignFirstResponder()
    }
    //キーボードが閉じられるときの呼び出しメソッド
    @objc func keyboardWillBeHidden(_ notification:NSNotification){
        //スクロールビューの位置を元に戻す。
        UIView.animate(withDuration: 0.5, animations: {self.myScrollView.contentOffset.y -= self.moveY})
        myScrollView.contentInset = UIEdgeInsets.zero
        moveY = 0
    }
}

ブラッシュアップ

今回も、ここまででやっちゃってるので、今回も割愛💦

感想

⒈サイト記事の説明が端折られすぎていて、

コード組み込みに行き着くまででもとにかく長い💦(良い感じにレイアウトするのに3時間くらい試行錯誤したわ🙇)

没にしたスクショの数🤣

⒉コードにいざ行き着いても、今回のコードは長い上に、

すでにサンプルコードの書き振りなんかも大幅に変わっちゃってるので、
この記事だけを見て組み込もうって思う人がいても、

@Objcと#selector
各プロパティ値の書き方が変わってる

なんかの前提知識がないと、
正直、実行エラーばかりになって心が折れるかなと。

メリトクラシーな人は

「そんなことは説明されなくてもできて当たり前」
と思うかもしれないけど、初学者は、

知らない、わからないが当たり前だからね〜〜〜〜

それを端折って、自分で調べるような記事を書いても、時間がかかるばかりで、

「学習コストがかかるばかりで、面倒だから。
もうSwiftでのアプリ開発いいや。」

ってなるかなと、、、💦

自分のわかる/できることは

「説明してなくても、出来て当たり前」

って思ってる人が、最近多いけど、そういう人ほどうちらの業界だと

指示漏れ、伝達漏れ、連携不足、確認不足
👉思い込みと勘違い
👉バグ発生

にしかならないからね🤔

一度も過去に説明してないのに、「わかって当たり前」

はなしにしよう🕺

やってて思った本音は、

「これだからUIKitでの開発は嫌いなんだよ!。こんな簡単な組み込みSwiftUIなら五分くらいで出来るのに、、、
いまだに、UIKitとかでやってる現場もあるらしいけど可哀想に💦」

てことくらい藁🤣
こういうややこしい組み込みを数行ずつのコード書けばできるように
最新のフレームワークをAppleが4年近く前にオープンソース化してくれてんのに、日本語の市販本ではSwiftUIが出た当時で止まってるから、いまだにUIKitでやり続けようとする企業が多いと聞く👀

Apple公式

さて、次回は

をレッツゴする。

ScrollViewも最後だね〜〜〜
次回は設定項目だから、また一覧表を使って終わらすかな💦

いいなと思ったら応援しよう!