Last active
March 9, 2020 22:35
-
-
Save ThePragmaticArt/6f98f3257fd239ddf76d82fd399a043f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import RealmSwift | |
public extension CodingUserInfoKey { | |
// Helper property to retrieve the context | |
static let realm = CodingUserInfoKey(rawValue: "realm")! | |
static let template = CodingUserInfoKey(rawValue: "template")! | |
} | |
public typealias DecodableManagedObject = DecoderUpdatable & ManagedObject & Decodable | |
public protocol CodingKeyIdentifiable: CodingKey { | |
static var idKey: Self { get } | |
} | |
public protocol DecoderUpdatable: ManagedObject { | |
associatedtype CodingKeys: CodingKeyIdentifiable | |
static func identifier(from decoder: Decoder) throws -> Self.ID | |
mutating func update(from decoder: Decoder) throws | |
} | |
extension DecoderUpdatable where ID: Decodable { | |
public static func identifier(from decoder: Decoder) throws -> Self.ID { | |
let container = try decoder.container(keyedBy: CodingKeys.self) | |
let identifier = try container.decode(ID.self, forKey: CodingKeys.idKey) | |
return identifier | |
} | |
} | |
public protocol DecodingFormat { | |
func decoder(for data: Data) -> Decoder | |
} | |
extension DecodingFormat { | |
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T { | |
return try T.init(from: decoder(for: data)) | |
} | |
} | |
extension DecodingFormat { | |
func update<T: DecoderUpdatable>(_ value: inout T, from data: Data) throws { | |
try value.update(from: decoder(for: data)) | |
} | |
} | |
struct DecoderExtractor: Decodable { | |
let decoder: Decoder | |
init(from decoder: Decoder) throws { | |
self.decoder = decoder | |
} | |
} | |
extension JSONDecoder: DecodingFormat { | |
public func decoder(for data: Data) -> Decoder { | |
return try! decode(DecoderExtractor.self, from: data).decoder | |
} | |
public func decode<ManagedDatabaseObject: Object>(_ type: ManagedDatabaseObject.Type, from data: Data, into realm: Realm? = nil, createIfNecessary: Bool = true) throws -> ManagedDatabaseObject where ManagedDatabaseObject: DecodableManagedObject { | |
userInfo[.realm] = realm ?? userInfo[.realm] | |
guard let realm = userInfo[.realm] as? Realm else { | |
fatalError("Missing context") | |
} | |
let decoder = self.decoder(for: data) | |
let identifier = try ManagedDatabaseObject.identifier(from: decoder) | |
var object = createIfNecessary ? ((try? ManagedDatabaseObject.fetch(identifier, in: realm)) ?? ManagedDatabaseObject()) : try ManagedDatabaseObject.fetch(identifier, in: realm) | |
if object.id != identifier { | |
object.id = identifier | |
} | |
realm.add(object, update: .modified) | |
try object.update(from: decoder) | |
return object | |
} | |
public func decode<ManagedDatabaseObject: Object>(_ type: [ManagedDatabaseObject].Type, from data: Data, into realm: Realm? = nil, createIfNecessary: Bool = true) throws -> [ManagedDatabaseObject] where ManagedDatabaseObject: DecodableManagedObject { | |
userInfo[.realm] = realm ?? userInfo[.realm] | |
guard let realm = userInfo[.realm] as? Realm else { | |
fatalError("Missing context") | |
} | |
let decoder = self.decoder(for: data) | |
var container = try decoder.unkeyedContainer() | |
var objects = [ManagedDatabaseObject]() | |
while !container.isAtEnd { | |
let decoder = try container.superDecoder() | |
let identifier = try ManagedDatabaseObject.identifier(from: decoder) | |
var object = createIfNecessary ? ((try? ManagedDatabaseObject.fetch(identifier, in: realm)) ?? ManagedDatabaseObject()) : try ManagedDatabaseObject.fetch(identifier, in: realm) | |
if object.id != identifier { | |
object.id = identifier | |
} | |
realm.add(object, update: .modified) | |
try object.update(from: decoder) | |
objects.append(object) | |
} | |
return objects | |
} | |
} | |
extension PropertyListDecoder: DecodingFormat { | |
public func decoder(for data: Data) -> Decoder { | |
return try! decode(DecoderExtractor.self, from: data).decoder | |
} | |
} | |
extension KeyedDecodingContainer { | |
func update<T: DecoderUpdatable>(_ value: inout T, forKey key: Key, userInfo: [CodingUserInfoKey: Any] = [:]) throws { | |
let nestedDecoder = NestedDecoder(from: self, key: key, userInfo: userInfo) | |
try value.update(from: nestedDecoder) | |
} | |
public func update<ManagedDatabaseObject: Object>(_ type: ManagedDatabaseObject.Type, forKey key: Key, userInfo: [CodingUserInfoKey: Any] = [:], createIfNecessary: Bool = false) throws -> ManagedDatabaseObject where ManagedDatabaseObject: DecodableManagedObject { | |
guard let realm = userInfo[.realm] as? Realm else { | |
fatalError("Missing context") | |
} | |
let nestedDecoder = NestedDecoder(from: self, key: key, userInfo: userInfo) | |
let identifier = try ManagedDatabaseObject.identifier(from: nestedDecoder) | |
var object = createIfNecessary ? ((try? ManagedDatabaseObject.fetch(identifier, in: realm)) ?? ManagedDatabaseObject()) : try ManagedDatabaseObject.fetch(identifier, in: realm) | |
if object.id != identifier { | |
object.id = identifier | |
} | |
realm.add(object, update: .modified) | |
try object.update(from: nestedDecoder) | |
return object | |
} | |
public func update<ManagedDatabaseObject: Object>(_ type: [ManagedDatabaseObject].Type, forKey key: Key, userInfo: [CodingUserInfoKey: Any] = [:], createIfNecessary: Bool = false) throws -> [ManagedDatabaseObject] where Key: CodingKeyIdentifiable, ManagedDatabaseObject: DecodableManagedObject { | |
guard let realm = userInfo[.realm] as? Realm else { | |
fatalError("Missing context") | |
} | |
let nestedDecoder = NestedDecoder(from: self, key: key, userInfo: userInfo) | |
var container = try nestedDecoder.unkeyedContainer() | |
var objects = [ManagedDatabaseObject]() | |
while !container.isAtEnd { | |
let decoder = try container.superDecoder() | |
let identifier = try ManagedDatabaseObject.identifier(from: decoder) | |
var object = createIfNecessary ? ((try? ManagedDatabaseObject.fetch(identifier, in: realm)) ?? ManagedDatabaseObject()) : try ManagedDatabaseObject.fetch(identifier, in: realm) | |
do { | |
if object.id != identifier { | |
object.id = identifier | |
} | |
realm.add(object, update: .modified) | |
try object.update(from: decoder) | |
objects.append(object) | |
} catch { | |
if let realm = object.realm { | |
realm.delete(object) | |
} | |
} | |
} | |
return objects | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
///A default protocol adding a cell identifier to a structure | |
public protocol CellIdentifiable { | |
///The cell identifier used for dequeuing and registering cells | |
static var cellIdentifier: String { get } | |
} | |
///A default protocol adding a header/footer identifier to a structure | |
public protocol HeaderFooterIdentifiable { | |
///The header/footer identifier used for dequeuing and registering header/footer views | |
static var headerFooterIdentifier: String { get } | |
} | |
///A default protocol adding a storyboard identifier to a structure | |
public protocol StoryboardIdentifiable { | |
///The storyboard identifier used for dequeuing a view from a storyboard | |
static var storyboardIdentifier: String { get } | |
} | |
extension StoryboardIdentifiable where Self: NSObject { | |
public static var storyboardIdentifier: String { | |
return className | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// A protocol that provides demangled information regarding core Swift structure naming components in a usable way. | |
public protocol StructureNameReflectable { | |
/** | |
An ordered list of domain named based components for the structure. | |
- Example: A simple example would be: | |
``` | |
class Dog: NSObject { | |
class Retriever: NSOjbect {} | |
} | |
let retriever = Retriever() | |
``` | |
`retriever.structuredName` would output `["Dog", "Retriever"]` | |
*/ | |
static var structureNameComponents: [String] { get } | |
///Outputs the structure's name in a string based form minus namespacing | |
static var structureName: String { get } | |
///Outputs the structure's name in a string based form with namespacing | |
static var namespacedStructureName: String { get } | |
///Outputs the bundle the structure is contained in | |
static var bundle: Bundle { get } | |
} | |
extension StructureNameReflectable { | |
public static var structureNameComponents: [String] { | |
let type = Mirror(reflecting: self).subjectType | |
let structureNameComponents = "\(type)".components(separatedBy: ".") | |
return structureNameComponents | |
} | |
public static var structureName: String { | |
var structureNameComponents = self.structureNameComponents | |
if structureNameComponents.count > 1 && structureNameComponents.last == "Type" { | |
structureNameComponents.removeLast() | |
} | |
return structureNameComponents.last! | |
} | |
public static var namespacedStructureName: String { | |
return structureNameComponents.joined(separator: ".") | |
} | |
} | |
extension StructureNameReflectable where Self: NSObject { | |
public static var bundle: Bundle { | |
return Bundle(for: self) | |
} | |
public static var className: String { | |
return structureName | |
} | |
} | |
extension NSObject: StructureNameReflectable { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
extension UIStoryboard { | |
public func instantiateViewController<ViewControllerClass: UIViewController>() -> ViewControllerClass { | |
return instantiateViewController(withIdentifier: ViewControllerClass.storyboardIdentifier) as! ViewControllerClass | |
} | |
@objc public func instantiateViewController(className: String, bundle: Bundle = .main) throws -> UIViewController { | |
guard let classType = bundle.classNamed(className) else { | |
throw NSError(domain: "com.storyboard", code: 404, userInfo: [NSLocalizedDescriptionKey: "Could not find class: \(className) in bundle: \(bundle)"]) | |
} | |
guard let viewControllerClassType = classType as? StoryboardIdentifiable.Type else { | |
throw NSError(domain: "com.storyboard", code: 404, userInfo: [NSLocalizedDescriptionKey: "Object does not conform to StoryboardIdentifiable protocol"]) | |
} | |
let viewController = instantiateViewController(withIdentifier: viewControllerClassType.storyboardIdentifier) | |
return viewController | |
} | |
} | |
public protocol StoryboardLoadable: StoryboardIdentifiable { | |
static func instantiateFromStoryboard<Class: UIViewController>(_ storyboard: String) -> Class | |
static func instantiateFromStoryboard<Class: UIViewController>() -> Class | |
static var designatedStoryboard: UIStoryboard { get } | |
static var designatedStoryboardName: String { get } | |
static var designatedStoryboardBundle: Bundle? { get } | |
} | |
extension StoryboardLoadable where Self: UIViewController { | |
public static func instantiateFromStoryboard<Class: UIViewController>() -> Class { | |
let viewController = instantiateFromStoryboard(name: designatedStoryboardName, bundle: designatedStoryboardBundle) as Class | |
return viewController | |
} | |
/// Instantiates a ViewController of the ViewController classed called from using the passed in storyboard name | |
/// | |
/// - Parameter storyboard: A parameter provided to specify the storyboard name. Should be a constant value from UIStoryboardNames | |
/// - Returns: A ViewController loaded from the specified storyboard name. By default, this is loaded from the main bundle | |
public static func instantiateFromStoryboard<Class: UIViewController>(_ storyboard: String) -> Class { | |
let storyboard = UIStoryboard(name: storyboard, bundle: designatedStoryboardBundle) | |
return storyboard.instantiateViewController() | |
} | |
public static func instantiateFromStoryboard<Class: UIViewController>(name: String, bundle: Bundle?) -> Class { | |
let storyboard = UIStoryboard(name: name, bundle: bundle) | |
return storyboard.instantiateViewController() | |
} | |
} | |
extension UIViewController: StoryboardLoadable { | |
@objc open class var designatedStoryboard: UIStoryboard { | |
let storyboard = UIStoryboard(name: designatedStoryboardName, bundle: designatedStoryboardBundle) | |
return storyboard | |
} | |
@objc open class var designatedStoryboardBundle: Bundle? { | |
let bundle = Bundle(for: self) | |
return bundle | |
} | |
@objc open class var designatedStoryboardName: String { | |
return UIStoryboard.main.rawValue | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public protocol ChildViewControllerContainer { | |
var parentViewController: UIViewController? { get } | |
} | |
public protocol ContentContainerTraversable { | |
func contentContainer<T: UIContentContainer>() -> T? | |
} | |
public protocol ViewContainer { | |
func subviewsOfKind<ViewClass: UIView>(_ kind: ViewClass.Type) -> [ViewClass] | |
} | |
extension UIView: ViewContainer { | |
public func subviewsOfKind<ViewClass: UIView>(_ kind: ViewClass.Type) -> [ViewClass] { | |
guard !subviews.isEmpty else { | |
return [ViewClass]() | |
} | |
let views = subviews.reduce([ViewClass]()) { (subviews, subview) -> [ViewClass] in | |
guard let view = subview as? ViewClass else { | |
return subviews + subview.subviewsOfKind(ViewClass.self) | |
} | |
let views = subviews + [view] + view.subviewsOfKind(ViewClass.self) | |
return views | |
} | |
return views | |
} | |
} | |
extension UIView: ContentContainerTraversable { | |
public func contentContainer<T: UIContentContainer>() -> T? { | |
var responder = next | |
while let currentResponder = responder { | |
guard responder is T else { | |
responder = currentResponder.next | |
continue | |
} | |
break | |
} | |
return responder as? T | |
} | |
} | |
extension UIView: ChildViewControllerContainer { | |
@objc public weak var parentViewController: UIViewController? { | |
return contentContainer() as UIViewController? | |
} | |
} | |
extension UITableViewCell { | |
@objc public weak var tableViewController: UITableViewController? { | |
return contentContainer() as UITableViewController? | |
} | |
} | |
extension UICollectionViewCell { | |
@objc public weak var collectionViewController: UICollectionViewController? { | |
return contentContainer() as UICollectionViewController? | |
} | |
} | |
extension UIViewController { | |
@objc public weak var topParent: UIViewController? { | |
var parent = self.parent | |
while let currentParent = parent { | |
guard let newParent = currentParent.parent else { | |
break | |
} | |
parent = newParent | |
} | |
return parent | |
} | |
} | |
extension UIViewController { | |
@objc public weak var parentNavigationController: UINavigationController? { | |
return parentViewController() as UINavigationController? | |
} | |
@nonobjc public func parentViewController<T: UIViewController>() -> T? { | |
var parentViewController = parent | |
while let currentParentViewController = parentViewController { | |
guard presentingViewController != currentParentViewController else { | |
break | |
} | |
guard parentViewController is T else { | |
parentViewController = currentParentViewController.parent | |
continue | |
} | |
break | |
} | |
return parentViewController as? T | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment