iOSアプリの開発効率をあげるSwiftの便利Extension
Swift の Extension を使うと既存クラスや自作クラスを拡張することができます。
会社や個人のプロジェクトでいつも使っている便利な拡張コードをまとめてみます。
便利な Extension の紹介
クラス名の取得
そのクラス・インスタンスのクラス名を取得するextensionです。
extension NSObject { class var className: String { get { return NSStringFromClass(self).componentsSeparatedByString(".").last! } } var className: String { get { return self.dynamicType.className } } }
以下のような感じで使います。
MyCustomClass.className // "MyCustomClass" let myCell = MyCustomClass() print(myCell.className) // "MyCustomClass"
TableViewCell の登録と利用
自作クラスを UITableView で扱う場合、
- あらかじめ
registerNib
やregisterClass
で登録し、 dequeueReusableCellWithIdentifier
して利用する
ことが多いですが、それを簡潔に書けるようにする拡張です。
extension UITableView { func registerCell<T: UITableViewCell>(type: T.Type) { let className = type.className let nib = UINib(nibName: className, bundle: nil) registerNib(nib, forCellReuseIdentifier: className) } func dequeueCell<T: UITableViewCell>(type: T.Type, indexPath: NSIndexPath) -> T { return self.dequeueReusableCellWithIdentifier(type.className, forIndexPath: indexPath) as! T } }
自分は「カスタムのCellを扱う場合、ReuseIdentifier はそのカスタムCellのクラス名にする」というルールで統一していて、それに準じたコードとなっています。
以下のように使います。
tableView.registerCell(MyCustomCell.self) // 登録 let cell = tableView.dequeueCell(MyCustomCell.self) // 利用
CollectionView の登録と利用を簡単に
上述したものの CollectionView 版です。
UICollectionViewCell に加えて、ReusableView についても定義しています。
extension UICollectionView { func registerCell<T: UICollectionViewCell>(type: T.Type) { let className = type.className let nib = UINib(nibName: type.className, bundle: nil) registerNib(nib, forCellWithReuseIdentifier: className) } func registerReusableView<T: UICollectionReusableView>(type: T.Type, kind: String) { let className = type.className let nib = UINib(nibName: className, bundle: nil) registerNib(nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: className) } func dequeueCell<T: UICollectionViewCell>(type: T.Type, forIndexPath indexPath: NSIndexPath) -> T { return dequeueReusableCellWithReuseIdentifier(type.className, forIndexPath: indexPath) as! T } func dequeueReusableView<T: UICollectionReusableView>(kind: String, type: T.Type, indexPath: NSIndexPath) -> T { return dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: type.className, forIndexPath: indexPath) as! T } }
使い方は以下の通り。
// Cell collectionView.registerCell(MyCustomCollectionViewCell.self) let cell = collectionView.dequeueCell(MyCustomCollectionViewCell.self) // ReusableView collectionView.registerReusableView(MyCustomReusableView.self, kind: UICollectionElementKindSectionHeader ) let view = collectionView.dequeueReusableView(UICollectionElementKindSectionHeader, type: MyCustomReusableView.self, indexPath: indexPath)
UIColor を使いやすく
Hexで色を指定したり、アプリでよく使うkeyColorを登録してます。
extension UIColor { class func color(hex: Int, alpha: Double = 1.0) -> UIColor { let red = Double((hex & 0xFF0000) >> 16) / 255.0 let green = Double((hex & 0xFF00) >> 8) / 255.0 let blue = Double((hex & 0xFF)) / 255.0 return UIColor(red: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: CGFloat(alpha)) } class func defaultColor(alpha: Double = 1.0) -> UIColor { return UIColor.color(0x00ACED, alpha: alpha) } }
// 使い方 let color = UIColor.color(0xCCCCCC) let defaultColor = UIColor.defaultColor()
現在表示中の ViewController を探す
presentedViewController をできるだけ探索し、表示中の ViewController を返します。
extension UIApplication { func topViewController() -> UIViewController? { if var topViewController = UIApplication.sharedApplication().keyWindow?.rootViewController { while (topViewController.presentedViewController != nil) { topViewController = topViewController.presentedViewController! } return topViewController } else { return nil } } }
UIAlertController と組み合わせて、以下のように使っています。
let alertController = UIAlertController(title: "成功!", message: "正常に削除できました", preferredStyle: .Alert) alertController.addAction(UIAlertAction( title: "OK", style: .Cancel, handler: nil)) UIApplication.sharedApplication().topViewController()? .presentViewController(alertController, animated: true, completion: nil)
UIAlertController は必ず ViewController から presentViewController
で呼ぶ必要があるため、topViewController() で取得できるようにしておくと昔のUIAlertViewのように使えて便利ですね。
ちなみに UIAlertView / UIActionSheet は iOS 8 から deprecated となっています。
NSUserDefaults で管理する値を拡張で
「チュートリアルが完了したか」「初回起動のユーザーか」などの管理に NSUserDefaults を使っていますが、そのフラグを extension のプロパティとして定義してます。
private let isFirstSessionDoneKey = "IS_FIRST_SESSION_DONE" extension NSUserDefaults { var isFirstSessionDone: Bool? { get { return self.objectForKey(isFirstSessionDoneKey) as? Bool } set { self.setObject(true, forKey: isFirstSessionDoneKey) self.synchronize() } } }
フラグ系の処理がバラバラにならずまとめて書けるので気に入ってます。管理する数が増えてきたら専用のクラスを作った方が良いかと思います。
// 使い方 NSUserDefaults.standardUserDefaults.isFirstSessionDone = true
Storyboard からのインスタンス作成
開発時の規則として、
- 基本的に1Storyboard - 1ViewController
- Storyboardの名前と、initial な ViewController のクラス名を同じにする
というルールがあり、その上で以下の初期化メソッドを定義しています。 (これは extension ではなく大域関数になります)
func instantiate<T: UIViewController where T: NSObject>(_: T.Type) -> T { let storyboard = UIStoryboard(name: T.className, bundle: nil) return storyboard.instantiateInitialViewController() as! T } func instantiate<T: UIViewController where T: NSObject>(_: T.Type, storyboard: String) -> T { let storyboard = UIStoryboard(name: storyboard, bundle: nil) return storyboard.instantiateViewControllerWithIdentifier(T.className) as! T }
使い方は以下の通り:
// initial な ViewController を取得 let topVC = instantiate(TopViewController.self) // initial でない ViewController を取得 // (1Storyboard - 1ViewController でない特別な場合でのみ利用) let topDetailVC = instantiate(TopDetailViewController.self, storyboard: "TopViewController")
詳細については以下のQiitaが詳しいです。
Nib からのインスタンス作成
カスタムのUIViewなどを扱う場合の記述です。
Storyboard と同じく、「MyCustomView」というクラス名の場合は「MyCustomView.xib」というファイル名にする規則を設けています。
func instantiateFromNib<T: UIView, U: AnyObject where T: NSObject>(_: T.Type, owner: U) -> T { return UINib(nibName: T.className, bundle: nil).instantiateWithOwner(owner, options: nil)[0] as! T }
使い方は以下の通りです。
let myCustomView = instantiateFromNib(MyCustomView.self, owner: self)
追記: namespaceの衝突について
既存クラスを extension で拡張する場合、メソッド名の衝突を気にする必要があります。
自分のいまの環境だと小規模&1人プロジェクトが多いためそのまま使ってますが、規模が大きくなったら Embedded Framework で細かく分けようかと考えていました。
ただ、誤って同名で変数やメソッドを定義してしまった場合にはコンパイルエラーを吐いてくれてそこで気づけるので、そこまで気にしなくても良いのかもしれません。
まとめ
自分がいつも使う extension を紹介しました。 よく使う処理を簡潔に書けるほか、Storyboard の命名規則や初期化の方法など、extension そのものがコーディング規約となる側面もあり、非常に有用だと感じています。
紹介したすべてのコードは以下のGistからお使いいただけます。
ほかにも便利な extension があるぞ、という方はぜひ教えてください :)
参考資料
おすすめ書籍
基礎から上級へのレベルアップに。
ステップごとに丁寧でわかりやすく、入門の方におすすめです。