How to properly switch UIViews - uiview

I want to have multiple views in my application that I switch between.
What is the proper way to switch between UIViews that also supports UISplitViewController?
Is there a support way to switch or are Apple's controller classes designed to be root and root only?
I've tried having one root view and root controller and swap subviews in and out. One of the subviews is a UISplitViewController. It did not like the arrangement and does not display correctly. The detail view was not displayed, the master view displayed wrong orientation and wrong size.
I've then tried managing adding and removing one subview from the UIWindow in the app delegate. This works most of the time. However, the views added after the applicationDidFinishLaunching method do not appear setup correctly. They mostly look correct, however sometimes the orientation thinks its portrait when in reality its landscape. Also, when I try to display a popover, it shows up in an incorrect location. If I change the orientation, it redraws correctly. I've also have some random instances where the UISplitViewController view does not fully display, as if its frame is incorrectly sized.
Any suggestions heartily appreciated.

In applicationDidFinishLaunching, your objects haven't completed loaded from NIBs yet. Try using a viewDidLoad method instead.
What is the user-interface for switching between views? If one of these views represents a transient mode that the user enters and then exits, consider using a modal view. (See presentModalViewController:animated:.)
I would need more details about what you're doing to answer more particularly.

Related

UICollectionViewLayout shouldInvalidateLayout(forBoundsChange:) Method Not Called on Device Rotation if View Controller Is In a UINavigationController

I am facing an issue where the shouldInvalidateLayout(forBoundsChange:) method of a UICollectionViewLayout subclass is not getting called on device rotation, but only when the view controller with the collection view is embedded inside of a UINavigationController. If I remove the root view controller from the navigation controller and show it on its own, this method gets called and my layout is able to then correctly invalidate and update for the new device orientation.
Here's a sample project I created to show the issue. The first tab has just the view controller displayed and you can see by rotating the simulator that shouldInvalidateLayout(forBoundsChange:) is called. The second tab has the same view controller embedded in a navigation controller and on rotation you can see in the console that shouldInvalidateLayout(forBoundsChange:) does not get called. The actual project that I'm seeing this occur in uses a slightly more complex subclass of UICollectionViewLayout, but poses the same issue as seen here with a simple subclass of UICollectionViewFlowLayout. Since our project's layout requires slightly more heavy computations, we rely on shouldInvalidateLayout(forBoundsChange:) returning true and invalidating the layout before recalculating all of the layout attributes. Since this is never called, it causes our cells to remain the same width after rotation instead of updating for the new width.
This behavior is very strange and seems undocumented to me from what I have seen. Does anyone have any insights as to why the navigation controller causes this change and how to modify it so that shouldInvalidate is called on rotation?
Some things I've tried (non-exhaustive list):
Various combinations of hiding and showing the navigation bar/toolbar to see if they have any effect on the bounds of the view on rotation).
Verifying that the bounds of the collection view change on rotation. They do, and you can see the updated bounds when debugging the view hierarchy in Xcode.
Creating the interface entirely programmatically and removing the storyboard. The issue still persists.
Verifying that this issue occurs on different device types. I've tested this on both iPhones with notch/home indicator safe area insets and those without and see the same issue.
Using a different collection view layout. The issue is present for both UICollectionViewFlowLayout and a custom UICollectionViewLayout subclass.
Some similar issues I've come across/read (again, a non-exhaustive list since I've been at this so long):
https://gist.github.com/NeilsUltimateLab/21d551126f0f03b11a0154a681a48e71
https://www.reddit.com/r/iOSProgramming/comments/bd0yh6/what_are_all_the_conditions_that_trigger/
How to properly rotate viewcontrollers in UINavigationController?
https://github.com/radianttap/Fields/issues/3
Thanks for any insights in advance!

if horizontal, it makes two side-by-side copies

In vertical orientation, this basic layout is fine. But in horizontal orientation, two copies of the layout are generated. Any hints about what might cause this?
Thanks in advance... puzzled, I'm still getting oriented to the Xcode interface.
No custom code yet, this is all done via the Xcode GUI.
I've added a screenshot showing the constraints.
Because there were no duplicate elements and zero logic in place yet, the issue was likely more fundamental. So we started with RootViewController.swift and found that some action or setting had inserted a custom pageViewController. Sure enough, it was creating two view controllers when in landscape orientation, as the following code comment describes:
// In landscape orientation: Set the spine location to "mid" and the page view controller's view controllers array to contain two view controllers. If the current page is even, set it to contain the current and next view controllers; if it is odd, set the array to contain the previous and current view controllers.
We commented out the code that had been inserted and the issue is resolved.

Display new iOS UIView everywhere in existing app [duplicate]

