tofucodes diary

にほんごのほう

IQKeyboardManagerで親Viewが異なるUITextFieldを兄弟とみなす方法

github.com

課題

Qiitaで紹介されていて使ってみたIQKeyboardManager、すごい便利ですね。

IQKeyboardManagerは、デフォルトでは画像のように別の親Viewに属しているUITextFieldなどを兄弟と見なしてくれません。(つまりToolbarの↑↓でUITextFieldを移動できません。)

f:id:tofucodes:20190117154326p:plain
2つのUITextFieldは兄弟にならない

原因

IQKeyboardManagerのソースコードを見てみます。 UITextField兄弟間をToolbarの↑↓で移動するロジックは、兄弟をresponderViews()という関数で探索していることが分かります。

@objc public var canGoNext: Bool {
    //Getting all responder view's.
    if let textFields = responderViews() {
        if let  textFieldRetain = _textFieldView {
            //Getting index of current textField.
            if let index = textFields.index(of: textFieldRetain) {
                
                //If it is not first textField. then it's previous object canBecomeFirstResponder.
                if index < textFields.count-1 {
                    return true
                }
            }
        }
    }
    return false
}

responderViews()はまず、全てのsuperViewからtoolbarPreviousNextAllowedClassesの配列に含まれるクラスと一致するものを探索します。

もし一致するsuperViewが見つかった場合は、そのViewの全ての子Viewから兄弟を探索し、Viewの配列として返却します。

もし一致するsuperViewが1つも見つからなかった場合は、対象のUITextFieldと同じ階層にある兄弟だけを探索して返却します。

/** Get all UITextField/UITextView siblings of textFieldView. */
private func responderViews()-> [UIView]? {
    
    var superConsideredView : UIView?

    //If find any consider responderView in it's upper hierarchy then will get deepResponderView.
    for disabledClass in toolbarPreviousNextAllowedClasses {
        
        superConsideredView = _textFieldView?.superviewOfClassType(disabledClass)
        
        if superConsideredView != nil {
            break
        }
    }

//If there is a superConsideredView in view's hierarchy, then fetching all it's subview that responds. No sorting for superConsideredView, it's by subView position.    (Enhancement ID: #22)
    if let view = superConsideredView {
        return view.deepResponderViews()
    } else {  //Otherwise fetching all the siblings
//...

つまり、兄弟にしたいUITextFieldを全て、toolbarPreviousNextAllowedClassesに含まれるクラスの子Viewにすれば、兄弟として判別してくれるというロジックのようです。

対策

まず、親View用のカスタムViewクラスを定義します。 今回はTextFieldsContainerViewという名前でクラスを定義しました。

import UIKit

/// Class for letting IQKeyboardManager treat all text fields/views whose parent is this container view
/// as a sibling so that it enables to handle all on its toolbar.
/// We don't need this class all the time but useful when we have text fields/views that have different parent.
/// See also: https://github.com/hackiftekhar/IQKeyboardManager/blob/master/IQKeyboardManagerSwift/IQKeyboardManager.swift#L1716-L1728
class TextFieldsContainerView: UIView {

}

次に、IQKeyboardManagerのtoolbarPreviousNextAllowedClassesに先ほど定義したTextFieldsContainerViewクラスを追加します。

IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses.append(TextFieldsContainerView.self)

最後に、TextFieldsContainerViewクラスをstoryboard/xibやコードで各UITextFieldの親Viewクラスになるように実装します。

f:id:tofucodes:20190117154307p:plain
2つのUITextFieldは兄弟と見なされる