(Translated by https://www.hiragana.jp/)
GitHub - dgrzeszczak/Loaders: Simple μFramework for loading Storyboard and Xib files for iOS.
Skip to content

Simple μみゅーFramework for loading Storyboard and Xib files for iOS.

License

Notifications You must be signed in to change notification settings

dgrzeszczak/Loaders

Repository files navigation

Carthage compatible Version License Platform

Simple μみゅーFramework for loading Storyboard and Xib files for iOS.

Loaders

Let's imagine:

You have Main.storyboard file with initial view controller and two other controllers with identifiers: "PageViewController", "PageDetailsViewController". You have to instantiate them programatically and all you need to do is to declare enum like that:

enum Main: String, Storyboard {
    case initialViewController, pageViewController, pageDetailsViewController
}

... and you can load view controllers:

let pageViewController = Main.pageViewController.load()  // type will be UIViewController

... or with type:

let pageViewController: PageViewController = Main.pageViewController.load() 

... and also you would like to write Unit Test which will check all the controllers are loading properly. So you write:

extension Main: CaseIterable { }

class AppTests: XCTestCase {

    func testMainStoryboard() {
        _ = Main.allCases.map { $0.load() } // [UIViewController] 
    }
}

Lucky you ;) You can do that with the Loaders.

Other possibilities with Storyboard

When you use single storyboard per view controller you can declare it:

enum Details: Storyboard, HasInitialController { }

Then you can instantiate controller like that:

_ = Details.instantiateInitialViewController() // UIViewController

Strong types view controllers

When you need typed initial view controller you have to specify typealias like that:

enum Details: Storyboard, HasInitialController { 
    typealias InitialControllerType = DetailsViewController
}

You can also declare strong type view controllers based on identifiers:

enum Main: Storyboard, HasInitialController {
    typealias InitialControllerType = MainViewController
    
    static var pageViewController: PageViewController { return load() }
    static var pageDetailsViewController: PageDetailsViewController { return load() }
}

then load:

_ = Main.instantiateInitialViewController() // MainViewController 
_ = Main.pageViewController // PageViewController
_ = Main.pageDetailsViewController // PageDetailsViewController

If you don't like computed property to work as a fabric you can use methods instead:

enum Main: Storyboard, HasInitialController {
    typealias InitialControllerType = MainViewController

    static func pageViewController() -> PageViewController { return load() }
    static func pageDetailsViewController() -> PageDetailsViewController { return load() }
}

then load:

_ = Main.instantiateInitialViewController() // MainViewController 
_ = Main.pageViewController() // PageViewController
_ = Main.pageDetailsViewController() // PageDetailsViewController

ControllerLoader & Loader<Controller: UIViewController>

Sometimes there is a need to pass the factory that creates UIViewControllers and create UIViewController later instead of passing UIViewController instance. For that you can use Loader struct or just ConrollerLoader

enum Main: Storyboard, HasInitialController {

    static var pageViewController: Loader<PageViewController> { return loader() }
    static var pageDetailsViewController: ControllerLoader { return loader() }
    
}

_ = Main.initialViewController.load()
_ = Main.pageViewController.load()
_ = Main.pageDetailsViewController.load()

or

enum Main: String, Storyboard {
    case mainViewController
}

let loader = Main.mainViewController.loader()
loader.load()

or

enum Main: String, Storyboard, ControllerLoader {
    case mainViewController
}

let loader = Main.mainViewController
loader.load()

Reusable Nibs

Loaders also provides a way to register and deque reusable views loaded from xib files. It works for UITableViewCell, UICollectionViewCell, UICollectionReusableView. The rule you has to follow is that the class name, xib file name and identifier has to be the same.

 enum FormCells: Nibs {
 
     static var firstTableViewCell: Reusable<FirstTableViewCell> { return load() }
     static var secondTableViewCell: Reusable<SecondTableViewCell> { return load() }
 }

then you can register them:

FormCells.firstTableViewCell.register(on: tableView)

and later dequeue:

let cell = FormCells.firstTableViewCell.dequeue(on: tableView, for: indexPath) // FirstTableViewCell

Note: It works exaclty the same for UICollectionView.

Modules

If you have storyboards or reusables in different module of your app you can simply enclose declaration in enum with the same name as module. You can still enclose your declaration by any enum for grouping purposes but please remember to not conflict it with any module in your app.

enum Storyboards { // there is no module 'Storyboards' in the app so it will use 'current' module for Main storyboard
    
    enum Main: String, Storyboard {
        case initialViewController, pageViewController, pageDetailsViewController
    }

    enum User { // there is module User in the app it will load storyboards 'Main' and 'Profile' from there
        
        enum Main: String, Storyboard {
            case initialViewController, userViewController, userDetailsViewController
        }
        
        enum Profile: Storyboard, HasInitialController { }
    }
}

Custom view from Nib

Creating custom view using xib files instead writing them by hand in code is great idea. It is simple but need some boilerplate and usually it is not obvious for beginers how to do it in proper way. Each developer who is using your custom view should have possibility to instantiate that view in storyboards but he also should be able to do it from the code. He also should see the custom view in storyboard properly instead of "white rectangles". Loaders provide simple mechanism for loading xib files to custom views corectly.

To make custom designable view you need to create Xib file with the same name as your custom class and set the 'File Owner' with that class to have all IBOutlets initialized properly (remember - do not set 'Custom Class' for the main view - set 'File Owner' only).

In your custom view you have to add two constructors and inside them you have to add "Xib file" by single line of code Nib.add(to: self`). Simple implementation may look like that.

@IBDesignable
class DesignableView: UIView {

    @IBOutlet private var label: UILabel!

    @IBInspectable var title: String = "title" {
        didSet {
            label.text = title
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        Nib.add(to: self)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        Nib.add(to: self)
    }
}

Summary

Loaders is a simple way to define all your storyboard's UIViewControllers and NIB reusables (UITableViewCell, UICollectionViewCell, UICollectionReusableView) in clean, declarative way. It brings autocomplete and compile time checking for storyboards and xib files.