Jak zresetować po powiększeniu UIScrollView?

Rysuję Wykres wewnątrz UIScrollView. Jest to jedna duża UIView używająca jako swojej warstwy niestandardowej podklasy CATiledLayer.

Kiedy powiększam i pomniejszam UIScrollView, chcę, aby Wykres zmieniał dynamicznie rozmiar, tak jak to robi, gdy zwracam Wykres z viewForZoomingInScrollView. Jednak Wykres ponownie rysuje się na nowym poziomie powiększenia i chcę zresetować skalę transformacji do 1x1, aby następnym razem, gdy użytkownik powiększy, transformacja rozpocznie się od bieżącego widoku. Jeśli zresetuję transformację do tożsamości w scrollViewDidEndZooming, to działa w symulatorze, ale rzuca EXC_BAD_ACCSES na urządzenie.

To nawet nie rozwiązuje problemu w całości na symulatorze, ponieważ następnym razem, gdy użytkownik powiększy, transformacja resetuje się do dowolnego poziomu powiększenia, na którym był, i tak wygląda, jeśli na przykład byłem powiększony do 2x, to nagle jest na 4x. kiedy zakończę powiększenie, kończy się na właściwej skali, ale rzeczywisty akt powiększenia wygląda źle.

Więc po pierwsze: jak pozwolić grafowi na przerysowanie się w standardzie skala 1x1 po powiększeniu i jak uzyskać płynne powiększenie?

Edit: nowe ustalenia Błąd wydaje się być "[CALayer retainCount]: message sent to deallocated instance"

Ja nigdy nie dealokuję żadnych warstw. Wcześniej nawet nie kasowałem żadnych widoków. Ten błąd był rzucany na zoom, a także na rotate. Jeśli usunę obiekt przed rotacją i dodam go ponownie, nie spowoduje to wyrzucenia wyjątku. Nie jest to opcja powiększania.

Author: Kampai, 2009-01-15

7 answers

Nie mogę ci pomóc z awarią, poza powiedzeniem, abyś sprawdził i upewnił się, że nie jesteś niezamierzonym autorelementem widoku lub warstwy gdzieś w Twoim kodzie. Widziałem, że symulator radzi sobie z czasem autoelementów inaczej niż na urządzeniu(najczęściej w przypadku wątków).

Skalowanie widoku jest problemem z UIScrollView, na który wpadłem. Podczas zdarzenia pinch-zooming, UIScrollView przyjmie widok określony w metodzie delegate viewForZoomingInScrollView: i zastosuje transformację za to. Ta transformacja zapewnia płynne skalowanie widoku bez konieczności przerysowywania każdej klatki. Pod koniec operacji powiększania zostanie wywołana twoja metoda delegate scrollViewDidEndZooming:withView:atScale:, która da Ci szansę na bardziej wysokiej jakości renderowanie widoku przy Nowym współczynniku skali. Ogólnie zaleca się Resetowanie transformacji w widoku na CGAffineTransformIdentity, a następnie ręczne wyrysowanie widoku w nowej skali rozmiaru.

Jednak powoduje to problem, ponieważ UIScrollView nie pojawia się aby monitorować transformację widoku zawartości, tak więc przy następnej operacji powiększenia ustawia transformację widoku zawartości na dowolny ogólny współczynnik skali. Ponieważ ręcznie przerysowano Widok przy ostatnim współczynniku skalowania, powoduje to związanie skalowania, czyli tego, co widzisz.

Jako obejście używam podklasy UIView dla mojego widoku zawartości z następującymi metodami zdefiniowanymi:

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

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

Gdzie previousScale jest zmienną instancji float widoku. Następnie realizuję powiększenie metoda delegowania w następujący sposób:

- (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;
}

W ten sposób transformacje wysyłane do widoku zawartości są dostosowywane na podstawie skali, w jakiej widok był ostatnio przerysowany. Po wykonaniu powiększenia, transformacja jest resetowana do skali 1,0 przez pominięcie korekty w normalnej metodzie setTransform:. Wydaje się, że zapewnia to prawidłowe skalowanie, a jednocześnie umożliwia rysowanie wyraźnego widoku po zakończeniu powiększenia.

Aktualizacja (23.07.2010): iPhone OS 3.2 i nowsze zmieniły zachowanie widoków przewijania w odniesieniu do powiększania. Teraz UIScrollView będzie respektować transformację tożsamości, którą zastosujesz do widoku zawartości i dostarczy względny współczynnik skali w -scrollViewDidEndZooming:withView:atScale:. Dlatego powyższy kod dla podklasy UIView jest niezbędny tylko dla urządzeń z systemem iPhone OS w wersji starszej niż 3.2.

 41
Author: Brad Larson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-06 12:42:41

Dzięki wszystkim poprzednim odpowiedziom, a oto moje rozwiązanie.
Implementacja metod 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 jest moją podklasą UIView
  • tmvScrollView - wyjście do UIScrollView
  • ustaw maximumZoomScale i minimumZoomScale przed
  • Utwórz zmienną instancji previousScale i ustaw jej wartość na 1.0 f
