I have a viewController with three subviews.
Also I use AutoLayout and size classes.
These views are animated and change location and size.
After the animations I update a label but the whole view is redrawn so each view is in their initial position and size. This shouldn't happen.
As in Apple developer reference that says:
"The default content mode of the UILabel class is
UIViewContentModeRedraw. This mode causes the view to redraw its
contents every time its bounding rectangle changes. You can change
this mode by modifying the inherited contentMode property of the
class."
It doesn't seem clear to me how to modify the -contentMode- in order to update that label and leave the view -as is-. Can anyone give me a clue?
Thanks in advance.
It sounds like you may be using autolayout to lay out your view (e.g., via constraints in IB), but then you're manipulating your views' frames directly for your animations - is this the case? If so, you should instead be animating the constant values of your constraints, or possibly the transforms of your subviews.
If you manipulate frames directly in a view which uses autolayout, your changes will be over-written the next time the system lays out your view (e.g. after a label's text changes).
You have 3 options to overcome your issue -
Stop using AutoLayout in your Storyboard/Xib.
Not a great solution
Animate changes to the transform property of your subviews. e.g. myView.transform = CGAffineTransformMakeScale(2.0,2.0);
Useful for presentation and dismissal animations, but mixing AutoLayout and transforms has some issues pre-iOS 8.0
Add IBOutlets for the constraints you need to change in your animations. Animate changes to the constant values of those constraints.
Most robust approach but can lead to a lot of properties and code for complex animations
Try contentMode = UIViewContentModeCenter. This should prevent the redraw you're seeing. Although that may not be the contentMode you want.
Option 2 is to use CATextLayer to draw the label. It will resize very nicely with animation, but it's a lot more work to set up.
Related
This may sound like a silly question, but most of my experience with iOS development has revolved around Auto Layout being prominent, and I'm curious now (for performance purposes) how I'd go about laying out a cell without Auto Layout and using only frames (and perhaps auto-resizing masks). Turns out this is very hard to Google due to the prominence of Auto Layout.
Essentially am I setting up and adding the subviews in the initWithStyle method of the UITableViewCell subclass?
Positioning wise, am I just relying on the bounds of the contentView, and then if I want one view beside another view, would I basically do newView.frame.origin.x = CGRectGetMaxX(otherView.frame) + spacing?
What happens when I rotate? I know I can watch for rotation in viewWillTransitionToSize, but how do I go about re-positioning the cells? Simply calling tableView.reloadData() would be both expensive and not do much as the cells are laid out in initWithStyle, correct?
I'm targeting iOS 8+.
Any insight would be truly appreciated.
Actually its
newView.frame.origin.x = CGRectGetMaxX(otherView.frame) + spacing + leftMargin;
// you have to include all the spacing, including margins
Tip:
Do not rely on bounds of the contentView by default it is set to maximum width of 320, i suggest you use main screen's frame for that.
AutoLayout is our friend and can save us a lot of time.
how do I go about re-positioning the cells?
If you're planning to do it programmatically, you need to setup the new height and width of the view after the rotation.
Calling tableView.reloadData() to update the views in the cell is not expensive, that's how it works. We don't have a choice but to live with it.
Then it will be better to write the frame calculation portion in viewDidLayoutSubViews.
I'm not entirely certain I understand your question. Please let me know if this makes sense:
Adding Subviews
To add subviews programmatically, it's actually best to do so in the override of layoutSubviews by checking if the contentView has subviews already added and calling a method to add them if needed. This way, you can load the table cell via either a storyboard/nib or using initWithStyle. Alternatively, you can use a shared commonInit method that you call in both initWithCoder and initWithStyle.
Layout of Subviews
To layout your subviews programmatically, you override layoutSubviews (and remember to call super) and set up the frames in that method. This method will be called whenever the view changes size (rotation, initial presentation, etc) and will always include the current bounds of the content view. To calculate your subview frames, you can do so as you've suggested, but you need to define the subview frame and then set the frame for the view:
frameB.origin.x = CGRectGetMaxX(viewA.frame) + spacing;
viewB.frame = frameB;
And keep in mind that this will not correct for the width of frameB. Therefore, you might want to consider using CGRectDivide() instead.
Scrolling performance
That said, the big performance hit when using auto layout in table view cells is not auto layout itself, but the calculations that are done many times on the same set of data to lay it out - all on the main thread. Such as calculating frames for all the text to layout a bunch of labels relative to one another or something that uses drawRect rather than drawing to an image bitmap on a background thread and then loading the image in to a UIImageView once the drawing is complete. Without knowing what data you are displaying it is hard to guess what is causing the performance issues, but you should be sure to use profiling to determine if you are actually improving performance. Additionally, you may need to consider moving some of the number crunching and/or image rendering onto a background thread.
Good luck!
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];
I'm having a hard time wrapping my head around how frame properties (height, width, posX, posY) work in terms of setting them in a View Controller vs on the Storyboard (Interface Builder).
For instance, let's say I have a UICollectionView object that I set to have a width of 400 and a height of 800. Then, in my code, I set the frame of that same object to 600 x 400. I haven't really found a consistent behavior. I tried setting the frame in viewDidLayoutSubviews and it sort of worked - but it seem to 'jump' back and forth between that and what was set on the storyboard.
Basically my question is, when do the properties on the storyboard change the UI object? I assume that I just need to know that, and then reset them in the View Controller after the fact. Or, is there a way to set the height and width empty so that I can do it all in the code?
Any insight into this would be very helpful!
If you use AutoLayout in your project then setting frames of the view objects you configured in storyboard won't work. Because after you set the frames, AutoLayout will update frames again which makes the frames set by you not working. If you want detail, you can check this article:Advanced Auto Layout Tools But you can set frames of view objects created programmatically to position them.
You can check if you have turned on AutoLayout in you storyboard file's file inspector. There is one thing though, if you do want to use AutoLayout, be sure to not set view's translatesAutoresizingMaskIntoConstraints to NO. The default value of this property is YES. If you use AutoLayout, this property is set by storyboard for you, because constraints created in storyboard are enough to layout the views.
EDIT:
1) if you are not using AutoLayout then setting frames in code should work as expected.
2) yes you can, a little tricky though. you must create UICollectionView yourself using [[UICollectionView alloc] init] or load it from nib. and then configure cell in IB with a xib file. you can use AutoLayout to layout subviews of cell in xib file. and register the class of cell to UICollectionView or load cell object from nib yourself. then you should calculate the size of every cell and let AutoLayout layout subviews of cells.
although this is easier than layout interface entirely in code, it's still a little complicated. the better way is using AutoLayout. Since not all the layout detail can be done in the design time since some views' frame may be different depending on data. you can make a basic layout with AutoLayout first, then IBOutlet the constraints you want to configure on the fly. and change the constant property of constraint objects later. this way, you can 100% control the layout process and also let AutoLayout do the dirty jobs you don't want to do yourself. I suggest you read official docs of AutoLayout and other good resources about it. The learning curve is steep at first, it may make you want to kill yourself too. But it's really powerful and easy to use. once you figured out how AutoLayout works, it will make your iOS development life much easier.
If you want to set the size of things through code, you could try creating outlets from the storyboard to the View Controller. Then in the View Controller, you can use viewDidLoad or viewDidAppear to set the size properties of your object(s).
viewDidLoad will get called when that view is first created. viewDidAppear will get called each time that view comes back onto the screen (like if you are going back with a navigation controller).
I'm new to core animation and I'm struggling with one thing - how to combine autolayout with core animation. Actually I've found only one sentence in the documentation of Core Animation which refers to Autolayout here is it
Remember to Update View Constraints as Part of Your Animation
If you are using constraint-based layout rules to manage the position of your views, you must remove any constraints that might interfere with an animation as part of configuring that animation. Constraints affect any changes you make to the position or size of a view. They also affect the relationships between the view and its child views. If you are animating changes to any of those items, you can remove the constraints, make the change, and then apply whatever new constraints are needed.
But as I've tried all is not as strait-forward as it may seem.
Here is my scenario.
I've designed a sliding menu which uses autolayout extensively. Here is the appearance of that view.
I'm using autolayout constraints to force proportional positioning of those items in the sliding menu. Actually there are a lot of constraints there and I didn't want to post all of those in my question, and even may be they are not needed for direct answer of this question, however if you need them I can update the post with those constraints.
The animation that you see in the gif was reached by only autolayout. I just added outlet to the height constraint of the sliding menu and changed it in this way: (the code is written using Xamarin Monotouch, but I believe it should be clear what is done here for pure iOS developers)
private void AnimateSlideMenuAppearance()
{
float height;
if (isSlideMenuShown) {
height = 0;
} else {
height = slideMenuHeight;
}
UIView.Animate (0.4,
delegate {
this.slideMenuHeightConstraint.Constant = height;
this.View.LayoutIfNeeded ();
},
delegate {
isSlideMenuShown = !isSlideMenuShown;
});
}
Now I want to get more sophisticated appearance transition. CLICK HERE to see the effect that I want to reach.
Just to try out I tried to implement the disappearing part of that animation with series of CABasicAnimations, but it was unsuccessful, I get strange behaviour.
Can anybody suggest what I should do here? Is that possible to use autolayout to calculate the positions of the views, but somehow override the animation between autolayout size changes? I mean in my concrete example instead of proportionally decreasing the sizes of all buttons in menu I need to add FadeOut animation to them, animate the bounds to zero and also radically increase begin time of the animations from button to button in order to get the effect that I want. Or may be I need to completely get rid of autolayout and manually calculate the sizes and animations?
What is the best practice in these kind of scenarios - when you have complex autolayouting and you need custom Core Animation transitions between autolayout changes? I hope that I described the question well.
Thank you for your answers.
This is completely feasible, while it may be complex solely because it looks like your desired cases will have multiple animations.
However, I noticed one thing in your code that's odd: you change the constant on the constraint (this.slideMenuHeightConstraint.Constant = height) in the animation block, instead of before it. For nearly all cases I can imagine, you should change the constraint before the animation block. Constraints are not visually rendered until either the next UI run loop (or by setNeedsUpdateConstraints to force it for the next run loop), or immediately by layoutIfNeeded. And since [UIView animate:...] is doing this for you, layoutIfNeeded should (generally) be the only thing in your animate block, when animating autolayout.
In your case, you will have to make the animation somewhat reactive, however - for e.g., if you want to add those buttons in like in the example and have them pop in, animate out, and grow. After calling layoutIfNeeded, you can safely check the frame size. If it's beyond your threshold (or some other metric), you can trigger then animations of the buttons. (So yes, this may be a case where I'd add more code inside the animate block -- check the threshold, begin other animation, etc).
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.