2010-05-10 85 views
103

任何人都可以对UIView'ssetNeedsLayout,layoutIfNeededlayoutSubviews方法之间的关系给出明确的解释吗?还有一个使用全部三个的示例实现。谢谢。UIView的setNeedsLayout,layoutIfNeeded和layoutSubviews之间的关系是什么?

让我感到困惑的是,如果我发送我的自定义视图setNeedsLayout消息,那么它在此方法之后调用的第二件事是layoutSubviews,跳过layoutIfNeeded。从文档我预计流程为setNeedsLayout>原因layoutIfNeeded被称为>原因layoutSubviews被调用。

回答

101

我仍然试图弄清楚自己,所以带着一些怀疑和原谅我,如果它包含错误。

setNeedsLayout很简单:它只是在UIView的某个地方设置一个标志,标志着它需要布局。这将迫使layoutSubviews在下一次重绘发生之前在视图上被调用。请注意,在许多情况下,由于autoresizesSubviews属性,您不需要明确地调用它。如果已设置(默认情况下),则对视图框架的任何更改都会导致视图布置其子视图。

layoutSubviews是你做所有有趣的东西的方法。如果你愿意的话,它相当于drawRect的布局。一个简单的例子可能是:

-(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一般不意味着在子类中被覆盖。这是一种当您想要放置视图时打算打电话给您的方法。苹果的实现可能是这个样子:

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

你会打电话layoutIfNeeded上以迫使它(以及它的superviews必要)立即布置。

+2

谢谢 - 很高兴有人终于回答了这个问题。在此期间,我还必须深入研究setNeedsDisplay - 它会导致drawRect被调用 - 和contentMode。只有contentMode = UIViewContentModeRedraw将导致setNeedsDisplay在视图边界更改时被调用。其他contentMode选项只会导致视图缩放,移动一定量,或者与边缘对齐。我的自定义UITableViewCell不想重新定位方向更改,它只会缩放,直到我将contentMode设置为UIViewContentModeRedraw。 – Tarfa 2010-05-29 13:08:05

+0

我想你的代码中的[self.subview1 layoutSubviews]应该替换为[self.subview1 setNeedsLayout]。由于layoutSubviews意味着被覆盖,但并不意味着从另一个视图调用。框架决定何时调用它(通过将多个布局请求分组为一个调用以提高效率),或者通过在视图层次结构中的某个位置调用layoutIfNeeded来间接执行此操作。 – Tarfa 2010-05-29 13:20:25

+0

更正以上我的评论:“...由于layoutSubviews是意图被覆盖或从自己调用,但并不意味着被调用或从另一个视图...” – Tarfa 2010-05-29 13:29:01

33

我想补充一下n8gray的回答,在某些情况下,您需要拨打setNeedsLayout然后layoutIfNeeded

比方说,您编写了一个扩展UIView的自定义视图,其中子视图的位置很复杂,无法使用autoresizingMask或iOS6 AutoLayout完成。定制定位可以通过覆盖layoutSubviews来完成。

举个例子,假设您有一个自定义视图,该视图具有contentView属性和edgeInsets属性,该属性允许设置contentView周围的边距。 layoutSubviews应该是这样的:

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

如果你希望能够随时更改edgeInsets属性动画帧的变化,你需要重写edgeInsets setter方法如下,并呼吁其次layoutIfNeededsetNeedsLayout

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

这样,如果您执行以下操作,如果更改动画块内的edgeInsets属性,则contentView的框架更改将为动画。

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

如果不添加调用layoutIfNeeded在setEdgeInsets方法,动画将无法工作,因为layoutSubviews将在下一个更新周期,这相当于调用它的动画块外调用。

如果只调用setEdgeInsets方法中的layoutIfNeeded,则不会设置setNeedsLayout标志。

+4

或者您应该只需在设置边缘插图后在动画块内调用'[self layoutIfNeeded]''。 – 2016-09-16 22:16:12

相关问题