Dla mnie działa idealnie. BR, Eugene.
 12
Author: dymv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-10 08:05:21

Właśnie chciałem zresetować przy ładowaniu do domyślnej skali powiększenia, ponieważ gdy ją powiększę, scrollview będzie miał tę samą skalę powiększenia na następny raz, dopóki nie zostanie dealokowany.

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

Zmienił grę.

 6
Author: preetam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-03 06:53:00

Mam szczegółowe omówienie jak (i dlaczego) uiscrollview zooming działa na github.com/andreyvit/ScrollingMadness/.

(link zawiera również opis jak programowo powiększać UIScrollView, jak emulować stronicowanie+powiększanie+przewijanie w stylu biblioteki zdjęć, przykładowy projekt i klasę ZoomScrollView, która zawiera część magii powiększania.)

Cytat:

UIScrollView nie ma pojęcia "bieżącego poziomu powiększenia" , ponieważ każdy subview to contains może mieć swój własny bieżący poziom powiększenia. Zauważ, że w UIScrollView nie ma pola pozwalającego zachować bieżący poziom powiększenia. Wiemy jednak, że ktoś przechowuje ten poziom powiększenia, ponieważ jeśli uszczypniesz-powiększysz subview, a następnie zresetujesz jego transformację do CGAffineTransformIdentity, a następnie ponownie uszczypniesz, zauważysz, że poprzedni poziom powiększenia subview został przywrócony.

Rzeczywiście, jeśli spojrzysz na demontaż, to UIView przechowuje swój własny poziom powiększenia (wewnątrz obiektu UIGestureInfo wskazywany przez pole _gestureInfo). Posiada również zestaw fajnych nieudokumentowanych metod, takich jak zoomScale i setZoomScale:animated:. (Pamiętaj, że ma również kilka metod związanych z rotacją, może wkrótce dostaniemy wsparcie dla gestów rotacji.)

Jeśli jednak stworzymy nowy interfejs użytkownika tylko do powiększania i dodamy nasz rzeczywisty widok z możliwością powiększania jako jego potomek, zawsze zaczniemy od poziomu powiększenia 1.0. Moja implementacja programowego powiększania bazuje na tej sztuczce.

 5
Author: Andrey Tarantsov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-07 23:04:08

[9]}dość niezawodne podejście, odpowiednie dla iOS 3.2 i 4.0 i późniejszych, jest następujące. Musisz być przygotowany do dostarczania skalowalnego widoku (głównego podglądu widoku przewijania) w dowolnej żądanej skali. Następnie w scrollViewDidEndZooming: usuniesz wersję przekształconą w niewyraźną skalę tego widoku i zastąpisz go nowym widokiem narysowanym do nowej skali.

Będziesz potrzebował:

  • Ivar dla zachowania poprzedniej skali; nazwijmy ją oldScale

  • Sposób na identyfikowanie skalowalnego widoku, zarówno dla viewForZoomingInScrollView:, jak i dla scrollViewDidEndZooming:; tutaj zastosuję tag (999)

  • Metoda, która tworzy skalowalny widok, taguje go i wstawia do widoku przewijania(tutaj nazwę go addNewScalableViewAtScale:). Pamiętaj, że musi robić wszystko zgodnie ze skalą - rozmiar widoku i jego podwidywań lub rysunku oraz Rozmiar zawartości widoku przewijania, aby pasował.

  • Stałe dla minimumZoomScale i maximumZoomScale. Są one potrzebne, ponieważ gdy zastąp skalowany widok szczegółową większą wersją, system pomyśli, że obecna skala to 1, więc musimy oszukać go, aby umożliwić użytkownikowi skalowanie widoku z powrotem w dół.

Bardzo dobrze. Podczas tworzenia widoku przewijania wstawia się widok skalowalny w skali 1.0. To może być w viewDidLoad, na przykład (sv to widok przewijania):
[self addNewScalableViewAtScale: 1.0];
sv.minimumZoomScale = MIN;
sv.maximumZoomScale = MAX;
self->oldScale = 1.0;
sv.delegate = self;

Potem w twoim scrollViewDidEndZooming: robisz to samo, ale kompensujesz zmianę skali:

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;
 3
Author: matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-12 05:33:14

Zauważ, że rozwiązanie Dostarczone przez Brada ma jeden problem: jeśli będziesz programowo powiększać (powiedzmy przy zdarzeniu double tap) i pozwolić użytkownikowi ręcznie (pinch-out) pomniejszyć, po pewnym czasie różnica między prawdziwą skalą a skalą śledzoną przez UIScrollView wzrośnie zbyt duża, więc previousScale w pewnym momencie spadnie z precyzji float, co ostatecznie spowoduje nieprzewidywalne zachowanie

]}
 2
Author: esad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 03:43:41

Swift 4.* i Xcode 9.3

Ustawienie skali powiększenia widoku przewijania na 0 spowoduje zresetowanie widoku przewijania do stanu początkowego.

self.scrollView.setZoomScale(0.0, animated: true)
 0
Author: Prashant Gaikwad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-29 05:21:45