名前付き UIView
Storyboard などで配置した UIView に @IBOutlet
を使わないでアクセスしたいとします。なぜ、@ IBOutlet
を使わないかは大人の事情で割愛するとして、そうした事情でコードなどで特定の UIView をアクセスしたい場合に、Storyboard で配置した UIView やそのサブクラスに、tag に数字を仕込み、 viewWithTag()
で 特定のViewにアクセスする事ができます。
// An integer that you can use to identify view objects in your application.
var tag: Int
// Returns the view whose tag matches the specified value.
func viewWithTag(Int) -> UIView?
最近、またこの大人の事情で、@IBOutlet
を使わないで、特定のViewにアクセスする必要が出てきました。以前と同様tag
を使ってもいいのですが、時代的には String
を使いたいと思いました。そこで、Storyboard の User Runtime Attribute を利用して、名前 name
でアクセスできるような extension を書いたので紹介いたします。
import UIKit
fileprivate var viewNameMap = NSMapTable<UIView, NSString>.weakToStrongObjects()
extension UIView {
@IBInspectable dynamic public var name: String? {
get {
return viewNameMap.object(forKey: self) as String?
}
set {
if let name = newValue {
viewNameMap.setObject(name as NSString, forKey: self)
}
else {
viewNameMap.removeObject(forKey: self)
}
}
}
public func view(named name: String) -> UIView? {
if self.name == name {
return self
}
for subview in self.subviews {
if let view = subview.view(named: name) {
return view
}
}
return nil
}
}
実は全ての UIView に name
プロパティを用意しているわけではなく、name
にwriteアクセスがあった場合に同UIView
とその名前の関係を記憶します。そして、同 View がリリースされる時に、name
もリリースされます。仕組みは、NSMapTable.weakToStrongObjects
を利用してます。
使い方は、先のコードをプロジェクトに組み込んだ状態で、Storyboard から目的の View に名前をつけます。「Identity Inspector」からでも結構ですし、「Attributes Inspector」経由でも結構です。
クライアント側のコードはこんな感じです。.view(named:)
でアクセス可能です。目的のUIView
のサブクラス特有のメソッドやプロパティにアクセスする場合は適切にキャストしてあげる必要があります。
class ViewController: UIViewController {
// ...
override func viewDidLoad() {
super.viewDidLoad()
let cyanView = self.view.view(named: "cyan")
let magentaView = self.view.view(named: "magenta")
let yellowView = self.view.view(named: "yellow")
cyanView?.backgroundColor = UIColor.cyan
magentaView?.backgroundColor = UIColor.magenta
yellowView?.backgroundColor = UIColor.yellow
}
}
ストアドプロパティもどき
応用すれば、自分で好きなストアドプロパティもどきを好きな View に追加可能です。試しに、dictionary プロパティを追加できる extension を用意してみましょう。
import UIKit
fileprivate var viewDictionaryMap = NSMapTable<UIView, NSDictionary>.weakToStrongObjects()
extension UIView {
public var configuration: NSDictionary? {
get {
return viewDictionaryMap.object(forKey: self)
}
set {
if let newValue = newValue {
viewDictionaryMap.setObject(newValue, forKey: self)
}
else {
viewDictionaryMap.removeObject(forKey: self)
}
}
}
}
これは、User Runtime Attribute で Dictionary を指定できないので、Storyboard 自体に情報を仕込む事はできませんが、コードで特定の view に NSDictionary
を set
/get
できるようになります。
時々、こんなようにストアドプロパティを追加する為だけにサブクラスを作りたくなる場合もあるかと思いますが、それだけの理由であれば、こんなトリックを使う方法もあるので紹介しておきます。
また、名前はあまり汎用的な名前を使うと、iOSの将来のバージョンに同じ名前のプロパティが追加される可能性があるので、注意して命名しましょう。
Git
まぁ、小ネタなので、必要ないかもしれませんが、参考までに Git にサンプルを用意いたしました。
Swift
執筆時に環境を記しておくとします。
Xcode Version 11.3 (11C29)
$ swift --version
Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
Target: x86_64-apple-darwin19.0.0