-
-
Save zats/5e3668c9c27f458a0c4584b606c6ea1d to your computer and use it in GitHub Desktop.
Init based Storyboard View Controller Instantiation
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 | |
// Some clearly non-NSObject-based models: | |
enum Coconut: String { | |
case small, medium, large | |
} | |
struct Tomato { | |
static var red = Tomato(color: #colorLiteral(red: 0.7490196078, green: 0.09803921569, blue: 0.02352941176, alpha: 1)) | |
static var green = Tomato(color: #colorLiteral(red: 0.4028071761, green: 0.7315050364, blue: 0.2071235478, alpha: 1)) | |
static var yellow = Tomato(color: #colorLiteral(red: 0.9346159697, green: 0.6284804344, blue: 0.1077284366, alpha: 1)) | |
let color: UIColor | |
init(color: UIColor) { | |
self.color = color | |
} | |
} | |
struct Banana { | |
let coconut: Coconut? | |
let tomato: Tomato | |
} | |
// MARK: - Coding | |
// We have to create our own protocol so structs and enums can use it too | |
protocol Coding { | |
init?(coder: NSCoder) | |
func encode(coder: NSCoder) | |
} | |
extension Tomato: Coding { | |
init?(coder: NSCoder) { | |
guard let color = coder.decodeAdhocObject(forKey: "color") as? UIColor else { | |
return nil | |
} | |
self.color = color | |
} | |
func encode(coder: NSCoder) { | |
coder.encodeAdhocObject(color, forKey: "color") | |
} | |
} | |
extension Coconut: Coding { | |
init?(coder: NSCoder) { | |
guard let size = coder.decodeAdhocObject(forKey: "size") as? String else { | |
return nil | |
} | |
self.init(rawValue: size) | |
} | |
func encode(coder: NSCoder) { | |
coder.encodeAdhocObject(rawValue, forKey: "size") | |
} | |
} | |
extension Banana: Coding { | |
init?(coder: NSCoder) { | |
guard let tomato = Tomato(coder: coder) else { | |
return nil | |
} | |
self.tomato = tomato | |
self.coconut = Coconut(coder: coder) | |
} | |
func encode(coder: NSCoder) { | |
tomato.encode(coder: coder) | |
coconut?.encode(coder: coder) | |
} | |
} |
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 | |
class RootViewController: UIViewController { | |
@IBAction func buttonAction(_ sender: AnyObject) { | |
// defined in Models.swift | |
let banana = Banana(coconut: .medium, tomato: .green) | |
let controller = ViewController(banana: banana) | |
present(controller, animated: true) | |
} | |
} | |
class ViewController: StoryboardBackedViewController { | |
let banana: Banana | |
@IBOutlet weak var label: UILabel! | |
init(banana: Banana) { | |
self.banana = banana | |
super.init(storyboardIdentifier: "ViewController") { coder in | |
banana.encode(coder: coder) | |
} | |
} | |
required init?(coder: NSCoder) { | |
guard let banana = Banana(coder: coder) else { | |
return nil | |
} | |
self.banana = banana | |
super.init(coder: coder) | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
view.backgroundColor = banana.tomato.color | |
label.text = banana.coconut?.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 <UIKit/UIKit.h> | |
NS_ASSUME_NONNULL_BEGIN | |
@interface StoryboardBackedViewController : UIViewController | |
// This method returns a different instance of self, so properties on self must be set *after* calling this initializer. | |
- (instancetype)initWithStoryboardIdentifier:(NSString *)identifier configurationBlock:(void(^_Nonnull)(NSCoder *coder))configurationBlock NS_DESIGNATED_INITIALIZER; | |
- (_Nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; | |
#pragma mark - Unavailable | |
- (instancetype)initWithNibName:(NSString * _Nullable)nibNameOrNil bundle:(NSBundle * _Nullable)nibBundleOrNil NS_UNAVAILABLE; | |
- (instancetype)init NS_UNAVAILABLE; | |
@end | |
@interface NSCoder (AdHoc) | |
- (nullable id)decodeAdhocObjectForKey:(nonnull NSString *)key; | |
- (void)encodeAdhocObject:(nonnull id)object forKey:(nonnull NSString *)key; | |
@end | |
NS_ASSUME_NONNULL_END |
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
// | |
// StoryboardBackedViewController.m | |
// Fabric | |
// | |
// Created by Javier Soto on 9/6/15. | |
// Copyright © 2015 Fabric. All rights reserved. | |
// | |
#import "StoryboardBasedViewController.h" | |
#import "Aspects.h" | |
#import <objc/runtime.h> | |
@interface UIStoryboard (FABMainStoryboard) | |
+ (instancetype)fab_mainStoryboard; | |
@end | |
@implementation UIStoryboard (FABMainStoryboard) | |
+ (instancetype)fab_mainStoryboard { | |
return [self storyboardWithName:@"Main" bundle:nil]; | |
} | |
@end | |
#pragma mark - Privates | |
@interface UIStoryboard (Privates) | |
- (nullable UINib *)nibForViewControllerWithIdentifier:(nonnull NSString *)identifier; | |
@end | |
@implementation StoryboardBackedViewController | |
// Instantiating controllers from UIStoryboard is a horrible API that breaks that designated initializer chain. | |
// Implementing this in Obj-C so that we can inherit from this and compose initializers properly. | |
#pragma clang diagnostic push | |
#pragma clang diagnostic ignored "-Wobjc-designated-initializers" | |
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { | |
NSAssert(NO, @"Must use -%@", NSStringFromSelector(@selector(initWithStoryboardIdentifier:configurationBlock:))); | |
return nil; | |
} | |
- (instancetype)initWithCoder:(NSCoder *)aDecoder { | |
self = [super initWithCoder:aDecoder]; | |
return self; | |
} | |
- (instancetype)initWithStoryboardIdentifier:(NSString *)identifier configurationBlock:(void (^ _Nonnull)(NSCoder * _Nonnull))configurationBlock { | |
SEL selector = @selector(unarchiverForInstantiatingReturningError:); | |
AspectOptions aspectOptions = AspectPositionInstead | AspectOptionAutomaticRemoval; | |
[UINib aspect_hookSelector:selector withOptions:aspectOptions usingBlock:^(id<AspectInfo> aspect, NSError **error){ | |
UINib *nib = aspect.instance; | |
NSInvocation *invocation = aspect.originalInvocation; | |
NSCoder *coder = nil; | |
[invocation invokeWithTarget:nib]; | |
[invocation getReturnValue:&coder]; | |
configurationBlock(coder); | |
coder = nil; | |
} error:nil]; | |
UIStoryboard *storyboard = [UIStoryboard fab_mainStoryboard]; | |
UINib *nib = [storyboard nibForViewControllerWithIdentifier:identifier]; | |
NSDictionary *options = @{ @"UINibExternalObjects": @{ @"UIStoryboardPlaceholder": storyboard } }; | |
id scene = [[NSClassFromString(@"UIStoryboardScene") alloc] init]; | |
[nib instantiateWithOwner:scene options:options]; | |
id controller = [scene performSelector:@selector(sceneViewController)]; | |
self = controller; | |
return self; | |
} | |
@end | |
#pragma clang diagnostic pop | |
@implementation NSCoder (AdHoc) | |
- (NSMutableDictionary <NSObject *, NSString *>*)adhoc_storage { | |
const void *key = @selector(adhoc_storage); | |
NSMutableDictionary *result; | |
result = objc_getAssociatedObject(self, key); | |
if (!result) { | |
result = [NSMutableDictionary dictionary]; | |
objc_setAssociatedObject(self, key, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
return result; | |
} | |
- (void)encodeAdhocObject:(id)object forKey:(NSString *)key { | |
self.adhoc_storage[key] = object; | |
} | |
- (id)decodeAdhocObjectForKey:(NSString *)key { | |
return self.adhoc_storage[key]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment