Нежелательная прокрутка при анимации zoomScale в UIScrollView - PullRequest
9 голосов
/ 04 сентября 2011

Резюме: Время от времени UIScrollView вносит нежелательные изменения в значение contentOffset, в результате чего приложение отображает неправильное местоположение в просматриваемом документе.Нежелательное изменение происходит в сочетании с анимированным изменением zoomScale.

анимированного представления. Подробности: У меня возникают проблемы при уменьшении с помощью CATiledLayer в UIScrollView,CATiledLayer содержит pdf, и когда contentOffset находится в определенном диапазоне, когда я уменьшаю масштаб, contentOffset изменяется (это ошибка) до того, как происходит увеличение.* * * * * * * * * * * * contentOffset В коде Apple, похоже, что-то изменилось.

Чтобы проиллюстрировать проблему, я изменил пример приложения Apple, ZoomingPDFViewer.Код на github: https://github.com/DirkMaas/ZoomingPDFViewer-bug

При касании значение zoomScale будет изменено на 0,5 с использованием animateWithDuration, что приведет к уменьшению.Если UIScrollView contentOffset.y меньше 2700 или больше 5900, анимация zoomScale работает нормально.Если касание происходит, когда contentOffset.y находится между этими двумя значениями, contentOffset.y переместится (не анимируется) к приблизительно 2700, и затем будет происходить анимация zoomScale, но прокрутка будет происходить одновременно, так что когдаанимация завершена, contentOffset.y находится там, где и должно быть.Но откуда происходит скачок?

Например, скажем, contentOffset.y - это 2000, когда на экран нажимают: анимация zoomScale работает просто отлично;contentOffset.y не изменяется.

Но если contentOffset.y равно 4000 при касании экрана: contentOffset.y будет прыгать без анимации примерно до 2700, а затем начнется масштабирование и прокрутка.указать и произойти одновременно.Когда анимация завершена, все выглядит так, как будто мы вернулись прямо с 4000, поэтому мы оказались в нужном месте, но поведение неправильное.

Примечание в интерфейсе пользователя:

  • текст можно прокручивать по вертикали обычным способом
  • текст можно увеличивать и уменьшать масштабированием обычным способом
  • одно касание приведет к тому, что zoomScale будетустановить на 0,5;изменение анимировано

Я заметил, что если zoomScale больше 0,5, скачок не такой большой.Кроме того, если я использую setZoomScale:animated: вместо animateWithDuration, ошибка исчезает, но я не могу использовать ее, потому что мне нужно объединить анимацию в цепочку.

Вот краткое изложение того, что я сделал (код вgithub включает эти изменения):

  • Скачал ZoomingPDFViewer из http://developer.apple.com/library/ios/#samplecode/ZoomingPDFViewer/Introduction/Intro.html и открыл его в XCode
  • Измененные настройки сборки |Архитектура |Базовый SDK для последней версии iOS (iOS 4.3) изменен Настройки сборки |GCC 4.2 - Язык |Исходники компиляции Что касается Objective-C ++
  • , то удалил TestPage.pdf из проекта
  • добавил вместо проекта "whoiam 5 24 обрезанный 3-2.pdf"
  • добавлено PDFScrollView *scrollView; в ZoomingPDFViewerViewController класс
  • изменено loadView в ZoomingPDFViewerViewController для инициализации scrollView вместо sv
  • добавлено viewDidLoad, handleTapFrom:recognizer и zoomOut to ZoomingPDFViewerViewController в PDFScrollview.m
  • закомментировано scrollViewDidEndZooming:withView:atScale и scrollViewWillBeginZooming:withView:, потому что они делают вещи на заднем плане изображения, которые отвлекают от рассматриваемой проблемы

Спасибо, чтомного для терпения со мной, и всякая помощь!

Ответы [ 5 ]

8 голосов
/ 15 сентября 2011

Один из самых сложных вещей, чтобы понять о масштабировании, что это всегда происходит вокруг точки, называемой Anchor Point. Я думаю, что лучший способ понять это - представить одну систему координат, наложенную поверх другой. Скажем, A - ваша внешняя система координат, B - внутренняя (B будет обзором прокрутки). Когда смещение B равно (0,0), а масштаб равен 1,0, то точка B (0,0) соответствует A (0,0), и в общем случае B (x, y) = A (x, y ).

Далее, если смещение B равно (xOff, yOff), то B (x, y) = A (x - xOff, y - yOff). Опять же, это все еще предполагает масштаб масштабирования 1,0.

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

