iOSエンジニアの庄司 (@WorldDownTown) です。
最近、業務で新しいiOSアプリを立て続けにいくつか開発する機会に恵まれました。 そんな中、いくつもアプリを使っていると、どのアプリでもよく使う処理があぶり出されてきます。 そういう処理はSwiftのExtensionとして別ファイルに書き出し、他のアプリへも切り出しやすいように個別のFrameworkにして管理しています。 Frameworkの管理については過去のこちらの記事を参考にしてみてください。
今記事では、最近の開発でよく使ったExtension集をご紹介します。
Swift標準ライブラリ
Date
private let formatter: DateFormatter = { let formatter: DateFormatter = DateFormatter() formatter.timeZone = NSTimeZone.system formatter.locale = Locale(identifier: "en_US_POSIX") formatter.calendar = Calendar(identifier: .gregorian) return formatter }() public extension Date { // Date→String func string(format: String = "yyyy-MM-dd'T'HH:mm:ssZ") -> String { formatter.dateFormat = format return formatter.string(from: self) } // String → Date init?(dateString: String, dateFormat: String = "yyyy-MM-dd'T'HH:mm:ssZ") { formatter.dateFormat = dateFormat guard let date = formatter.date(from: dateString) else { return nil } self = date } } Date().string(format: "yyyy/MM/dd") // 2017/02/26 Date(dateString: "2016-02-26T10:17:30Z") // Date
同じモデルクラスを使って日付を表示する場合でも、リストページでは日付だけ、詳細ページでは時間も表示したいというときに便利です。
またイニシャライザの方は、ユーザーが入力した日付文字列から Date
インスタンスを作ることができます。
Dictionary
// Dictionary同士を`+`演算子でマージできるようにする public func +<K, V>(lhs: [K: V], rhs: [K: V]) -> [K: V] { var lhs = lhs for (key, value) in rhs { lhs[key] = value } return lhs } ["key1": 0] + ["key1": 1, "key2": 2] // ["key2": 2, "key1": 1]
APIリクエスト時の動的パラメータと固定のパラメータのDictionaryをマージするときなどに使います。
Int
private let formatter: NumberFormatter = NumberFormatter() public extension Int { private func formattedString(style: NumberFormatter.Style, localeIdentifier: String) -> String { formatter.numberStyle = style formatter.locale = Locale(identifier: localeIdentifier) return formatter.string(from: self as NSNumber) ?? "" } // カンマ区切りString var formattedJPString: String { return formattedString(style: .decimal, localeIdentifier: "ja_JP") } // 日本円表記のString var JPYString: String { return formattedString(style: .currency, localeIdentifier: "ja_JP") } // USドル表記のString var USDString: String { return formattedString(style: .currency, localeIdentifier: "en_US") } } let million: Int = 1_000_000 million.formattedJPString // 1,000,000 million.JPYString // ¥1,000,000 million.USDString // $1,000,000.00
どのアプリも価格を表示するところは多数あると思いますが、計算型プロパティ一つで面倒なカンマ区切り処理を書けるので、コードがかなりスッキリします。
UIKit
UIColor
public extension UIColor { // RGBのイニシャライザ public convenience init(rgb: UInt, alpha: CGFloat = 1.0) { let red: CGFloat = CGFloat((rgb & 0xff0000) >> 16) / 255.0 let green: CGFloat = CGFloat((rgb & 0x00ff00) >> 8) / 255.0 let blue: CGFloat = CGFloat(rgb & 0x0000ff) / 255.0 self.init(red: red, green: green, blue: blue, alpha: alpha) } public struct iq { // プロジェクトに合わせた名前で良い public static let pink: UIColor = UIColor(rgb: 0xfa4664) public static let textBlack: UIColor = UIColor(rgb: 0x333333) } } UIColor.iq.pink // #fa4664 UIColor.iq.textBlack // #333333
一つのプロジェクトで使う色数は、数え切れる程度になることが多いので、UIColor
のExtensionに名前を付けて管理します。
Extensionの中でstructを定義して、その中に色をまとめることで、UIColor
の本来の名前空間を汚してしまうことがなくなりますし、開発者が拡張しているというのがコードの読み手にも伝わりやすくなります。
UIView
public extension UIView { // 子Viewを親Viewのサイズいっぱいに表示するための制約を設定する func addConstraints(for childView: UIView, insets: UIEdgeInsets = .zero) { childView.translatesAutoresizingMaskIntoConstraints = false topAnchor.constraint(equalTo: childView.topAnchor, constant: insets.top).isActive = true bottomAnchor.constraint(equalTo: childView.bottomAnchor, constant: insets.bottom).isActive = true leadingAnchor.constraint(equalTo: childView.leadingAnchor, constant: insets.left).isActive = true trailingAnchor.constraint(equalTo: childView.trailingAnchor, constant: insets.right).isActive = true } } view.addSubview(childView) view.addConstraints(for: childView) let insets: UIEdgeInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) view.addConstraints(for: childView, insets: insets)
Interface Builderではなく、コードでviewをaddSubview
するとき、親Viewと同じサイズにしたいときに使います。
デフォルト引数は省略可能ですが、UIEdgeInsets
でマージンを指定することもできます。
UIViewController
public extension UIViewController { // ViewControllerのファクトリーメソッド static func create() -> Self { let name: String = "\(type(of: self))".components(separatedBy: ".").first! return instantiate(storyboardName: name) } private static func instantiate<T>(storyboardName: String) -> T { let storyboard: UIStoryboard = UIStoryboard(name: storyboardName, bundle: nil) let vc: UIViewController? = storyboard.instantiateInitialViewController() return vc as! T } } let vc = SomeViewController.create()
VASILYのiOS開発では、 1ViewController / 1Storyboard でViewControllerを管理し、ViewControllerのクラス名とStoryboardのファイル名を揃えるルールで運用しています。 そのため、文字列を使わずにViewControllerを初期化することができます。
サードパーティライブラリ
SVProgressHUD
import SVProgressHUD public extension SVProgressHUD { public struct iq { // プロジェクトに合わせた名前で良い // プロジェクト固有の初期設定 public static func setup() { SVProgressHUD.setDefaultStyle(.custom) SVProgressHUD.setFont(UIFont.boldSystemFont(ofSize: 14.0)) SVProgressHUD.setForegroundColor(UIColor.iq.pink) SVProgressHUD.setBackgroundColor(UIColor.white.withAlphaComponent(0.9)) SVProgressHUD.setMinimumDismissTimeInterval(2.0) } public static func show(maskType: SVProgressHUDMaskType = .none) { SVProgressHUD.setDefaultMaskType(maskType) SVProgressHUD.show() } } }
サードパーティライブラリは、アプリ全体で設定を有効にするために、AppDelegateに処理を書くことがよくあります。
複数のライブラリを使っていると、application(_:didFinishLaunchingWithOptions:) -> Bool
が 各ライブラリの初期設定処理で膨れ上がってしまいます。
上記のExtensionは、複数行の処理をExtensionにまとめることによってこの問題を回避できるようになります。
ここでも、サードパーティライブラリの名前空間を汚さないように処理をstructの中に分けて書いています。
// AppDelegate func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [Hashale: Any]) -> Bool { SVProgressHUD.iq.setup() ... return true }
また、現在のSVProgressHUDはMaskTypeがグローバルに設定されてしまうため、MaskTypeを変更したいときは、setDefaultMaskType(_:)
を実行してから表示する必要があります。
このExtensionでは2行必要な処理を一つのメソッドでMaskTypeを引数に取れるように変更しています。
小さな変更ですが、SVProgressHUDはどのアプリでも頻繁に登場するため、冗長なコードを減らすことができます。
// 従来 SVProgressHUD.setDefaultMaskType(.clear) SVProgressHUD.show() SVProgressHUD.setDefaultMaskType(.none) SVProgressHUD.show() // Extension SVProgressHUD.iq.show(maskType: .clear) SVProgressHUD.iq.show() // デフォルト値:.none
まとめ
iOSアプリ開発でよくある処理のExtensionの数々を紹介しました。 これらの処理が一つのFrameworkにまとまっていると、新しいアプリを作る時にも共通処理を持ってくることができます。 新規開発時に試してみてください。
現在VASILYでは、IQON以外にも新規でいくつかiOS/Androidアプリを開発しています。 ゼロからアプリを一緒に開発してくれるiOSエンジニアを募集しています。興味がある方は以下のリンクをご覧ください。