Как reset после масштабирования UIScrollView?

У меня есть график, который рисуется внутри UIScrollView. Он один большой UIView, используя свой собственный подкласс CATiledLayer в качестве своего слоя.

Когда я увеличиваю и уменьшаю масштаб из UIScrollView, я хочу, чтобы график динамически изменялся так же, как и при возврате графика из viewForZoomingInScrollView. Тем не менее, График перерисовывается на новом уровне масштабирования, и я хочу reset масштаб преобразования в 1x1, чтобы в следующий раз, когда пользователь увеличил масштаб, преобразование начинается с текущего вида. Если я reset преобразование в Identity в scrollViewDidEndZooming, оно работает в симуляторе, но выбрасывает EXC_BAD_ACCSES на устройстве.

Это даже не решает проблему полностью на симуляторе, так как при следующем увеличении размера пользователя трансляция сбрасывается до любого уровня масштабирования, на котором она была, и, похоже, если бы я был увеличен до 2x, например, внезапно в 4 раза. Когда я заканчиваю масштабирование, он заканчивается в правильном масштабе, но фактический акт масштабирования выглядит плохо.

Итак, во-первых: как разрешить графику перерисовывать себя в стандартном масштабе 1x1 после масштабирования и как добиться плавного увеличения масштаба?

Изменить: Новые результаты Кажется, что ошибка "[CALayer retainCount]: message sent to deallocated instance"

Я никогда не снимаю никаких слоев самостоятельно. Раньше я даже не удалял никаких взглядов или чего-то еще. Эта ошибка была выбрана при масштабировании, а также при повороте. Если я удалю объект перед ротацией и повторно добавлю его потом, он не будет генерировать исключение. Это не вариант масштабирования.

Ответ 1

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

Масштабирование просмотра - проблема с UIScrollView, с которой я столкнулся. Во время события масштабирования-масштабирования UIScrollView отобразит представление, указанное в методе делегата viewForZoomingInScrollView:, и применит к нему преобразование. Это преобразование обеспечивает плавное масштабирование вида без необходимости перерисовывать его в каждом кадре. В конце операции масштабирования будет вызываться метод делегата scrollViewDidEndZooming:withView:atScale: и даст вам возможность сделать более качественный рендеринг вашего представления при новом масштабном коэффициенте. Как правило, он предложил, чтобы reset преобразование в вашем представлении было CGAffineTransformIdentity, а затем ваше представление вручную перерисовывалось в новом размере.

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

В качестве обходного пути я использую подкласс UIView для моего представления содержимого со следующими методами:

- (void)setTransformWithoutScaling:(CGAffineTransform)newTransform;
{
    [super setTransform:newTransform];
}

- (void)setTransform:(CGAffineTransform)newValue;
{
    [super setTransform:CGAffineTransformScale(newValue, 1.0f / previousScale, 1.0f / previousScale)];
}

где previousScale - это переменная экземпляра float в представлении. Затем я реализую метод делегата масштабирования следующим образом:

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale;
{
    [contentView setTransformWithoutScaling:CGAffineTransformIdentity];
// Code to manually redraw view at new scale here
    contentView.previousScale = scale;
    scrollView.contentSize = contentView.frame.size;
}

Таким образом, преобразования, отправленные в представление контента, корректируются в зависимости от масштаба, на котором последний раз перерисовывался вид. Когда выполняется масштабирование зума, преобразование reset равно шкале 1.0, минуя настройку в обычном методе setTransform:. Это, по-видимому, обеспечивает правильное поведение масштабирования, позволяя вам получить четкий вид при завершении масштабирования.

UPDATE (7/23/2010): iPhone OS 3.2 и выше изменили поведение прокрутки в отношении масштабирования. Теперь UIScrollView будет уважать преобразование идентичности, которое вы применяете к представлению контента, и предоставляете только относительный масштабный коэффициент в -scrollViewDidEndZooming:withView:atScale:. Поэтому приведенный выше код для подкласса UIView необходим только для устройств с версиями ОС iPhone старше 3.2.

Ответ 2

Благодаря всем предыдущим ответам, и вот мое решение.
Внедрите методы UIScrollViewDelegate:

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return tmv;
}

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
    CGPoint contentOffset = [tmvScrollView contentOffset];   
    CGSize  contentSize   = [tmvScrollView contentSize];
     CGSize  containerSize = [tmv frame].size;

    tmvScrollView.maximumZoomScale = tmvScrollView.maximumZoomScale / scale;
    tmvScrollView.minimumZoomScale = tmvScrollView.minimumZoomScale / scale;

    previousScale *= scale;

    [tmvScrollView setZoomScale:1.0f];

    [tmvScrollView setContentOffset:contentOffset];
    [tmvScrollView setContentSize:contentSize];
    [tmv setFrame:CGRectMake(0, 0, containerSize.width, containerSize.height)];  

    [tmv reloadData];
}
  • tmv - мой подкласс UIView
  • tmvScrollView - выход в UIScrollView
  • установить максимумZoomScale и minimumZoomScale до
  • создайте переменную экземпляра previousScale и установите ее значение в 1.0f

