Bernd Rabe
Bernd Rabe

Reputation: 790

Swift protocol extension

Very much appreciated the article about getting rid of using strings when instantiating UIViewController or UIStoryboard.

Though there is one point where I wanted to change the behavior. Instead of giving a Storyboard enum into the class method for getting a storyboard, I wanted a type here that conforms to a protocol.

extension UIStoryboard {
    class func storyboard(storyboard: StoryboardRepresentable, bundle: NSBundle? = nil) -> UIStoryboard {
         return UIStoryboard(name: storyboard.storyboardName, bundle: bundle)
    }
}

protocol StringRawRepresentable: RawRepresentable {
    typealias RawValue = String
    var rawValue: String { get }
}

protocol StoryboardRepresentable {
    var storyboardName: String { get }
}

extension StoryboardRepresentable where Self: StringRawRepresentable {
    var storyboardName: String {
        return self.rawValue
    }
}

enum SomeOtherEnum: String, StoryboardRepresentable {
    case BlaMain
    case BlaSub
    case BlaSomeThing

    var storyboardName: String { return self.rawValue }
}

With that (given you have several moduls interesting in using this implementation) the models themselves could have new enum types conforming to StoryboardRepresentable instead of having a centralized enum with knowledge about all storyboards in use and thus creating a dependency.

And here is my problem. Though I have implemented the storyboardName in the extension, I got a compiler error complaining about non-protocol conformance when I remove the storyboardName on SomeOtherEnum!?

Upvotes: 1

Views: 1509

Answers (3)

shio
shio

Reputation: 408

You can manage Storyboards and ViewController instantiation with help of several protocols/extensions.

UIStoryboardInstantiatable is a wrapper for generic instantiation methods:

public protocol UIStoryboardInstantiatable {
    func instantiate<T: UIViewController>(controller: T.Type) -> T?
    func instantiateInitial<T: UIViewController>(controller: T.Type) -> T?
    func instantiateInitial() -> UIViewController?
}

public extension UIStoryboardInstantiatable {
    func instantiateInitial() -> UIViewController? {
        return instantiateInitial(controller: UIViewController.self)
    }
}

public extension UIStoryboardInstantiatable where Self: UIStoryboardRepresentable {
    func instantiate<T: UIViewController>(controller: T.Type) -> T? {
        return storyboard.instantiate(controller: T.self)
    }

    func instantiateInitial<T: UIViewController>(controller: T.Type) -> T? {
        return storyboard.instantiateInitial(controller: T.self)
    }
}

public extension UIStoryboardInstantiatable where Self: UIStoryboard {
    func instantiate<T: UIViewController>(controller: T.Type) -> T? {
        return instantiateViewController(withIdentifier: T.className) as? T
    }

    func instantiateInitial<T: UIViewController>(controller: T.Type) -> T? {
        return instantiateInitialViewController() as? T
    }
}

In the future all our storyboards will be UIStoryboardRepresentables.

public protocol UIStoryboardRepresentable: UIStoryboardInstantiatable {
    var storyboard: UIStoryboard { get }
    var bundle: Bundle { get }
}

public extension UIStoryboardRepresentable where Self: RawRepresentable, Self.RawValue == String {
    var storyboard: UIStoryboard {
        return UIStoryboard.init(name: rawValue, bundle: bundle)
    }
}

Now you can create list of storyboards presented in your application:

public enum AppStoryboard: String, UIStoryboardRepresentable {
    case components
    case main
    case news

    public var bundle: Bundle {
        return Bundle.main
    }

    public var rawValue: String {
        return "\(self)".capitalizingFirstLetter()
    }
}

Small String extension for capitalizing first letter of rawValue. Because by convention Storyboard names are pascal case and the enum cases are camel case.

extension String {
    public func capitalizingFirstLetter() -> String {
        return prefix(1).uppercased() + dropFirst()
    }
}

Usage:

let controller = AppStoryboard.main.instantiate(controller: TestViewController.self)

From project to project only AppStoryboard is changing.

Upvotes: 0

Bernd Rabe
Bernd Rabe

Reputation: 790

extension UIStoryboard {
    class func storyboard(storyboard: StoryboardRepresentable, bundle: NSBundle? = nil) -> UIStoryboard {
        return UIStoryboard(name: storyboard.storyboardName, bundle: bundle)
    }
}

protocol StoryboardRepresentable {
    var storyboardName: String { get }
}

extension StoryboardRepresentable where Self: RawRepresentable, Self.RawValue == String {
    var storyboardName: String {
        return self.rawValue
    }
}

enum SomeOtherEnum: String, StoryboardRepresentable {
    case BlaMain
    case BlaSub
    case BlaSomeThing
}

StoryboardRepresentable can now be applied to any enum of type String but not to type Int.

Upvotes: 4

vadian
vadian

Reputation: 285069

StringRawRepresentable is not directly related to StoryboardRepresentable.
Since the protocol extension affects only StoryboardRepresentableobjects which also conform to StringRawRepresentable you have to declare SomeOtherEnum

enum SomeOtherEnum: String, StoryboardRepresentable, StringRawRepresentable {

Upvotes: 0

Related Questions