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. :)