Работает для меня отлично.

BR, Евгений.

Ответ 3

У меня есть подробное обсуждение того, как (и почему) масштабирование UIScrollView работает на github.com/andreyvit/ScrollingMadness/.

(Ссылка также содержит описание того, как программно масштабировать UIScrollView, как эмулировать пейджинг в стиле фотобиблиотеки + масштабирование + прокрутка, примерный проект и класс ZoomScrollView, который инкапсулирует некоторую магию масштабирования.)

Цитата:

UIScrollView не имеет понятия "текущего уровня масштабирования", потому что каждый из его подсмотров может иметь свой собственный текущий уровень масштабирования. Обратите внимание, что в UIScrollView нет поля для сохранения текущего уровня масштабирования. Однако мы знаем, что кто-то сохраняет этот уровень масштабирования, потому что, если вы увеличите масштабирование подвью, затем reset его преобразование в CGAffineTransformIdentity, а затем снова зажмите, вы заметите, что предыдущий уровень масштабирования subview был восстановлен.

Действительно, если вы посмотрите на разборку, UIView сохраняет свой собственный уровень масштабирования (внутри объекта UIGestureInfo, на который указывает поле _gestureInfo). Он также имеет набор хороших недокументированных методов, таких как zoomScale и setZoomScale:animated:. (Имейте в виду, у него также есть куча связанных с ротацией методов, возможно, мы скоро получим поддержку жестов).

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

Ответ 4

Достаточно надежный подход, соответствующий iOS 3.2 и 4.0 и более поздним, заключается в следующем. Вы должны быть готовы предоставить масштабируемое представление (основное представление в представлении прокрутки) в любом запрошенном масштабе. Затем в scrollViewDidEndZooming: вы удалите размытую масштабируемую версию этого представления и замените ее новым представлением, нарисованным в новом масштабе.

Вам понадобится:

  • Ивар для поддержания прежней шкалы; позвоните ему oldScale

  • Способ идентификации масштабируемого представления как для viewForZoomingInScrollView:, так и для scrollViewDidEndZooming:; здесь, я собираюсь применить тег (999)

  • Метод, который создает масштабируемое представление, помещает его и вставляет его в вид прокрутки (здесь я назову его addNewScalableViewAtScale:). Помните, что он должен делать все в соответствии с масштабом - он изменяет вид и его подзаголовки или чертеж, а также размер прокрутки, отображающий contentSize для соответствия.

  • Константы для MinimumZoomScale и maximumZoomScale. Это необходимо, потому что, когда мы заменяем масштабированное представление более подробной версией, система будет думать, что текущий масштаб равен 1, поэтому мы должны обмануть его, чтобы позволить пользователю масштабировать вид назад.

Хорошо. Когда вы создаете представление прокрутки, вы вставляете масштабируемое представление в шкале 1.0. Это может быть в вашем viewDidLoad, например (sv - это прокрутка):

[self addNewScalableViewAtScale: 1.0];
sv.minimumZoomScale = MIN;
sv.maximumZoomScale = MAX;
self->oldScale = 1.0;
sv.delegate = self;

Затем в вашем scrollViewDidEndZooming: вы делаете то же самое, но компенсируете изменение масштаба:

UIView* v = [sv viewWithTag:999];
[v removeFromSuperview];
CGFloat newscale = scale * self->oldScale;
self->oldScale = newscale;
[self addNewScalableViewAtScale:newscale];
sv.minimumZoomScale = MIN / newscale;
sv.maximumZoomScale = MAX / newscale;

Ответ 5

Обратите внимание, что решение, предоставленное Brad, имеет одну проблему: если вы продолжаете программно увеличивать масштаб (скажем, при событии с двойным нажатием) и позволять пользователю вручную (отжимать) уменьшать масштаб, через некоторое время разница между истинным масштабом и масштабирование UIScrollView будет расти слишком большим, поэтому previousScale в какой-то момент выпадет из точности float, что в конечном итоге приведет к непредсказуемому поведению

Ответ 6

Я просто смотрел reset на масштаб масштабирования по умолчанию, потому что, когда я его увеличиваю, scrollview имеет тот же масштаб масштабирования в следующий раз, пока он не будет освобожден.

-(void) viewWillAppear:(BOOL)animated {
      [self.scrollView setZoomScale:1.0f];
}

Изменена игра.