dcutting / Remix

Main page for the Remix iOS architecture and in-depth example apps.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Remix - NavigationWireFrame Question

Gdev82 opened this issue · comments

After watching the presentation of the Remix architecture which I found very interesting, I downloaded the code from your repo, and started to play around with it and making it part of my own project.

Later on, I discovered a case I was trying to implement in my project that involved having a "master" ViewController and stacking all other ViewControllers on top of it. Just like a "container" ViewController that has child view controllers.

I have a question regarding the approach I though about taking in implementing such feature.
What I came up with is the following

public protocol ContainerViewable:Viewable{
    func navigationContainerFrameDidGoBack()
}

extension ContainerViewable{
    public func navigationContainerFrameDidGoBack(){}
}


public protocol NavigationContainerWireFrameFactory{
    func make()->NavigationContainerWireFrame
}

public struct ContainerFrame{
    let x:Double
    let y:Double
    let width:Double
    let height:Double
    
    public init(x:Double=0,y:Double=0,width:Double,height:Double){
        self.x = x
        self.y = y
        self.width = width
        self.height = height
    }
}

public protocol NavigationContainerWireFrameDelegate:class{
    func navigationContainerDidAddChildView()
    func navigationContainerDidRemoveChildView()
}

public protocol NavigationContainerWireFrame:ContainerViewable{
    var containerWidth:Double{get}
    var containerHeight:Double{get}
    weak var delegate:NavigationContainerWireFrameDelegate?{get set}
    func setRoot(view:ContainerViewable,inFrame frame:ContainerFrame)
    func addChild(view:ContainerViewable,in frame:ContainerFrame)
    func setPopCheckPoint()
    func popToLastCheckPoint()
}

Would this approach be something considered as valid and compatible with the Remix Architecture?

Thank you in advance for any input you may have.

George

Hi George,

So sorry for not replying to you earlier! I've only just seen this message (my GitHub notifications were off for some reason).

Did you make any progress on this?

If I follow you correctly, I think you're trying to make something like a navigation controller where the view controllers in the stack don't take up the whole area and are instead partially overlapping one another.

The gist of Remix wireframes is to make navigation easily testable by using a protocol with only the bare essentials that the coordinator depends upon.

The approach you've got looks pretty good to me. Your protocol would let you make a spy to test that children are added in the right locations and your "real" implementation would be a UIKit container view controller.

You may not need the containerWidth and containerHeight properties since they seem more like implementation details that you wouldn't want to test (but maybe you do, it depends on your app). Possibly the setRoot function isn't needed either and you could just use addChild in all cases.

Happy to look at any code or chat further if you like, and apologies again for such a late reply.

Cheers,
Dan

Hi Dan,

I am currently using this implementation as I originally thought about it (with any drawbacks it may carry), but I am willing to make changes to get the best out of this, since I am not really far out on this project.

You are exactly right about my case, and what I am trying to accomplish. I also see the point of not needing the containerWidth and containerHeight properties for making a spy.

The issue I had if I did not include them in the protocol, was that I would need some way to calculate the offset based on the size of the parent's frame. For example, in one coordinator I have something looking like this.

private func makeChildFrame()->ContainerFrame{
        let containerWidth = deps.wireFrame.containerWidth
        let containerHeight = deps.wireFrame.containerHeight
        
        let childWidth = containerWidth
        let childHeight = containerHeight*0.2
        let x = 0.0
        let y = containerHeight - childHeight
        return ContainerFrame.init(x: x, y: y, width: childWidth, height: childHeight)
    }

The wireframe passed as a dependency is a NavigationContainerWireFrame and when pushing a view its used like so

let viewFrame = makeChildFrame()
deps.wireFrame.addChild(view: view, in: viewFrame)
deps.wireFrame.setPopCheckPoint()

The x, and y coordinates can vary based on what I am trying to show on the screen, so if I do not have the two properties in the protocol how would I perform this calculation when I am about to push a child on to its container?

For the setRoot function, the logic behind it was that the root ViewController would be notified when a child ViewController was either pushed or popped to update its view, maybe by looking at how many children are still on top (by having a counter or something similar) and change its menu icon to a back arrow and visa versa.

Can I accomplish this behaviour in some other more efficient way?

I think the ReMix architecture is great, and since it needs just some tweaks to cover this case, I want to go the extra mile and add it.

I appreciate you taking the time and helping me out with this. Any ideas you may have please let me know since it would mean a great deal for me to move forward with this.

Thank you,

George

Hi George,

The way I would think of it is like this:

Do you imagine that the NavigationContainerWireFrame would stack children the same for every app? I.e., is the stacking placement an intrinsic part of the wireframe itself, or does the app using it need fine-grained control of the locations of the children as they are stacked?

If the former, I would put that x/y/width/height logic in the concrete implementation of that container and remove that concept from the coordinator and wireframe protocol. If the latter, then the way you have it is fine, as you would want to test this behaviour.

The setRoot approach sounds fine too.

Glad to hear you're enjoying Remix! There's no hard and fast rules for how to apply Remix; the general idea is really just to separate things as much as you need to make it easy to test and change, and that will vary over time and depending on what you're making.

It's working really well for me in my projects. Being able to unit test entire navigation flows in under a second without running the simulator is probably my favourite feature. :)