UISplitViewController and complex view hierarchy - iphone

I'm doing an iPad tech demo and I'm running into a serious technical problem.
I have an app concept that leverages UISplitViewController, but NOT as the primary controller for the entire app.
The app flow could be described roughly as this:
Home screen (UIViewController)
List->Detail "Catalog" (UISplitViewController)
Super Detail Screen (UIViewController but could conceivable also be a child of SplitView).
The problem is in the flow between Home and Catalog. Once a UISplitViewController view is added to the UIWindow, it starts to throw hissy fits.
The problem can be summarized at this:
When a UISplitView generates a popover view, it appears to then be latched to its parent view. Upon removing the UISplitView from the UIWindow subviews, you will get a CoreGraphics exception and the view will fail to be removed.
When adding other views (presumably in this case, the home screen to which you are returning), they do not autorotate, instead, the UISplitView, which has failed to be removed due to a CG exception, continues to respond to the rotation instead, causing horrible rendering bugs that can't be just "dealt with". At this point, adding any views, even re-adding the SplitView, causes a cascade of render bugs.
I then tried simply to leave the SplitView ever present as the "bottom" view, and keeping adding and removing the Home Screen from on top of it, but this fails as SplitView dominates the Orientation change calls, and Home Screen will not rotate, even if you call [homeScreen becomeFirstResponder]
You can't put SplitView into a hierarchy like UINavigationController, you will get an outright runtime error, so that option is off the table. Modals just look bad and are discourages anyway.
My presumption at this moment is that the only proper way to deal with this problem is so somehow "disarm" UISplitViewController so that it can be removed from its parent view without throwing an unhandled exception, but I have no idea how.
If you want to see an app that does exactly what I need to do, check out GILT Groupe in the iPad app store. They pulled it off, but they seem to have programmed an entire custom view transition set.
Help would be greatly appreciated.

Apple states:
The split view controller’s view
should always be installed as the root
view of your application window. You
should never present a split view
inside of a navigation or tab bar
interface.
This does mean it should be root view and not subview of another view. Even though they add:
You should never present a split view inside of a navigation or tab bar interface
That does not mean you can add it as a subview of any other controller either. (sorry)
I have a feeling that what you are experiencing is the byproduct of trying to do so. I am actually surprised that GILT Groupe's app did not get rejected. Apple has a tendency to enforce these HIG guidelines rather strictly lately. They (as you found out already) cause a rather nasty runtime error when you attempt to add them to a NavigationController.

I've solved this for myself... actually worked around... by presenting all other possible full screen views as modals of the SplitView...
This is an unsavory way of doing things in my book, but Apple leaves you little choice if you want to leverage a SplitView only "sometimes" within an app.

I had some success by creating a second UIWindow. I associate the UISplitViewController with that, and switch it out with the main window when I want to show the splitview. It seems to work they way I wanted, except for a slight delay in rotations and a log message about "wait_fences".

Unless your developing for jail-broken devices then bending apples rules/wishes isn't a good idea. Like Jann and Jasconius state above this means keeping a splitView controller view root, not over-using modals (vague) and not using multiple windows.
Also, the Gilt app is only available in the US
I'v been trying to find a solution too and have ended up programatically removing views from the window like Tuannd talks about but the landscape rendering bug is unforgivable.
#Jasconius, What is the max number of modals are you are presenting at any time?

I am struggling with this same issue. I've been trying various things poking at the UISplitViewController as a black box and see how it reacts.
I seem to have come up with a solution to my case which seems to be working satisfactorily.
The key appears to be that the first view added to the UIWindow is the only view properly initialized. All the problems I've had tend to stem from incorrect notification of the orientation of the device. The first view added, apparently has this correctly configured.
In my case I didn't want the UISplitView as the first view. The following is working for me.
The app delegate application:didFinishLaunching method is special. Adding the view to the UIWindow must occur here. If it is done elsewhere it does not get configured properly.
Essentially the magic sauce, is have the split view be the first view added to the window. Its then ok to remove it as long as you retain the UISplitViewController. From then on you can swap other views in and out, including the UISplitView and most things seem to be ok.
I've still run into a few issues. Popovers on views other than the split view are confused on the views frames and location of toolbar buttons and will display in the wrong location. I place then in a specific location and that seems to handle that case.
If a popover on the split view is still displayed, and you try to view another view, the orientation of the second view is confused and shows up sideways. If that view is accessed before the popup is displayed, all is well. I've fixed this my manually dismissing the popover before switching to any other view.
Here's the code if it helps. All the controllers are instance variables of appDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// This also seems to work as good magic. Seems to set orientation and size properties that persist.
[window addSubview:splitViewController.view];
[splitViewController.view removeFromSuperview];
[self switchToNewViewController:firstController];
[window makeKeyAndVisible];
return TRUE;
}
- (void)switchToNewViewController:(UIViewController *)newViewController {
[popoverController dismissPopoverAnimated:FALSE];
if (newViewController != currentViewController) {
[currentViewController removeFromSuperview];
currentViewController = newViewController;
[window addSubView:newViewController.view];
}
}

Just wanted to say that I was running into these same issues, found this forum topic, and followed the advice from g051051 above. This is working perfectly for me. I am not seeing any glitch, and no messages about wait_fences in the console of the device.
I simply used IB to create two UIWindow objects in the main XIB, created as normal the UISplitViewController and then also an instance of my other controller derived from UIViewController (which I am using for full screen display). I have simply hooked them up by attaching the rootViewController for each UIWindow to its appropriate controller.
In application:didLaunch...: method I can decide which window to send the makeKeyAndVisible method and which to set to hidden. When the user want to switch back and forth I simply have to send makeKeyAndVisible to one and set the hidden property on the other, that's all there is to it.
As indicated all of the rotation related messages are sent to each controller appropriately, regardless of which one is associated with the currently visible window.
Anyway, works great for me, and actually quite easy to set up.

Related

iOS Storyboard Modal Segues and Memory

My apps "short" description:
Basically an interactive storybook, I have a class that sets up a audio session and audio player which every other class(ViewControllers) in my app imports and calls a function or two to set the right sound to be played each time something happens(for instance.. user reads the story). Each ViewController has it's own .m and .h classes and uses them for animations and action handling. My app is only about 60 mb's in size (audio/images/code).
Now these ViewControllers are set up in a storyboard (they are 13 now) and are modal segued from one to the next one and then programmatically dismissed to go back.
When I run my app on my iPad now, I'm starting to get memory warnings and yes Instruments is showing me that my app is adding roughly about 40 Mb's for every ViewController that I segue to.
My questions are:
Do they reside in real memory no matter what I do? (I thought I wasn't holding any strong pointers to these view controllers).
Is there an easy way for me to dismiss one controller and still use a modal segue to get to the next one?(ran into troubles trying this)
Modal Segues are probably not the way I should be doing things in my App are they?!. They looked so nice and easy for my "storybook", but now they are giving me a very rough time.
Any other tips you can give me from what I described are appreciated.
Thank you.
Yes, as long as you present it modally. The presenter view controller keeps a strong pointer to the presented view controller. What you could do here is in the viewWillDisappear: release all the images and other views that might use memory !
You could instantiate your view controller using the method instantiateViewControllerWithIdentifier: and then presentViewController:animated:completion: like you would do with any view controller
It's up to you to decide. But you could easily mimic the animation if you wanted to.
If I understood everything correctly I would go with a singleton class kinda like 'AudioEngine' which is accessible from anywhere in any class. Then I would design all my viewcontrollers in my storyboard like you did. When I need to present modally another view controller, I'll do it using the answer of your 2nd question. If I still had a memory issue, I would try to cheat and keeps always 3 view controllers (like we do with the UIScrollView's infinite scroll) that I would reuse and I'll mimic the modal presentation using UIView's animation blocks.

Multiple UIWindow vs single UIWindow [duplicate]

