I have a UIStackView, in it there's a subView called A. I want to rotate the view A. But nothing happened. I can rotate the A's layer, it's OK. So how to make animation of subviews of UIStackView?
Once you add an "arranged subview" to a stack view, its frame is automatically managed by the constraints added implicitly by the stack view. While you could hypothetically add additional constraints which could be animated, there's no set of constraints that would allow you to accomplish a rotation.
One solution would be to nest another view inside the one that's being managed by the stack. You could then override layoutSubviews in the outer view to opt out of auto layout for the inner view, so you can manage its rotation. Whatever updates that you make there to the inner view's frame could then be placed inside an animation block.
EDIT:
According to the docs for the transform property of UIView, "In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame." That seems to imply that a rotation should work even in the presence of auto layout constraints. That said, my guess is that transforming the view is really just a convenience for transforming the underlying layer.
Related
Apple doc regarding viewDidLayoutSubviews says:
When the bounds change for a view controller's view, the view
adjusts the positions of its subviews and then the system calls this
method. However, this method being called does not indicate that the
individual layouts of the view's subviews have been adjusted. Each
subview is responsible for adjusting its own layout.
Your view controller can override this method to make changes after the view lays out its subviews. The default implementation of this
method does nothing.
I have view1 which contains view2 means view2 is a subview of view1. I am creating a CALayer for my view2 which I want to be exact size of view2. I am using auto layouts so I want to create the CALayer when auto layout finish its work so I can have correct values to set for CALayer frame.
But in viewDidLayoutSubviews method I can't detect when exactly view2 frame is set by auto layout because Apple doc is also saying that
this method being called does not indicate that the individual
layouts of the view's subviews have been adjusted.
after that Apple doc saying
Your view controller can override this method to make changes after
the view lays out its subviews.
I am so confused at what point auto layout will set view2 frame so I can set my CALayer frame accordingly.
As I mentioned in the comments, you don't have to do anything special if your view is aligned with auto layout. If you want a single layer to adjust its bounds to the view's bounds, the most simple and correct way will be overriding layerClass method of the view - in that case layer bounds will be adjusted within a view automatically. If you need one more layer in addition to the standard one, the best place to update layer's bounds may be setFrame: method override.
As of viewDidLayoutSubviews, it is just an event to inform you, that all subviews of viewcontroller's root view are positioned at the desired places. You are not guaranteed that all the rest subviews of those subviews will also be aligned, since it is a responsibility of the parent view itself. they can be both auto layout positioned and manual layout positioned. This event also does not guarantee that all the the views are displayed as desired, especially if their frame change is animated.
I have a uiscrollview that has multiple subviews. They are stacked one after the next with spacing constraints. They, with auto layout, define the uiscrollview's contentsize.
Each view is my "snippet view" - a 100 px view. When a user taps a snippet view, i need to replace it with my "message view" - a much taller view. The message view has an intrinsic content size.
When I replace it, I remove all of my constraints, and then apply them again so they stack all the views on top of each other and the newly added message view gets inserted in the proper order.
This actually works fine, but its not smooth. I'd like to animate this so the snippet is removed, the views below are scooted down to make room for the taller message view, the new message view is added with the origin of the previous snippet, and I animate its frame height to fill the space.
I have code that does this without auto layout and it works well. But its a tremendous amount of layout code and I was hoping to do with auto layout.
Ive tried doing the remove / reapply constraints process, and then putting a layoutIfNeeded in an animation block. The problem is the newly added message view gets added with an origin of 0,0 and then animates down to its proper position, which is not a good effect.
You need to perform an initial layout pass to get the new view into position first.
Add it as a subview, with constraints to give it the right position (you can pin to the top of the outgoing view for this purpose). Call layoutIfNeeded, then remove and update all of your constraints and perform an animated layout as you are now.
Alternatively, before you do the animated layout, manually set the frame of the incoming view to be the same as the outgoing view. The layout pass will then animate from this instead of CGRectZero. That's probably a much neater solution.
Usually you'd add your new constraints, and then animate the application of those constraints, e.g.:
[UIView animateWithDuration:0.4 animations:^{
[self.view layoutIfNeeded];
}];
That will yield a smoother transition as the new constraints are applied.
If you want to avoid having the new subview start at 0,0 and jump down, you might create a container view (which has constraints to all the other views), and add your new view to that container. So the old "snippet view" would be in that container, you would remove it from that container, put the new one in that container, and then animate the layoutIfNeeded once all the new constraints are in place. That should avoid the effect you describe. (It should also simplify the code because you'll only be mucking about with the constraints that dictate the relationship between the container view and the subviews you add to it, and everything else should be driven from that.)
I have a containing view called containerView which is a UIView. When the app starts, it's just that view. During the course of the app's execution, I want to swap two "full-size" sub views in and out of the main containerView.
My question is, how do I make sure that the sub views fill up the entire containerView regardless of the orientation of the iPad. The containerView is 300 wide, but the height varies based on orientation.
I've tried:
setting the frame of the subview's from viewDidLoad, viewWillAppear, viewDidAppear directly equal to the side of the containerView frame,
creating LayoutConstraints that force the subview to conform to the containerView's proportions from viewDidLoad, viewWillAppear, and viewDidAppear. The question here is when to apply these constraints, and since the view is removed periodically, when to reapply the constraints.
I don't know what the idiomatic approach is and I want my code to be maintainable and reusable. Am i approaching this the wrong way? Is there some other way to make sure a subview fills up its containing view?
Example Code:
https://gist.github.com/Sahasrara/6817105
You should set the frame size in viewDidLayoutSubviews. By then all the autolayout nonsense has occurred.
I'm trying to animate a view from a thumbnail to a fullscreen view when touched. When the view is in thumbnail mode it is a subview of the UITableView.tableFooterView. When I animate to fullscreen I move the view to the controller's root view before updating the constraints. I do this because the tableview clips the subviews.
This is working perfectly, but when I try to do the reverse animation it's not so easy. I have to first move the thumbnail view back into a subview of the UITableView.tableFooterView before I can update the constraints. I then undo the constraints (basically set them to what there where originally). This works but the animation does not look right because as soon as the view is moved back into UITableView.tableFooterView it's clipped again by the UITableView and the animation is partially hidden behind all the tableview adornments!
My idea is to get the calculated frame for the constraints and perform old fashioned frame animation and then install the constraints after.
Is there a known way to ask the layout system given an array of constraints what will the frame be without actually installing those constraints?
Thanks.
I don't think there is such a way.
Note that the constraints you define (NSLayoutConstraint) are not the only constraints. Other constraints are defined by the properties of your views (e.g. intrinsic content size, hugging priorities etc.).
I guess this is something that would work better with autoresizing instead of autolayout.
Another solution would be to use a hidden placeholder view in the table footer. When you are returning the view back into the footer, you can just ask for the frame of the placeholder.
I have a tableview with cells containing text views as well as imageviews. My project is currently using AutoLayout. My goal is to get the imageview to display in fullscreen when it is tapped. One option is to use a modal view controller, but I want to have this work sort of like the way tapping on images in the facebook app works, the app centers the image and fades the background.
Since I'm using autolayout, I cannot simply set the frame of the imageview to fill the screen. Instead, I need to use autolayout constraints. My image view has 5 constraints, a constraint setting a distance from the bottom of the cell, as well as the left an right sides, and one controlling the image height. The last is a vertical space constraint between the textview above the image view and the top of the image. While this would appear to conflict with the height and bottom constraints, for some reason interface builder forces me to have this. To avoid problems, I set this constraint's priority to be less than 1000 (the image should never overlap the textview anyways, since the tableview cell height is set so everything will fit perfectly).
To center the image, I set the distance from the left and right to be zero and remove the vertical space constraint. In order to center the image, I replace the bottom space constraint with a center y alignment constraint to the UIWindow as opposed to the tableviewcell. I want to have it be in the center of the screen, not the cell.
To get the main window I use this:
AppDelegate* myDelegate = (((AppDelegate*) [UIApplication sharedApplication].delegate));
//access main window using myDelegate.window
Then, to set the constraint:
//currently sets the distance from the bottom of the cell to 14
//changing it...
[cellselected removeConstraint:cellselected.imagebottomspace];
cellselected.imagebottomspace = [NSLayoutConstraint constraintWithItem:cellselected.viewimage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:myDelegate.window attribute:NSLayoutAttributeCenterY multiplier:0 constant:0];
[cellselected addConstraint:cellselected.imagebottomspace];
However, this doesn't work. The changes in the width and height of the image view apply just fine. However, when readding the imagebottomspace constraint, I get an unsatisfiable layout--apparently the constraint conflicts with another constraint which sets the distance between the bottom and the image view to 14, the very constraint I just removed. So it seems that it isn't actually removing the constraint.
When I proceed and let the app break a constraint, the imageview moves, but to the wrong place. It isn't centering in the screen. It moves way up and off the screen.
Obviously what I'm doing isn't right. What am I doing wrong?
So I guess you want something like this:
First, you need to know that as of Xcode 4.6.3, the nib editor (“Interface Builder”) has a bug when setting up constraints in a table view cell. It should create the constraints between the subviews and the cell's content view, but instead it creates the constraints between the subviews and the cell itself. This tends to screw up layout at runtime. (This bug is fixed in Xcode 5 and later.)
The consequence of this is that you should either remove all of the constraints that were in the nib and recreate them in code, or just get rid of the nib and create the cell's entire view hierarchy in code.
Second, there's an easier way to do the image zooming. Here's the basic procedure when a cell is selected:
Convert the selected cell's image view bounds to a CGRect in the top-level view's coordinate system.
Create a new image view just for zooming and set its frame to that CGRect. Set its userInteractionEnabled to YES. Set its autoresizingMask to flexible width and height. Add a tap gesture recognizer.
Add the new image view as a subview of the top-level view.
Set the cell's image view's hidden property to YES.
In an animation block, set the new image view's frame to the top-level view's bounds.
Disable the table view's panGestureRecognizer.
When the new image view is tapped, reverse the procedure:
Convert the selected cell's image view bounds to a CGRect in the top-level view's coordinate system.
In an animation block, set the zoomed image view's frame to that CGRect.
In the animation completion block:
Remove the zoomed image view from its superview.
Set the cell's image view's hidden property to NO.
Enable the table view's panGestureRecognizer.
Since you're not moving the original image view, you don't have to mess with its constraints. Hidden views still participate in layout.
Since you're creating the new image view in code, it will have translatesAutoresizingMaskIntoConstraints set to YES by default. This means that you can just set its frame. Auto layout will automatically turn the frame into constraints.
You can find the full source code in this github repository.
I've just come across a similar issue. I think that the reason for these problems are that the views embedded in UIScrollViews exist in a different bounds system to those of the views outside it. This is effectively how scrolling works in the first place, think of it as just applying a variable offset to the views it contains. Autolayout doesn't know how to translate between these different coordinate systems so any constraints that bridge across aren't going to be applied the way you expect.
To quote from Erica Sadun's excellent book iOS Auto Layout Demystified (from the section 'Constraints, Hierarchies, and Bounds Systems'):
"Be aware of bounds systems. You should not relate a button on some
view, for example, with a text field inside a separate collection
view. If there's some sort of content view with its own bounds system
(such as collection views, scroll views, and table views), don’t hop
out of that to an entirely different bounds system in another view."