Источник UIScrollView изменяется после возврата к UIViewController


у меня есть UIViewController подкласс как сцена в раскадровке, которая содержит UIScrollView содержит различные подвиды. Одним из подвидов является UIButton который переходит в другую сцену UIViewController подкласс. Когда я вернусь из вида (поп UIViewController от стека навигационного контроллера), я нахожу, что происхождение вида прокрутки каким-то образом изменилось, хотя contentsize и contentoffset кажется правильным.

что также интересно, так это то, что приложение имеет панель вкладок, а когда я вкладываю и возвращаясь к этому виду, вид прокрутки устанавливается правильно со смещением (0, 0).

в этом процессе в основном нет кода, так как это почти все в раскадровке. Поскольку я довольно новичок в использовании раскадровки, я думаю, что делаю что-то неправильно, хотя я не знаю, что. Есть идеи, что это может быть? Возможно, проблемы с калибровкой или ограничения?

16 55

16 ответов:

в iOS 7/8/9 просто self.automaticallyAdjustsScrollViewInsets = NO; решил проблему в моем случае.

попробуйте это в viewWillAppear контроллера вида вы поп обратно в:

 self.scrollView.contentOffset = CGPointMake(0, 0);

Edit: при добавлении кода Питера вы получаете лучшие результаты с помощью:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:YES];
    self.scrollView.contentOffset = CGPointMake(0, 0);
}

плюс

- (void)viewWillDisappear:(BOOL)animated {  
    self.recentContentOffset = self.scrollView.contentOffset;
    [super viewWillDisappear:animated];
}

и

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.scrollView.contentOffset = CGPointMake(0, self.recentContentOffset.y);
}

вы возвращаетесь в исходное положение прокрутки, не имеете визуального побочного эффекта, и сам вид прокрутки правильно расположен в своем супервизоре (что было проблемой) после возвращения.

на самом деле, я положил эту строку кода в viewDidDisappear, и чтобы он помнил смещение, когда вид снова появляется, я добавил эту строку перед ним

 self.contentOffset = self.scrollView.contentOffset;

а также

 - (void)viewDidLayoutSubviews { 
       self.scrollView.contentOffset = self.contentOffset; 
 }

У меня была аналогичная проблема, после увольнения viewController, contentOffset из моего tableView был изменен на (0, -64).

мое решение было немного странным, я попробовал все другие ответы, но не имел успеха, единственное, что исправило мою проблему, - это переключить положение tableView в дереве элементов управления .xib

Это был первый элемент управления в Родительском представлении, как это:

before

я переместил tableView сразу после ImageView и это сработало:

after

кажется, что помещение табличного представления в первое положение вызывало проблемы, а перемещение табличного представления в другое положение исправляло проблему.

P. D. я не использую autoLayout ни раскадровки

надеюсь, что это может помочь кому-то!

Я использую collectionView и у меня была аналогичная проблема. Для iOS 11: в инспекторе размеров есть "вставка содержимого". Установите для этого значение "Никогда". Это решило проблему для меня. Надеюсь, это кому-то поможет.

Цель C:

if (@available(iOS 11, *)) {
   [UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}

Swift:

if #available(iOS 11, *) {
UIScrollView.appearance().contentInsetAdjustmentBehavior = .never
}

в iOS 11 я столкнулся с аналогичной проблемой, когда, когда я возвращаюсь после появления контроллера вида, tableview в предыдущем viewcontroller использовал для автоматической настройки его вставки содержимого, что привело к резкому скачку содержимого tableview сверху. Следующее решение работает для меня в iOS 11.

в раскадровке выберите tableview и перейдите в Инспектор атрибутов и снимите флажок "Автоматически" для полей высоты строки и оценки. Также измените содержимое вставки из "автоматического" на "никогда".

[Table view]

к сожалению, предложения Питера и Макмарка не сработали для меня (Xcode 5 w/ auto-layout). Решение состояло в том, чтобы перейти к раскадровке, выбрать контроллер вида и Reset to Suggested Constraints in View Controller.

enter image description here

Я недавно отвечал на вопрос о подобной проблеме, но в другом контексте: рамка не отражает ограничения автоматической компоновки после отклонения контроллера модального вида

в моем случае начало пользовательского представления контейнера внутри представления прокрутки смещается, а не начало самого представления прокрутки. Что касается автоматической компоновки, пользовательский вид контейнера прикрепляется к четырем сторонам вида прокрутки.

создается представление контейнера и настроен программно, а не в IB. Хотя ограничения представления контейнера остаются неизменными, начало представления контейнера смещается после отклонения контроллера модального представления. Это разъединение между ограничениями и фреймами необъяснимо.

еще более примечательно то, что проблема смещения начала координат осталась после того, как я удалил и повторно добавил все связанные ограничения.

Я придумал аналогичное решение: сохранить значение текущего содержимого смещение, установите смещение содержимого на "ноль", позвольте системе поменять местами ваши представления, а затем восстановите смещение содержимого (т. е. танец смещения содержимого).

в моем случае, однако, мне пришлось предпринять дополнительные шаги, чтобы решить эту проблему. Мой вид прокрутки-это вид прокрутки подкачки, который получает свои подвиды из метода tilePages (демонстрируется в старом видео WWDC). Изменение смещения содержимого представления прокрутки запускает метод tilePages с помощью метода scrollViewDidScroll: UIScrollViewDelegate. Мне пришлось установить флаг, чтобы отключить tilePages в то время как я сделал content-offset танец, в противном случае приложение будет сбой.

надеюсь, я могу удалить код для content-offset dance при обновлении до iOS 7.

