[iOS] バージョンアップでも対応可能!?Closureを使った再帰でUIViewの階層を取得する方法(UISearchBar / UITableViewCell編)

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは!モバイルアプリ開発部の田中孝明です。

WWDC2016も終わり各所で振り返りが行われる頃でしょうか?

弊社でもWWDC2016の現地レポートをアップしております!
また、6/24に弊社でAKIBA.swift 第3回としてWWDC2016の振り返り会を行いますので是非お越しください!

開発者も次のバージョンのことを踏まえて色々準備しなければならない頃合いですね!
そこで今回のブログが少しでも役に立てばと思います。

UIViewのカスタマイズについて

UISearchBarなどが持っているsubviewを調べる際、プライベートメソッドのrecursiveDescriptionでView階層をダンプして目的のViewを探し、階層順にViewを指定する方法があります。
しかし、Viewの構造はバージョンによって変わる可能性があり、あまり効率のよい方法とは言えません。

そういう時はclosureを再帰呼び出しして目的のViewを取り出す方法が便利です。

Subviewsの特定のViewを取り出したい時

UISearchBarのカーソルの色を変えたい場合を例にします。
カーソルの色を変えるためにはUISearchBarのsubviewsのUITextFieldを探しだしてtintColorを変更する必要があります。

そこで以下のコードを実装します。

extension UISearchBar {
    func greenCursorStyle() {
        var recursiveDetectSubviews: ((UIView) -> Void)?

        recursiveDetectSubviews = { view in
            view.subviews.forEach { subview in

                if let textField = subview as? UITextField {
                    textField.tintColor = UIColor.greenColor()
                } else {
                    recursiveDetectSubviews?(subview)
                }
            }
        }

        recursiveDetectSubviews?(self)
    }
}

やっている処理は単純です。
recursiveDetectSubviewsを再帰呼び出しし、UITextFieldを探しだしています。
こうすることでiOSのバージョンアップの伴いUITextFieldの階層が変わっても対応することが出来ます。

searchBar.greenCursorStyle()

スクリーンショット 2016-06-19 23.39.48

superviewの特定のViewを取り出したい時

カスタムのUITableViewCellを作成する場合を例にします。
独自のボタンを実装し、Cell自身のNSIndexPathを返したい場合、UITableViewindexPathForCellを使います。
その際にUITableViewCellのsuperviewであるUITableViewを辿る必要があります。

@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var actionButton: UIButton!

var didTouchUpInside: ((indexPath: NSIndexPath?) -> Void)?

override func awakeFromNib() {
    super.awakeFromNib()

    actionButton.addTarget(
        self,
        action: #selector(actionButtonTouchUpInside(_:)),
        forControlEvents: .TouchUpInside)
}

ボタンを押下した際の処理でsuperviewを再帰で探す処理を実装します。

func actionButtonTouchUpInside(sender: AnyObject) {
    var recursiveDetectSuperview: ((UIView) -> Void)?

    recursiveDetectSuperview = { [weak self] view in
        guard let `self` = self else { return }
        guard let superview = view.superview else { return }

        if let tableView = superview as? UITableView {
            let indexPath = tableView.indexPathForCell(self)
            self.didTouchUpInside?(indexPath: indexPath)
        } else {
            recursiveDetectSuperview?(superview)
        }
    }

    recursiveDetectSuperview?(self)
}

これでUITableViewの階層が変わっても対応することができます。

まとめ

あたらしいバージョンのOSが出ると、必ず下位互換を考慮したコードを書く必要が出てきます。
どのバージョンでも動くコードを書くことで、新OSバージョン対応の際に少しでも楽をしたいですよね!

リンク集