2

I am trying to get a UIView to expand using an animation block, which works perfectly. However, I want a UILabel to start at 0 and every 0.01 seconds to add 1 until it gets to 100. I created a thread after the animation to accomplish this and it works but it causes the animation I setup to do nothing. I have tried many different things but have had no luck. What would be the best way to accomplish this?

My simplest attempt with the same result as all the others:

[UIView animateWithDuration:1 animations:^{
    _lView.frame = CGRectMake(_lView.frame.origin.x,_lView.frame.origin.y+_lView.frame.size.height,_lView.frame.size.width,-500);
}];

[[[NSThread alloc]initWithTarget:self selector:@selector(startcounting) object:nil]start];


-(void)startcounting{
for(int x=0; x<100; x++){
    [NSThread sleepForTimeInterval:0.01];
    ++_mcount;
    dispatch_async(dispatch_get_main_queue(), ^{
        _cLabel.text = [NSString stringWithFormat:@"%i",_mcount];
    });
  }
}
JeremyF
  • 735
  • 3
  • 10
  • 19

3 Answers3

1

There are two issues:

  1. Regarding the animation of the frame while simultaneously changing the label, the issue is that changing the label's text causes the constraints to be reapplied. The correct solution is to not animate by changing the frame, but rather by changing the constraints that dictate the frame and then calling layoutIfNeeded in the animation block. See Animating an image view to slide upwards

  2. Regarding the animating of the label:

    • You have no assurances that updates can be processed that quickly. (In fact you should plan on never realizing more than 60 fps.)

    • Even if you were able to reduce the frequency of the updates sufficiently, you are never assured that they'll be processed at a constant rate (something else could always block the main thread for a few milliseconds, yielding an inconsistent incrementing of the numbers).

    So, you should instead use a timer (or better a "display link", which is like a timer, but coordinated to be called when the screen is ready to be refreshed), calculate the time elapsed (that way the counter won't be affected by other things going on, but the value will go from 0 to 100 quickly enough that it will yield the effect you were looking for), and update the label accordingly. For example:

    @interface ViewController ()
    
    @property (nonatomic, strong) CADisplayLink *displayLink;
    @property (nonatomic) CFTimeInterval startTime;
    
    @property (weak, nonatomic) IBOutlet UILabel *label;
    
    @end
    
    @implementation ViewController
    
    - (void)startDisplayLink {
        self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
        self.startTime = CACurrentMediaTime();
        [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    
    - (void)stopDisplayLink {
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
    
    - (void)handleDisplayLink:(CADisplayLink *)displayLink {
        CFTimeInterval elapsed = CACurrentMediaTime() - self.startTime;
    
        if (elapsed < 1) {
            self.label.text = [NSString stringWithFormat:@"%.0f", elapsed * 100.0];
        } else {
            self.label.text = @"100";
            [self stopDisplayLink];
        }
    }
    

Thus, combining these two points, just adjust the constraint (after adding the outlet to the constraint) and call startDisplayLink (from the main thread) and your label will be updated from 0 to 100 over the span of one second as the view animates:

[self startDisplayLink];
self.topConstraint.constant += self.lView.frame.size.height;
[UIView animateWithDuration:1.0 animations:^{
    [self.view layoutIfNeeded];
}];
Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thank you so much for your help I am now using this method. I've noticed that the view I am trying to update will move simultaneously with the label counting but it's size will not change. When it moves it starts at the position it is going to end at and moves to the position it was originally at. When I remove/comment the code for the label counter it works fine: it resizes and changes position as set. Any idea why the label counters existence would cause the animation to perform like this? You're answer is very helpful but does not fix the issue unfortunately. – JeremyF Aug 14 '15 at 08:13
  • Actually I figured out the problem and it is not working fine. Thank you though because your code is definitely quite important and I have updated my code accordingly. – JeremyF Aug 14 '15 at 08:30
  • 1
    @JeremyF - Yes, the issue is that in addition to changing the label with display link, the other issue is that changing the text will result in the constraints to be reapplied (interrupting the animation). When you animate a view in autolayout, you shouldn't change the `frame` directly, but rather alter the constraints and call `layoutIfNeeded`. See revised answer. – Rob Aug 14 '15 at 11:01
0
_lView.frame = CGRectMake(_lView.frame.origin.x,[[UIScreen mainScreen] bounds].size.height,_lView.frame.size.width,_lView.frame.size.height);    
[[[NSThread alloc]initWithTarget:self selector:@selector(startcounting) object:nil]start];


/*
Comment this code

[UIView animateWithDuration:1 animations:^{
_lView.frame = CGRectMake(_lView.frame.origin.x,_lView.frame.origin.y+_lView.frame.size.height,_lView.frame.size.width,-500);
}];
*/

Modify this method

-(void)startcounting{
int initialY = _lView.frame.origin.y;
for(int x=0; x<=100; x++){
    [NSThread sleepForTimeInterval:0.01];
    dispatch_async(dispatch_get_main_queue(), ^{
        if (initialY>=_lView.frame.origin.y-(x*0.03))//adjust 0.03 as you required
        {
            _lView.frame = CGRectMake(_lView.frame.origin.x,_lView.frame.origin.y-(x*0.03),_lView.frame.size.width,_lView.frame.size.height);//adjust 0.03 as you required
        }
        else
        {
            _lView.frame = CGRectMake(_lView.frame.origin.x,initialY,_lView.frame.size.width,_lView.frame.size.height);
        }
       _cLabel.text = [NSString stringWithFormat:@"%i",x];
    });
}
}
Shebin Koshy
  • 1,182
  • 8
  • 22
0

I finally figured it out. I should have seen this before.

First I figured it was an issue with the constraints so I removed the constraints from the UIView I am updating. This was necessary but did not fix the issue.

The entire view controller still has its own constraints and I cannot remove these as they are needed. So what I did was added a new UIView with constraints and put the UIView to be animated on this new UIView.

This fixed the problem because the animated UIView is no longer responsible for any constraints or responsible for being affected by any other constraints.

What I still do not understand is how updating the UILabel simultaneously causes the animation to fail when constraints are involved, but work perfectly fine when the UILabel code is removed. However though this did solve the issue.

JeremyF
  • 735
  • 3
  • 10
  • 19