UITextViewの上にViewを載せて気持ちよくスクロールする

October 27, 2014

UITextViewの上にビューが乗っていて、UITextViewと一緒に気持ちよく動く。これがやりたかった。伝わる人には伝わるけれど、伝わらない人には伝わらない。UITextView面倒。。。いつもハマる。

でも、やってみましたー。とりあえず、とりいそぎ書いておきます。

完成形

Shot

とりあえずこんな感じになります。画面全体の大きさのUITextViewがあって、その上に黄緑のUIViewが乗っています。更にUIViewの上にボタンが有ります。UITextViewの上にビューがいくつもあるものの、全体としてスクロールが出来て、ボタンも押せます。更に、UITextViewの編集に入るタイミングと、編集が終わったタイミングで変な動きもしません。

ポイント

  • textContainerInsetsを指定してテキストの領域を制限していること。contentInsetsでも同じようにテキストの領域を制限できるが、この場合、テキストのないところはタップできなくなってしまう。textContainerInsetsだとちゃんとタップできる。

キーボードが出てきた時の動きなどもあるので、キーボードの処理もついでに書いてあります。

コード

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var textView: UITextView!
@IBOutlet weak var textViewBottomConstraint: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.textView.textContainerInset = UIEdgeInsetsMake(100.0, 0.0, 0.0, 0.0)
    let view = UIView(frame: CGRectMake(0.0, 0.0, CGRectGetWidth(self.textView.frame), 100.0))
    view.backgroundColor = UIColor.greenColor()
    self.textView.addSubview(view)
    
    let button = UIButton.buttonWithType(.Custom) as UIButton
    button.frame = CGRectMake(10.0, 10.0, 100.0, 44.0)
    button.backgroundColor = UIColor.grayColor()
    button.setTitle("Push Me!", forState: .Normal)
    button.addTarget(self, action: "push:", forControlEvents: .TouchUpInside)
    view.addSubview(button)
}

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    
    let notifications = \[UIKeyboardWillShowNotification, UIKeyboardWillChangeFrameNotification, UIKeyboardWillHideNotification\];
    for notification in notifications {
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "fitKeyboard:", name: notification, object: nil)
    }
}

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

func fitKeyboard(notification: NSNotification) {
    let userInfo = notification.userInfo
    let duration = (userInfo?\[UIKeyboardAnimationDurationUserInfoKey\] as NSNumber).doubleValue
    let curve = (userInfo?\[UIKeyboardAnimationCurveUserInfoKey\] as NSNumber).unsignedIntegerValue
    let keyboardFrame = (userInfo?\[UIKeyboardFrameEndUserInfoKey\] as NSValue).CGRectValue()
    let keyboardHeight = CGRectGetHeight(self.view.frame) - CGRectGetMinY(keyboardFrame)
    
    self.textViewBottomConstraint.constant = keyboardHeight
    let options = UIViewAnimationOptions(UInt(curve))
    UIView.animateWithDuration(duration, delay: 0.0, options: options, animations: { () -> Void in
        self.view.layoutIfNeeded()
    }, completion: nil)
}

func push(sender: UIButton) {
    println("button pushed!")
}

}

ざっと書いたので少し雑です。誰かの参考になれば。

課題

  • 閉じるボタンの代わりに、StoryboardでDismiss Interactivelyの設定をした。これ、いいんだけど、閉じるときにキーボードのところでText viewが切れているのがかっこ悪い。(これ使わないでデリゲートで独自の書くともうちょっといい感じになる)
  • UITextViewにコードでAutolayoutを書こうと思ったけどうまく行かなかった。ScrollView系だから左上右+高さ指定だけじゃダメなのかな。
  • 本当はストーリーボード上だけで表現したかった。

サンプルコード

すいません、ちょっと雑なのですが。。。


Profile picture

Written by morizotter who lives and works in Tokyo building useful things. You should follow them on Twitter