Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Init based Storyboard View Controller Instantiation
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)
}
}
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
}
}
#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
//
// 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