2013-09-25 11 views
5

Używam iOS 6, stronicującego UIScrollView i czystego automatycznego układu.Ramka nie odzwierciedla ograniczeń automatycznego układu po zwolnieniu kontrolera widoku modalnego.

Podsumowanie: Stworzyłem kontroler widoku, który przewija strony zawartości. Niektóre widoki są tworzone i konfigurowane w storyboardzie, inne są programowane. Oto hierarchia widoków:

- Main view (storyboard) 
    - UIScrollView (storyboard) 
    - content view (programmatically) 
     - subviews representing pages of content (programmatically) 

Ograniczenia dla widoku przewijania są konfigurowane w IB. Oto jak ja skonfigurowane ograniczenia dotyczące zawartości widoku w kodzie:

- (void)viewDidLoad 
{ 
    // ABPageScrollerContentView is a subclass of UIView; it overrides intrinsicContentSize; the size is calculated without referencing the scroll view's dimensions 
    self.contentView = [[ABPageScrollerContentView alloc] init]; 
    self.contentView.translatesAutoresizingMaskIntoConstraints = NO; 
    [self.pageScrollerView addSubview:self.contentView]; 

    // configure constraints between scroll view and content view... 
    UIView *contentView = self.contentView; 
    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView); 

    [self.pageScrollerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewsDictionary]]; 
    [self.pageScrollerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewsDictionary]]; 

    // the content view's subviews are added/removed in the tilePages method (not shown); tilePages is called later in the view controller lifecycle... 
} 

Jeżeli użytkownik kliknie przycisk Edytuj, inny kontroler widoku jest przedstawiony modally użyciu segue w serii ujęć. Po zwolnieniu kontrolera widoku system wydaje się w niewytłumaczalny sposób modyfikować ramkę widoku treści, mimo że ograniczenia są niezmienione.

I odrzucenie przedstawionego kontroler widoku w następujący sposób Delegat:

- (void)didExitEditPageViewVC:(id)controller 
{ 
    // update currently displayed page view from data model... 

    // logged content view frame = (0, 0; 1020, 460) 

    [self dismissViewControllerAnimated:YES completion:^{ 

     // logged content view frame = (-170, 0; 1020, 460) 
    }]; 
} 

Nie rozumiem, w jaki sposób x składnik pochodzenia ramy zmieniło od 0 do -170. Ograniczenia są identyczne przed i po zwolnieniu kontrolera widoku.

Oto ramy i ograniczenia tuż przed wywołaniem dismissViewControllerAnimated: Zakończenie: metody:

(lldb) po self.contentView 
$0 = 0x1ede2b40 <AEBPageScrollerContentView: 0x1ede2b40; frame = (0 0; 1020 460); layer = <CALayer: 0x1edd6f00>> 

(lldb) po self.pageScrollerView.constraints 
$1 = 0x1ed076c0 <__NSArrayM 0x1ed076c0>(
<NSLayoutConstraint:0x1ede2980 H:|-(0)-[AEBPageScrollerContentView:0x1ede2b40] (Names: '|':UIScrollView:0x1edd3410)>, 
<NSLayoutConstraint:0x1eded480 H:[AEBPageScrollerContentView:0x1ede2b40]-(0)-| (Names: '|':UIScrollView:0x1edd3410)>, 
<NSLayoutConstraint:0x1edecbc0 V:|-(0)-[AEBPageScrollerContentView:0x1ede2b40] (Names: '|':UIScrollView:0x1edd3410)>, 
<NSLayoutConstraint:0x1ede1040 V:[AEBPageScrollerContentView:0x1ede2b40]-(0)-| (Names: '|':UIScrollView:0x1edd3410)> 
) 

Oto ramy i ograniczenia po widoku przedstawianie kontrolerów pojawi się ponownie:

contentView = <AEBPageScrollerContentView: 0x1ede2b40; frame = (-170 0; 1020 460); layer = <CALayer: 0x1edd6f00>> 