Recently I've been wondering about the fact that that an iOS app only has one UIWindow.
It does not seem to be an issue to create another UIWindow and place it on screen.
My question is kind of vague, but I'm interested in:
What could I potentially achieve with a second UIWindow that cannot be done in other ways?
What can go wrong when using multiple UIWindow instances?
I have seen that people use a 2nd UIWindow to display popover like views on iPhone. Is this a good way of doing it? Why? Why not?
Are there other examples where it is making perfectly sense to have another UIWindow?
It's not that I'm missing something. I have never felt the need to create another UIWindow instance but maybe it would allow doing amazing things I'm not aware of! :-)
I'm hoping that it might help me solve this problem:
I need to add a "cover view" over whatever is currently displayed. It should also work if there are already one or more modal controllers presented. If I add a UIView to the root controller's view, the modal controllers sit on top, so do the popover controllers.
If I present the cover view modally and there is already a modal controller, only part of the screen is covered.
Starting with Rob's answer I played around a bit and would like to write down some notes for others trying to get information on this topic:
It is not a problem at all to add another UIWindow. Just create one and makeKeyAndVisible. Done.
Remove it by making another window visible, then release the one you don't need anymore.
The window that is "key" receives all the keyboard input.
UIWindow covers everything, even modals, popovers, etc. Brilliant!
UIWindow is always portrait implicitly. It does no rotate. You'll have to add a controller as the new window's root controller and let that handle rotation. (Just like the main window)
The window's level determines how "high" it gets displayed. Set it to UIWindowLevelStatusBar to have it cover everything. Set its hidden property to NO.
A 2nd UIWindow can be used to bring views on the screen that float on top of everything. Without creating a dummy controller just to embed that in a UIPopoverController.
It can be especially useful on iPhone where there is no popover controller but where you might want to mimic something like it.
And yes, it solved of course my problem: if the app resigns activation, add a cover window over whatever is currently shown to prevent iOS from taking a screenshot of your app's current content.
A UIWindow can float above other UI elements like the system keyboard.
To address your last paragraph: Make a UIWindow with the same frame as your main window. Set its windowLevel property to UIWindowLevelStatusBar. Set its hidden property to NO.
Here is Apple's Documentation for better understanding UIWindow:
https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/WindowAndScreenGuide/WindowScreenRolesinApp/WindowScreenRolesinApp.html
One good though specific reason to use multiple instances of UIWindow is when you need to video record the app screen. You may not want to include certain elements (recording button, recording status, etc.) in the final recorded video, so you can put those elements in a separate UIWindow on top.
In fact, if you are using ReplayKit, you will have to use a separate UIWindow for these excluded UI elements. More info here: https://medium.com/ar-tips-and-tricks/how-to-record-a-screen-capture-with-replaykit-whilst-hiding-the-hud-element-bedcca8e31e

iPad : two-stage method implementation

here is my problem, I hope someone can help me.
My iPad app is made like this : I have several viewControllers added as tabs in a tabBarController added to window in my appDelegate. In each of these viewControllers I got two navigationControllers with one viewController in.
Appdelegate
TabBarController
viewController
navigationController
viewController
navigationController
viewController
viewController
navigationController
viewController
navigationController
viewController
Everything work fine in simulator but when I run it on device I got this warning :
Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
It only happen when I run it on a device in landscape mode. The first navigationController of the default view shown by the tabBarController take all the screen width (not important here but it's to explain that this warning makes my layout going crazy). In portrait nothing is traced in the console.
I read many things about this message but I can't find any solution to make it go away.
I'm sure it's a story of shouldAutoRotate or something like this.
I also notice that if i remove the tabBarController and simply add my first viewController to window, the message is not shown.
Very strange... please help :)
I think you should rely on the standard UISplitViewController available under iPad. This controller is what is used under the Settings app and various other apps.
If you do not feel comfortable with such a tool, you can take a look at the APSplitViewController plugin developed by slatvick on GitHub. This could solve a lot of your problems, I hope.
Edit
Googling about this issue, I found out that this warning appears when you create a wrong hierarchy of view controllers. In this case, you are pushing two navigation controllers inside a view controller, even if a view controller is not meant to contain different navigation controller. Probably there could be a problem related on how you send up to the parent view controller methods like viewDidLoad or viewWillLoad: etc. You should try to understand if you are passing all these call correctly to the parent view controller from the inner navigation controllers.

