使うと手放せなくなるSwift Extension集 (Swift2版)


`2016/8/15更新`

汎用性高めのExtension集です。後半にライブラリもまとめました。
Swift2.2で確認済みです。

紹介している全てのextensionを導入したリポジトリを公開しています（紹介していないものも含みます）。
詳しくは下記をご覧ください。
[iOSアプリをハッカソンで作るときに便利な初期プロジェクトを作ってみた](http://qiita.com/tattn/items/c0e3940781eefa69ec52)

Swift3版は[こちら](http://qiita.com/tattn/items/bdce2a589912b489cceb)です。

## クラス名の取得
```extension
extension NSObject {
    class var className: String {
        return String(self)
    }

    var className: String {
        return self.dynamicType.className
    }
}

```
```usage
MyClass.className   //=> "MyClass"
MyClass().className //=> "MyClass"
```

## XIBの登録・取り出し
XIBファイルとクラスを同じ名前にして利用してください。
上記の「クラス名の取得」を利用しています。
### UITableView
```extension
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 registerCells<T: UITableViewCell>(types: [T.Type]) {
        types.forEach { registerCell($0) }
    }

    func dequeueCell<T: UITableViewCell>(type: T.Type, indexPath: NSIndexPath) -> T {
        return self.dequeueReusableCellWithIdentifier(type.className, forIndexPath: indexPath) as! T
    }
}
```
```usage
tableView.registerCell(MyCell.self)
tableView.registerCells([MyCell1.self, MyCell2.self])

let cell = tableView.dequeueCell(MyCell.self)
```

### UICollectionView
```extension
extension UICollectionView {
    func registerCell<T: UICollectionViewCell>(type: T.Type) {
        let className = type.className
        let nib = UINib(nibName: className, bundle: nil)
        registerNib(nib, forCellWithReuseIdentifier: className)
    }
    
    func registerCells<T: UICollectionViewCell>(types: [T.Type]) {
        types.forEach { registerCell($0) }
    }

    func registerReusableView<T: UICollectionReusableView>(type: T.Type, kind: String = UICollectionElementKindSectionHeader) {
        let className = type.className
        let nib = UINib(nibName: className, bundle: nil)
        registerNib(nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: className)
    }
    
    func registerReusableViews<T: UICollectionReusableView>(types: [T.Type], kind: String = UICollectionElementKindSectionHeader) {
        types.forEach { registerReusableView($0, kind: kind) }
    }

    func dequeueCell<T: UICollectionViewCell>(type: T.Type, forIndexPath indexPath: NSIndexPath) -> T {
        return dequeueReusableCellWithReuseIdentifier(type.className, forIndexPath: indexPath) as! T
    }

    func dequeueReusableView<T: UICollectionReusableView>(type: T.Type, indexPath: NSIndexPath, kind: String = UICollectionElementKindSectionHeader) -> T {
        return dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: type.className, forIndexPath: indexPath) as! T
    }
}
```
```usage
collectionView.registerCell(MyCell.self)
collectionView.registerCells([MyCell1.self, MyCell2.self])
let cell = collectionView.dequeueCell(MyCell.self)

collectionView.registerReusableView(MyReusableView.self)
collectionView.registerReusableViews([MyReusableView1.self, MyReusableView2.self])
let view = collectionView.dequeueReusableView(type: MyReusableView.self, indexPath: indexPath)
```


## 16進数でUIColorの作成
```extension
extension UIColor {
    convenience init(hex: Int, alpha: Double = 1.0) {
        let r = CGFloat((hex & 0xFF0000) >> 16) / 255.0
        let g = CGFloat((hex & 0x00FF00) >> 8) / 255.0
        let b = CGFloat(hex & 0x0000FF) / 255.0
        self.init(red: r, green: g, blue: b, alpha: CGFloat(alpha))
    }
}
```
```usage
let color = UIColor.color(0xAABBCC)
```

## 最前面のUIViewControllerを取得
```extension
extension UIApplication {
    var topViewController: UIViewController? {
        guard var topViewController = UIApplication.sharedApplication().keyWindow?.rootViewController else { return nil }

        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }
    
    var topNavigationController: UINavigationController? {
        return topViewController as? UINavigationController
    }
}
```
```usage
UIApplication.sharedApplication().topViewController
```

## StoryboardのViewControllerを生成
```extension
protocol StoryBoardHelper {}

extension StoryBoardHelper where Self: UIViewController {
    static func instantiate() -> Self {
        let storyboard = UIStoryboard(name: self.className, bundle: nil)
        return storyboard.instantiateViewControllerWithIdentifier(self.className) as! Self
    }

    static func instantiate(storyboard: String) -> Self {
        let storyboard = UIStoryboard(name: storyboard, bundle: nil)
        return storyboard.instantiateViewControllerWithIdentifier(self.className) as! Self
    }
}

extension UIViewController: StoryBoardHelper {}
```
```usage
MyViewController.instantiate() // Storyboardファイルとクラスが同じ名前の場合
MyViewController.instantiate("MyStoryboard")
```
※ 上記の「クラス名の取得」を利用しています。

## XIBのViewを生成
```extension
protocol NibHelper {}

extension NibHelper where Self: UIView {
    static func instantiate() -> Self {
        let nib = UINib(nibName: self.className, bundle: nil)
        return nib.instantiateWithOwner(nil, options: nil)[0] as! Self
    }
}

extension UIView: NibHelper {}
```
```usage
MyView.instantiate()
```
※ XIBファイルとクラスは同じ名前にしてください。
※ 上記の「クラス名の取得」を利用しています。

## 子Viewを全て削除
```extension
extension UIView {
    func removeAllSubviews() {
        for subview in self.subviews {
            subview.removeFromSuperview()
        }
    }
}
```
```usage
view.removeAllSubViews()
```

## Selectorを集約化
```extension
private extension Selector {
    static let buttonTapped = #selector(MyViewController.buttonTapped(_:))
}
```
```usage
let button = UIButton()
button.addTarget(self, action: .buttonTapped, forControlEvents: .TouchUpInside)
```

## 配列でオブジェクトのインスタンスを検索して削除

```swift:extension
extension Array where Element: Equatable {
    mutating func remove(element element: Element) -> Bool {
        guard let index = indexOf(element) else { return false }
        removeAtIndex(index)
        return true
    }

    mutating func remove(elements elements: [Element]) {
        for element in elements {
            remove(element: element)
        }
    }
}
```
```usage
let array = ["foo", "bar"]
array.remove(element: "foo")
```

## Out of Rangeを防いで、要素を取得
```swift:extension
extension CollectionType {
    subscript (safe index: Index) -> Generator.Element? {
        get {
            return indices.contains(index) ? self[index] : nil
        }
    }
}
```
```usage
let array = [0, 1, 2]
if let item = array[safe: 5] {
    // unreachable
}
```

## ２つのDictionaryを結合
```swift:extension
extension Dictionary {
    mutating func merge<S: SequenceType where S.Generator.Element == (Key, Value)>(sequence: S) {
        for (key, value) in sequence {
            self[key] = value
        }
    }
}
```
```usage
var dic1 = ["key1": 1]
let dic2 = ["key2": 2]
dic1.merge(dic2) // => ["key1": 1, "key2": 2]
```

## 指定範囲内に値を収める
Viewの配置をコードで実装するときに便利です。

```swift:extension
extension Comparable {
    func clamped(min min: Self, max: Self) -> Self {
        if self < min {
            return min
        }

        if self > max {
            return max
        }

        return self
    }
}
```
```usage
let x: CGFloat = 20
x.clamped(min: 0, max: 10) // => 10
```

## NSLocalizedStringを使いやすくする
```swift:extension
extension String {
    var localized: String {
        return NSLocalizedString(self, comment: self)
    }

    func localizedWithOption(tableName tableName: String? = nil, bundle: NSBundle = NSBundle.mainBundle(), value: String = "") -> String {
        return NSLocalizedString(self, tableName: tableName, bundle: bundle, value: value, comment: self)
    }
}
```
```usage
let message = "Hello".localized
```

## クラスのプロパティを全て出力
```swift:extension
extension NSObjectProtocol where Self: NSObject {
    var description: String {
        let mirror = Mirror(reflecting: self)
        return mirror.children.map { element -> String in
            let key = element.label ?? "Unknown"
            let value = element.value
            return "\(key): \(value)"
        }
        .joinWithSeparator("\n")
    }
}
```
```usage
class Hoge: NSObject {
    var foo = 1
    let bar = "bar"
   }
}

Hoge().description // => "foo: 1\nbar: bar"
```

## 文字列からNSURLを取得
```swift:extension
extension String {
    var url: NSURL? {
        return NSURL(string: self)
    }
}
```
```usage
if let url = "https://example.com".url {
}
```

## UIAlertControllerをBuilderパターンっぽく扱う
Androidアプリも書いている方は欲しくなるはずです。。。

```swift:extension
extension UIAlertController {

    func addAction(title title: String, style: UIAlertActionStyle = .Default, handler: (UIAlertAction -> Void)? = nil) -> Self {
        let okAction = UIAlertAction(title: title, style: style, handler: handler)
        addAction(okAction)
        return self
    }

    func addActionWithTextFields(title title: String, style: UIAlertActionStyle = .Default, handler: ((UIAlertAction, [UITextField]) -> Void)? = nil) -> Self {
        let okAction = UIAlertAction(title: title, style: style) { [weak self] action in
            handler?(action, self?.textFields ?? [])
        }
        addAction(okAction)
        return self
    }

    func configureForIPad(sourceRect: CGRect, sourceView: UIView? = nil) -> Self {
        popoverPresentationController?.sourceRect = sourceRect
        if let sourceView = UIApplication.sharedApplication().topViewController?.view {
            popoverPresentationController?.sourceView = sourceView
        }
        return self
    }

    func configureForIPad(barButtonItem: UIBarButtonItem) -> Self {
        popoverPresentationController?.barButtonItem = barButtonItem
        return self
    }

    func addTextField(handler: UITextField -> Void) -> Self {
        addTextFieldWithConfigurationHandler(handler)
        return self
    }

    func show() {
        UIApplication.sharedApplication().topViewController?.presentViewController(self, animated: true, completion: nil)
    }
}
```
```swift:usage
UIAlertController(title: "ログイン", message: "IDを入力してください", preferredStyle: .Alert)
.addTextField { textField in
    textField.placeholder = "ID"
}
.addActionWithTextFields(title: "OK") { action, textFields in
    // validation
}
.addAction(title: "キャンセル", style: .Cancel)
.show()
```

## UIViewのスクショを撮る

```swift:extension

extension UIView {
    func screenShot(width width: CGFloat) -> UIImage? {
        let imageBounds = CGRect(x: 0, y: 0, width: width, height: bounds.size.height * (width / bounds.size.width))

        UIGraphicsBeginImageContextWithOptions(imageBounds.size, true, 0)

        drawViewHierarchyInRect(imageBounds, afterScreenUpdates: true)

        var image: UIImage?
        let contextImage = UIGraphicsGetImageFromCurrentImageContext()

        if let cgImage = contextImage.CGImage {
            image = UIImage(
                CGImage: cgImage,
                scale: UIScreen.mainScreen().scale,
                orientation: contextImage.imageOrientation
            )
        }

        UIGraphicsEndImageContext()

        return image
    }
}
```
```swift:usage
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
label.text = "Hello"
label.textAlignment = .Center
label.backgroundColor = .whiteColor()

let image = label.image(width: 200)
```


## ライブラリ

開発を楽にしてくれる汎用性の高いextension系のライブラリをまとめました。

### SwiftDate

https://github.com/malcommac/SwiftDate

NSDateの扱いを簡単にしてくれるライブラリ。

```example
let date1 = NSDate(year: 2016, month: 12, day: 25, hour: 14)
let date2 = "2016-01-05T22:10:55.200Z".toDate(DateFormat.ISO8601)
let date3 = "22/01/2016".toDate(DateFormat.Custom("dd/MM/yyyy"))
let date4 = (5.days + 2.hours - 15.minutes).fromNow
let date5 = date4 + 1.years + 2.months + 1.days + 2.hours
```

より詳しく知りたい方は[こちら](https://github.com/malcommac/SwiftDate/blob/master/Documentation/UserGuide.md)へ

### Chameleon

https://github.com/ViccAlexander/Chameleon

![Chameleon.png](https://camo.githubusercontent.com/53009c62fa5a31d11f2acc81276cae5564fe0a50/687474703a2f2f692e696d6775722e636f6d2f6c41344a33376f2e706e67)

いい感じのフラットカラーを用意してくれるライブラリ。

```example
let color1 = UIColor.flatGreenColorDark()
let color2 = FlatGreenDark() // 上の短縮版
let color3 = RandomFlatColor()
let color4 = ComplementaryFlatColorOf(color1) // 補色

UIColor.pinkColor().flatten()
FlatGreen.hexValue //=> "2ecc71"
UIColor(averageColorFromImage: image)
```

コントロールの色を一括変更することもできます。

```example
Chameleon.setGlobalThemeUsingPrimaryColor(FlatBlue(), withSecondaryColor: FlatMagenta(), andContentStyle: UIContentStyle.Contrast)
```

### R.swift

https://github.com/mac-cain13/R.swift

AndroidのR.javaのようにファイル名などをプロパティ化してくれるライブラリ。
Typoがコンパイル時にわかるので、幸せになれます。

Before:

```example
let icon = UIImage(named: "settings-icon")
let font = UIFont(name: "San Francisco", size: 42)
let viewController = CustomViewController(nibName: "CustomView", bundle: nil)
let string = String(format: NSLocalizedString("welcome.withName", comment: ""), locale: NSLocale.currentLocale(), "Arthur Dent")
```

After:

```example
let icon = R.image.settingsIcon()
let font = R.font.sanFrancisco(size: 42)
let viewController = CustomViewController(nib: R.nib.customView)
let string = R.string.localizable.welcomeWithName("Arthur Dent")
```


### SwiftString

https://github.com/amayne/SwiftString

Stringに便利なメソッドを追加してくれるライブラリ。

```example
"foobar".contains("foo")         //=> true
",".join([1,2,3])                //=> "1,2,3"
"hello world".split(" ")[1]      //=> "world"
"hello world"[0...1]             //=> "he"
"hi hi ho hey hihey".count("hi") //=> 3
```
 
### SwiftyUserDefaults

https://github.com/radex/SwiftyUserDefaults

NSUserDefaultsをSwiftっぽく扱えるライブラリ。

```example
extension DefaultsKeys {
    static let username = DefaultsKey<String?>("username")
    static let launchCount = DefaultsKey<Int>("launchCount")
}

// 取得と設定
let username = Defaults[.username]
Defaults[.hotkeyEnabled] = true

// 値の変更
Defaults[.launchCount]++
Defaults[.volume] += 0.1
Defaults[.strings] += "… can easily be extended!"

// 配列の操作
Defaults[.libraries].append("SwiftyUserDefaults")
Defaults[.libraries][0] += " 2.0"

// カスタム型もOK
Defaults[.color] = NSColor.whiteColor()
Defaults[.color]?.whiteComponent // => 1.0
```

### TextAttributes

https://github.com/delba/TextAttributes

![TextAttributes.png](https://raw.githubusercontent.com/delba/TextAttributes/assets/demo.gif)

NSAttributedStringを簡単に設定できるライブラリ。

```example
let attrs = TextAttributes()
    .font(name: "HelveticaNeue", size: 16)
    .foregroundColor(white: 0.2, alpha: 1)
    .lineHeightMultiple(1.5)

NSAttributedString("ほげ", attributes: attrs)
```

### Async

https://github.com/duemunk/Async

Grand Central Dispatch (GCD) を使いやすくするライブラリ。

Before:

```example
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
    print("This is run on the background queue")

    dispatch_async(dispatch_get_main_queue(), {
        print("This is run on the main queue, after the previous block")
    })
})
```

After:

```example
Async.background {
    print("This is run on the background queue")
}.main {
    print("This is run on the main queue, after the previous block")
}
```

### AsyncKit

https://github.com/mishimay/AsyncKit
http://qiita.com/mishimay/items/7df447969a1c38d856d8

複数の非同期処理の終了後に次の処理を行うことができるライブラリ。

```example
let async = AsyncKit<String, NSError>()

async.parallel([
    { done in done(.Success("one")) },
    { done in done(.Success("two")) }
]) { result in
    print(result) //=> Success(["one", "two"])
}
```