I'm writing an iOS app, hoping that the user can toggle between view A and B, operate on either one, and screen elements retain their states (maps, lists, text) after toggle away.
I've been thinking of several options:
Use navigation controller, start with A, push B, pop B. This way, every time I have to re-set B's elements right?
Start with A, present B modally from A, same effect as option 1?
Use UISegmentedControl on one view controller for A and B. This will work, except the default segmented control is hard to customize to the look I need.
Do you have other suggestions that avoid the drawbacks of the above? Please correct me if my assumptions are not accurate too. Thanks!
Related
In this app the user can do either Activity A or B. When doing A, the viewer sees embedded View A; but also during A the viewer can choose to see embedded View B. (The same goes for Activity B, only with converse views.)
My setup in IB is diagrammed below. Not knowing any better way, I created twice as many activity scenes as I would have liked: For the "Do Activity A" branch, I have two side-by-side copies of the Activity A scene. The first one has an embed segue to View A; and the second, an embed segue to View B. (The "B" setup is the converse.)
To avoid more duplication, I did make both activity branches share Views A and B. And although the activity scenes were doubled, the classes that they link to (ActivityAViewController and ActivityBViewController) were not. I haven't yet noticed any complications from that. . . .
In contrast to other workarounds I've seen, I chose the above "push–pop" layout because Views A and B are expensive. I don't want to discard the primary view just to load the secondary, and I don't want to load both unnecessarily. Also, I want to do this in IB because the real app is more complicated, and seeing all of my layout there helps me understand and develop it. With that said, my approach still seems klutzy.
My questions are
Is there a better way to set this up in IB?
What happens with the activity scenes' shared view controllers? Is a separate class instance created for each, or do the two scenes end up sharing the same instance?
Similarly, what happens with the shared views?
Apple's explanations seem clear enough, though they assume more knowledge of Cocoa Touch and related jargon than I currently have. Any clarifications and insights will be appreciated.
Is there a better way to set this up in IB?
There are other ways to set it up. They all have tradeoffs. Your method has a certain look, and if you want to hook it up differently, it will look a little different. Your method also has the advantage that all of the navigation is handled by the Storyboard; no extra code is needed.
Alternatives include imbedding views A and B in their own little navigation controller. Then switching to view the other would involve clicking on that navigationController. This avoids the duplication of the outer Activity A and B controllers, but it would change the look by adding a navigationBar to the embedded views.
You could also do that and hide the navigation bar and pass a message from the outer Activity A or B controller to tell it when to push the other viewController. This has the disadvantage of some additional nontrivial coding; the Storyboard no longer manages the navigation for you.
You could also run this in a UITabBarController. That has the disadvantage of creating both Activity A and Activity B at the same time if you only need one, but you can freely switch between them without any recreation of any of the views.
What happens with the activity scenes' shared view controllers? Is a
separate class instance created for each, or do the two scenes end up
sharing the same instance?
Every segue (including embedded segues) creates a new viewController instance. No instances are shared.
Similarly, what happens with the shared views?
A new instance of A and B will be created each time.
I have a series of three view controllers, the first two of which are wrapped in a UINavigationController, and are managed by the default Main.storyboard file. Let's call them A and B, respectively. I have more view controllers throughout the file, each instantiated and managed separately, though I am working on moving them into all-code solutions. The aforementioned third view controller I have already in code (C) is one that at various times is instantiated and presented by B.
A and B have a normal segue relationship setup in Interface Builder and called via code. I do the same with C from B, via code (instantiate and presentViewController(_:)). There are a few cases where an action in C invalidates B's raison d'être, and thus I must dismiss both.
So far, I have been calling dismissViewControllerAnimated(_:) from C, and then checking in B's viewDidAppear(_:) whether it should be dismissed, and dismissing it the same way if so. During this process, the user is thrown back though the VC hierarchy, watching as empty views fly back to whence they came, leaving time for them to experiment with controls that no longer work, and may crash the app. This is a rather disconcerting user experience that I wish to do away with. I could simply disable everything, but that's a lot of controls that I'd rather not mess with if I can avoid it...
I understand that IB supports "unwind segues," which can dismiss an entire VC hierarchy simultaneously, though those only seem to handle view controllers in the storyboard. Is there any way to get this behavior (dismiss everything to A) programmatically, without having to revert much of the work I've already done, considering that part of my hierarchy is contained in a UINavigationController?
UPDATE:
So, I got the dismissal to work properly, by passing a reference to the presenting view, and calling its dismiss segue before leaving. That approach came with its own issues and navigation controller weirdness, but with some tweaking it might be usable.
Honestly, it would be much easier to remove the feature entirely at this point, as it's mostly a convenience.
For the sake of science, I'm going to keep at it until I decide ether way, and answer back here for anyone googling this way.
UPDATE:
Ew... It seems this code was older than I thought. I actually have two navigation controllers, to support a custom modal animation into and out of B, with a custom unwind segue there (there you go). In order to get the animation I want, I may as well toss C into the storyboard and make a custom unwind segue.
If I don't care about animation, simply disabling animation on the custom unwind got both B and C to vanish promptly, and together. Trouble is, it's a bit jolting for my taste...
vacawama's suggestion actually makes a lot of sense, and serves me right for not checking the documentation! UIViewController already keeps a reference to its presentingViewController. Going back by two is a pinch, simply climbing back that hierarchy and dismissing the one I want. Works like a charm, without animation doesn't happen at all...
Gonna post an answer here soon with what I've found thus far.
So, science prevails, and a solution is found.
It's really a pain to do if you don't like writing custom segues and animators. Gonna do that eventually, but for now it's more profitable to just not enable the feature at all. (Thank goodness it's easy to toggle on my end).
I found that running my dismiss segue (I use a custom one) on B, then dismissing C did the trick, so long as I didn't use animations for either. Nothing unexpected there at all!
Further, I could get the same effect in one line (animation doesn't matter, so neither does custom segue) by running:
presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
from C's equivalent of goAllTheWayBack().
Gross, but it got the job done. A bit more tweaking, like actually using the storyboard (ugh) for its unwind segue and writing a custom animator, it'd look fancier than a pig in a blanket!
I'd about declare this horse good and dead. Thanks all!
Have you tried Atomic Object's approach
TL;DR
use IB and ctrl-drag 'C' view controller (yellow) to 'Exit',
select 'prepareForUnwind',
give the unwind segue an identifier,
then you can perform the unwind programmatically and it will skip 'B' view controller.
I have a storyboard with over 20 scenes. I want to be able to deep link to one of these scenes. In order to do so, I'd like to perform the necessary segues from my initial view controller.
So say I have view controllers A, B, and C with segues laid out as follows.
->A --segue1--> B --segue2--> C
What's the easiest way to get from A to C without adding special code to B? I need the unwind segues from C to B to remain intact. The only solution I've come up with to add special logic in B that performs segue2 after appearing if a flag is set. This is not ideal as I have some use-cases where this chain is much deeper. I'd rather have code in A that does something like [A performSegues:[#"segue1", #"segue2"]].
Is there a better way to do this?
Thanks!
You can use setViewControllers:animated: to build the whole stack of view controllers that you want all at once. If you start with A, you can make a mutable copy of the navigation controller's viewControllers array, add as many other view controllers to it as you need, then pass that array to setViewControllers:animated:. If you set animated to YES, you will see a single push from A to what ever the last controller in the array is. You will still be able to use an unwind to go back to any of those controllers.
I don't think there's any good way to do this with segues, as you would see all the transitions from one controller to the next (unless that's what you want to see).
I have made various attempt to go storyboard only and limit my code when it comes to UI. Everytime I was getting stuck and reverting back to code. Since the release of XCode 6 and the new iPhone that have multiple screen sizes it make more sense that ever to go Storyboard only.
This time I am stuck on the following scenario. I want to create a custom search view controller that will have 2 states:
Search State. It will prompt the user for a keyword to search.
Result State. It will display the results to the user.
I am aware that this can be accomplished using the UISearchController, but the customer wants to customise the behaviour. Currently I have two view controllers and a push/show segue between them. I would like to replace that with one view controller and animate the display of the results.
Is there any way that storyboard can accomplish that. I am thinking of creating two view controllers (in storyboard) with different layouts. Both will be linked on the same class. I could create a segue between them, but then I will lose all the variable stored inside them and will not be able to animate between them.
On the transition between the two states some UI elements will be hidden and some others will be moved. I would like that to be animated.
I know how to do that in code without using storyboard, but then I will have to cover all different screen scenarios. I hope there is an alternative way.
You can do this with two view controllers, and set a segue between them. The trick is that the animation bit will have to be done in code, unless you write a custom UIStoryboardSegue to handle the animation.
I'm very new to iOS but have been put on a complex app. All the tutorials I have seen on storyboards, segues, views, etc. are very simple.
I have been able to use a segue to show a view and go back, but management doesn't care about segues. After moving through a complex app the user is supposed to be able to just jump way over to another part of the app, as if the user had gone back twice then selected another view, and then selected another view.
I'm just lost as to how to make this happen. Add a new direct segue in the storyboard editor? Or something to do with a custom segue created programatically? I need some hints on direction, like what methods to look at, what to google or a advanced tutorial and such. Thanks in advance.
Usually, the Navigation Controller allows single 'pop's of view controllers in the stack. This works great for master-detail apps and linear workflows.
When one view controller is connected to another in a web-like fashion, things get difficult to manage.
If you want to rely on the automatted management of a view controller stack but go back more than one item at once, have a look at unwind segues: What are Unwind segues for and how do you use them? -- the answer is illustrated really well.
If you can navigate in circles, it gets more intricate. Essentially, your navigation controller would put new objects of already intantiated classes onto the stack to maintain its breadcrumb trail all the way back to the root view controller.
In some cases, this is not desirable. You could use a UINavigationControllerDelegate which removes items from the stack when certain conditions are met.
Let's say you have 4 view controllers, A--D. The possible connections are: A - B - C - {B,D} - A. From D, when you're finished, you want to unwind to A. From C, you may want to add an additional item at B, but you don't want to keep track of all the B-C-B-C-B-C-... connections. In these cases, altering the navigation controller history is useful and won't break the expected behavior of the "back" button.