新しいアプリを作るときによく使うSwift Extension集

> この記事は[VASILY DEVELOPERS BLOG](http://tech.vasily.jp/entry/swift_extensions)にも同じ内容で投稿しています。よろしければ他の記事もご覧ください。

最近、業務で新しいiOSアプリを立て続けにいくつか開発する機会に恵まれました。
そんな中、いくつもアプリを使っていると、どのアプリでもよく使う処理があぶり出されてきます。
そういう処理はSwiftのExtensionとして別ファイルに書き出し、他のアプリへも切り出しやすいように個別のFrameworkにして管理しています。

今記事では、最近の開発でよく使ったExtension集をご紹介します。

<!-- more -->

<br />

# Swift標準ライブラリ
## Date

```swift
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` インスタンスを作ることができます。

<br />

## Dictionary

```swift
// 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をマージするときなどに使います。

<br />

## Int

```swift
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
```

どのアプリも価格を表示するところは多数あると思いますが、計算型プロパティ一つで面倒なカンマ区切り処理を書けるので、コードがかなりスッキリします。

<br />

# UIKit
## UIColor

```swift
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`の本来の名前空間を汚してしまうことがなくなりますし、開発者が拡張しているというのがコードの読み手にも伝わりやすくなります。

<br />

## UIView

```swift
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`でマージンを指定することもできます。

<br />

## UIViewController

```swift
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を初期化することができます。

<br />

# サードパーティライブラリ
## SVProgressHUD

```swift
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の中に分けて書いています。

```swift
// AppDelegate

func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [Hashale: Any]) -> Bool {
    SVProgressHUD.iq.setup()
    ...
    return true
}
```

<br />

また、現在のSVProgressHUDはMaskTypeがグローバルに設定されてしまうため、MaskTypeを変更したいときは、`setDefaultMaskType(_:)`を実行してから表示する必要があります。
このExtensionでは2行必要な処理を一つのメソッドでMaskTypeを引数に取れるように変更しています。
小さな変更ですが、SVProgressHUDはどのアプリでも頻繁に登場するため、冗長なコードを減らすことができます。

```swift
// 従来
SVProgressHUD.setDefaultMaskType(.clear)
SVProgressHUD.show()

SVProgressHUD.setDefaultMaskType(.none)
SVProgressHUD.show()

// Extension
SVProgressHUD.iq.show(maskType: .clear)
SVProgressHUD.iq.show()    // デフォルト値：.none
```

<br />

# まとめ
iOSアプリ開発でよくある処理のExtensionの数々を紹介しました。
これらの処理が一つのFrameworkにまとまっていると、新しいアプリを作る時にも共通処理を持ってくることができます。
新規開発時に試してみてください。