I am subclassing UIApplication to intercept and display touches in my TouchDisplay view. I would like to extend the Application, Window, Delegate or Main ViewController in order to keep my TouchDisplay view on top of all other views. As my and most other applications work, views and controllers are added and removed all the time. I figure the correct answer will be able to deal with these additions and removals and stil keep the TouchDisplay view on top.
Thanks for your help,
Joe
Here are a few approaches you could take for this:
If you're targeting iOS 5+ and iPad only, you can make a top-level view controller which has two contained view controllers. The first would be a view controller for your "TouchDisplay" view. The second would be the application's normal root view controller. (i.e. your existing main view controller; you'll need to set definesPresentationContext to YES on this view controller) Since you're writing the container view controller, you can order those two subviews however you like. There is a WWDC 2011 Talk on view controller containment that goes into great detail about this. This is the most "correct" approach IMHO, because it gives you a view controller for your TouchDisplay view, handles rotation and generally plays nice with others. (This only works on iPad, because on iPhone a new modal view always covers the full screen.)
A more straight-forward approach is to simply add your TouchView to your existing top-level UIWindow as a subview with addSubview:. Most applications don't actually remove the top-level view controller or add new top-level ones; they just present other view controllers from it. A view you add in the top-level window will stay above those. Of course, your app may not follow this rule, in which case you might try option #3 instead. This has rotation gotchas (your view will not auto-rotate when the device rotates, so you need to do this yourself.) You could also force your view back to top, say, on a 1-second timer, if you are having issues with other things covering it. This is also not as nice as option #1 because you don't get a UIViewController, just a UIView.
The most extreme approach is that you can create another UIWindow and give it a higher window level, such as UIWindowLevelAlert and put your TouchDisplay view in that. You can then make the window background transparent, and it will stay above your normal app content. There are lots of gotchas here, especially about auto-rotation and which window is the keyWindow (which is why you should use #1 or #2 instead if you can).
After some time I was able to get my app working. I have made an easy to use overlay that shows touch feedback over your existing application.
You can download the project here:
https://github.com/megaplow/FingerTracks/tree/master/FingerTracks
Happy coding,
Joe

Keep a UIView or UIViewController on top of all others

I am subclassing UIApplication to intercept and display touches in my TouchDisplay view. I would like to extend the Application, Window, Delegate or Main ViewController in order to keep my TouchDisplay view on top of all other views. As my and most other applications work, views and controllers are added and removed all the time. I figure the correct answer will be able to deal with these additions and removals and stil keep the TouchDisplay view on top.
Thanks for your help,
Joe
Here are a few approaches you could take for this:
If you're targeting iOS 5+ and iPad only, you can make a top-level view controller which has two contained view controllers. The first would be a view controller for your "TouchDisplay" view. The second would be the application's normal root view controller. (i.e. your existing main view controller; you'll need to set definesPresentationContext to YES on this view controller) Since you're writing the container view controller, you can order those two subviews however you like. There is a WWDC 2011 Talk on view controller containment that goes into great detail about this. This is the most "correct" approach IMHO, because it gives you a view controller for your TouchDisplay view, handles rotation and generally plays nice with others. (This only works on iPad, because on iPhone a new modal view always covers the full screen.)
A more straight-forward approach is to simply add your TouchView to your existing top-level UIWindow as a subview with addSubview:. Most applications don't actually remove the top-level view controller or add new top-level ones; they just present other view controllers from it. A view you add in the top-level window will stay above those. Of course, your app may not follow this rule, in which case you might try option #3 instead. This has rotation gotchas (your view will not auto-rotate when the device rotates, so you need to do this yourself.) You could also force your view back to top, say, on a 1-second timer, if you are having issues with other things covering it. This is also not as nice as option #1 because you don't get a UIViewController, just a UIView.
The most extreme approach is that you can create another UIWindow and give it a higher window level, such as UIWindowLevelAlert and put your TouchDisplay view in that. You can then make the window background transparent, and it will stay above your normal app content. There are lots of gotchas here, especially about auto-rotation and which window is the keyWindow (which is why you should use #1 or #2 instead if you can).
After some time I was able to get my app working. I have made an easy to use overlay that shows touch feedback over your existing application.
You can download the project here:
https://github.com/megaplow/FingerTracks/tree/master/FingerTracks
Happy coding,
Joe

How to create UISplitViewController's portrait behavior in landscape orientations?

I'm trying to find a solution I've seen implemented in some iPad apps where what appears to be a UISplitViewController does not display the master view docked to the left in landscape orientation. Instead, the behavior is exactly the same in landscape as in portrait, with a UIBarButtonItem on the left side of a UIToolbar at the top of the screen bringing up a UIPopoverController with the master view controller's view. This presents some menu options that, when selected, appear to launch new UIViewController-derived classes into the detail view.
The app I'm working on needs to take advantage of as much screen real estate as possible and having the master view with the menu options docked to the left side doesn't add much value; it actually hinders the app.
So actually what I'm trying to do is two-fold:
Suppress the docked master view in landscape orientation
Have the selection of a row (menu option) in the master view load a new UIViewController-derived class into the detail view.
I've seen examples of each by themselves, respectively:
http://vimeo.com/13054813 (Hiding the Root View of a UISplitViewController)
http://bit.ly/aypcr0 (MultipleDetailViews code example from Apple)
However, I can't seem to get both of these working together.
The reason for using this approach is that I have multiple UIViewController-derived classes that I want to display when the appropriate menu option is selected. I could just instantiate them and add their views to the existing detail view and they would display fine. The problem is that none of the UIViewController lifecycle methods ever get called besides viewDidLoad (e.g. viewWillAppear:, viewDidUnload, etc.). This also includes orientation changes, and this is a big problem for the app. It seems that the only times a UIViewController-derived class acts like a UIViewController is when it is added as a subview of the app's UIWindow, or to a container class (like UINavigationController or UISplitViewController).
Am I going down the right path with the UISplitViewController, or is there a better solution?
Thanks for all of your help in advance!
Justin
This is a good UISplitViewController replacement that has the features you want (and more). It is a direct "drop in" replacement for the real UISplitViewConroller.
http://mattgemmell.com/2010/07/31/mgsplitviewcontroller-for-ipad

Resources