Reputation: 2940
Here is the sample project demonstrating the problem
What is it?
Two UIViewControllers
in stacks under UINavigationController
. Each has nothing to do with each other other than presenting from one to another. In both controllers, there is a UILabel
. Each uses Autolayout
. Each label holds arbitrary number of lines label.numberOfLines = 0
.
What works?
Transitioning from viewController A
(root) to viewController B
. B
gets allocated and initiated. B
looks well.
What went wrong?
Transitioning back from B
to A
. At ViewDidDisappear
, label in B
decided it will no longer show more than 1 line even though its numberOfLines
is set at 0. When B
was pushed into the stack, its label only shows 1 liner instead of multiple.
What caused it?
No idea. BUT looking in A
the label numberOfLines
was set to 0. If the line were to be removed, label in B
would not have collapsed.
Questions: But WHY? and I like A
to have multiple lines label, how can I overcome this?
Codes
A
@implementation FirstViewController
{
BugController *_bugController;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Bar Button
UIBarButtonItem *helpBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"BUG" style:UIBarButtonItemStylePlain target:self action:@selector(bugTapped)];
[self.navigationItem setLeftBarButtonItem:helpBarButtonItem];
// A sample label
UILabel *someLabel = [[UILabel alloc] init];
[someLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[someLabel setText:@"Tap BUG to see a bunch of text in many lines... tap back... then tap BUG again to see that the text has gone to 1 liner.... WTF?"];
[self.view addSubview:someLabel];
// Comment it out to see that problem is fixed
#warning This one liner is a culprit, removing it will make everything normal but WHY?
[someLabel setNumberOfLines:0];
#warning end of warning
//Constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[someLabel]-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(someLabel)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[someLabel]-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(someLabel)]];
}
- (void)bugTapped
{
// Reuse controller
if (!_bugController) {
_bugController = [[BugController alloc] init];
}
[self.navigationController pushViewController:_bugController animated:YES];
}
B
@implementation BugController
- (void)viewDidLoad
{
[super viewDidLoad];
// Setting up stuff
UILabel *header = [[UILabel alloc] init];
[header setTranslatesAutoresizingMaskIntoConstraints:NO];
[header setText:@"This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. "];
[header setNumberOfLines:0];
[self.view addSubview:header];
// Constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[header]-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(header)]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:header attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
}
Do check it out in the sample code link above!
Update 1 with workaround! After working with the code provided by Misha Vyrko, I realized that setting preferredMaxLayoutWidth to non-zero overcomes the bug in the UILabel.
Added to the BugViewController
// Constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[header]-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(header)]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:header attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
[header setPreferredMaxLayoutWidth:1]; // NEWLY ADDED
I am still looking for an explanation to why this happens and a better way if there is any.
Update 2 with corrections By setting PreferredMaxLayoutWidth to 1, the label.frame.size.height
actually expands to the height expected, if the width were actually 1. This means if you have any constraints dependency on the height of your label, it will not work. You will need to explicitly set it to the estimate width. It will not handle rotation without aids so watch out for that!
Upvotes: 11
Views: 1842
Reputation: 1000
In bugController you haven't got enough constraints to layout label if you set up all constraints all will be ok
@interface BugController ()
@property (nonatomic, strong) UILabel *header;
@end
@implementation BugController
- (void)viewDidLoad
{
[super viewDidLoad];
// Setting up stuff
self.header = [[UILabel alloc] init];
[self.header setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.header setText:@"This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing! "];
[self.header setBackgroundColor:[UIColor redColor]];
[self.header setNumberOfLines:0];
[self.view addSubview:self.header ];
// Constraints
// [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[header]-|"
// options:0
// metrics:0
// views:NSDictionaryOfVariableBindings(header)]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.header
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.header
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.header
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1
constant:0]];
self.header.preferredMaxLayoutWidth = self.view.frame.size.width;
[self.view needsUpdateConstraints];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
self.header.preferredMaxLayoutWidth = self.view.frame.size.width;
[self.view needsUpdateConstraints];
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
@end
Upvotes: 4
Reputation: 522
Here is the link for the working project.
The origin of problem is unclear, but following the principle "if you don't know what to do, do something", I've made it work by storing both labels as properties (this might be unnecessary, probably instance variables would be ok) and implementing following callbacks in both view controllers (remember, you said, that removing a line [someLabel setNumberOfLines:0]; in first controller fixes the second one, so here we go):
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.yourLabel.numberOfLines = 1;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.yourLabel.numberOfLines = 0;
}
It smells like a bug in UILabel class, but I'm not that cool to be sure. Good luck!
Upvotes: 1
Reputation: 2369
The behavior you're experiencing is pretty strange. Here is one way around it:
I'd recommend you stop re-using your BugController. It is a clean solution, and you should not have to cache ViewControllers explicitly. (the OS can handle efficiency on its own, and you should be able to rebuild your view controller at any time based on underlying Model data if you're adhering to MVC standards)
to do this, remove the BugController iVar, and change bugTapped
to:
- (void)bugTapped
{
BugController *bugVC = [[BugController alloc] init];
[self.navigationController pushViewController:bugVC animated:YES];
}
Upvotes: 0
Reputation: 26
Let me answer your question.
I didnt figure it out but going through my code might answer you. My wild guess is, it due to creating a new UILabel in viewDidLoad each time without clearing previous ones.
I tried few things. Following things worked for me
I tried to present _bugController instead of pushing it in navigation controller.
[self presentViewController:_bugController animated:YES completion:nil]
Not reusing the bugController object. Each time create a new object when you want to push it.
Move your code to viewWillAppear and on disappear make all UILabel nil. Here is my code:
FirstViewController
#import "FirstViewController.h"
#import "BugController.h"
@interface FirstViewController ()
@end
@implementation FirstViewController
{
BugController *_bugController;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Bar Button
UIBarButtonItem *helpBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"BUG" style:UIBarButtonItemStylePlain target:self action:@selector(bugTapped)];
[self.navigationItem setLeftBarButtonItem:helpBarButtonItem];
}
- (void)viewWillAppear:(BOOL)animated {
// A sample label
UILabel *someLabel = [[UILabel alloc] init];
[someLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[someLabel setText:@"Tap BUG to see a bunch of text in many lines... tap back... then tap BUG again to see that the text has gone to 1 liner.... WTF?"];
[self.view addSubview:someLabel];
// Comment it out to see that problem is fixed
#warning This one liner is a culprit, removing it will make everything normal but WHY?
[someLabel setNumberOfLines:0];
#warning end of warning
//Constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[someLabel]-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(someLabel)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[someLabel]-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(someLabel)]];
}
- (void)viewWillDisappear:(BOOL)animated {
for (UIView *subView in self.view.subviews) {
if ([subView isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)subView;
label.text = @"";
label = nil;
}
}
[super viewWillDisappear:animated];
}
- (void)bugTapped
{
// Reuse controller
if (!_bugController) {
_bugController = [[BugController alloc] init];
}
[self.navigationController pushViewController:_bugController animated:YES];
}
@end
BugController
//
#import "BugController.h"
@interface BugController ()
@end
@implementation BugController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
UILabel *header = [[UILabel alloc] init];
[header setTranslatesAutoresizingMaskIntoConstraints:NO];
[header setText:@"This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. This is a multiple line thing. "];
[header setNumberOfLines:0];
header.tag = 111;
[self.view addSubview:header];
// Constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[header]-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(header)]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:header attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
}
- (void)viewWillDisappear:(BOOL)animated {
for (UIView *subView in self.view.subviews) {
if ([subView isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)subView;
label.text = @"";
label = nil;
}
}
[super viewWillDisappear:animated];
}
- (void)dismiss {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
I hope this helps you!
Upvotes: 0