продолжение вопроса при выполнении текущих ответов:

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

  1. Итак, Я сброс ограничения collectionView в раскадровке, убедившись, что они были прикреплены к основному presentingViewController вид.

  2. добавил:self.view.translatesAutoresizingMaskIntoConstraints = YES; внутри viewDidLoad контроллера представления вида.

  3. и хранящиеся, в частном порядке, contentOffset представления коллекции до появления модально представленного контроллера представления:

    (void)viewWillDisappear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
    
        self.contentOffset = self.collectionView.contentOffset;
        self.collectionView.contentOffset = CGPointZero;
    }
    
    - (void)viewDidLayoutSubviews {
         [super viewDidLayoutSubviews];
         self.collectionView.contentOffset = self.contentOffset;
    }
    

недавно я столкнулся с этой ошибкой, может быть решена с помощью этих кодов:

// fix ios 6 bug begin
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7) {
        isRecoredforios6 = YES;
        recordedOffsetforios6 = self.tableView.contentOffset;
        recordedSizeforios6 = self.tableView.contentSize;
    }
}

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    if (isRecoredforios6) {
        isRecoredforios6 = NO;
        self.tableView.contentSize = recordedSizeforios6;
        self.tableView.contentOffset = recordedOffsetforios6;
    }
}
// fix ios 6 bug end

спасибо Питер Джейкобс!

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7) {
        _isRecoredforios6 = YES;
        _recordedOffsetforios6 = _scrollView.contentOffset;
        _recordedSizeforios6 = _scrollView.contentSize;
        _scrollView.contentOffset = CGPointZero;
    }

}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    if (_isRecoredforios6) {
        _isRecoredforios6 = NO;
        _scrollView.contentSize = _recordedSizeforios6;
        _scrollView.contentOffset = _recordedOffsetforios6;
    }
}

я исправил эту ошибку ios6, может быть решена с помощью этих кодов. Я решил, что ошибка произошла в Scrollview. Спасибо прежде всего моим друзьям!

после того, как попробовал много вещей:

  • установите automaticallyAdjustsScrollViewInsets в false, чтобы увидеть, если проблема была с панелью навигации.
  • игра с фиксированной высоты ячейки...
  • кэширование высоты ячеек...
  • сохранение свойства со смещением представления таблицы, кэширование его и установка его на viewWillAppear...
  • etc.

Я понял, что вопрос был о estimatedRowHeight значением, которое было установлено в 50px, когда это должно быть ~150px. Обновление значения исправило проблему, и теперь табличное представление сохраняет то же смещение.

вы пробовали проверять расширенные края под непрозрачными барами? Его можно найти в инспекторе атрибутов контроллера внутри раскадровки.

Он сделал трюк для меня с помощью XCode 9 iOS 11.

enter image description here

ни один из вышеперечисленных не работал для меня, мне удалось сделать свою собственную анимацию Push/Pull, и она работает как шарм.

во-первых, добавьте этот класс, который реализует сценарий Push

class PushAnimator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.5
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

    // get reference to our fromView, toView and the container view that we should perform the transition in
    let container = transitionContext.containerView
    let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
    let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!

    // start the toView to the right of the screen
    var frame = toView.frame
    frame.origin.x = container.frame.width
    toView.frame = frame

    // add the both views to our view controller
    container.addSubview(toView)
    container.addSubview(fromView)

    // get the duration of the animation
    let duration = self.transitionDuration(using: transitionContext)

    // perform the animation!
    UIView.animate(withDuration: duration, animations: {

        var frame = fromView.frame
        frame.origin.x = -container.frame.width
        fromView.frame = frame

        toView.frame = container.bounds

    }, completion: { _ in
        // tell our transitionContext object that we've finished animating
        transitionContext.completeTransition(true)

    })
}
}

затем добавьте этот класс, который реализует pop scenario

import Foundation
import UIKit

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    // get reference to our fromView, toView and the container view that we should perform the transition in
    let container = transitionContext.containerView
    let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
    let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!

    // set up from 2D transforms that we'll use in the animation
    let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)

    // start the toView to the right of the screen
    var frame = toView.frame
    frame.origin.x = -container.frame.width
    toView.frame = frame

    // add the both views to our view controller
    container.addSubview(toView)
    container.addSubview(fromView)

    // get the duration of the animation
    let duration = self.transitionDuration(using: transitionContext)

    // perform the animation!
    UIView.animate(withDuration: duration, animations: {

        fromView.transform = offScreenRight
        toView.frame = container.bounds

    }, completion: { _ in
        // tell our transitionContext object that we've finished animating
        transitionContext.completeTransition(true)

    })
}
}

затем добавьте эту строку в метод viewDidLoad, чтобы изменить делегат NavigationController по умолчанию

self.navigationController?.delegate = self

и увидеть волшебство :)

я использовал комбинацию различных решений, размещенных повсюду на SO, и придумал этот подкласс:

// Keeps track of the recent content offset to be able to restore the
// scroll position when a modal viewcontroller is dismissed
class ScrollViewWithPersistentScrollPosition: UIScrollView {

    // The recent content offset for restoration.
    private var recentContentOffset: CGPoint = CGPoint(x: 0, y: 0)

    override func willMove(toWindow newWindow: UIWindow?) {
        if newWindow != nil {
            // save the scroll offset.
            self.recentContentOffset = self.contentOffset
        }
        super.willMove(toWindow: newWindow)
    }

    override func didMoveToWindow() {
        if self.window != nil {
            // restore the offset.
            DispatchQueue.main.async(execute: {
                // restore it
                self.contentOffset = self.recentContentOffset
            })
        }
        super.didMoveToWindow()
    }
 }

протестировано на iOS 11.2 / Xcode версии 9.2.

выключить AutoLayout вариант для этого xib в противном случае.