85
87

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-03-07

この記事はVASILY DEVELOPERS BLOGにも同じ内容で投稿しています。よろしければ他の記事もご覧ください。

最近、業務で新しいiOSアプリを立て続けにいくつか開発する機会に恵まれました。
そんな中、いくつもアプリを使っていると、どのアプリでもよく使う処理があぶり出されてきます。
そういう処理はSwiftのExtensionとして別ファイルに書き出し、他のアプリへも切り出しやすいように個別の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にまとまっていると、新しいアプリを作る時にも共通処理を持ってくることができます。
新規開発時に試してみてください。

85
87
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
85
87

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?