UIScrollView анимация высоты и содержимогоОффсет "прыгает" содержимое снизу

Попытавшись сделать что-то похожее на поведение Message.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];
}

Видео проблемы доступно здесь.

Спасибо!

Ответ 1

Это может быть ошибка в UIKit. Это происходит при одновременном изменении size и contentOffset UIScrollView. Было бы интересно проверить, не происходит ли это поведение без автоматического макета.

Я нашел две обходные пути для этой проблемы.

Использование contentInset (подход "Сообщения" )

Как видно из приложения "Сообщения", высота UIScrollView не изменяется при отображении клавиатуры - сообщения видны под клавиатурой. Вы можете сделать то же самое. Удалите ограничение между UICollectionView и представлением, содержащим UITextField и UIButton (я назову его messageComposeView). Затем добавьте ограничение между UICollectionView и Bottom Layout Guide. Сохраните ограничение между messageComposeView и Bottom Layout Guide. Затем используйте contentInset, чтобы сохранить последний элемент UICollectionView визуально над клавиатурой. Я сделал это следующим образом:

- (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.bottomSpaceConstraint является ограничением между messageComposeView и Bottom Layout Guide. Вот видео, показывающее, как оно работает. ОБНОВЛЕНИЕ 1: Здесь мой источник проекта на 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 height меньше некоторого фиксированного значения (в моем случае вокруг 800, но мой макет может немного отличаться).

В конце концов, я думаю, что подход, который я представил в Using contentInset (the Messages approach), лучше, чем изменение размера UICollectionView. При использовании contentInset мы также получаем видимость элементов под клавиатурой. Это, безусловно, соответствует стилю iOS 7.

Ответ 2

У меня была аналогичная проблема - при анимации кадра и смещении содержимое "прыгало" прямо перед анимацией в позицию - и решение ENTIRE просто добавляло UIViewAnimationOptionBeginFromCurrentState к параметрам анимации. Вуаля!

Ответ 3

Действительно ли нужен setNeedsUpdateConstraints? не автоматическая компоновка делает это автоматически?

Если нет, я бы предложил вам сделать всю анимацию изменения размера без использования автоматического макета. похоже, проблема заключается в том, что вы пытаетесь объединить обе анимации (но это не в одно и то же время)

Ответ 4

Попробуйте удалить строку [self.view layoutIfNeeded] и посмотреть, не исчезла ли проблема или возникли ли другие проблемы, и если да, если они выглядят как-то связанные.

Кроме того, всегда хорошая идея для reset позиции ваших просмотров непосредственно перед анимацией. Поэтому попробуйте установить нормальное смещение непосредственно перед линией анимации (и, возможно, даже вызовите метод layoutIfNeeded там). Сортировка всего по порядку непосредственно перед началом анимации.

Ответ 5

Я не уверен, что это именно то, что вы хотите, но, возможно, это может дать вам толчок в правильном направлении: Проект Github

Я установил два ограничения: одно для текстового поля (в нижнем руководстве), а другое для просмотра прокрутки (в текстовое поле).

Затем, когда вы вызываете анимацию, я одушевляю свойство "center" обоих элементов, а не contentOffset, и я обрабатываю значение анимации и значение ограничения отдельно.

Конечный результат здесь:

Youtube video

Ответ 6

У меня была та же проблема, я смог ее решить, зарегистрировав NSNotifications для клавиатуры, скрывая и показывая. Я предоставляю код для этого. Надеюсь, это поможет вам.

просто объявите BOOL isMovedUp в вашем классе .h

В 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 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];
}