ryu1
4/12/2018 - 12:37 PM

SwiftExtensions.swift

import Foundation
import UIKit

// --------------------------------------------------
//  UIStoryboard+.swift
// --------------------------------------------------
protocol StoryboardIdentifiable {
    
    // Stroryboardに定義されたStoryboard ID(= ViewControllerクラス名)
    static var storyboardIdentifier: String { get }
}

extension StoryboardIdentifiable where Self: UIViewController {
    
    static var storyboardIdentifier: String {
        
        return String(describing: self)
    }
}

extension UIViewController: StoryboardIdentifiable { }

extension UIStoryboard {
    
    // アプリ内でStoryboardファイル名をenumで定義します
    //    enum StoryboardCode: String {
    //
    //        case main = "Main"
    //        case menu = "Menu"
    //    }
    
    convenience init<T>(code: T, bundle: Bundle? = nil) where T: RawRepresentable, T.RawValue == String {
        self.init(name: code.rawValue, bundle: bundle)
    }
    
    func instantiateViewController<T: UIViewController>() -> T {
        
        let optionalViewController = self.instantiateViewController(withIdentifier: T.storyboardIdentifier)
        
        guard let viewController = optionalViewController as? T else {
            fatalError("something error")
        }
        
        return viewController
    }
}


// --------------------------------------------------
//  UIView.swift
// --------------------------------------------------

// swiftlint:disable force_cast

// MARK: - NibInstantiate

protocol NibInstantiate {}

extension UIView: NibInstantiate {}

extension NibInstantiate where Self: UIView {
    
    /// Viewに対応するNibをロードしてViewインスタンスを返します.
    ///
    /// 以下のxxxは同一.
    /// - xibのファイル名は、`xxx.xib`
    /// - xib上で、対象のViewの`Custom Class -> Class`は、`xxx`とする
    /// - Viewのクラス名は、`xxx`とする
    static func instantiateFromNib() -> Self {
        
        let className = String(describing: self)
        let nib = UINib(nibName: className, bundle: nil)
        return nib.instantiate(withOwner: nil, options: nil).first as! Self
    }
    
    /// Viewに対応するNibをロードしてViewインスタンスを返します.
    ///
    /// 以下のxxxは同一.
    /// - xibのファイル名は、`xxx.xib`
    /// - xib上で、対象のViewの`Custom Class -> Class`は、`xxx`とする
    /// - Viewのクラス名は、`xxx`とする
    static func instantiateFromNib(withOwner: Any) -> Self? {
        
        let className = String(describing: self)
        let nib = UINib(nibName: className, bundle: Bundle.main)
        return nib.instantiate(withOwner: withOwner, options: nil).first as? Self
    }
    
    /// Viewに対応するNibをロードします.
    ///
    /// Nibを、Storyboard上で直接使用したいケースでは、Viewインスタンスの@IBOutletがnilになってしまいますが、
    /// このメソッドからViewをロードすると、nilにならなくなります.
    ///
    /// 以下のxxxは同一.
    /// - xibのファイル名は、`xxx.xib`
    /// - xib上で、対象のViewの`Custom Class -> Class`は、空にする
    /// - xib上で、対象のViewの`Placeholder -> File's Owner -> Custom Class -> Class`は、`xxx`とする
    /// - Viewのクラス名は、`xxx`とする
    /// - Viewの初期化処理をoverrideして、loadFromNib()を呼び出す
    ///
    /// ```
    /// override init(frame: CGRect) {
    ///     super.init(frame: frame)
    ///     loadFromNib()
    /// }
    ///
    /// required init?(coder aDecoder: NSCoder) {
    ///     super.init(coder: aDecoder)
    ///     loadFromNib()
    /// }
    ///
    /// ```
    ///
    /// See:
    ///     https://stackoverflow.com/questions/9282365/load-view-from-an-external-xib-file-in-storyboard
    ///     https://stackoverflow.com/questions/9251202/how-do-i-create-a-custom-ios-view-class-and-instantiate-multiple-copies-of-it-i
    ///     https://stackoverflow.com/questions/46723683/ib-designables-failed-to-render-and-update-auto-layout-status
    ///     https://qiita.com/usagimaru/items/e1c349c5bc51a1c4a92c
    func loadFromNib() {
        
        let nibname = String(describing: type(of: self))
        let view = Bundle.main.loadNibNamed(nibname, owner: self, options: nil)!.first as! UIView
        
        addSubview(view)
        
//        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = false
        if #available(iOS 11.0, *) {
            view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
            view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
            view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
            view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
        } else {
            view.leadingAnchor.constraint(equalTo: self.leadingAnchor)
            view.topAnchor.constraint(equalTo: self.topAnchor)
            view.trailingAnchor.constraint(equalTo: self.trailingAnchor)
            view.bottomAnchor.constraint(equalTo: self.bottomAnchor)
        }
    }
    
}

