Animating UILabel origin and width

I have what I thought would be a simple requirement but I can't seem to get the effect that I want.

I have a button and a label next to each other. When the button is touched it disappears and the label is to expand to occupy the space vacated by the button. So, basically, the label's origin and width need to change but the right amrgin stays where its at.


I'm not using auto-layout in this case.


I've played around with the label's frame, center and bounds properties inside UIView.animateWithDuration() but I can't seem to get the effect that I want. For example, with the code below, the label snaps to the new center and then grows to occupy it's final location.


  UIView.animateWithDuration(0.5, animations: { 
     label.frame = newFrame
     label.center = newCenter 
  })


I would like for the label's right margin to stay where it is and the animation of the origin and width to make the label grow to the left to occupy the space where the button was.

My next thought was to look at layer animations but I've wasted a few hours on this and would like some guidance.


Thank you!

Two approaches. Assuming your 'newFrame' is correct (it contains a center):


[UIView animateWithDuration:0.5 animations:^{       
     label.frame=newFrame;
    }];



and if they it is not correct this will work:


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

I wish it were that simple ... I've tried code like you suggest. I do get an animation but not the way I need it.


With both of your suggestions the label appears to snap to the final size and then go to its final location.


That's not the effect I need. I for the label to stay on its right margin and change it's origin to compensate for teh new size.


I wrote a standalone test app to troubleshoot this. Here's the complete in Swift using Xcode 7.3.

import UIKit
class ViewController: UIViewController {

  var toggleState = false
  var originalFrame: CGRect = CGRectZero

  let sizeChange: CGFloat = 50

  var testLabel = UILabel()

  override func viewDidLoad() {
    super.viewDidLoad()
  
    testLabel = UILabel(frame: CGRectMake(70, 40, 200, 50))
    testLabel.center = CGPointMake(160, 284)
    testLabel.textAlignment = NSTextAlignment.Center
    testLabel.text = "I'am a test label"
    testLabel.backgroundColor = UIColor.yellowColor()
    self.view.addSubview(testLabel)
    originalFrame = testLabel.frame
    let button   = UIButton(type: UIButtonType.System) as UIButton
    button.frame = CGRectMake(20, 20, 100, 50)
    button.backgroundColor = UIColor.greenColor()
    button.setTitle("Toggle Label", forState: UIControlState.Normal)
  
    button.addTarget(self, action: #selector(ViewController.buttonAction(_:)), forControlEvents: UIControlEvents.TouchUpInside)
  
    self.view.addSubview(button)
  }
  func buttonAction(sender:UIButton!) {
    print("Old Frame: \(testLabel.frame) Center: \(testLabel.center)")
    UIView.animateWithDuration(0.5, animations: {
      self.testLabel.frame = CGRect(
        x: self.toggleState ? self.originalFrame.origin.x : (self.originalFrame.origin.x - self.sizeChange),
        y: self.testLabel.frame.origin.y,
        width: (self.toggleState ? self.originalFrame.size.width : (self.originalFrame.size.width + self.sizeChange)),
        height: self.testLabel.frame.size.height)
      }, completion: { finished in
        print("New Frame: \(self.testLabel.frame) Center: \(self.testLabel.center)")
    })
    toggleState = !toggleState
  }
}


Just touch the toggle label button. You'll see that the label animates as I describe.

My code works for me for some animations but I see it fails for others! It is easier to see what works by setting the duration to 5 seconds. In my case I can get your label to grow smoothly and expand to the left but then when I want it to shrink to the right it snaps to a smaller size and moves right.

I have no idea what is wrong. Perhaps it is a bug. Here is what I had been doing for animations:


  // create this dictionary of variables:
    NSMutableDictionary *theVariables=[NSMutableDictionary dictionaryWithObjectsAndKeys:
              testLabel@"TheView",   //  the UIView object you want to animate
             [NSValue valueWithCGRect:CGRectMake(x,y,width,height),@"TheFinalFrame", // the frame to move the object to
             [NSNumber numberWithFloat:0.50],@"TheDuration", nil]; // the duration in seconds
    // then call:
    [self smoothMoveWithVariables:theVariables];
-(void)smoothMoveWithVariables:(NSMutableDictionary *)theVariables{
    UIView *theView=[theVariables objectForKey:@"TheView"];
    float theDurationRemaining=[[theVariables objectForKey:@"TheDuration"] floatValue];
    CGRect theFinalFrame=[[theVariables objectForKey:@"TheFinalFrame"] CGRectValue];
    float framesPerSecond=30; /
    int framesRemaining=theDurationRemaining*framesPerSecond;
    if(framesRemaining>0){
        theView.frame=CGRectMake(
                                 theView.frame.origin.x+(theFinalFrame.origin.x - theView.frame.origin.x)/framesRemaining,
                                 theView.frame.origin.y+(theFinalFrame.origin.y - theView.frame.origin.y)/framesRemaining,
                                 theView.frame.size.width+(theFinalFrame.size.width - theView.frame.size.width)/framesRemaining,
                                 theView.frame.size.height+(theFinalFrame.size.height - theView.frame.size.height)/framesRemaining);
        theDurationRemaining=theDurationRemaining-1/framesPerSecond;
        if(theDurationRemaining>1/framesPerSecond){
            [theVariables setObject:[NSNumber numberWithFloat:theDurationRemaining] forKey:@"TheDuration"];
            [self performSelector:@selector(smoothMoveWithVariables:) withObject:theVariables afterDelay:0.033f];  /
        }
    }
}

You may want to be a bit careful. Although this method can be called by overlapping labels, if you deallocate the class before it completes then it could crash. Before deallocating the class call [NSObject cancelPreviousPerformRequestsWithTarget:self];

Animating UILabel origin and width
 
 
Q