2010-05-10 10 views
103

Kann jemand die Beziehung zwischen den Methoden UIView's , layoutIfNeeded und layoutSubviews definitiv erklären? Und eine Beispielimplementierung, bei der alle drei verwendet würden. Vielen Dank.Welche Beziehung besteht zwischen UIViews setNeedsLayout, layoutIfNeeded und layoutSubviews?

Was macht mich verwirrt ist, dass wenn ich meine Gewohnheit eine setNeedsLayout Nachricht schon am nächsten, was es nach dieser Methode layoutSubviews ist aufruft sehen senden, direkt über layoutIfNeeded überspringen. Aus den Dokumenten würde ich erwarten, dass der Fluss setNeedsLayout> verursacht layoutIfNeeded aufgerufen werden> Ursachen layoutSubviews aufgerufen werden soll.

Antwort

101

Ich versuche immer noch, das herauszufinden, also nimm das mit Skepsis und vergib mir, wenn es Fehler enthält.

setNeedsLayout ist eine einfache: Es setzt nur eine Flagge irgendwo in der UIView, die es als benötigtes Layout markiert. Dadurch wird erzwungen, dass layoutSubviews in der Ansicht aufgerufen wird, bevor die nächste Neuzeichnung erfolgt. Beachten Sie, dass Sie dies in vielen Fällen aufgrund der Eigenschaft autoresizesSubviews nicht explizit aufrufen müssen. Wenn dies festgelegt ist (was standardmäßig der Fall ist), bewirkt jede Änderung des Rahmens einer Ansicht, dass die Ansicht ihre Unteransichten auslegt.

layoutSubviews ist die Methode, in der Sie alle interessanten Sachen machen. Es ist das Äquivalent von drawRect für das Layout, wenn Sie so wollen. Ein triviales Beispiel könnte sein:

-(void)layoutSubviews { 
    // Child's frame is always equal to our bounds inset by 8px 
    self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0); 
    // It seems likely that this is incorrect: 
    // [self.subview1 layoutSubviews]; 
    // ... and this is correct: 
    [self.subview1 setNeedsLayout]; 
    // but I don't claim to know definitively. 
} 

AFAIK layoutIfNeeded ist im Allgemeinen nicht in der Unterklasse werden überschrieben gemeint. Es ist eine Methode, die Sie anrufen sollten, wenn Sie möchten, dass eine Ansicht gerade angelegt wird. Apples Implementierung könnte wie folgt aussehen:

-(void)layoutIfNeeded { 
    if (self._needsLayout) { 
     UIView *sv = self.superview; 
     if (sv._needsLayout) { 
      [sv layoutIfNeeded]; 
     } else { 
      [self layoutSubviews]; 
     } 
    } 
} 

Sie layoutIfNeeded auf einen Blick nennen würde es (und seine superviews je nach Bedarf) zu zwingen, sofort angelegt werden.

+2

Danke - froh, dass jemand endlich das beantwortet hat. In der Zwischenzeit musste ich mich auch mit setNeedsDisplay - was bewirkt, dass drawRect aufgerufen wird - und contentMode beschäftigen. Nur contentMode = UIViewContentModeRedraw bewirkt, dass setNeedsDisplay aufgerufen wird, wenn sich die Grenzen einer Ansicht ändern. Die anderen contentMode-Optionen bewirken nur, dass die Ansicht skaliert, um einen bestimmten Betrag verschoben oder an einer Kante ausgerichtet wird. Mein benutzerdefiniertes UITableViewCell wollte bei Orientierungsänderungen nicht neu zeichnen, es würde nur skalieren, bis ich contentMode auf UIViewContentModeRedraw setze. – Tarfa

+0

Ich denke, dass die [selfview1 layoutSubviews] in Ihrem Code durch [selfview1 setNeedsLayout] ersetzt werden sollte. Da layoutSubviews überschrieben werden soll, aber nicht von oder zu einer anderen Ansicht aufgerufen werden soll. Entweder bestimmt das Framework, wann es aufgerufen werden soll (indem mehrere Layoutanfragen in einem Aufruf zur Effizienz zusammengefasst werden), oder Sie tun dies indirekt, indem Sie layoutIfNeeded irgendwo in der Ansichtshierarchie aufrufen. – Tarfa

+0

Korrektur zu meinem obigen Kommentar: "... da layoutSubviews dazu gedacht ist, überschrieben oder von selbst aufgerufen zu werden, aber nicht von oder zu einer anderen Ansicht aufgerufen werden soll ..." – Tarfa

33

Ich möchte hinzufügen, n8gray die Antwort, dass in einigen Fällen müssen Sie setNeedsLayout gefolgt von layoutIfNeeded aufrufen.

Nehmen wir an, Sie haben beispielsweise eine benutzerdefinierte Ansicht geschrieben, die UIView erweitert, in der die Positionierung von Unteransichten komplex ist und nicht mit autoresizingMask oder iOS6 AutoLayout durchgeführt werden kann. Die benutzerdefinierte Positionierung kann durch Überschreiben layoutSubviews vorgenommen werden.

Angenommen, Sie haben eine benutzerdefinierte Ansicht mit einer contentView-Eigenschaft und einer edgeInsets-Eigenschaft, mit der Sie die Ränder um die contentView herum festlegen können. layoutSubviews würde wie folgt aussehen:

- (void) layoutSubviews { 
    self.contentView.frame = CGRectMake(
     self.bounds.origin.x + self.edgeInsets.left, 
     self.bounds.origin.y + self.edgeInsets.top, 
     self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right, 
     self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom); 
} 

Wenn Sie den Bildwechsel in der Lage sein wollen, animieren, wenn Sie die edgeInsets Eigenschaft zu ändern, müssen Sie die edgeInsets Setter außer Kraft zu setzen wie folgt und rufen setNeedsLayout gefolgt von layoutIfNeeded:

- (void) setEdgeInsets:(UIEdgeInsets)edgeInsets { 
    _edgeInsets = edgeInsets; 
    [self setNeedsLayout]; //Indicates that the view needs to be laid out 
          //at next update or at next call of layoutIfNeeded, 
          //whichever comes first 
    [self layoutIfNeeded]; //Calls layoutSubviews if flag is set 
} 

Wenn Sie folgendermaßen vorgehen, wenn Sie die EdgeInsets -Eigenschaft innerhalb eines Animationsblocks ändern, wird der Rahmenwechsel der ContentView animiert.

[UIView animateWithDuration:2 animations:^{ 
    customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34); 
}]; 

Wenn Sie den Anruf nicht zu layoutIfNeeded im setEdgeInsets Methode hinzufügen, wird die Animation nicht funktionieren, weil die layoutSubviews wird beim nächsten Update-Zyklus aufgerufen, die es außerhalb des Animationsblocks Aufruf entspricht.

Wenn Sie in der setEdgeInsets-Methode nur layoutIfNeeded aufrufen, geschieht nichts, da das Flag setNeedsLayout nicht gesetzt ist.

+4

Oder Sie sollten einfach '[self layoutIfNeeded] 'innerhalb des Animationsblocks aufrufen, nachdem Sie die Kanteneinsätze gesetzt haben. –

Verwandte Themen