controlling orientation changes from within a subview - ios

Have a very large program where there is always a superview that just encompasses a custom segment controller. This view sits at the top of screen and controls navigation in several ways.
So the problem arose in only a selected few view controllers where everything was 100% programmaticly created. Essentially CGRect are not being defined in the property dynamic coordinates. But are not being recalculated on orientation change. Does anyone have a simple way to control this in the subview? I'm about to code something in the superview to pass to orientation to other subviews.. but there has to be a better way. Ideas?

Couple of pointers:
You can use auto-resizing masks to determine what happens to your views when their bounds change (ie, when the orientation changes). So UIViewAutoresizingFlexibleWidth means your view will 'stretch' proportionally with the superview when the bounds are changed. UIViewAutoresizingFlexibleLeftMargin means your view will effectively be right-aligned, as the left margin will adjust according to the width, etc etc.
Sometimes auto-resizing masks aren't enough - perhaps you have to change the view's content on an orientation, or do a complex animation. In this case, you use the willAnimateRotationToInterfaceOrientation method in your view controller. Your subviews might have a custom adjustForOrientation method that you've written that you can trigger when willAnimateRotation... is called.
Finally, on iOS 5 you can actually nest view controllers inside of view controllers, in which case orientation events get passed through automatically...but this is probably needlessly complex for what you're trying to do.

Related

What are the advantages of using Auto Layout with views created programmatically?

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.

iOS - viewDidLayoutSubviews called before auto-layout completed on iOS7

We're currently having a problem that only seems to affect iOS7 devices.
Within our .xib file we have two views within a container view (i.e.: not at the top level of the view hierarchy) that need to be circular on display. The views have constraints applied to their position and horizontal spacing within the container, and an aspect ratio condition requiring they are square. The views should expand in width/height on larger screen sizes, respecting the constraints described.
In our VC, we have the following in viewDidLayoutSubviews to force these views to appear circular:
- (void)viewDidLayoutSubviews {
self.progressContentContainerView.layer.cornerRadius = self.progressContentContainerView.frame.size.width/2;
}
This seems to work fine on iOS8, however on iOS7 there is a period after the view has been displayed where the constraints have not yet been applied and the size of the view/views is incorrect (see attached screenshots). This resolves itself and correctly renders a circle after half a second. This only appears to happen when the views that we intend to be circular are NOT at the top level of the VC's view hierarchy which seems to imply that viewDidLayoutSubviews is called before the subviews of subviews have also been laid out.
My guess is that we could potentially fix this issue by subclassing UIView for the nested container, adding references to the circular view within this subclass and overriding viewDidLayoutSubviews here to make the cornerRadius adjustment. This seems like a bit of a workaround though and I'm interested to see if there are other options.
Is there a cleaner/more idiomatic solution to this problem?
I know this is an old question but have you tried calling either:
[self.progressContentContainerView setNeedsUpdateConstraints];
or:
[self.progressContentContainerView layoutIfNeeded];

What is the purpose of UIView's autoresizingMask?

After reading about UIView's autoresizingMask on SO and developer.apple.com I'm still unclear what the purpose is. What's a situation where setting this property is necessary?
Yes, it is often necessary to set it if you don't want to resize the views manually. Note that it is mostly useful for subviews (i.e. those views that don't take the whole screen) rather then the main view of your app.
Views typically may need resizing if:
the device is rotated
an extra view (say, an ad) is added to the view, so the existing subviews have less available space.
For example, suppose if you have a view with two buttons on it, one in the top-left corner, another in the top-right corner. In order for the buttons to get wider when the view transitions from portrait to landscape, you need to set the FlexibleLeftMargin to the right button, FlexibleRightMargin to the left button.
Edit: autoresizingMask is also the first thing to look at if you see weird holes or overlaps when device is rotated or a new subview is added. Quite often the proper setting of these masks for subviews can get you a nice looking view in both orientations without having to lay out subviews manually - but usually it takes some experimenting.
Edit2: (since this is still gathering upvotes) Autoresizing masks are now mostly superseded with "Auto Layout", which allows for much more flexible constraints on views' sizes and positions. That being said, translatesAutoresizingMaskIntoConstraints is still occasionally useful for dynamically added views.
The purpose is that UIView properly shifts and resizes when its superview changes due to resizing, orientation change, showing editing controls in tableview cells etc.

changing subviews when rotating on ios

I need a view controller(on ipad) to share two modes, one in portrait and one in landscape. Actually, I pretty much want to mimic the functionality of UISplitViewController, but I want to be able to use not as the top level view controller. HIG guidelines aside, I have a general problem that I think anyone who is switching views between orientations will run into.
1) To provide for smooth transitions between views, I would like to call my view changes( and animations) inside willRotateToInterfaceOrientation:duration instead of didRotateToInterfaceOrientation method. The problem is, at this stage, the view frame and bounds have not yet changed to their new ones, so you end up having to set the frame manually, like:
subview.frame = CGRectMake(0,0,320,768);
instead of something nicer, maybe:
subview.frame = CGRectMake(0,0,320, self.view.frame.height);
2) Furthermore, even if you try such shenanigans, if the view autoresizes its subviews, you still get nowhere with it. If you disable the autoresizing of subviews, then stuff like this does not even work anymore:
subview.frame = self.view.frame; //because then you'd have to always specify the exact rect.
Does anyone have any insight to offer on this?
Thanks!
Try using willAnimateRotationToInterfaceOrientation:duration: instead.
Also, if you have your autoresizing masks set up correctly on the subviews, you shouldn't need to care whether the main view has been adjusted yet or not. Just size the subviews to fit appropriately inside the main view as it is sized now and it will just work.
If you don't (or can't) have your autoresizing masks set up correctly, you should then already have code to handle size changes in the main view's layoutSubviews method. So again, you shouldn't have to care much whether the main view has been resized yet or not.

