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)]
}
}
}