高度和contentOffset的UIScrollView动画从底部“跳转”内容

时间:2013-10-11 05:45:15

标签: ios objective-c uiscrollview core-animation autolayout

尝试做类似于Messages.app的行为,我有一个UIScrollView并在其下面有一个文本字段,并尝试对其进行动画处理,以便当键盘出现时,所有内容都会向上移动到键盘上方将字段向上移动的约束(以及由于自动布局导致UIScrollView的高度也发生变化),同时将contentOffset设置为同时滚动到底部。

代码完成了想要的最终结果,但是在动画期间,当键盘动画开始时,滚动视图变为空白,然后内容从底部向上滚动,而不是从动画开始时的位置滚动

动画是这样的:

- (void)updateKeyboardConstraint:(CGFloat)height animationDuration:(NSTimeInterval)duration {
    self.keyboardHeight.constant = -height;
    [self.view setNeedsUpdateConstraints];

    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
        [self.view layoutIfNeeded];
        self.collectionView.contentOffset = 
            CGPointMake(0, self.collectionView.contentSize.height - self.collectionView.bounds.size.height);
    } completion:nil];
}

该问题的视频可用here

谢谢!

6 个答案:

答案 0 :(得分:33)

这可能是UIKit中的一个错误。当同时更改sizecontentOffset UIScrollView时会发生这种情况。测试在没有自动布局的情况下是否也会发生此行为会很有趣。

我找到了解决此问题的两种方法。

使用contentInset(消息方法)

在消息应用程序中可以看到,UIScrollView的高度在显示键盘时不会改变 - 键盘下方可以看到消息。你可以用同样的方式做到这一点。删除UICollectionView与包含UITextFieldUIButton的视图之间的约束(我将其称为messageComposeView)。然后在UICollectionViewBottom Layout Guide之间添加约束。保持messageComposeViewBottom Layout Guide之间的约束。然后使用contentInsetUICollectionView的最后一个元素保持在键盘上方。我是通过以下方式做到的:

- (void)updateKeyboardConstraint:(CGFloat)height animationDuration:(NSTimeInterval)duration {
    self.bottomSpaceConstraint.constant = height;

    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
        CGPoint bottomOffset = CGPointMake(0, self.collectionView.contentSize.height - (self.collectionView.bounds.size.height - height));
        [self.collectionView setContentOffset:bottomOffset animated:YES];

        [self.collectionView setContentInset:UIEdgeInsetsMake(0, 0, height, 0)];

        [self.view layoutIfNeeded];
    } completion:nil];
}

此处self.bottomSpaceConstraintmessageComposeViewBottom Layout Guide之间的约束。 Here's the video showing它是如何运作的。 更新1: Here's my project's source on GitHub。这个项目有点简化。我应该考虑- (void)keyboardWillShow:(NSNotification *)notif中通知中传递的选项。

在队列中执行更改

不是一个确切的解决方案,但如果将其移动到完成块,滚动工作正常:

} completion:^(BOOL finished) {
    [self.collectionView setContentOffset:CGPointMake(0, self.collectionView.contentSize.height - self.collectionView.bounds.size.height) animated:YES];
}];

键盘显示需要0.25秒,因此动画的开头之间的差异可能会很明显。动画也可以按相反的顺序完成。

更新2:我也注意到OP的代码可以正常使用此更改:

CGPoint bottomOffset = CGPointMake(0, self.collectionView.contentSize.height - (self.collectionView.bounds.size.height - height));

但仅当contentSize的{​​{1}}小于某个固定值(in my case around 800时,但我的布局可能会略有不同)。

最后,我认为我在height中提出的方法比调整Using contentInset (the Messages approach)的方法要好。使用UICollectionView时,我们还可以获得键盘下元素的可见性。它当然更适合iOS 7风格。

答案 1 :(得分:8)

我有一个类似的问题 - 当动画框架和偏移内容时,#34;跳跃"在动画到位之前 - 整个解决方案只是将UIViewAnimationOptionBeginFromCurrentState添加到动画选项中。瞧!

答案 2 :(得分:1)

setNeedsUpdateConstraints真的需要吗?是不是自动进行自动布局?

如果没有 - 我建议你单独使用所有调整大小的动画而不使用自动布局。看起来问题是当你尝试同时做两个动画时(但它不在同一时间)

答案 3 :(得分:1)

我不确定这是你想要的行为,但也许它可以让你在正确的方向上轻推:Github project

我所做的是设置两个约束,一个用于文本字段(到底部指南),另一个用于滚动视图(到文本字段)。

然后在调用动画时,我为两个元素的“center”属性设置动画,而不是contentOffset,并且我分别处理动画值和约束值。

最终结果如下:

Youtube video

答案 4 :(得分:1)

尝试删除[self.view layoutIfNeeded]行,看看问题是否仍然存在,或者是否出现其他问题,如果出现问题,如果它们看起来有任何关联。

此外,在动画之前重置视图的位置始终是个好主意。因此,尝试在动画行之前设置正常偏移(甚至可能在那里调用layoutIfNeeded方法)在开始动画之前将所有内容按顺序放置。

答案 5 :(得分:0)

我遇到了同样的问题,我可以通过注册NSNotifications键盘/隐藏和显示来解决它。我提供相同的代码。希望它会对你有所帮助。

在你的.h类中声明BOOL isMovedUp

在ViewDidLoad

// registering notifications for keyboard/hiding and showing
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];    
}

#pragma mark-keyboard notifications
- (void)keyboardWillShow {
    // Animate the current view out of the way
    if (isMovedUp==YES){

    } else {

        [self setViewMovedUp:YES];
        isMovedUp=YES;
    }
}

- (void)keyboardWillHide {
    if (isMovedUp==YES) {
        [self setViewMovedUp:NO];
        isMovedUp=NO;
    }        
}

//method for view transformation

-(void)setViewMovedUp:(BOOL)movedUp {
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.5]; // if you want to slide up the view

    CGRect rect = self.winePopUpView.frame;

    if (movedUp) {
        //isKeyBoardDown = NO;
        // 1. move the view's origin up so that the text field that 
        // will be hidden come above the keyboard 
        // 2. increase the size of the view so that the area 
        // behind the keyboard is covered up.
        rect.origin.y -= 100;
        //rect.size.height += 100;
    } else  {
        // revert back to the normal state.
        rect.origin.y += 100;
        //rect.size.height -= 100;
        //isKeyBoardDown = YES;
    }
    self.winePopUpView.frame = rect;
    [UIView commitAnimations];
}