2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

<Swift>UITextFieldとUIPickerViewをRxSwiftでバインドさせる方法(閉じるボタン付き、カーソルなし)

Last updated at Posted at 2020-07-23
TodolistEditViewController.swift
class TodolistEditViewController: UIViewController {

    @IBOutlet weak var categoryTextField: UITextField!
        
    override func viewDidLoad() {
        super.viewDidLoad()
        setupView()
    }
    
    private func setupView(){
        // Picker
        let pickerView = UIPickerView()
        categoryTextField.inputView = pickerView
        let strs = ["カテゴリーなし", "カテゴリー1", "カテゴリー2"]
        Observable.just(strs)
            .bind(to: pickerView.rx.itemTitles) { _, str in
                return str
        }.disposed(by: disposeBag)
        pickerView.rx.modelSelected(String.self)
            .map { strs in
                return strs.first
            }
            .bind(to: categoryTextField.rx.text)
            .disposed(by: disposeBag)
        
        // Pickerのヘッダー
        let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 35))
        let spacelItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
        let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: nil)
        toolbar.setItems([spacelItem, doneItem], animated: true)
        categoryTextField.inputAccessoryView = toolbar
        let doneItemObservable = doneItem.rx.tap.asObservable()
        doneItemObservable.subscribe(
            onNext: { [weak self] in
                // pickerを閉じるには、TextFieldの編集モードを終了させる
                self?.categoryTextField.endEditing(true)
            }
        ).disposed(by: disposeBag)
    }
}

上記のままでは、テキストフィールドに対してUIPickerView以外からの入力も受け付けてしまう。
そこで、以下の対処を入れる。

TodolistEditViewController.swift
class TodolistEditViewController: UIViewController {
    private func setupView(){
        ...
        // UIPickerView以外からの入力を拒否するためのdelegate実装
        categoryTextField.delegate = self
        // 上記だけではカーソルは消えないので、カーソルの色をクリアにする
        categoryTextField.tintColor = UIColor.clear
        ...
    }
}

extension TodolistEditViewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        return false
    }
}

ただし、TextFieldを長押しするとcopy/cut/paseteのメニューが表示される。
これを防ぐには、以下のようにカスタムViewにするしか方法がなさそう。
https://riptutorial.com/ja/ios/example/30704/
https://qiita.com/Simmon/items/f9d60ab51cc6b0b4b3bc

■補足1
初期値をUIPickerViewに設定したい場合は以下を追加する
※UITextFieldには自動的に反映される

TodolistEditViewController.swift
class TodolistEditViewController: UIViewController {
    private func setupView(){
        ...
        let selectedRow = 1
        pickerView.selectRow(selectedRow, inComponent: 0, animated: false)
        pickerView.delegate?.pickerView!(pickerView,
        didSelectRow: selectedRow,
        inComponent: 0)
        ...
    }
}

■補足2
UIPickerViewにキャンセルボタンを設定したい場合は以下のように変更する

TodolistEditViewController.swift
class TodolistEditViewController: UIViewController {
    private let pickerView = UIPickerView()
    ...
    private func setupView(){
        ...
        // カテゴリーPickerのヘッダー
        let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 35))
        let cancelItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: nil)
        let spaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
        let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: nil)
        toolbar.setItems([cancelItem,spaceItem, doneItem], animated: true)
        categoryTextField.inputAccessoryView = toolbar
        // ヘッダーのキャンセルボタン
        let cancelItemObservable = cancelItem.rx.tap.asObservable()
        cancelItemObservable.subscribe(
            onNext: { [weak self] in
                // 初期値に戻す
                self?.resetCategoryTextField()
                // pickerを閉じる
                self?.categoryTextField.endEditing(true)
            }
        ).disposed(by: disposeBag)
        // ヘッダーの決定ボタン
        let doneItemObservable = doneItem.rx.tap.asObservable()
        doneItemObservable.subscribe(
            onNext: { [weak self] in
                // pickerを閉じる
                self?.categoryTextField.endEditing(true)
            }
        ).disposed(by: disposeBag)
        ...
    }

    private func resetCategoryTextField(){
        // ドラムロールを初期値に戻す
        // TODO idから動的に取得
        let selectedRow = 1
        pickerView.selectRow(selectedRow, inComponent: 0, animated: false)
        pickerView.delegate?.pickerView!(pickerView,
        didSelectRow: selectedRow,
        inComponent: 0)
    }
}

参考にしたサイト
https://culumn.hatenablog.com/entry/2018/06/07/120000
https://tech.studyplus.co.jp/entry/2018/10/15/114548
http://www.366service.com/jp/qa/d46ff39ee4fe4c17803c9c7affb4495f
https://www.egao-inc.co.jp/programming/swift_uipickerview/
https://grandbig.github.io/blog/2018/10/13/popuppickerview/
https://makotton.com/2016/10/13/1544
https://qiita.com/kazuhiro4949/items/d20f08343937cab82390
https://xyk.hatenablog.com/entry/2014/10/02/105147
https://qiita.com/wai21/items/765206fdc37b2c258481
https://ja.stackoverflow.com/questions/28246/
https://stackoverflow.com/questions/50460554/rxswift-set-initial-value-for-uipickerview

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?