self.pageScrollerView.constraints = 
(
    "<NSLayoutConstraint:0x1ede2980 H:|-(0)-[AEBPageScrollerContentView:0x1ede2b40] (Names: '|':UIScrollView:0x1edd3410)>", 
    "<NSLayoutConstraint:0x1eded480 H:[AEBPageScrollerContentView:0x1ede2b40]-(0)-| (Names: '|':UIScrollView:0x1edd3410)>", 
    "<NSLayoutConstraint:0x1edecbc0 V:|-(0)-[AEBPageScrollerContentView:0x1ede2b40] (Names: '|':UIScrollView:0x1edd3410)>", 
    "<NSLayoutConstraint:0x1ede1040 V:[AEBPageScrollerContentView:0x1ede2b40]-(0)-| (Names: '|':UIScrollView:0x1edd3410)>" 
) 

Dlaczego ramka widoku treści zmieniła się nieoczekiwanie? I dlaczego nie pasuje do tego, co jest podyktowane ograniczeniami?

Opóźnione wywołanie metody hasAmbiguousLayout zwraca fałszywie zaskakująco. Nie są zgłaszane żadne wyjątki. Nawet przewijany widok jest przewijany, chociaż widok zawartości jest częściowo poza ekranem.

Nie, gdzie wyraźnie określić rozmiar treści widoku przewijania; Zostawiam to systemowi. Widok treści ma nieodłączny rozmiar (rozmiar widoku treści wydaje się być w porządku, ale przyczyną jest widok pochodzenia zawartości).

Przesunięcie widoku przewijania jest takie samo przed i po zamknięciu kontrolera widoku. Jednak przesunięcie komponentu x pochodzenia widoku treści jest proporcjonalne do przesunięcia treści. Im większe przesunięcie zawartości, tym bardziej negatywny jest komponent x pochodzenia widoku treści po odrzuceniu kontrolera widoku modalnego. I, przy przesunięciu zawartości o "zero", składnik x wynosi zero. Więc jeśli kontroler widoku modalnego jest prezentowany podczas przeglądania pierwszej strony zawartości (gdy przesunięcie treści wynosi "zero"), ramka widoku treści jest poprawna po zwolnieniu kontrolera widoku. Przesunięcie treści zerowej to jedyna okoliczność, w której ramka widoku treści poprawnie odzwierciedla ograniczenia.

Próbowałem wstawiania połączeń do layoutIfNeeded w różnych miejscach bez wyników.

Wszelkie sugestie?

Odpowiedz

7

stworzyłem podklasa UIScrollView, który działa wokół tego problemu (który przy okazji jest ustalona w iOS7):

@interface ConstraintsSafeScrollView : UIScrollView 
@end 

@implementation ConstraintsSafeScrollView { 
    CGPoint _savedContentOffset; 
    UIEdgeInsets _savedContentInset; 
} 

- (void)willMoveToWindow:(UIWindow *)newWindow { 
    if (newWindow) { 
    // Reset the scrollview to the top. 
    [super setContentOffset:CGPointMake(-_savedContentInset.left, -_savedContentInset.top)]; 
    } 
    [super willMoveToWindow:newWindow]; 
} 

// Overridden to store the latest value. 
- (void)setContentOffset:(CGPoint)contentOffset { 
    _savedContentOffset = contentOffset; 
    [super setContentOffset:contentOffset]; 
} 

// Overridden to store the latest value. 
- (void)setContentInset:(UIEdgeInsets)contentInset { 
    _savedContentInset = contentInset; 
    [super setContentInset:contentInset]; 
} 

- (void)didMoveToWindow { 
    if (self.window) { 
    // Restore offset and insets to their previous values. 
    self.contentOffset = _savedContentOffset; 
    self.contentInset = _savedContentInset; 

    } 
    [super didMoveToWindow]; 
} 

@end 
+1

