Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
61
Help us understand the problem. What is going on with this article?
@kazuhiro4949

UIControlとUIKeyInputでUIPickerViewを手軽に下からシュッと出す

More than 3 years have passed since last update.

UIControlUIKeyInputを使ったUIPickerViewの出し入れ実装です。システムのキーボードとタッチジェスチャーで、ピッカーの入出力をViewに対し簡単に導入します。

最終的に出来上がるもの

実装

1. UIControlを継承する

PickerKeyboardというクラスを新しく作ります。

class PickerKeyboard: UIControl {}

このクラスは最終的に、

  • タッチジェスチャーに反応してUIPickerViewを出す
  • 選択した列の文字列を表示させる

という役割を持ったViewにします。ただしここではUIViewではなく、その子クラスである、タッチジェスチャーに対する便利機能を持ったUIControlを継承します。

2. UIkeyInputを実装して、キーボードを表示させられるようにする

UIKeyInputを実装しているViewは、システムキーボードを表示させて入力文字を処理することができます。

class Pickerkeyboard: UIControl {
    // 1.で作ったクラスへ、入力文字列を保存するためのプロパティを追加
    private var textStore: String = ""
}

// UIKeyInputを適用させる
extension PickerKeyboard: UIKeyInput {

    // 以下の3つのメソッドはUIkeyInputで必ず実装しなければならないメソッド
    // 主にキーボード入力が行われたときにそれぞれのメソッドが呼び出される

    // 入力されたテキストが存在するか
    func hasText() -> Bool {
        return !textStore.isEmpty
    }

    // テキストが入力されたときに呼ばれる
    func insertText(text: String) {
        textStore += text
        setNeedsDisplay()
    }

    // バックスペースが入力されたときに呼ばれる
    func deleteBackward() {
        textStore.removeAtIndex(textStore.characters.endIndex.predecessor())
        setNeedsDisplay()
    }
}

これでViewに対してキーボードを呼び出せるようになりました。

3. タッチジェスチャーでViewをFirst Responderにする

システムキーボードを出すには、UITextFieldやUITextViewと同じく、UIKeyInputが実装されたViewをFirst Responderにすればよいです。

class PickerKeyboard: UIControl {
    // ...省略

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        // viewのタッチジェスチャーを取る
        addTarget(self, action: #selector(PickerKeyboard.didTap(_:)), forControlEvents: .TouchDown)
    }

    // タッチされたらFirst Responderになる
    func didTap(sender: PickerKeyboard) {
        becomeFirstResponder()
    }

    // First Responderになるためにはこのメソッドは常にtrueを返す必要がある
    override func canBecomeFirstResponder() -> Bool {
        return true
    }
}

これでタッチしたときにシステムキーボードが表示されます。

4. UIResponderのinputViewプロパティをオーバーライドして、UIPickerViewを返す

UIResponderを継承したクラス(つまりUIView系)は全て、inputViewというプロパティを持ちます。ここへ任意のViewを入れることでそれをシステムキーボードの代わりに表示させられます。

class PickerKeyboard: UIControl {
    // ... 省略

    // ピッカーに表示させるデータ
    var data: [String] = ["月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日", "日曜日"]

    // inputViewをオーバーライドさせてシステムキーボードの代わりにPickerViewを表示
    override var inputView: UIView? {
        let pickerView = UIPickerView()
        pickerView.delegate = self
        let row = data.indexOf(textStore) ?? -1
        pickerView.selectRow(row, inComponent: 0, animated: false)
        return pickerView
    }
}

// UIPickerViewDelegateとDataSourceを実装して、dataの内容をピッカーへ表示させる
extension PickerKeyboard: UIPickerViewDelegate, UIPickerViewDataSource {
    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return data.count
    }

    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return data[row]
    }

    func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        // ピッカーから選択されたらその値をtextStoreへ入れる
        textStore = data[row]
        setNeedsDisplay()
    }
}

これでシステムキーボードの代わりにピッカーが表示され、ピッカーから文字列を選択できるようになりました。

