実装が楽になるSwiftのExtension集(Swift4)

## はじめに

私が使っているSwiftのエクステンションをまとめて紹介します。
少しでもSwiftを使っている方のご参考になればと思います:relaxed:

## 環境

- Swift：4.0.2 or 4.2  
4.0.2と4.2の両方を紹介しています。  
特に記載がないものは共通です。

## Storyboard上でUIViewの枠線の色・幅・角の半径を変更する

```swift:UIView+Border.swift
import UIKit

/// UIView拡張(枠線)
public extension UIView {
    
    // MARK: Properties
    
    /// 枠線の色
    @IBInspectable public var borderColor: UIColor? {
        get {
            return self.layer.borderColor.map { UIColor(cgColor: $0) }
        }
        set {
            self.layer.borderColor = newValue?.cgColor
        }
    }
    
    /// 枠線の幅
    @IBInspectable public var borderWidth: CGFloat {
        get {
            return self.layer.borderWidth
        }
        set {
            self.layer.borderWidth = newValue
        }
    }
    
    /// 角の半径
    @IBInspectable public var cornerRadius: CGFloat {
        get {
            return self.layer.cornerRadius
        }
        set {
            self.layer.cornerRadius = newValue
            self.clipsToBounds = newValue > 0
        }
    }
    
}
```

__使い方__

プロパティに `@IBInspectable` 属性を付けることで、StoryboardのAttributes inspectorから変更できるようになります。

<img width="261" alt="スクリーンショット 2018-12-01 20.05.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/93641bbb-7460-8df1-6185-644b6adfdf3f.png">

エクステンションでは `@IBDesignable` 属性を付けられないので、 `UIButton` などの標準クラスではStoryboard上に反映されないのが残念です。

__用途__

- UIの実装をできる限りStoryboard上で完結させる

__参考リンク__

