Edited at

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

More than 1 year has passed since last update.


この記事は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にまとまっていると、新しいアプリを作る時にも共通処理を持ってくることができます。

新規開発時に試してみてください。