I've been trying to figure out what can go wrong when using UIViews instead of UIViewControllers and haven't been able to find any therefore I've been just using custom UIViews when generally UIViewController is recommended for some reason.
I prefer UIViews mainly because when custom animating for transitions they're easier to manipulate as far as I know. Correct me if I'm wrong.
Also, I customize everything on my own programmatically such as tab bar, navigation bar etc hence to me, a custom UIView does everything a UIViewController does..
So, is using a separate custom UIView instead of a new UIViewController problematic? If so, please enlighten me..!
Edit
I am aware of MVC model and by UIViews instead of UIViewControllers I mean ignoring the whole one UIViewController per screen thing and use UIView as a container for all objects for certain screens.
For example, when showing menu screen, instead of pulling a UIViewController up for its own "section", I just don't do UIViewController at all and do it with a custom UIView which works as a container, draw/add everything in there. The same goes for the rest of "sections".(settings, option etc etc)
Is this problematic?
I've been trying to figure out what can go wrong when using UIViews instead of UIViewControllers
You cannot do it generally, because view objects and view controller objects occupy different places in the Model-View-Controller hierarchy. They are not even one-to-one with each other, because a single controller often manages multiple views.
I prefer UIViews mainly because when custom animating for transitions they're easier to manipulate as far as I know.
In situations when a piece of functionality can reasonably go in either a view or in a view controller, it most likely belongs in the view, not in the view controller, so your observation is correct. Custom animation that can be encapsulated in a single UIView should be encapsulated in the UIView, even though the same code could go in a UIViewController as well.
Is there a way to use custom segues to individually animate several different subviews.
For example, I want my modal view to appear by the UINavigationBar fading in (as the source destination's UINavigationBar fades out) and then a UITableView to slide down the screen 'over' the source destination's view controller.
When I try to implement this in the - (void)perform method. My properties don't animate using [UIView animateWithDuration: animations: completion:].
Can anyone provide me with a solution?
Thanks in advance!
You can certainly use custom segues to achieve this - however, I don't think you'll get much help without more details about the setup of your view controllers.
Everything you describe is correct: to create a custom segue you animate the views inside your sourceViewController and destinationViewController inside the segue's perform: method. If they're not animating you might want to check that your segue is actually getting called (you can use breakpoints in the debugger to check this), or that the views you're trying to access inside your view controllers actually exist at that point in time (again, something you can check using the debugger).
For a solution specific to your app you're almost certainly going to have to provide more details about the two view controllers you're trying to transition between. Perhaps you could post your perform: method.
I'm working on an app for a blog site, and I'm trying to keep the Default.png launch image up with a spinning indicator while I load the initial headlines into the tableview.
I set up a viewcontroller/view in my storyboard with the launch image and indicator.
I then have the following in the viewDidLoad: method of my navigationController's rootview
[self.navigationController presentModalViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"SplashLoader"] animated:NO];
And once the headlines are loaded I use:
[self.navigationController dismissModalViewControllerAnimated:NO];
Am I way off base here? Or is this the right way to be doing this?
I've seen people doing something like this in app delegate, but that was before storyboards... If I'm supposed to be doing this in the app delegate then how do I instantiate the view controller out of the storyboard?
Thanks,
Any advice or suggestions would be appreciated.
The way I've done it is to make a UIImageView using the Default.png image. In viewWillAppear: I add it to the view controller's view property. In viewDidAppear: I use a UIView animation to fade out the image view by setting its alpha to 0. Upon completion of the animation, viewDidAppear: removes the image view from its superview and releases it (sets it to nil).
You have to keep a record of how many time viewWillAppear: and viewDidAppear: have been called because you only want this animation to happen when the app launches. Also, you have to think about which image to use to create the image view. If this is an iPhone app, you want to use Default.png. If it's an iPad app, you want to use Default-Portrait~ipad.png or Default-Landscape~ipad.png, depending on the orientation of the device when the app launches.
I'm not sure how you would accomplish the same effect in the app delegate. That seems like an unnecessarily complicated approach to me.
I downloaded the free iPad application "SketchBook Express" by "Autodesk".
In it, they have a drawing surface, and (what I believe is) a UINavigationController. What interests me is that the navigation controller bar rotates with the iPad, but the content of the drawing does not. So if I put the iPad from portrait to landscape, the nav bar moves to the new top, but the sketch I've drawn is now on its side. I would like to implement something similar for my program, but I have been unable to.
I've played with shouldAutorotateToInterfaceOrientation: but so far I've just figured out it is only called once. I made a subclass of UINavigationController and had that return YES, and had my subview controller return NO. That did not do the trick.
Is there a simple way to do this?
What rotates is probably contained by a UIViewController (or UINavigationController)'s view.
Other things (that don't rotate) are probably contained by a view that is not contained by any UIViewController (or a controller that returns NO in shouldAutorotateToInterfaceOrientation method).
This means that you could try setting your drawing view as a subview of the keyWindow.
Other things could be in the view controller as usual.
Is not I have tried, this is just an idea. And I would be interested in knowing if this worked. :)
UPDATE: See my answer to this question first. This appears to be a bug. A minimal test case has been created and a report has been filed with Apple. (Fixed as of iPhone OS 3.1.)
Here's a puzzler from the "I'm so close!" department.
I have a Tab Bar-based iPhone app. Each tab features a UINavigationController with the usual suspects (nav bar, table view ... which in turn can lead to another VC, etc.).
Now, one of those lower-level VCs is to be used in portait and landscape modes. But there's a problem. Our landscape-friendly VC's shouldAutorotateToInterfaceOrientation: won't get called out-of-the-box! What to do?
Here's what we do. In my Tab Bar Controller, which I have implemented in its own file, I have this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
This ends up propagating the request to my landscape-friendly VC, which also responds to this message. All my other VCs don't implement this method, so they simply go with the default portrait orientation.
Problem solved!!! Yay!
Well, not quite. :(
Seems like things don't go so well when my landscape-friendly VC is invoked from within the depths of the tab bar controller's MoreNavigationController.
I decided to compare/contrast between a VC called from within one of the first four tab bar UINavigationControllers ... and that same VC called from within the MoreNavigationController. This is going to be a bit ultra-detailed, so bear with me. Hopefully the play by play proves useful for sleuthing things out.
When the app loads, there are several initial calls to the tab bar controller's shouldAutorotate... method. In these early cases, selectedViewController is nil. However, we eventually finish loading, the initial tab item is selected, and all is well.
Right. First, let's pick one of the first four tab bar items and drill down to our VC.
We'll pick third nav bar item, so that's the third nav controller. We drill down to our VC that supports rotation. A quick inspection confirms that the parent is indeed the third nav controller from our tab bar's view controller list. Good!
Let's rotate the device. The tab bar controller is asked to autorotate (see the above code). We observe that selectedViewController is also that third nav controller, plus the nav controller's top and visible view controllers are both set to our trusty VC that supports rotation.
Thus, the tab bar controller will forward the shouldAutorotate message over to the third nav controller ... but our rotation-friendly VC ultimately gets the message. (I'm not doing anything special here. Maybe the desired VC gets the message because it's the top and/or visible VC?) In any event, we rotate to landscape, things are resized, and all is well. "Great Success!"
Now let's hit that back button and pop the VC stack, leaving landscape mode in the process. The tab bar controller is queried again.
Time for a little aside here. The topViewController for our nav controller is still that rotation-friendly VC, but visibleViewController is now set to UISnapshotModalViewController! Heh. Never saw this one before ... but Erica Sadun has. Looks like it's for "disappearing view controllers" (which in this case is certainly true - it's disappearing alright).
As I keep stepping through, the visible VC stays as Snapshot, but the top VC eventually changes to the next VC on the stack, since my special VC is eventually gone. Fair enough.
So that's the scenario where everything works well.
Now let's try the same test, only this time we're going to go to the MoreNavigationController (the More tab bar item) and drill down to the same VC class as before. In my case it happens to be the 7th one in the tab bar controller's VC list.
We enter the rotation-aware VC and ... this time it gets asked to rotate directly! The Tab Bar Controller is not asked for permission to rotate at all. Hmm.
A quick check of the parent VC shows it is a MoreNavigationController. OK, that makes sense.
Now let's try to rotate the device. NOTHING GETS CALLED. None of our breakpoints get hit. Not in our VC. Not in our tab bar controller. (Huh?!?!)
O-kaaaay. Let's pop the stack, go back into the same VC and try to rotate again. Weird. NOW we get a call in the Tab Bar Controller asking for autorotation permission. Here, the selected Controller is our trusty Nav controller (#7), but this time its visibleViewController and topViewController are SET TO NIL!
Once we continue from here, a mysterious message appears in the debugger console:
Using two-stage rotation animation. To
use the smoother single-stage
animation, this application must
remove two-stage method
implementations.
Mysterious because I'm not using two-stage rotation animation! No SecondHalf method variants are in play anywhere in my source code.
Alas, my rotation-aware VC is never told that rotation is occurring (even though rotation does occur on-screen), so of course my view is all fouled up as a result. Mayhem and sadness ensue. :(
We won't even bother popping the stack at this point.
I think the View Controller doc hints at the possible problem:
If you want to perform custom
animations during an orientation
change, you can do so in one of two
ways. Orientation changes used to
occur in two steps, with notifications
occurring at the beginning, middle,
and end points of the rotation.
However, in iPhone OS 3.0, support was
added for performing orientation
changes in one step. Using a one-step
orientation change tends to be faster
than the older two-step process and is
generally recommended for any new
code.
I wonder if MoreNavigationController is still responding to the two-step process, and is thus tripping up any attempts to use the one-step process? Note that, if you respond to the two-step messages, the one-step variant will not work (again, per the docs). I'm not responding to them, but I have a sneaking suspicion something behind-the-scenes IS.
In fact, if I comment out the single-step method, and try to respond to willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:, I do get the memo! But it still doesn't pop off the stack very cleanly (in terms of visuals). Even stranger: willAnimateFirstHalfOfRotationFromInterfaceOrientation:duration: is NOT called, even when I tried to sneak a call to self (using the FirstHalf message) in shouldAutorotateToInterfaceOrientation:. It returns immediately during a trace, as if I never even defined it. Sigh.
So that's the play-by-play.
In summary, has anyone successfully handled one-step device rotation for a VC invoked from within a Tab Bar Controller's MoreNavigationController? Inquiring minds want to know!
Apple advises against subclassing UITabBarController, so I found an easy way to handle autorotation using categories instead. It doesn't fix your bug with the More... view controllers, but I think it's a more Apple-friendly way of getting the job done (and means less subclassing for you).
To make every tab in my application autorotate properly, I've defined -shouldAutorotateToInterfaceOrientation: in my custom view controllers, but they are all inside UINavigationControllers within a UITabBarController, so the message won't get sent down the chain to my VC until those two also respond. So I added the following lines to my app delegate files:
Added to the bottom of MyAppDelegate.h
#interface UITabBarController (MyApp)
#end
#interface UINavigationController (MyApp)
#end
Added to the bottom of MyAppDelegate.m
#implementation UITabBarController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
#end
#implementation UINavigationController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
#end
It appears we have a bug. I have created a reproducible, minimal test case, and reported it via Apple Bug Reporter (RADAR Problem 7139857).
Update: This has been fixed as of iPhone OS 3.1.
The essential problem is:
View controllers already on a
Navigation Controller stack do not
receive
willAnimateRotationToInterfaceOrientation:duration:
messages when a Tab Bar Controller's
'More Navigation Controller' is in
use.
This problem does not occur when the tab bar item view controllers are basic view controllers. Only when they are navigation controllers and only when the "More" navigation hierarchy is in use.
The console message (regarding two-stage rotation animation) suggests that something within the framework (the More Navigation Controller?) is still using a two-stage animation, even though single stage is now recommended as of iPhone OS 3.0.
That could explain why willAnimateRotationToInterfaceOrientation: is not called in that particular case. Per Apple's view controler documentation, this message will NOT be invoked when two-stage, first/second-half orientation messages are being responded to instead.
A slightly modified version of Victorb's answer which allows every single view controller to decide if it allows rotation.
Here as a gist for easier copying & forking
AppDelegate.h
#interface UITabBarController (MyApp)
#end
#interface UINavigationController (MyApp)
#end
AppDelegate.m
#implementation UITabBarController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
UIViewController *selectedVC = [self selectedViewController];
if ([selectedVC respondsToSelector:#selector(shouldAutorotateToInterfaceOrientation:)]) {
return [selectedVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}
//optimistic return - if you want no rotation, you have to specifically tell me!
return YES;
}
#end
#implementation UINavigationController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
UIViewController *visibleVC = [self visibleViewController];
if ([visibleVC respondsToSelector:#selector(shouldAutorotateToInterfaceOrientation:)]) {
return [visibleVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}
//optimistic return - if you want no rotation, you have to specifically tell me!
return YES;
}
#end