In my viewDidAppear i"m changing the frame of one of my ImageViews. The view and all the other methods will not show it until i will [self viewDidAppear] it.
I feel its not right, is there some reloadData message ?
Thank you.
Exactly, calling [self viewDidappear] yourself is not right.
If you need to change the frame of the views while being on the view, create a method yourself that you can call every time you want, or use viewDidLayoutSubviews;
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.
Also, check that your method has a correct implementation:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//Your code
}
It would be better to change your image view's frame in viewDidLayoutSubviews.
You probably implemented the wrong function. The proper method signature is:
- (void)viewDidAppear:(BOOL)animated
not just viewDidAppear.
Related
In my view controller's viewDidLoad, I'm adjusting a UIImageView's constraints to reflect its image's aspect ratio. After it's done, I'd like to know the image view's updated bounds. The bounds do update in the UI, but when viewWillLayoutSubviews and viewDidLayoutSubviews are called (each is called only once), the bounds are still the original bounds from before I changed the aspect ratio. When should I check the bounds and make my associated changes?
After you update the constraints in the viewDidLoad, update them using either one below
[yourImageViewOL setNeedsDisplay];
[yourImageViewOL layoutIfNeeded]; //Allows you to perform layout before the drawing cycle happens. -layoutIfNeeded forces layout early
Then you can look for the updated bounds in the - (void)layoutSubviews; method as this method gets called by layoutIfNeeded automatically.
As of iOS 6.0, when constraints-based layout is used the base implementation applies the constraints-based layout, otherwise it does nothing.
Try to override the method -updateViewConstraints
- (void)updateViewConstraints {
[super updateViewConstraints];
/// your code here
}
I have used autolayout constraints from storyboard. However in some cases, I want to calculate dynamic height of subview. I code this in viewDidAppear(), it works fine because this method is called after all view frames are set by layout constraints. The problem here is that I can see the frame set by constraints for half a second. And then the code reframes the view.
I came to know about viewDidLayout() which is called after constraints has set the frame so I can change. But it doesn't work. It is like this method is called before constraints are used.
The viewDidAppear method is called at the end of the view life cycle. So if you change the constraints here, it will always be visible.
If the change you want to do is a one time event, then you may do so in the method viewWillAppear. But this won't always help because the view drawing may not always be finished by this time. So a better option is to place the code in viewWillLayoutSubviews or viewDidLayoutSubviews.
NOTE: viewWillLayoutSubviews or viewDidLayoutSubviews will be called multiple times in a view's life cycle, such as orientation change or moving in and out of another view or any frame change for that matter. If the code change you want to put here is a one time event, then please make sure to use flags.
eg:-
- (void)viewWillLayoutSubviews {
if(firstTime) {
// put your constraint change code here.
}
}
Hope this helps! :)
As name suggests, viewDidLayoutSubviews is called when the view of your viewController has just finished its laying out and you can assume that at that dynamic height your views are in their correct places/frames according to your autolayout constraints.
Swift :
override func viewDidLayoutSubviews() {
// Set your constraint here
}
Objective C :
-(void)viewDidLayoutSubviews {
// Set your constraint here
}
I am not sure what you are actually trying to do in viewDidLayoutSubviews(). I always use to customise view by modifying layout constraint values overriding this method.
// Called to notify the view controller that its view has just laid out its subviews
override func viewDidLayoutSubviews() {
//Write your code here, any constraint modification etc.
super.viewDidLayoutSubviews()
}
When I use a subclass of UIView and put it on the storyboard with some constraints,i check its frame in awakeFromNib() or didMoveToWindow(). The result is not correct cause the constraints are not applied yet.
Is there any callback to inform me that the constraints are applied? So after that i can do sth with the correct frame.
When awakeFromNib viewDidLoad or didMoveToWindow is called, layout is not completed yet. You have to use viewDidLayoutSubviewsof a viewController. This method will be called after applying constraints and completing layout.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
}
I have an iOS app in which I need to know when a new view is completely visible on-screen; that is, when Autolayout has finished its calculations and the view has finished drawing.
ViewDidAppear seems to fire well before the view is completely visible. If I turn off Autolayout, the timing seems to line up as far as human perception goes, but I need to use Autolayout in this project (so this isn't a solution...just a test).
Is there any method that fires when Autolayout is done calculating? Or another method that fires when the view is ACTUALLY visible (since ViewDidAppear doesn't work for this)?
Thanks!
The following can be used to avoid multiple calls:
- (void) didFinishAutoLayout {
// Do some stuff here.
NSLog(#"didFinishAutoLayout");
}
and
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:#selector(didFinishAutoLayout)
object:nil];
[self performSelector:#selector(didFinishAutoLayout) withObject:nil
afterDelay:0];
}
I'm using viewDidLayoutSubviews for this. Apple's documentation says, "Called to notify the view controller that its view has just laid out its subviews."
If you watched 2018's WWDC about "High-Performance AutoLayout", you would know the answer to this question.
Technically, there is no such API method that will be called when autolayout has completed your view's layout. But when autolayout has completed the calculations, your view's setBounds and setCenter will be called so that your view gets its size and position.
After this, your view's layoutSubviews will be called. So, layoutSubviews can, to some degree, be thought of as the method that fires after autolayout has done calculations.
As to view controller's viewDidLayoutSubviews, this is a bit complicated. The documentation 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.
So when viewDidLayoutSubviews called on a view controller, only the view controller'view 's first-level subviews are guaranteed to be laid out correctly.
What it worked in my case was request layout after changed a constraint value:
self.cnsTableviewHeight.constant = 50;
[self layoutIfNeeded];
Later on override layoutSubviews method:
- (void) layoutSubviews { //This method when auto layout engine finishes
}
You can call setNeedsLayout also instead of layoutIfNeeded
I guess implementing viewDidLayoutSubviews is the correct way but I used an animation just to write the completion callback inside the same method.
someConstraint.constant = 100; // the change
// Animate just to make sure the constraint change is fully applied
[UIView animateWithDuration:0.1f animations:^{
[self.view setNeedsLayout];
} completion:^(BOOL finished) {
// Here do whatever you need to do after constraint change
}];
You might face this problem not just with UIViewControllers but also UIViews. If you have a subview and want to know if AutoLayout has updated it's bounds, here is the Swift 5 implementation,
var viewBounds: CGFloat = 0.0
var autoLayoutHasCompleted: Bool = false
override func awakeFromNib() {
super.awakeFromNib()
// someSubView is the name of a view you want to check has changed
viewBounds = someSubView.bounds.width
}
override func layoutSubviews() {
if viewBounds != someSubView.bounds.width && !autoLayoutHasCompleted {
// Place your code here
autoLayoutHasCompleted = true
}
}
I noticed that if I change the frame of a UICollectionView (e.g. when the toggling the in-call status bar), the collection view doesn't update its cells properly for its new frame. It's probably easiest to see in a short video:
http://cl.ly/2t2Y2A3A2w1D/CollectionViewTest.mov
The source files for that simple test are here:
http://cl.ly/0S0A360B3I3Q/CollectionViewTest.zip
It doesn't seem to matter whether I use UICollectionViewController or UIViewController. Has anyone seen this? Am I missing something? The only workaround I've found is to call reloadData on the collection view in the view controller's viewWillLayoutSubviews or viewDidLayoutSubviews which works but is far from ideal when the collection view's frame is being affected by a user drag since reloadData is called many times and results in very sluggish UI updates while the user is dragging.
I had a similar problem, where my UICollectionView's frame changed and unless I call reloadData it got stuck in the same spot without moving with the CollectionView.
I've fixed it by adding this to my CollectionViewFlowLayout:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
Worked nicely.
I had a similar problem. I just needed to resize the collection view frame so that a menu could show below it and the user could still scroll up far enough that the menu doesn't cover the cells at the bottom.
Changing the content inset worked for me.
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, rect.size.height+8, 0);
I couldn't get changing the frame of the collection view to work without reloading the data, which would screw up cells that were already selected by the user.
You can have your UICollectionViewController register for the UIApplicationWillChangeStatusBarFrameNotification, and reloadData in the selector. That way, you'll only be reloading the data when the status bar changes frame.
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(callCameIn:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
}
-(void)callCameIn:(NSNotification *) aNote {
[self.collectionView reloadData];
}