I'm in the market for a nice thorough example/tutorial link or demo on programmatically creating and redrawing/resizing on rotation a view with nested tiled views. This means that the root view will need to rotate on rotation, and trigger all nested views to also rotate and resize.
For example, lets say you have a view with forty rectangle views tiled within, Id like to rotate an iphone/ipad and have the forty nested views also rotate (not hard) but more importantly resize and move. A four by ten grid might change to be five by eight.
I'm able to effect this programmatically but I'm finding that the x/y bounds etc are all off kilter.
Please no comments about "But just use storyboards"...
Thanks!
I've found that it can often be a bit of effort to get it working as you'd like/expect. Things to take into consideration are the callback you are using to pick up rotation, there are 3 and they serve a slightly different purpose
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
This is probably the one you want to use, at this point when you query self.view.frame (or whatever you use to get the super frame) it will return the value that the frame will be once the rotation has complete. It also takes into account auto-resizing, and it gives you this before the rotation has taken place (ie no visible effect yet). Use this to calculate all the new positions and sizes of the views and set them.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
This tells you the rotation is about to happen, but still returns the frame for the current orientation, useful for hiding/showing views, but not for calculating new positions!
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
This is called once rotation is complete, it returns the new sizes and frames, but if you attempt to reposition views here they will look as though they are jumping around after the rotation is complete.
I'd also recommend playing about with auto-resizing, as this can be a real bitch. I sometimes set the auto-resizing in code as well as in a xib juts to be sure there is no funny business!
If the rectangles are nested in a view controller they should rotate automatically (you probably knew that just thought I add it in case)
Related
Auto Layout Constraints allow me to size and lay out views perfectly, without knowing beforehand what screen they will be rendered on. This works reasonably well when I'm drawing a UI on a Storyboard.
Let's move on to creating views programmatically.
My app is running, the interface has been rendered on the screen, I have all the necessary coordinates and the sizing has been done.
I would like to create a few views dynamically, for instance:
a UIImageView, that appears at the press of a button, gets animated, then disappears
a collection of custom UIView, created and laid out on the screen depending on the underlying data model (imagine a sequence of events here, arranged on a custom timeline)
In similar scenarios, I still tend to use the good old frames, e.g.:
let myView = UIView(frame: CGRectMake(x, y, w, h))
without adding any NSLayoutConstraint.
Is there any definite advantage of using constraints instead, given the added complexity, especially when you need to animate views?
The most important aspects are when you actually calculate your frame (which method), if you want universal support and if you need to support various orientations.
Inside a viewController, if you print out your self.view frame in viewDidLoad and viewWillAppear: or viewWillLayoutSubviews, it might have different values. The final frame (for example, the one for iPhone 6 plus) is not calculated in viewDidLoad. So if you make your view setup there, the calculation will be wrong.
But with constraints, it does not matter where you add them. You might need to call layoutIfNeeded after the view changes, but you don't need to worry weather the frames have their final value.
For views that are visible only for a short time, auto layout could make a difference for different orientations. By using frames, you will have to update the frame of your temporary view in the orientation change callback.
Also, if you use auto layout in the storyboard, using static frames for views created programmatically might not give you the results you are expecting. But, of course, it depends on the particularities of your project.
I have a UIViewController that overlays controls on a view presenting what the camera sees. I have a couple of scenarios I would like to allow.
For the iPad, I want to keep the controls on the right most edge of the device, by your right thumb, no matter what the device's rotation. The controls should rotate their content so that their top is always upwards (away from the ground). I don't want the camera view to rotate at all, because that would just be silly – its position & size should stay the same and its contents shouldn't rotate either.
For the iPhones, I want to keep the controls at the bottom of the device's screen, by to the home button, wherever the home button actually is. The controls should rotate their content so that up is always pointing upwards. Again, I don't want the camera view's frame or content to take part in any view rotation animation at all.
I'm using auto-layout.
I'm wondering if there is any way to describe some or all of this in a storyboard. In particular, it'd be great to be able to describe that some view positions need to autorotate (ie, the controls, on iPad), but that other views don't (the camera view).
A question from 2011 indicates this wasn't possible at the time, but perhaps things have moved on since then? If it's not directly supported, can you suggest an approach and are there some sensible places to be hooking in to autorotation to achieve this?
Ok, this isn't quite a complete answer, but I tried a few things which look promising.
First, you can create a separate set of constraints for portrait vs. landscape using the size specifiers: landscape is w Regular, h Any; portrait is w Any, h Regular (I think -- double-check these) This is accessible via the pop-up control in the bottom-center of the storyboard view. By installing different constraints for portrait and landscape, it should be possible to scale the width and height of your controls' container view so it appears to be in a constant position w.r.t. device orientation; in other words, the container doesn't actually counter-rotate -- it scales so it effectively looks like it has counter-rotated.
I got this close to working. It looks like it's doing the correct thing in the storyboard view, but when I actually run it, I get debug messages about conflicting constraints. Not sure how to fix this, but maybe play with the constraint priorities? That sometimes helps.
A second thing I (partially) tried was creating a custom container view class which counter-rotates itself to the correct position based on the device orientation (in the UIDevice class). You implement this by overriding layoutSubviews. For each orientation, you define a transform which puts it in the correct position, and set the view's transform property.
Another possible solution is to override updateConstraints in your view controller and add/remove constraints to position/scale your container to the correct place for each orientation.
For all of these, the idea is that you "force" the container to be in the correct place, but leave the subviews (the actual controls) alone. The controls should do the right thing if their constraints are independent of the specific orientation of the container view.
So, those are some ideas anyway... if they lead you to an actual solution, could you post it? I anticipate having a need for this myself.
With AutoLayout, is it possible to keep a View (button, image, etc) in the same location but just rotate it 90 degrees when the device is rotated?
For example, in the following images the Views stay exactly as they were placed in the portrait orientation (same distances from the edges), but are rotated in landscape.
You describe the views in the images you've provided as "staying in exactly the same place", but I think this is to misunderstand autolayout and the rotation behaviour. Your layout has changed significantly between the two examples. Where both views were previously aligned along their left edges, now they're aligned along their bottom edges.
Basically: when you rotate the device (lets say from standard portrait to home-left-landscape) you're changing which view is the top, not the direction the top view is pointing.
If you want to recreate the look of the rotated view you provided, you have a few options. I'd suggest looking at the visual formatting language, which is a good way of adding constraints programatically... it's easier then it seems. Take a look at the iOS auto-layout talks from WWDC 2012 if you want a good introduction. You could then add and remove the appropriate constraints when the device rotates. (it might take a bit of playing around). There's also a section in the View Controller Programming Guide on 'creating a custom landscape orientation' that might be helpful.
If you're allowing the interface orientation to rotate, then you'll have to change your constraints on rotation to put the views where you want them.
If you're not allowing the interface orientation to rotate, then you'll have to subscribe to device orientation change notifications. When the orientation changes, you can update the transforms of the views to rotate them. If the views are square, that should suffice. If the views are not square, you also need to modify your constraints based on the rotated frames.
The question: Is it possible to obtain the size that an autorotated UIView will be given after the screen is rotated, but to obtain this value before the rotation is completed? If so, how?
Background: I'm building an iPad app that has a screen where several UILabels are arranged in columns within a containing UIView (which is smaller than the screen's main UIView.) The number of labels and the length of text in each label is not known during compile-time. They are layed out on the screen dynamically at runtime with an algorithm that tries to choose the optimum number of columns and widths that keeps things from being clipped off the screen. To make the rejiggering process appear as smooth as possible to the user, I believe I need to do this inside of:
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
But of course this method is called before the rotation is completed and hence the need to know in advance the size that the containing UIView will soon have.
In iOS 5, viewWillLayoutSubviews will achieve what you're looking for.
In iOS 4, you can make the moves a little less jarring by animating the moving of your controls/subviews, even though it takes place in didRotateFromInterfaceOrientation (I know it doesn't sound like it, but it makes a huge difference). Thus, if you want to animate the move of a button to newFrame, it's as simple as:
[UIView animateWithDuration:0.25 animations:^{
[button setFrame:newFrame];
}];
Finally, judicious use of autoResizingMask, helps immensely, too, though I gather that this is not a possibility given that you are moving controls around in respect to each other.
Check out:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
You can find all those methods here:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
iPad app; I'm trying to resize my view when the keyboard appears. It amounts to calling this code at appropriate times:
CGRect adjustedFrame = self.frame;
adjustedFrame.size.height -= keyboardFrame.size.height;
[self setFrame:adjustedFrame];
Using this technique for a view contained in a uisplitview-based app works in all 4 orientations, but I've since discovered that a vanilla uiview-based app does not work.
What happens is that apparently the uisplitview is smart enough to convert the coordinates of its subviews (their frame) such that the origin is in the "viewer's top left" regardless of the orientation. However, a uiview is not able to correctly report these coordinates. Though the origin is reported as (0,0) in all orientations, the view's effective origin is always as if the ipad were upright.
What is weird about this is that the view correctly rotates and draws, but it always originates in the literal device top left. How can I get the view to correctly make its origin the "top left" to the viewer, not the device's fixed top left? What am I missing? Please, for something so trivial I've spent about 6 hours on this already with every brute force technique and research angle I could think of.
This is the original source which doesn't work in this case:
move up UIToolbar
OK, I don't know what the ACTUAL answer is to the original question, but I can say with certainty that one way to resolve the issue is to always ensure that you don't manipulate a viewController's view directly. Always wrap your view inside a container view inside the main "view", then have that container view adjust its position etc as needed. Works exactly as the splitview does, probably because in both cases now the view in question is a subview of the main "view". What a relief!