Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating UILabel Text resets storyboard?

I came across a weird scenario, that I just can't figure out on my own.

I started a little test scenario, with an almost empty iOS Single View Application and a storyboard. The story boards contains a label and single UIView, 10x10, just for presenting the issue. Of course they have the appropriate IBOutlets assigned and connected:

@property (nonatomic, weak) IBOutlet UILabel *label;
@property (nonatomic, weak) IBOutlet UIView *dot;

In the (void)viewDidLoad method I start a timer and add it to the run loop like that:

- (void)viewDidLoad {
    [super viewDidLoad];

    NSTimer * timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

In the (void)doSomething method I do the following:

-(void)doSomething {
    // Lets move the dot to the right a bit further
    _dot.center = CGPointMake(_dot.center.x+1, _dot.center.y);

    // About every ten steps, update the label with the current
    // X position, to better show the problem
    CGFloat moduloResult = (float)((int)_dot.center.x % (int)10.0);
    if(moduloResult <= 0)
        _label.text = [NSString stringWithFormat:@"%f", _dot.center.x+1];
}

So basically, I change the center of the little 10x10 dot-view every time by 1, and every 10th time I update the label to show the current X position. For that matter it doesn't even matter with what I update the label text, it's just important that it changes. And I only change the text every 10 times to better show the problem here.

If I run that code the dot moves just as intended to the right, but then when I update the label text, the dot view gets reset and jumps back to it's original position. So updating my label text resets my other views on the Storyboard, which totally confuses me.

Can someone reproduce that or even explain it?

Thanks all, in advance :)

Alex

like image 328
green4367 Avatar asked May 29 '15 07:05

green4367


3 Answers

I was able to reproduce described behaviour, here is why it is happening:

  1. By default, position of elements is determined by auto generated AutoLayout constraints
  2. When changing position of dot you are only updating it's current frame, not underlying constraints
  3. Changing the text of UILabel possibly changes size of UILabel's content, so it's frame needs to be recalculated. Here entire layout is recalculated based on constraints, thus also reseting frame of the dot.

For resolving, I'd suggest to properly specify AutoLayout constraints and updating them instead of position directly.

like image 114
Jakub Vano Avatar answered Sep 21 '22 01:09

Jakub Vano


That was pretty much it, the AutoLayout constraints pushed the view always back to it' original place, which kind of makes sense I guess, that's what the AutoLayout is supposed to do.

So, since I wanted my litte 10x10 dot-view to move around the screen (for practicing purposes, I changed my code. I try to explain it, lack of reputation just currently keeps me from posting the appropriate images.

I made two custom AutoLayout constraints, telling it to keep the view always 120px from the left of the superview and 100px from the top of the superview. I also specified the constraint for a fixed width and height of each 10. Since for a valid constraint you need the combination of x and y as well as height and width (at least as far as I understand it).

                  -
                  |
                  | Constraint *n* from top
                  |

                  |------|
  Constraint      | view |
  *n* from left   | _dot |
|---------------  |------|

Then I created two IBOutlets for the two Constraints:

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *c1X;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *c1Y;

I changed my (void)doSomething method to this:

-(void)doSomething {

    // Lets move the dot to the right a bit
    // further by changing its Constraint
    _c1X.constant++;

    // About every ten steps, update the labe with the current
    // X position, to better show the problem
    CGFloat moduloResult = (float)((int)_dot.center.x % 10);
    if(moduloResult <= 0)
        _label.text = [NSString stringWithFormat:@"%f - %f - %f", _c1X.constant, _dot.center.x, _dot.center.y];
}

It works, the changing of the constraint instead of the actual view now moves the view over the screen. If this is best practice, I don't know. If I wouldn't have tried it for practice purposes, following a aged tutorial, I wouldn't come up with that in the first place.

In the hopes this might help one day. Any comments or improvements are welcome :)

Alex

like image 45
green4367 Avatar answered Sep 21 '22 01:09

green4367


This seems to be an old question but it bothered me too. I was puzzled at the fact that changing the text in UILabel in the bottom of the visual hierarchy makes the whole storyboard to re-layout.

More than that, having some Android development background I never witnessed such behavior in Android with the (analogous) TextView. You change the text in it how you wish -- never updated the higher levels of the UI.

But the answer by @Jakub Vano made me to realize one thing:

The TextView in Android has always fixed width and height and there is no way the updating of the text inside the view would change its borders. On the contrary, the iOS' UILabel can be left with self-formatting width and height based on the text in it. Unfortunately, even fixing the width and height in the storyboard doesn't solve the issue: updating the text still fires the request to re-layout everything.

I found the solution in using CATextLayer -- it does not impact the layout and you can put there any text you want. But the price for that is that you have to take care about everything programmatically, keeping in mind you deal with real pixels, not dots, like so:

            let mScale = UIScreen.main.scale

            indicatorLeft = CATextLayer ()
            indicatorLeft.fontSize = FONT_SIZE * mScale
            indicatorLeft.string = "text in CA layer"
            indicatorLeft.foregroundColor = UIColor.white.cgColor
            indicatorLeft.shadowColor = UIColor.gray.cgColor
            indicatorLeft.isHidden = false
            indicatorLeft.contentsScale = mScale

            indicatorLeft.frame = CGRect(x: 0, y:  0,
                                 width: VIEW_WIDTH * mScale, height: VIEW_HEIGHT * mScale )

            leftView.layer.addSublayer(indicatorLeft)
like image 32
rommex Avatar answered Sep 21 '22 01:09

rommex