// MARK: - Misc.

extension UIView {
    
    func rotate(degree: Double) {
        transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi * degree / 180.0))
    }
    
}

// MARK: - Animation
extension UIView {
    
    enum AnimationKey: String {
        case blinking
    }

    func startBlinking(timeInterval: TimeInterval = 0.5) {
        if isBlink {
            return
        }
        
        self.alpha = 0.0
        
        let animation = CABasicAnimation(keyPath: "opacity")
        animation.fromValue = 1
        animation.toValue = 0
        animation.duration = timeInterval
        animation.autoreverses = true
        animation.repeatCount = Float.infinity
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
        
        layer.add(animation, forKey: AnimationKey.blinking.rawValue)
    }
    
    func stopBlinking() {
        layer.removeAnimation(forKey: AnimationKey.blinking.rawValue)
        alpha = 1
    }

    var isBlink: Bool {
        if let result = layer.animationKeys()?.contains(AnimationKey.blinking.rawValue) {
            return result
        }
        return false
    }
}

// swiftlint:enable force_cast
// --------------------------------------------------
//  UIViewController+.swift
// --------------------------------------------------
extension UIViewController {

    /// 最前面のViewControllerを取得します。表示中のViewControllerと一致するとは限りません。
    ///
    /// - Parameter viewController: 基準となるViewController
    /// - Returns: UIViewController
    class func forefrontViewController(_ viewController: UIViewController) -> UIViewController {
        if let presentedViewController = viewController.presentedViewController {
            return UIViewController.forefrontViewController(presentedViewController)
        }
        return viewController
    }
}

// --------------------------------------------------
//  UIImage+.swift
// --------------------------------------------------
extension UIImage {
    
    convenience init?<T>(named: T) where T: RawRepresentable, T.RawValue == String {
        self.init(named: named.rawValue)
    }
    
}

// --------------------------------------------------
//  UserDefaults+.swift
// --------------------------------------------------
extension UserDefaults {
    
    subscript<K, T: Any>(key: K, defaultValue: T) -> T where K: RawRepresentable, K.RawValue == String {
        get {
            let value = object(forKey: key.rawValue)
            return (value as? T) ?? defaultValue
        }
        set {
            set(newValue, forKey: key.rawValue)
            synchronize()
        }
    }
    
    subscript<K, T: Any>(key: K) -> T? where K: RawRepresentable, K.RawValue == String {
        get {
            let value = object(forKey: key.rawValue)
            return value as? T
        }
        set {
            guard let value = newValue else {
                removeObject(forKey: key.rawValue)
                return
            }
            set(value, forKey: key.rawValue)
            synchronize()
        }
    }
    
    subscript<K, T: NSCoding>(key: K) -> T? where K: RawRepresentable, K.RawValue == String {
        get {
            return data(forKey: key.rawValue).map { NSKeyedUnarchiver.unarchiveObject(with: $0) } as? T
        }
        set {
            if let value = newValue {
                self[key] = NSKeyedArchiver.archivedData(withRootObject: value)
            } else {
                self[key] = newValue
            }
        }
    }
    
    subscript<K, T: NSCoding>(key: K, defalutValue: T) -> T? where K: RawRepresentable, K.RawValue == String {
        get {
            let value = data(forKey: key.rawValue).map { NSKeyedUnarchiver.unarchiveObject(with: $0) } as? T
            return value ?? defalutValue
        }
        set {
            if let value = newValue {
                self[key] = NSKeyedArchiver.archivedData(withRootObject: value)
            } else {
                self[key] = newValue
            }
        }
    }
    
//    func archive<K, T: NSCoding>(key: K, value: T?) where K: RawRepresentable, K.RawValue == String {
//        if let value = value {
//            self[key] = NSKeyedArchiver.archivedData(withRootObject: value)
//        } else {
//            self[key] = value
//        }
//    }
//
//    func unarchive<K, T: NSCoding>(key: K) -> T? where K: RawRepresentable, K.RawValue == String {
//        return data(forKey: key.rawValue)
//            .map { NSKeyedUnarchiver.unarchiveObject(with: $0) } as? T
//    }
//
//    func unarchive<K, T: NSCoding>(key: K, defalutValue: T) -> T where K: RawRepresentable, K.RawValue == String {
//        let value = data(forKey: key.rawValue)
//            .map { NSKeyedUnarchiver.unarchiveObject(with: $0) } as? T
//        return value ?? defalutValue
//    }
    
}

// --------------------------------------------------
//  Data+.swift
// --------------------------------------------------
extension Data {

    func chunks(_ chunkSize: Int) -> [Data] {
        return stride(from: 0, to: self.count, by: chunkSize).map {
            return self[$0..<Swift.min($0 + chunkSize, self.count)]
        }
    }

}