DeclarativeHub / TheBinderArchitecture

A declarative architecture based on bindings
MIT License
148 stars 6 forks source link

Using with storyboard files? #9

Closed danielt1263 closed 4 years ago

danielt1263 commented 4 years ago

I prefer to use .storyboard or .xib files for my UI layout. The problem as I see it is that the UIViews are not available on construction but only have viewDidLoad is called. What options are available to deal with this?

npvisual commented 4 years ago

Hi @danielt1263 ,

I was in the same predicament a while ago and came up with this solution, which fits my needs (and preferences) a lot better than using Layoutless.

The migration from Layoutless was pretty straightforward. I am using a "skeletal storyboard pattern" approach, which relies on the Binders to essentially load the controller from the storyboard where the bulk of the "skeleton" for the controller has already been created. The Views are used to link the different controls and sub-views defined in the Xcode storyboard Editor (via @IBOutlet for example).

Here's an example below using a member detail view for an application I am working on.

The Views end up being very simple (and short) as most of the static configuration is done in the storyboards -- but you can change that based on your preferences.

The Binders, overall, retain the same structure, except for the call to storyboard.instantiateViewController(withIdentifier :). So the controller is loaded in the static makeViewController() function, which happens way before viewDidLoad() is called.

View

public class MemberDetailController: UIViewController {

    @IBOutlet public weak var nameLabel: UILabel!
    @IBOutlet public weak var usernameLabel: UILabel!

    @IBOutlet public weak var refreshLocation: UIButton!
    @IBOutlet public weak var removeMember: UIButton!

    public override func viewDidLoad() {
        super.viewDidLoad()
        // Do more stuff to setup the view here....
        removeMember.setTitle("Remove", for: .normal)
    }
}

Binder

extension MemberDetailController {

    /// Binder is a function that creates and configures the view controller. It binds the data
    /// from the service to the view controller and user actions from the view controller
    /// to the service.
    static func makeViewController(with session: AuthenticatedSession, member: User) -> MemberDetailController {

        let storyboard = UIStoryboard(name: "FamilyMembers", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "MemberDetail") as! MemberDetailController

        let service = session.familyService

        // The view is loaded if necessary. We need to call this since we have a lot of weak references from
        // the IB outlet references (since we're using the skeletal storyboard architecture, combined with
        // the Binder architecture).
        controller.loadViewIfNeeded()

        // ..... setup the bindings between the services and view components, via Bond / ReactiveKit.

        return controller
    }

You can probably come up with a similar solution for .xib files. HTH, N.

danielt1263 commented 4 years ago

I've done similar but I didn't know about the loadViewIfNeeded() method which was a problem. Thanks for the pointer!