Last active
April 13, 2020 23:26
-
-
Save IanKeen/8410c17a95d6574090b2ca4f229cf0d8 to your computer and use it in GitHub Desktop.
Segue<Destination> to tame Storyboard segues
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
// | |
// Coordinator | |
// | |
// Created by Ian Keen. | |
// Copyright © 2018 Ian Keen. All rights reserved. | |
// | |
import ObjectiveC | |
import UIKit | |
public extension UIViewController { | |
/// Configures the swizzling required to hook into the segue mechanism | |
public static let enableCoordinatorSegues: Void = { | |
swizzle(UIViewController.self, #selector(prepare(for:sender:)), #selector(swizzled_prepare(for:sender:))) | |
}() | |
} | |
public struct Segue<Destination: UIViewController> { | |
public typealias Configure = (Destination) -> Void | |
public let identifier: String | |
public let configure: Configure? | |
public init(identifier: String, configure: Configure? = nil) { | |
self.identifier = identifier | |
self.configure = configure | |
} | |
} | |
public protocol Coordinator { | |
func performSegue<T>(_ segue: Segue<T>) | |
} | |
extension UINavigationController: Coordinator { } | |
extension UITabBarController: Coordinator { } | |
extension Coordinator where Self: UIViewController { | |
public func performSegue<T>(_ segue: Segue<T>) { | |
let box = SegueBox { viewController in | |
segue.configure?(viewController as! T) | |
} | |
performSegue(withIdentifier: segue.identifier, sender: box) | |
} | |
} | |
public extension UIViewController { | |
public func attemptSegue<T>(_ segue: Segue<T>) { | |
guard let controller = firstSeguePerformer(withIdentifier: segue.identifier) as? Coordinator | |
else { fatalError("Unable to find a Coordinator to execute segue '\(segue.identifier)'") } | |
controller.performSegue(segue) | |
} | |
} | |
private extension UIViewController { | |
func canPerformSegue(withIdentifier identifier: String) -> Bool { | |
guard let segues = value(forKey: "storyboardSegueTemplates") as? [NSObject] | |
else { return false } | |
return segues.contains(where: { ($0.value(forKey: "identifier") as? String) == identifier }) | |
} | |
func firstSeguePerformer(withIdentifier identifier: String) -> UIViewController? { | |
return sequence(first: self, next: { $0.next as? UIViewController }) | |
.first(where: { $0.canPerformSegue(withIdentifier: identifier) }) | |
} | |
} | |
private var segueBoxRef = "segueBoxRef" | |
private extension UIViewController { | |
@objc func swizzled_prepare(for segue: UIStoryboardSegue, sender: Any?) { | |
if let sender = sender as? SegueBox { | |
sender.configure(segue.destination) | |
} | |
swizzled_prepare(for: segue, sender: sender) | |
} | |
var segueBox: SegueBox? { | |
get { return objc_getAssociatedObject(self, &segueBoxRef) as? SegueBox } | |
set { objc_setAssociatedObject(self, &segueBoxRef, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } | |
} | |
} | |
private class SegueBox { | |
let configure: (UIViewController) -> Void | |
init(configure: @escaping (UIViewController) -> Void) { | |
self.configure = configure | |
} | |
} | |
private func swizzle(_ `class`: AnyClass, _ originalSelector: Selector, _ swizzledSelector: Selector) { | |
let originalMethod = class_getInstanceMethod(`class`, originalSelector)! | |
let swizzledMethod = class_getInstanceMethod(`class`, swizzledSelector)! | |
let didAdd = class_addMethod( | |
`class`, originalSelector, | |
method_getImplementation(swizzledMethod), | |
method_getTypeEncoding(swizzledMethod) | |
) | |
if didAdd { | |
class_replaceMethod( | |
`class`, swizzledSelector, | |
method_getImplementation(originalMethod), | |
method_getTypeEncoding(originalMethod) | |
) | |
} else { | |
method_exchangeImplementations(originalMethod, swizzledMethod) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage
Somewhere, i.e. appdelegate
Define Segues..
In your 'meta' VCs to perform segues
In your 'leaf' VCs to ask any 'meta' VCs to perform segues