📜 ⬆️ ⬇️

Swift improve performSegueWithIdentifier or convenient router with storyboards

A rare developer for iOS or OS X did not use storyboards and even fewer programmers did not transfer data between screens.
We all know the performSegueWithIdentifier method and the difficulty of working with it.

Having started a project in Swift at one moment I felt hurt: “Why should we use a wrapper for data transfer in a strictly-typed language?”
After a couple of minutes, a vision of the solution was formed and, shortly, implementation.

I thought for a long time whether to write about it, because the material is extremely small, but these 50 lines can help you a lot
')
menuController?.performSegueWithIdentifier(changeItemIdentifier, sender: nil) { segue in let controller = segue.destinationViewController as! ChangeMenuItemController controller.viewModel.sourceMenuItem = item } 


Thus, it is easy to use storyboards and routers together. Eliminate the growth of prepareForSegue and increase the code reading context. At the same time, it does not force to abandon prepareForSegue in convenient situations.

We will understand how this works? I do not pretend to be unique, but I think the approach is interesting.


1) Create a method similar to performSegueWithIdentifier, but with the optional parameter closure (configurate)
2) Save the configurate
3) Call the performSegueWithIdentifier method
4) At the time of the call to prepareForSegue, it remains only to call the configurate

You can write a class, but it is much more productive to write a category.
The subtle point is to save the configurate and call it from prepareForSegue (which is important not to break with your actions).
For preservation we will use associative links. Instead of prepareForSegue, our method will be called, from where the configurate will be called and then the original method will be called.

High level code
 typealias ConfiguratePerformSegue = (UIStoryboardSegue) -> () func performSegueWithIdentifier(identifier: String, sender: AnyObject?, configurate: ConfiguratePerformSegue?) { swizzlingPrepareForSegue() configuratePerformSegue = configurate performSegueWithIdentifier(identifier, sender: sender) } 



The implementation is very small, and it is easier to attach the code under the spoiler
Implementation
 class Box { let value: Any init(_ value: Any) { self.value = value } } extension UIViewController { struct AssociatedKey { static var ClosurePrepareForSegueKey = "ClosurePrepareForSegueKey" static var token: dispatch_once_t = 0 } typealias ConfiguratePerformSegue = (UIStoryboardSegue) -> () func performSegueWithIdentifier(identifier: String, sender: AnyObject?, configurate: ConfiguratePerformSegue?) { swizzlingPrepareForSegue() configuratePerformSegue = configurate performSegueWithIdentifier(identifier, sender: sender) } private func swizzlingPrepareForSegue() { dispatch_once(&AssociatedKey.token) { let originalSelector = #selector(UIViewController.prepareForSegue(_:sender:)) let swizzledSelector = #selector(UIViewController.closurePrepareForSegue(_:sender:)) let instanceClass = UIViewController.self let originalMethod = class_getInstanceMethod(instanceClass, originalSelector) let swizzledMethod = class_getInstanceMethod(instanceClass, swizzledSelector) let didAddMethod = class_addMethod(instanceClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) if didAddMethod { class_replaceMethod(instanceClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) } else { method_exchangeImplementations(originalMethod, swizzledMethod) } } } func closurePrepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { configuratePerformSegue?(segue) closurePrepareForSegue(segue, sender: sender) configuratePerformSegue = nil } var configuratePerformSegue: ConfiguratePerformSegue? { get { let box = objc_getAssociatedObject(self, &AssociatedKey.ClosurePrepareForSegueKey) as? Box return box?.value as? ConfiguratePerformSegue } set { objc_setAssociatedObject(self, &AssociatedKey.ClosurePrepareForSegueKey, Box(newValue), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) } } } 



That's all. This is a very small hack that works on both objc and Swift.
This not only does not violate the code you wrote earlier, but also allows you to combine both approaches or completely switch to configuration from blocks.
It also allows you to make the dependence of the transitions between the screens in a special Router class and use together with storyboards

upd: after communication laid out in the form of pod'a + use with auto- destination destination UIViewController.
cocoapods.org/pods/PureSegue

Source: https://habr.com/ru/post/275783/


All Articles