- [SmartTechVentures/swift-style-guide: スマートテック・ベンチャーズ Swiftコーディング規約](https://github.com/SmartTechVentures/swift-style-guide#extensionのファイル名)

## UIView→UIImageに変換する

```swift:UIView+Image.swift
import UIKit

/// UIView拡張(イメージ)
public extension UIView {
    
    // MARK: Public Methods
    
    /// ビュー→イメージに変換する
    ///
    /// サブビューもイメージ化される
    ///
    /// - Returns: 対象ビューのイメージ
    public func toImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
        guard let context = UIGraphicsGetCurrentContext() else {
            print("現在のコンテキストを取得できませんでした。")
            return UIImage()
        }
        
        self.layer.render(in: context)
        
        guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
            print("ビューをイメージに変換できませんでした。")
            return UIImage()
        }
        
        UIGraphicsEndImageContext()
        
        return image
    }
    
}
```

__使い方__

```swift
let view = UIView()
let viewImage = view.toImage()
```

__用途__

- 特定のビューをイメージに変換する  
ビューのスクリーンショットを撮る場合に使えます。

__参考リンク__

- [[Swift]UIViewからUIImageを作成しローカルに保存 – RE:ENGINES](https://re-engines.com/2017/11/22/swiftuiviewからuiimageを作成しローカルに保存/)

## UIImage→Data(PNG, JPEG)に変換する

__Swift 4.0.2__

```swift:UIImage+Data.swift
import UIKit

/// UIImage拡張(データ)
public extension UIImage {
    
    // MARK: Public Methods
    
    /// イメージ→PNGデータに変換する
    ///
    /// - Returns: 変換後のPNGデータ
    public func toPNGData() -> Data {
        guard let data = UIImagePNGRepresentation(self) else {
            print("イメージをPNGデータに変換できませんでした。")
            return Data()
        }
        
        return data
    }
    
    /// イメージ→JPEGデータに変換する
    ///
    /// - Returns: 変換後のJPEGデータ
    public func toJPEGData() -> Data {
        guard let data = UIImageJPEGRepresentation(self, 1.0) else {
            print("イメージをJPEGデータに変換できませんでした。")
            return Data()
        }
        
        return data
    }
    
}
```

__Swift 4.2__

```diff_swift:UIImage+Data.swift
import UIKit

/// UIImage拡張(データ)
public extension UIImage {
    
    // MARK: Public Methods
    
    /// イメージ→PNGデータに変換する
    ///
    /// - Returns: 変換後のPNGデータ
    public func toPNGData() -> Data {
-       guard let data = UIImagePNGRepresentation(self) else {
+       guard let data = self.pngData() else {
            print("イメージをPNGデータに変換できませんでした。")
            return Data()
        }
        
        return data
    }
    
    /// イメージ→JPEGデータに変換する
    ///
    /// - Returns: 変換後のJPEGデータ
    public func toJPEGData() -> Data {
-       guard let data = UIImageJPEGRepresentation(self, 1.0) else {
+       guard let data = self.jpegData(compressionQuality: 1.0) else {
            print("イメージをJPEGデータに変換できませんでした。")
            return Data()
        }
        
        return data
    }
    
}
```

Swift 4.2からは `UIImage` に `pngData()` と `jpegData(compressionQuality:)` メソッドが追加され、このエクステンションはあまり必要がなくなりました。

__使い方__

```swift
let image = UIImage()
let pngData = image.toPNGData()
```

__用途__

- Realmへ画像を保存する  
RealmはUIImageを保存できないので、バイナリのData型に変換してから保存します。

## Data(PNG, JPEG)→UIImageに変換する

```swift:Data+Image.swift
import UIKit

/// Data拡張(イメージ)
public extension Data {
    
    // MARK: Public Methods
    
    /// データ→イメージに変換する
    ///
    /// - Returns: 変換後のイメージ
    public func toImage() -> UIImage {
        guard let image = UIImage(data: self) else {
            print("データをイメージに変換できませんでした。")
            return UIImage()
        }
        
        return image
    }
    
}
```

__使い方__

```swift
let image = UIImage()
let pngData = image.toPNGData()
let pngImage = pngData.toImage()
```

__用途__

- Realmに保存した画像データをイメージへ戻す

## Date→Stringに変換する

```swift:Date+String.swift
import Foundation

/// Date拡張(文字列)
public extension Date {
    
    // MARK: Public Methods
    
    /// 日付→文字列に変換する
    ///
    /// - Parameter format: フォーマット
    /// - Returns: 変換後の文字列
    public func toString(format: String) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: format, options: 0,
                                                        locale: Locale(identifier: "ja_JP"))
        return formatter.string(from: self)
    }
    
}
```

今までの経験上、 `DateFormatter` を隠蔽すると実装が楽になります。

__使い方__

```swift
let now = Date()
let nowString = now.toString(format: "yMd") // 2018/12/14
```

__用途__

- 日付をラベルなどのUIに表示する

## String→Dateに変換する

```swift:String+Date.swift
import Foundation

/// String拡張(日付)
public extension String {
    
    // MARK: Public Methods
    
    /// 文字列→日付に変換する
    ///
    /// - Parameter format: フォーマット
    /// - Returns: 変換後の日付
    public func toDate(format: String) -> Date {
        let formatter = DateFormatter()
        formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: format, options: 0,
                                                        locale: Locale(identifier: "ja_JP"))
        guard let date = formatter.date(from: self) else {
            return Date()
        }
        
        return date
    }
    
}
```

こちらも `DateFormatter` を隠蔽します。

__使い方__

```swift
"2018-12-14".toDate(format: "yyyy-MM-dd")
```

__用途__

- テキストフィールドに入力した文字列を日付に変換する

## Boolの値を反転する

__Swift 4.0.2__

```swift:Bool+Toggle.swift
/// Bool拡張(トグル)
public extension Bool {

    // MARK: Public Methods

    /// 値を反転させる
    public mutating func toggle() {
        self = !self
    }

}
```

__Swift 4.2__

標準で実装されたのでエクステンション不要です。

__使い方__

```swift
var isEmpty = true

// before
isEmpty = !isEmpty

// after
isEmpty.toggle()
```

ちょっとしたエクステンションですが、より関数型っぽく書けるようになります。

__用途__

- Boolの値を反転する

## 文字列をカタカナやひらがなに変換する

```swift:String+Transform.swift
import Foundation

/// String拡張(変換)
public extension String {
    
    // MARK: Public Methods
    
    /// ひらがな→カタカナに変換する
    ///
    /// - Returns: 変換後の文字列(変換に失敗した場合は空を返す)
    public func toKatakana() -> String {
        return transform(transform: .hiraganaToKatakana, reverse: false)
    }
    
    /// カタカナ→ひらがなに変換する
    ///
    /// - Returns: 変換後の文字列(変換に失敗した場合は空を返す)
    public func toHiragana() -> String {
        return transform(transform: .hiraganaToKatakana, reverse: true)
    }
    
    // MARK: Private Methods
    
    private func transform(transform: StringTransform, reverse: Bool) -> String {
        guard let string = self.applyingTransform(transform, reverse: reverse) else {
            print("文字列を変換できませんでした。")
            return ""
        }

        return string
    }
    
}
```

__使い方__

```swift
"あいうえお".toKatakana() // "アイウエオ"
"カキクケコ".toHiragana() // "かきくけこ"
```

__用途__

- ひらがなやカタカナを区別せずに検索する

## 文字列を半角や全角に変換する

```swift:String+Transform.swift
import Foundation

/// String拡張(変換)
public extension String {
    
    // MARK: Public Methods

    /// 全角→半角に変換する
    ///
    /// - Returns: 変換後の文字列(変換に失敗した場合は空を返す)
    public func toHalfwidth() -> String {
        return transform(transform: .fullwidthToHalfwidth, reverse: false)
    }
    
    /// 半角→全角に変換する
    ///
    /// - Returns: 変換後の文字列(変換に失敗した場合は空を返す)
    public func toFullwidth() -> String {
        return transform(transform: .fullwidthToHalfwidth, reverse: true)
    }
    
    // MARK: Private Methods
    
    private func transform(transform: StringTransform, reverse: Bool) -> String {
        guard let string = self.applyingTransform(transform, reverse: reverse) else {
            print("文字列を変換できませんでした。")
            return ""
        }

        return string
    }
    
}
```

__使い方__

```swift
"ABCDE".toFullwidth() // "ＡＢＣＤＥ"
"アイウエオ".toHalfwidth() // "ｱｲｳｴｵ"
```

__用途__

- 半角や全角を区別せずに検索する

## おわりに

上記のコードはGitHubに上げています。(Swift 4.2のみ)
https://github.com/uhooi/SwiftExtensions/tree/swift4.2/SwiftExtensions/Extensions

もっといい書き方や他にオススメのエクステンションなどがありましたら、コメントなどで教えていただけると嬉しいです:bow:

## 他のエクステンション

他にも単体で記事にしているエクステンションがあるので、リンクを貼ります。

- [iOSでステータスバーの背景色を変える拡張関数(Swift) - Qiita](https://qiita.com/uhooi/items/a4fd1e115196d6b48949)
- [NSAttributedStringをかんたんに生成する拡張関数(Swift) - Qiita](https://qiita.com/uhooi/items/85bea99341deec618795)