iosautolayoutios-autolayoutios-keyboard-extension

Animate adding height constraint to keyboard extension view


In my keyboard extension, there is one particular viewController which I would like to cause the keyboard to increase in height. Before presenting this viewController, the main UIInputViewController calls this method on itself, passing a value of 400.0:

- (void)setKeyboardHeight:(CGFloat)height {
    if (self.heightConstraint) {
        [self.view removeConstraint:self.heightConstraint];
        self.heightConstraint = nil;
   }

    self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:height];

    [self.view addConstraint:self.heightConstraint];

    [UIView animateWithDuration:0.5 animations:^{
        [self.view layoutIfNeeded];
    }];
}

The height of the keyboard does change to the specified value, but it isn't animated. Is this impossible to accomplish, or am I just using the wrong method in a keyboard extension?


Solution

  • Here's one solution I came up with. Call animateKeyboardHeightTo: speed: and pass in the height you want the keyboard to change to as targetHeight and the number of points per ms as speed, and the keyboard will increase or decrease to the specified height. I tried to use duration rather than speed, increase by one point per iteration and calculate the duration for dispatch_after so that the whole animation would occur within the specified duration, but I couldn't get dispatch_after to execute fast enough. I don't know if it is simply not sufficiently precise, or if it cannot execute at less than 1 second intervals, but this was the only solution I could come up with.

    If you want to toggle between sizes, you can store self.view.frame.size.height in a property before making the initial call to animateKeyboardHeightTo: speed:, and then call this method again and pass in the stored height to restore the keyboard to its initial height.

    - (void)animateKeyboardHeightTo:(CGFloat)targetHeight speed:(CGFloat)speed {
        CGFloat initialHeight = self.view.frame.size.height;
    
        if (targetHeight < initialHeight) {
            speed = -speed;
        }
        [self setKeyboardHeight:initialHeight];
        [self increaseKeyboardHeightToTargetHeight:targetHeight speed:speed];
    }
    
    - (void)increaseKeyboardHeightToTargetHeight:(CGFloat)targetHeight speed:(CGFloat)speed {
        CGFloat currentHeight = self.heightConstraint.constant;
        CGFloat nextHeight = currentHeight + speed;
        if ((speed > 0 && nextHeight > targetHeight) || (speed < 0 && nextHeight < targetHeight)) {
            nextHeight = targetHeight;
        }
        [self setKeyboardHeight:nextHeight];
    
        if ((speed > 0 && nextHeight < targetHeight) || (speed < 0 && nextHeight > targetHeight)) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
                [self increaseKeyboardHeightToTargetHeight:targetHeight speed:speed];
            });
        }
    }
    
    - (void)setKeyboardHeight:(CGFloat)height {
        [self removeHeightConstraint];
        [self addHeightConstraintWithHeight:height];
    }
    
    - (void)removeHeightConstraint {
        if (self.heightConstraint) {
            [self.view removeConstraint:self.heightConstraint];
            self.heightConstraint = nil;
        }
    }
    
    - (void)addHeightConstraintWithHeight:(CGFloat)height {
        self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:height];
    
        [self.view addConstraint:self.heightConstraint];
    }