Если ваша точка привязки равна (0,5, 0,5) (точка привязки нормализована, т. Е. От 0 до 1, так что 0,5 - это половина длины поперечного сечения), то центральная точка остается фиксированной, в то время как все остальные точки перемещаются наружу. Это означает, что смещение должно измениться, чтобы отразить это. Если он находится на iPhone в портретном режиме и вы увеличиваете масштаб до 2,0, значение точки привязки x переместится на половину ширины экрана, 320/2 = 160.

Фактическая позиция представления содержимого представлений прокрутки на экране определяется ОБА смещением и точкой привязки. Таким образом, если вы просто измените точку привязки слоев внизу, не внося соответствующее изменение в смещение, вы увидите, что вид, как кажется, прыгает в другое место, даже если смещение одинаковое.

Я предполагаю, что это основная проблема здесь. Когда вы анимируете масштабирование, Core Animation должна выбрать новую опорную точку, чтобы масштабирование выглядело правильно. Это также изменит смещение, так что фактическая видимая область на экране не переместится. Попробуйте регистрировать местоположение точки привязки в разное время на протяжении всего этого процесса (она определяется в базовом CALayer любого представления, к которому вы обращаетесь со свойством "layer" в представлениях).

Также, пожалуйста, смотрите документацию здесь для красивых картинок и, вероятно, гораздо лучшего описания ситуации, чем я дал здесь:)

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

-(CGPoint)setNewAnchorPointWithoutMoving:(CGPoint)newAnchor {
    CGPoint currentAnchor = CGPointMake(self.anchorPoint.x * self.contentSize.width,
                                        self.anchorPoint.y * self.contentSize.height);

    CGPoint offset = CGPointMake((1 - self.scale) * (currentAnchor.x - newAnchor.x),
                                 (1 - self.scale) * (currentAnchor.y - newAnchor.y));


    self.anchorPoint = CGPointMake(newAnchor.x / self.contentSize.width,
                                   newAnchor.y / self.contentSize.height);

    self.position = CGPointMake(self.position.x + offset.x, self.position.y + offset.y);

    return offset;
}
3 голосов
/ 22 сентября 2011

Я играл с кодом, который вы опубликовали, и я думаю, что нашел, что происходит.Когда вы делаете анимацию блока, как в вашем коде:

[UIView animateWithDuration:4.0
                 animations:^ {
                     self.scrollView.zoomScale = 0.5;
                 }
                 completion:nil];

Блок анимации фактически вызывается в начале анимации.Базовая анимация внутренне обрабатывает слои, которые делают его похожим на движущийся.Существует список анимируемых свойств в Центре разработки, и zoomScale среди них отсутствует.

При изменении zoomScale scrollView автоматически обновляет свой contentOffset.Таким образом, когда анимация начинается, скачок, который вы видите, - это изменение содержимого для нового zoomScale.Затем слой анимируется с правильным масштабом масштабирования, но целевой contentOffset (т. Е. Каким должен быть contentOffset в конце масштабирования) уже установлен в начале масштабирования.

Именно поэтому масштабированиеПохоже, что это сосредоточено вокруг точки за пределами экрана.Вам либо придется использовать setZoomScale: animated: либо вы сами можете анимировать слой и смещение ...

2 голосов
/ 20 сентября 2011

У меня было много ошибок с масштабированием, когда моя функция viewForZoomingInScrollView возвращала представление прокрутки напрямую.

Многие из этих странных поведений исчезли после того, как я установил представление внутри scrollView, содержащего все, и вернул его в viewForZoomingInScrollView. Может, это тоже решит твою проблему:

-(void) someIntializationFunction {
    [myScrollView addSubView:self.globalContainer];
    // Now had everything in the container and not in the scrollView directly
    ....
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; {
    return self.globalContainer;
}

Еще одна вещь, которую я видел, zoomToRect: функция гораздо более надежная, чем изменение zoomScale scrollView. Это также может помочь, даже если у вас будет больше расчетов, чтобы сделать ...

2 голосов
/ 15 сентября 2011

Вы должны использовать scrollViewDidEndZooming: withView: atScale, чтобы уведомить вас о завершении масштабирования.В противном случае, я полагаю, вам придется отказаться от использования свойства zoomScale UIScrollView и вместо этого полностью изменить масштабирование вручную, например http://jonathanwatmough.com/2008/12/implementing-tap-to-zoom-in-uiscrollview-on-an-iphone/.

0 голосов
/ 26 октября 2013

Я думаю, что вы должны манипулировать contentInset и / или contentOffset самостоятельно, при каждом событии прокрутки. Управление опорными точками не поможет. Посмотрите на этот вопрос и мой ответ.

...