5. inputAccessoryViewをオーバーライドしてピッカーを閉じるボタンを配置

UIResponderはinputViewの他にinputAccessoryViewというものを持っています。inputAccessoryViewはUITextFieldやUITextViewではキーボードの上にボタンを配置するために使用していたと思います。同じようにピッカーの選択完了ボタンをおきます。

class PickerKeyboard: UIControl {
    // ... 省略

        override var inputAccessoryView: UIView? {
        // キーボードを閉じるための完了ボタン
        let button = UIButton(type: .System)
        button.setTitle("Done", forState: .Normal)
        button.addTarget(self, action: #selector(PickerKeyboard.didTapDone(_:)), forControlEvents: .TouchDown)
        button.sizeToFit()

        // キーボードの上に置くアクセサリービュー
        let view = UIView(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: 44))
        view.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
        view.backgroundColor = .groupTableViewBackgroundColor()

        // ボタンをアクセサリービュー上に設置
        button.frame.origin.x = 16
        button.center.y = view.center.y
        button.autoresizingMask = [.FlexibleRightMargin, .FlexibleBottomMargin, .FlexibleTopMargin]
        view.addSubview(button)

        return view
    }

    // ボタンを押したらresignしてキーボードを閉じる
    func didTapDone(sender: UIButton) {
        resignFirstResponder()
    }    
}

これでピッカーの出し入れが実装できました。

6. 選択された文字列を表示

最後に選択された文字列を"PickerKeyboard"上へ表示させます。UILabelをサブビューとして置くやり方もありますが、ここではdrawRect(_:)内で直接文字列を書いてしまいます。

class PickerKeyboard: UIControl {

    // ...省略    

    // PickerViewで選択されたデータを表示する
    override func drawRect(rect: CGRect) {
        UIColor.blackColor().set()
        UIRectFrame(rect)
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.alignment = .Center
        let attrs: [String: AnyObject] = [NSFontAttributeName: UIFont.systemFontOfSize(17), NSParagraphStyleAttributeName: paragraphStyle]
        NSString(string: textStore).drawInRect(rect, withAttributes: attrs)
    }
}

「最終的に出来上がるもの」で貼ったgifアニメのようなビューを作ることができました。あとはstoryboardなどでUIViewを置いて、クラス名をPickerKeyboardにしましょう。タッチジェスチャーに対してPickerの出し入れができ、選択した文字列がView上に表示されると思います。

コード全体

コード全体はgistの方に書いておきました。
https://gist.github.com/kazuhiro4949/da5c8d5159e4dd8707f766f0661ee892

他のやり方

同じようにUIPickerViewを下からシュッと出すやり方としては、他はこんな感じなのがあるかなぁと思います。

  • UIActionSheetのviewをUIPickerViewに差し替える
    • 昔はこのやり方でやってたけど、今はUIActionSheetはdepricatedな上にUIAlertControllerでは同様のことはできなさそう? (と思ったけど気になってやってみたらiOS9で普通にできました。規約的にどうだろうというのと、レイアウト調整が難しそうですが...)
  • UITextFieldのinputViewとしてUIPickerViewを設定する
    • これでも問題なく実現できる。ただしUITextFieldそのものを使うわけではない場合に若干イケてないのとカスタマイズ性が低い
  • UIViewの上において、UIPresentationControllerでカスタムモーダルとして下から表示させる
    • 一番カスタマイズ性は高い気がするし素直な実装だけれど、まぁまぁ面倒くさい。ActionSheetの上に置いていた頃のような見た目にもできる。

まとめ

UIKeyInputを実装することで、システムキーボードとインタラクションできるカスタムViewを作ることができます。UIResponderはinputViewとinputAccessoryViewの2つを持ち、これをカスタマイズすることでキーボードの代わりに別の何かを出すことができます。また、タッチジェスチャーに対応させるのであれば、UIViewではなくUIControlを継承するのが楽でしょう。

資料

61
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
61
Help us understand the problem. What is going on with this article?