Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to animate slide up UIView with AutoLayout?

this is what I like to achieve: enter image description here

I want to perform a slide up animation where a user can slide a UIView2 up, and the UIView2 will stop half-way on the screen.

I know how to present a UIViewControler modally via a UIButton action like so:

[self presentViewController:newController animated:NO completion:nil];

I also know you need this type of code for animations:

[UIView animateWithDuration:0.5f animations:^{
    [self.customView layoutIfNeeded];
} completion:^(BOOL finished) {

}];

However, I'm not sure how to achieve this with UIViews, especially since I use AutoLayout for my project and I use constraints to easily support multiple screen sizes.

How can I achieve this without breaking AutoLayout and the constraints I have set up?

Thanks

like image 493
Pangu Avatar asked Dec 22 '14 18:12

Pangu


People also ask

Does UIView animate need weak self?

No, it is not needed in this case. animations and completion are not retained by self so there is no risk of strong retain cycle.

Does UIView animate run on the main thread?

The contents of your block are performed on the main thread regardless of where you call [UIView animateWithDuration:animations:] .


1 Answers

What I typically do in this situation is the following.

Add two constraints to your scene. One where UIView2 is aligned to the bottom of UIView1. The second, where it's aligned center of UIView1 (you'll want to ctrl+drag between the views to add the constraints appropriately). These constraints will initially conflict with one another, and that's fine.

Add IBOutlets to your view controller for the NSLayoutConstraints and assign the two constraints we've created to those IBOutlets.

Set the constraint priority on your initial condition to 999 (i.e. constraint priority on align to bottom should be 999). Set the constraint priority on the destination constraint to 998 (i.e. constraint priority on align center is 998). You'll now see that these constraints will no longer conflict. This is because the priority on one constraint overrides the other.

You may see where this is headed now. So when you want to animate UIView2 between the constraints, swap the priorities and animate!

Code:

@interface MyViewController ()
    @property (nonatomic, weak) IBOutlet NSLayoutConstraint* constraint0;
    @property (nonatomic, weak) IBOutlet NSLayoutConstraint* constraint1;
@end

- (void)someMethodWhereIWantToAnimate
{
    NSInteger temp = self.constraint0.priority;
    self.constraint0.priority = self.constraint1.priority;
    self.constraint1.priority = temp;

    [UIView animateWithDuration:0.3 animations:^{
        // Simplest is to use the main ViewController's view
        [self.view layoutIfNeeded];
    }];
}

To enable panning and using that to initiate your animation add a Pan Gesture Recognizer to your view controller. Ctrl+drag from UIView2 to the Pan Gesture Recognizer and set it as a gestureRecognizer. Now when you drag on UIView2, you'll be able to receive pan events.

Add an IBAction to handle the pan:

- (IBAction)onPan:(id)sender
{
}

Ctrl+drag from the pan gesture recognizer to the view controller and set onPan: as the sent action.

sender is actually the pan gesture recognizer itself. So we'll be able to fill out this method to handle a pan and have UIView2 follow under the user's finger and then initiate the animation when they let go.

Let's start filling out the code:

- (IBAction)onPan:(id)sender
{
    UIPanGestureRecognizer* recognizer = (UIPanGestureRecognizer*)sender;
    CGPoint translation = [recognizer translationInView:self.view];
    NSLog(@"State: (%d) Translation in view: (%f, %f)", recognizer.state, translation.x, translation.y);
}

If you run with this code, and drag your finger over UIView2, you'll see output like this:

State: (1) Translation in view: (0.000000, -2.500000)
State: (2) Translation in view: (0.500000, -7.500000)
State: (2) Translation in view: (0.500000, -7.500000)
State: (2) Translation in view: (1.500000, -12.000000)
State: (2) Translation in view: (2.500000, -16.500000)
State: (2) Translation in view: (2.500000, -19.500000)
State: (2) Translation in view: (2.500000, -24.500000)
State: (2) Translation in view: (2.500000, -25.000000)
State: (2) Translation in view: (2.500000, -25.500000)
State: (2) Translation in view: (2.500000, -27.000000)
State: (2) Translation in view: (2.500000, -29.500000)
State: (2) Translation in view: (2.500000, -31.000000)
State: (2) Translation in view: (2.500000, -31.500000)
State: (3) Translation in view: (2.500000, -31.500000)

Notice that the values are constantly increasing. We'll want the incremental amount. Notice the state in the log. State (1) is drag start. State (2) is drag changed. State (3) is drag ended. Using this information, we can calculate the delta.

Add a CGPoint property to your view controller and add UIView2 as an IBOutlet as well:

@property (nonatomic) CGPoint lastTranslation;
@property (nonatomic, weak) IBOutlet UIView* uiView2;

Finally, let's fill out the final form of our pan method:

- (IBAction)onPan:(id)sender
{
    UIPanGestureRecognizer* recognizer = (UIPanGestureRecognizer*)sender;
    CGPoint delta;
    switch(recognizer.state) {
        case UIGestureRecognizerStateBegan:
            // On state begin: delta is the translation. Store it in our member for later user.
            self.lastTranslation = delta = [recognizer translationInView:self.view];
            break;
        case UIGestureRecognizerStateChanged:
            // On state changed: calculate the difference between the translation and our
            // previous translation.
            delta = CGPointApplyAffineTransform([recognizer translationInView:self.view], CGAffineTransformMakeTranslation(-self.lastTranslation.x, -self.lastTranslation.y));
            self.lastTranslation = [recognizer translationInView:self.view];
            break;
        case UIGestureRecognizerStateEnded:
            // On state ended: Let's just do the constraint animation.
            [self someMethodWhereIWantToAnimate];
            break;
        default:
            break;
    }
    delta.x = 0; // Forces only vertical drag on the UIView2.
    self.uiView2.center = CGPointApplyAffineTransform(self.uiView2.center, CGAffineTransformMakeTranslation(delta.x, delta.y)); // Move our uiView2 based on the delta.
    NSLog(@"State: (%d) Translation in view: (%f, %f)", recognizer.state, delta.x, delta.y);
}

This should get you well on your way. You may want to look at tweaking the UIView animation on the constraints a bit based on the pan gesture's velocityInView: method, but that I'll leave as an exercise.

like image 141
Sandy Chapman Avatar answered Sep 22 '22 05:09

Sandy Chapman