Reputation: 633
Everybody knows that you can't trust the frame size on a UIViewController init/viewDidLoad method; this:
- (void)viewDidLoad: {
NSLog(@"%d", self.view.frame.size.width);
}
will print wrong sizes in many occasions (in particular it's pretty much broken in landscape mode)
This will actually return always corrected results so it's good to layout the subviews:
- (void)viewWillAppear: {
NSLog(@"%d", self.view.frame.size.width);
}
The problem is that viewWillAppears gets called every time the view appears, so it's not suitable to alloc or add subviews. So you end up declaring every single view in the interface and you end up with huge header files that I don't like at all since most of the items don't need any more manipulations after the initial setup.
So question one is: Is there a better way to handle subviews positioning?
Question two is very related, let's say I have a subclass of UIView including various others subviews. I declare it inside my interface, and i alloc/init it in my init/viewDidLoad method.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
...
menu = [[SNKSlidingMenu alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
...
}
As we already know we now need to reposition it in viewWillAppear to get a more accurate reading
- (void)viewWillAppear:(BOOL)animated{
....
menu.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
....
}
The problem is that of course all the subviews needs to be repositioned as well. This is done by the layoutSubviews function that get called automatically, but we got the same problem: All the subviews need to be declared inside the interface of the SNKSlidingMenu class.. Is there a way around this?
Thanks.
Upvotes: 11
Views: 14537
Reputation: 1187
This saved my life more than once (Swift 4):
override func viewDidLoad() {
super.viewDidLoad()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
Basically this forces the viewcontroller to correctly layout it's view and from there you can get the correct frames for all your subviews. This particularly helps when doing transition animations and your view controllers are using autolayout and interface builder.
From what I've noticed it looks like the initial frames are set to whatever your interface builder's default size class is set to. I normally edit using the iPhone XS size class so in viewDidLoad
it seems that the view's width is always 375 regardless whether you are using an iPhone XR or not. This corrects itself before viewWillAppear
though.
The above code will correct this issue and allow you to get the correct frames for your view / subviews before the view controller is rendered to the screen.
Upvotes: 2
Reputation: 316
viewWillLayoutSubviews
and viewDidLayoutSubviews
can resolve this problem.
But the two methed would be performed more times.
this is my code to get correct self.view.frame
.
- (void)viewDidLoad {
[super viewDidLoad];
...
dispatch_async(dispatch_get_main_queue(), ^{
// init you view and set it`s frame. this can get correct frame.
...
}
...
}
Upvotes: 5
Reputation: 25011
If you are targetting iOS 5.0 or better you can use viewWillLayoutSubviews
and viewDidLayoutSubviews
to make changes.
As for your second question, if you need access to an instance variable in other method than init
, you need to keep it around, I don't see a problem with it.
You can, however, try to use Auto Layouts and set up rules between the subviews so it's automatically laid out for you without the need to keep a reference.
Upvotes: 11
Reputation: 66302
viewDidLoad
only gets called when your view is created, but lots of things can affect the frame
's size, and it doesn't get called again when frame
changes.
Instead:
viewDidLoad
viewWillLayoutSubviews
.See some additional discussion here for handling rotation: https://stackoverflow.com/a/16421170/1445366
Upvotes: 6