rozwiązanie Clever. Podoba mi się pomysł podklasy UIScrollView. Łatwo usunąć, gdy pojawi się poprawka. Zaktualizowałem swój projekt do iOS 7, ale przeniosłem się do innej części mojego projektu. Po powrocie do tego kontrolera widoku mogę usunąć moją wersję poprawki. – bilobatum

0

Ponieważ to jedyny przypadek, w którym ramka zawartości widoku był poprawny po oddaleniu widoku kontrolera modalne kiedy zawartość widoku przewijania offset było „zero”, to problem został rozwiązany w następujący sposób:

// presenting view controller's implementation 
self.contentOffsetBeforeModalViewControllerDismissal = self.pageScrollerView.contentOffset; 

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

[self dismissViewControllerAnimated:YES completion:nil]; 

Następnie , I przywrócił rzeczywistej zawartości offsetu w metodzie viewDidLayoutSubviews na przedstawiając widok kontrolera:

- (void)viewDidLayoutSubviews 
{ 
    if (!CGPointEqualToPoint(self.contentOffsetBeforeModalViewControllerDismissal, CGPointZero)) { 

     self.pageScrollerView.contentOffset = self.contentOffsetBeforeModalViewControllerDismissal; 

     self.contentOffsetBeforeModalViewControllerDismissal = CGPointZero; 
    } 
} 

Jak się okazuje, nie mogłem przywrócić zawartość offset viewWillAppear na przedstawiając widok kontrolera: metoda, ponieważ jest zbyt wcześnie. Przywracanie przesunięcia zawartości w viewDidAppear: było za późno, ponieważ wywołało drastyczną aktualizację interfejsu użytkownika. Podczas pracy z układem automatycznym odkryłem, że najczęściej viewDidLayoutSubviews jest w sam raz pod względem czasu.

To wydaje się trochę hackować; ale jest to znany hack: zapisz trochę stanu, pozwól systemowi to zrobić, przywróć ten stan.

Niestety, kolejny błąd szybko pojawił się. Jeśli przedstawiłem i zwolniłem kontroler widoku modalnego, przewinięto na inną stronę, a następnie ponownie przedstawiłem kontroler widoku modalnego, aplikacja zawiesiłaby się po zwolnieniu kontrolera widoku modalnego. W konsoli nie podano informacji o przyczynach awarii aplikacji. Wyjątek został zgłoszony podczas wywołania metody dismissViewControllerAnimated: completion :.

Ten ostatni problem został rozwiązany przez wyłączenie mojej niestandardowej metody układania stron (która jest wyzwalana w dowolnym momencie, gdy zmienia się przesunięcie zawartości widoku przewijania). Metoda układania stron zapewnia, że ​​wyświetlane są prawidłowe strony zawartości i przetwarza subviews. Nie potrzebuję wstawiania stron podczas mojego content-offset-dance, ponieważ prawidłowe subviews są już załadowane. Więc oto ostateczny fix dla odwoływanie modalne kontroler widoku bez przemieszczania ramkę zawartości widoku lub awarii aplikacji:

// presenting view controller's implementation 
self.disablePageTiling = YES // flag causes tilePages to do nothing 

self.contentOffsetBeforeModalViewControllerDismissal = self.pageScrollerView.contentOffset; 

self.pageScrollerView.contentOffset = CGPointMake(0, 0); // triggers tilePages, but nothing will happen because of the flag 

[self dismissViewControllerAnimated:YES completion:^{ 
    self.disablePageTiling = NO; 
}]; 

Realizacja viewDidLayoutSubviews pozostaje taka sama jak wyżej.

Nie uważam, aby te kwestie zostały całkowicie rozwiązane w jakikolwiek sposób. Podczas korzystania z automatycznego układu z widokiem przewijania, jestem konsekwentnie pozostawiony z tym dokuczliwym uczuciem, że piszę zły kod. Dlatego cieszę się z dalszego wglądu. Jestem również ciekawy, czy iOS 7 wpłynie na te problemy.

Powiązane problemy