This article is based on @JaviSoto’s gist. He did the way in the Twitter’s official Fabric app to work around this in UIKit. It looks reliable and useful, so I just write it up for a memo.

The trivial one problem is that I always use a bunch of Storyboards not only Main.storyboard. I customized like this.

// StoryboardBackedViewController.h

#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)initWithStoryboardWithName:(NSString *)storyboardName withStoryboardIdentifier:(NSString *)storyboardIdentifier NS_DESIGNATED_INITIALIZER;

#pragma mark - Unavailable

- (instancetype)initWithNibName:(NSString * _Nullable)nibNameOrNil bundle:(NSBundle * _Nullable)nibBundleOrNil NS_UNAVAILABLE;
- (_Nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;

@end

NS_ASSUME_NONNULL_END
// StoryboardBackedViewController.m

#import "StoryboardBackedViewController.h"

// 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"

@implementation StoryboardBackedViewController

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    NSAssert(NO, @"Must use -%@", NSStringFromSelector(@selector(initWithStoryboardWithName:withStoryboardIdentifier:)));
    return nil;
}

- (instancetype)initWithStoryboardWithName:(NSString *)storyboardName withStoryboardIdentifier:(NSString *)storyboardIdentifier {
    return [[UIStoryboard storyboardWithName:storyboardName bundle:nil] instantiateViewControllerWithIdentifier:storyboardIdentifier];
}

@end

#pragma clang diagnostic pop

All setup finished, so I use this API from Swift codes.

// ViewController.swift

final class ViewController: StoryboardBackedViewController {
    var item: Item!
    init(item: Item) {
        super.init(storyboardWithName: "Hoge", withStoryboardIdentifier: "ViewController")
        self.item = item
    }
}

You can instantiate ViewController easily. The way may useful for Tests and Dependency Injection etc.

In this case, you cannot put only init() in ViewController because you marked - (instancetype)init NS_UNAVAILABLE;.

// ViewController.swift

final class ViewController: StoryboardBackedViewController {
    init() {
        super.init(storyboardWithName: "Hoge", withStoryboardIdentifier: "ViewController")
    }
}

Ref: Imagining Dependency Injection via Initializer with Storyboards