When is layoutSubviews called?

I have a custom view that's not getting layoutSubview messages during animation.
I have a view that fills the screen. It has a custom subview at the bottom of the screen that correctly resizes in Interface Builder if I change the height of the nav bar. layoutSubviews is called when the view is created, but never again. My subviews are correctly laid out. If I toggle the in-call status bar off, the subview's layoutSubviews is not called at all, even though the main view does animate its resize.
Under what circumstances is layoutSubviews actually called?
I have autoresizesSubviews set to NO for my custom view. And in Interface Builder I have the top and bottom struts and the vertical arrow set.
Another part of the puzzle is that the window must be made key:
[window makeKeyAndVisible];
of else the subviews are not automatically resized.
I had a similar question, but wasn't satisfied with the answer (or any I could find on the net), so I tried it in practice and here is what I got:
init does not cause layoutSubviews to
be called (duh)
addSubview: causes
layoutSubviews to be called on the
view being added, the view it’s being
added to (target view), and all the
subviews of the target
view setFrame
intelligently calls layoutSubviews on
the view having its frame set only
if the size parameter of the frame is
different
scrolling a UIScrollView
causes layoutSubviews to be called on
the scrollView, and its superview
rotating a device only calls
layoutSubview on the parent view (the
responding viewControllers primary
view)
Resizing a view will call layoutSubviews on its superview (Important: views with an intrinsic content size will re-size if the content that determines their size changes; for example, updating the text on a UILabel will cause the intrinsic content size to be updated and thus call layoutSubviews on its superview)
My results - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/
Building on the previous answer by #BadPirate, I experimented a bit further and came up with some clarifications/corrections. I found that layoutSubviews: will be called on a view if and only if:
Its own bounds (not frame) changed.
The bounds of one of its direct subviews changed.
A subview is added to the view or removed from the view.
Some relevant details:
The bounds are considered changed only if the new value is different, including a different origin. Note specifically that is why layoutSubviews: is called whenever a UIScrollView scrolls, as it performs the scrolling by changing its bounds' origin.
Changing the frame will only change the bounds if the size has changed, as this is the only thing propagated to the bounds property.
A change in bounds of a view that is not yet in a view hierarchy will result in a call to layoutSubviews: when the view is eventually added to a view hierarchy.
And just for completeness: these triggers do not directly call layoutSubviews, but rather call setNeedsLayout, which sets/raises a flag. Each iteration of the run loop, for all views in the view hierarchy, this flag is checked. For each view where the flag is found raised, layoutSubviews: is called on it and the flag is reset. Views higher up the hierarchy will be checked/called first.
https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1
Layout changes can occur whenever any of the following events happens
in a view:
a. The size of a view’s bounds rectangle changes.
b. An interface orientation change occurs, which usually triggers a change in the root view’s bounds rectangle.
c. The set of Core Animation sublayers associated with the view’s layer changes and requires layout.
d. Your application forces layout to occur by calling the setNeedsLayout or layoutIfNeeded method of a view.
e. Your application forces layout by calling the setNeedsLayout method of the view’s underlying layer object.
Some of the points in BadPirate's answer are only partially true:
For addSubView point
addSubview causes layoutSubviews to be called on the view being added, the view it’s being added to (target view), and all the subviews of the target.
It depends on the view's (target view) autoresize mask. If it has autoresize mask ON, layoutSubview will be called on each addSubview. If it has no autoresize mask then layoutSubview will be called only when the view's (target View) frame size changes.
Example: if you created UIView programmatically (it has no autoresize mask by default), LayoutSubview will be called only when UIView frame changes not on every addSubview.
It is through this technique that the performance of the application also increases.
For the device rotation point
Rotating a device only calls layoutSubview on the parent view (the responding viewController's primary view)
This can be true only when your VC is in the VC hierarchy (root at window.rootViewController), well this is most common case. In iOS 5, if you create a VC, but it is not added into any another VC, then this VC would not get any noticed when device rotate. Therefore its view would not get noticed by calling layoutSubviews.
I tracked the solution down to Interface Builder's insistence that springs cannot be changed on a view that has the simulated screen elements turned on (status bar, etc.). Since the springs were off for the main view, that view could not change size and hence was scrolled down in its entirety when the in-call bar appeared.
Turning the simulated features off, then resizing the view and setting the springs correctly caused the animation to occur and my method to be called.
An extra problem in debugging this is that the simulator quits the app when the in-call status is toggled via the menu. Quit app = no debugger.
calling
[self.view setNeedsLayout];
in viewController makes it to call viewDidLayoutSubviews
have you looked at layoutIfNeeded?
The documentation snippet is below. Does the animation work if you call this method explicitly during the animation?
layoutIfNeeded
Lays out the subviews if needed.
- (void)layoutIfNeeded
Discussion
Use this method to force the layout of subviews before drawing.
Availability
Available in iPhone OS 2.0 and later.
When migrating an OpenGL app from SDK 3 to 4, layoutSubviews was not called anymore. After a lot of trial and error I finally opened MainWindow.xib, selected the Window object, in the inspector chose Window Attributes tab (leftmost) and checked "Visible at launch". It seems that in SDK 3 it still used to cause a layoutSubViews call, but not in 4.
6 hours of frustration put to an end.

Resources