Subviews disappearing from UIViewController

I have application with multiple UIViewControllers using navigation controller. UIViewController contains tableView, searchbar (that I can show/hide) and toolbar. All of this is added as subviews to its view. All this subviews are created after UIViewController is initialized and their content depend on UIVievController's content.
It works fine expect one problem. When I play with my app a little, move back and forth, open some modal views etc sometimes after navigating back to my root VC all it's subviews dissapear and all I get is white screen.
I double checked all my code and I can't find source of problem (I certainly don't remove them myself). I wasn't able to find exact patern how to reproduce this, it seems random. Any idea why iphone would remove my subviews from VC? I would post some code, but I don't want to put it all here and I am not sure which part is important, so if you wish to see some, let me know
Add your views in loadView or viewDidLoad: when viewDidUnload is called, the view is released, so they need to be created again when the view is shown again.

IPhone - Which View Controller methods to use

I'm trying to figure out what logic should go into the different UIViewController methods like viewDidLoad, viewDidAppear, viewWillAppear, ...
The structure of my app is that I have a root view controller that doesn't really have a view of its own, rather it has a tab view controller and loads other view controllers into it. But in the future, it may have to load other view controllers instead of the tab bar controller depending on app logic.
My main question is, what do people usually put into the viewDidLoad, .... methods.
Currently I:
viewDidLoad - setup the tab bar controller and set its view to the view controller's own view
viewDidAppear - check if user has stored login info
if not - present with login
if so, login and get app data for first tab
I'm trying to figure out now if my logic for setting up my tab bar controller should go into loadView rather than viewDidLoad.
Any help would be great. Small examples found on the web are great, but they don't go into detail on how larger apps should be structured.
You should not implement both -viewDidLoad and -loadView; they are for different purposes. If you load a NIB, you should implement -viewDidLoad to perform any functions that need to be done after loading the NIB. Wiring up the tabbar is appropriate there if you haven't already done it in the NIB.
-loadView should be implemented if you do not use a NIB, and should construct the view.
-viewWillAppear is called immediately before you come onscreen. This is a good place to set up notification observations, update your data based on model classes that have changed since you were last on screen, and otherwise get your act together before the user sees you. You should not perform any animations here. You're not on the screen; you can't animate. I see a lot of animation glitches due to this mistake. It kind of works, but it looks weird.
-viewDidAppear is called after you come onscreen. This is where you do any entry animations (sliding up a modal, for instance; not that you should do that very often, but I was just looking at some code that did).
-viewWillDisappear is called right before you go offscreen. This is where you can do any leaving animations (including unselecting tableview cells and the like).
-viewDidDisappar is called after you're offscreen (and the animations have finished). Tear down any observations here, free up memory if possible, go to sleep as best you can.
I touch on setting up and tearing down observations here. I go into that in more depth in View controllers and notifications.
viewDidLoad will be called once per lifetime of each UIViewController's view. You put stuff in there that needs to be set up and working before the user starts interacting with the view.
viewDidAppear is called whenever the view has appeared to the user. It could potentially be called more than once. An example would be the root screen of an app using a UINavigationController to push and pop a hierarchy of views. Put stuff in there that you'd want done every time. For example, you might want to hide the UINavigationBar of the root screen, but show it for all subscreens, so you'd do the hiding of the bar here.
Therefore, you'd put your logic for setting up your UITabBarController in viewDidLoad, since it only should be done once.
Regarding your app, is there a reason why you don't just make the UITabViewController be the controller loaded by your app delegate? It seems that you have a level of indirection in your app that you may or may not need. It's probably better to simplify it now, and refactor later if you need something more complex.