ZOZOTOWN iOS アプリで利用している Tips の一部

ZOZOTOWN iOS の内部で利用している UI/NS 系クラス拡張につきまして、ニッチすぎる内容を除き紹介させていただきます！

**※下記のコードはすべて、 Swift 4.2 / iOS12 SDK で動作するものです。**

## Array+Safe

配列の範囲外にアクセスしたさい、クラッシュせず `nil` を返して欲しい場合に利用します。

```Swift:Array+Safe.swift
extension Array {

    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }

}
```

#### Use Case

```Swift
func xxx(index: Int) {
    let list: [String] = ["a", "b", "c"]

    if let item = list?[safe: index] {
        ...
    }
}
```


## String+HtmlMutableAttributedString

HTMLが格納されている `String` をHTML形式の `NSMutableAttributedString` に変換するさいに利用します。
**※メインスレッド以外で利用するとクラッシュするため、ご注意ください。**


```Swift:String+HtmlMutableAttributedString.swift
extension String {

    var htmlMutableAttributedString: NSMutableAttributedString? {
        if let encodedData = data(using: .unicode, allowLossyConversion: true) {
            let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [.documentType: NSAttributedString.DocumentType.html]
            let htmlText = try? NSMutableAttributedString(data: encodedData, options: options, documentAttributes: nil)
            return htmlText
        }
        return nil
    }

}
```

## NSMutableParagraphStyle+Initializer

`NSAttributedText` を作成するさいに利用します。

NSAttributedTextを作成するさい、

```Swift
let paragraph = NSMutableParagraphStyle()
paragraph.lineSpacing = 6
paragraph.textAlignment = .center
paragraph.lineBreakMode = .byWordWrapping 
```

上記がリーダブルではなく、コードを書く手間を削減したかったので、下記のように簡単に書けるようにしました。


```Swift:NSMutableParagraphStyle+Initializer.swift
extension NSMutableParagraphStyle {

    convenience init(lineSpacing: CGFloat? = nil, textAlignment: NSTextAlignment? = nil, lineBreakMode: NSLineBreakMode? = nil, lineHeight: CGFloat? = nil) {
        self.init()

        if let lineSpacing = lineSpacing {
            self.lineSpacing = lineSpacing
        }
        if let textAlignment = textAlignment {
            self.alignment = textAlignment
        }
        if let lineBreakMode = lineBreakMode {
            self.lineBreakMode = lineBreakMode
        }
        if let lineHeight = lineHeight {
            self.maximumLineHeight = lineHeight
            self.minimumLineHeight = lineHeight
        }
    }

}
```

#### Use Case

```Swift
func xxx() {
        let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 14),
                                                         .foregroundColor: UIColor.black,
                                                         .paragraphStyle: NSMutableParagraphStyle(lineSpacing: 6, textAlignment: .center, lineBreakMode: .byWordWrapping)]
    ...
}
```

## UIColor+Hex

CSSで利用されるような16進数表記文字列（ `#808080` など）を `UIColor` にするさいに利用します。

```Swift:UIColor+Hex.swift
extension UIColor {

    public convenience init(hex: Int, alpha: CGFloat) {
        let b = CGFloat((hex & 0x000000ff) >> 0) / 255.0
        let g = CGFloat((hex & 0x0000ff00) >> 8) / 255.0
        let r = CGFloat((hex & 0x00ff0000) >> 16) / 255.0

        self.init(red: r, green: g, blue: b, alpha: alpha)
    }

    public convenience init(hexWithAlpha: UInt32) {
        let b = CGFloat((hexWithAlpha & 0x000000ff) >> 0) / 255.0
        let g = CGFloat((hexWithAlpha & 0x0000ff00) >> 8) / 255.0
        let r = CGFloat((hexWithAlpha & 0x00ff0000) >> 16) / 255.0
        let a = CGFloat((hexWithAlpha & 0xff000000) >> 24) / 255.0

        self.init(red: r, green: g, blue: b, alpha: a)
    }

    public convenience init(hex: Int) {
        self.init(hex: hex, alpha: 1.0)
    }

    public convenience init(hexString: String) {
        var rgbString = hexString.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
        if rgbString.hasPrefix("#") {
            rgbString.remove(at: rgbString.startIndex)
        }

        switch rgbString.count {
        case 8:
            var argbValue: UInt32 = 0
            Scanner(string: rgbString).scanHexInt32(&argbValue)

            self.init(hexWithAlpha: argbValue)
        case 6:
            var rgbValue: UInt32 = 0
            Scanner(string: rgbString).scanHexInt32(&rgbValue)

            self.init(hex: Int(rgbValue))
        default:
            self.init(hex: 0)
        }
    }

}
```

#### Use Case

```Swift
func xxx() {
    let hexWithAlphaColor1 = UIColor(hex: 0xffffff, alpha: 0.8) // rgba: 1.0 / 1.0 / 1.0 / 0.8
    let hexWithAlphaColor2 = UIColor(hexWithAlpha: 0xffffffcc) // rgba: 1.0 / 1.0 / 1.0 / 0.8
    let hexColor = UIColor(hex: 0xffffff) // rgba: 1.0 / 1.0 / 1.0 / 1.0
    let stringColor1 = UIColor(hexString: "ffffff") // rgba: 1.0 / 1.0 / 1.0 / 1.0
    let stringColor2 = UIColor(hexString: "#ffffff") // rgba: 1.0 / 1.0 / 1.0 / 1.0
    let stringColor3 = UIColor(hexString: "#ffffffcc") // rgba: 1.0 / 1.0 / 1.0 / 0.8
}
```

## URL+Queries

APIに `GET` リクエストを送信する場合など、クエリーパラメーターを `Dictionary` で設定するさいに利用します。

```Swift:URL+Queries.swift
extension URL {

    func addQueries(_ queries: [String: String]) -> URL? {
        if queries.isEmpty {
            return self
        } else {
            if var urlComponents = URLComponents(string: absoluteString) {
                urlComponents.queryItems = (urlComponents.queryItems ?? []) + queries.map({ URLQueryItem(name: $0.key, value: $0.value) })
                return urlComponents.url
            } else {
                return self
            }
        }
    }

}
```

#### Use Case

```Swift
func writeAppStoreReview() {
    guard let appStoreUrl = URL(string: String(format: "itms-apps://itunes.apple.com/app/id%@", "(Apple ID)")) else {
        return
    }

    if let appStoreWithWriteReviewUrl = appStoreUrl.addQueries(["action": "write-review"]) {
        _ = openURL(appStoreWithWriteReviewUrl)
    }
}
```

### おまけ：NSURL+Queries

Objective-C のコードから上記 `URL+Queries` をそのまま利用できなかったため、 `NSURL` 版も作成しました。

```Swift:NSURL+Queries.swift
// MARK: - Definition

extension NSURL {

    @objc func addQueries(_ queries: [String: String]) -> NSURL? {
        return (self as URL).addQueries(queries) as NSURL?
    }

}
```

## 最後に

もしご自身のプロダクトに使えそうなら、ご利用いただけると幸いです！