2013-09-25 21 views
5

Sto utilizzando iOS 6, un UIScrollView di impaginazione e layout automatico puro.Il frame non riflette i vincoli di layout automatico dopo aver chiuso il controller di visualizzazione modale

Riepilogo: ho creato un controller di visualizzazione che scorre pagine di contenuto. Alcune delle viste sono create e configurate nello storyboard, altre in modo programmatico. Ecco la gerarchia delle viste:

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

I vincoli per la vista di scorrimento sono configurati in IB. Ecco come ho configurato i vincoli per la visualizzazione di contenuti in codice:

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

Se l'utente tocca un pulsante di modifica, un altro controller della vista si presenta con un modale segue nello storyboard. Dopo che il controller della vista è stato eliminato, il sistema sembra modificare in modo inspiegabile il frame della vista del contenuto anche se i vincoli non sono stati modificati.

ho respingere il controller della vista presentato nella seguente metodo delegato:

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

io non capisco come la componente x di origine del telaio cambiato da 0 a -170. I vincoli sono identici prima e dopo aver chiuso il controller della vista.

Ecco il telaio ed i vincoli destra prima di chiamare il dismissViewControllerAnimated: completamento: Metodo:

(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)> 
) 

Ecco il telaio ed i vincoli dopo la vista presentando controllore riappare:

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)>" 
) 

Perché il frame della vista del contenuto è cambiato in modo imprevisto? E perché non corrisponde a ciò che è dettato dai vincoli?

Una chiamata ritardata a hasAmbiguousLayout restituisce false sorprese. Non vengono lanciate eccezioni. La visualizzazione a scorrimento scorre anche, anche se la visualizzazione del contenuto è parzialmente fuori campo.

No dove imposto esplicitamente la dimensione del contenuto della vista di scorrimento; Lascio questo al sistema. La vista del contenuto ha una dimensione intrinseca (la dimensione della vista del contenuto sembra essere buona, è l'origine della vista del contenuto che è il problema).

L'offset del contenuto della vista di scorrimento è lo stesso prima e dopo aver chiuso il controller della vista. Tuttavia, lo spostamento del componente x dell'origine della vista del contenuto è proporzionale all'offset del contenuto. Maggiore è l'offset del contenuto, più negativo è il componente x dell'origine della vista del contenuto dopo che il controller della vista modale è stato eliminato. E, con un offset del contenuto di "zero", il componente x è zero. Quindi, se il controller della vista modale viene presentato mentre si visualizza la prima pagina del contenuto (quando l'offset del contenuto è "zero"), il frame della vista del contenuto è corretto al momento del licenziamento del controller della vista. Il caso content-offset-of-zero è l'unica circostanza in cui il frame della vista del contenuto riflette correttamente i suoi vincoli.

Ho provato a inserire chiamate a layoutIfNeed in vari luoghi senza risultati.

Qualche suggerimento?

risposta

7

ho creato una sottoclasse UIScrollView che funziona a questo problema (che BTW è stato risolto nel 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

soluzione intelligente. Mi piace l'idea di sottoclasse di UIScrollView. Facile da rimuovere quando arriva la correzione. Ho aggiornato il mio progetto a iOS 7, ma sono passato a un'altra parte del mio progetto. Quando torno a questo controller di visualizzazione, posso rimuovere la mia versione di una correzione. – bilobatum

0

Dal momento che l'unico caso in cui telaio della visualizzazione del contenuto era corretta dopo il licenziamento del controller di vista modale è stato quando compensata contenuto della vista di scorrimento era "zero", ho risolto il problema nel modo seguente:

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

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

[self dismissViewControllerAnimated:YES completion:nil]; 

Poi , ho ripristinato l'offset nel metodo di viewDidLayoutSubviews vista del controller che presenta contenuto effettivo:

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

     self.pageScrollerView.contentOffset = self.contentOffsetBeforeModalViewControllerDismissal; 

     self.contentOffsetBeforeModalViewControllerDismissal = CGPointZero; 
    } 
} 

Come si è visto, non ho potuto ripristinare il contenuto di offset in viewWillAppear vista del controller che presenta: metodo, perché è troppo presto. Ripristino dell'offset del contenuto in viewDidAppear: era troppo tardi perché ha prodotto un aggiornamento UI stridente. Quando lavoro con il layout automatico, ho riscontrato che il più delle volte viewDidLayoutSubviews è giusto in termini di tempo.

Questo sembra un po 'un trucco; ma è un trucco familiare: salva un po 'di stato, lascia che il sistema faccia la sua parte, ripristina quel pezzetto di stato.

Sfortunatamente, un altro bug è emerso rapidamente. Se presentassi e disattivi il controller della vista modale, scorressi su un'altra pagina, quindi presentai di nuovo il controller della modal view, l'app si arresterebbe in modo anomalo quando il controller della modal view fu espulso. Nella console non è stata fornita alcuna informazione sul motivo per cui l'app potrebbe essersi arrestata in modo anomalo. L'eccezione è stata lanciata alla chiamata di dismissViewControllerAnimated: completion :.

Questo ultimo problema è stato risolto disattivando il metodo di piastrellatura della pagina personalizzata (che viene attivato ogni volta che l'offset del contenuto della vista di scorrimento cambia). Il metodo di affiancamento della pagina assicura che vengano visualizzate le pagine corrette del contenuto e ricicli le sottoview. Non ho bisogno che la pagina di affiancamento si verifichi durante il mio contenuto-offset-dance, perché le sottoview corrette sono già state caricate. Quindi, ecco la soluzione finale per respingere il controller di vista modale senza spostare la cornice della vista dei contenuti o crash dell'app:

// 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; 
}]; 

L'attuazione del viewDidLayoutSubviews rimane lo stesso come sopra.

Non considero questi problemi completamente risolti con alcun mezzo. Quando si utilizza il layout automatico con una vista di scorrimento, sono costantemente lasciato con questa sensazione fastidiosa che sto scrivendo codice cattivo. Quindi accolgo con favore ulteriori approfondimenti. Sono anche curioso di sapere se iOS 7 avrà effetti su questi problemi.

Problemi correlati