子类化UIView的正确做法?


158

我正在研究一些基于UIView的自定义输入控件,并且正在尝试确定用于设置视图的正确做法。当一个UIViewController的工作,这是相当简单的使用loadView和相关viewWillviewDid方法,但是继承一个UIView的时候,我有最接近methosds是`awakeFromNibdrawRectlayoutSubviews。(我在考虑设置和拆卸回调的问题。)就我而言,我在中设置框架和内部视图layoutSubviews,但屏幕上看不到任何内容。

确保我的视图具有想要的正确高度和宽度的最佳方法是什么?(尽管可能有两个答案,但无论是否使用自动版式,我的问题都适用。)什么是最佳的“最佳实践”?

Answers:


298

苹果公司非常清楚地定义了如何UIView在文档中进行子类化。

请查看下面的列表,特别是查看initWithFrame:layoutSubviews。前者用于设置您的框架,UIView而后者则用于设置框架及其子视图的布局。

还请记住,initWithFrame:仅当您以UIView编程方式实例化时才调用它。如果您是从笔尖文件(或情节提要)加载的,initWithCoder:则将使用。并且initWithCoder:尚未计算框架中的框架,因此您无法修改在Interface Builder中设置的框架。如该答案中所建议您可能会想到initWithFrame:从进行呼叫initWithCoder:以设置框架。

最后,如果UIView从笔尖(或情节提要)中加载图像,还可以awakeFromNib执行自定义框架和布局初始化,因为awakeFromNib调用时,可以确保层次结构中的每个视图都已取消存档和初始化。

从的文档NSNibAwaking(已由的文档取代awakeFromNib):

可以从awakeFromNib内部安全地发送到其他对象的消息,这时可以确保所有对象都已取消归档和初始化(尽管不一定会唤醒)

还值得注意的是,使用自动布局时,您不应显式设置视图的框架。相反,您应该指定一组足够的约束,以便布局引擎自动计算框架。

直接来自文档

替代方法

初始化

  • initWithFrame:建议您实现此方法。除此方法之外,或代替此方法,您还可以实现自定义初始化方法。

  • initWithCoder: 如果从Interface Builder nib文件加载视图并且视图需要自定义初始化,请实现此方法。

  • layerClass仅当您希望视图对其后备存储使用其他Core Animation图层时,才实现此方法。例如,如果您使用OpenGL ES进行绘制,则需要覆盖此方法并返回CAEAGLLayer类。

绘图和印刷

  • drawRect:如果您的视图绘制自定义内容,请实现此方法。如果您的视图没有做任何自定义绘图,请避免重写此方法。

  • drawRect:forViewPrintFormatter: 仅当您要在打印过程中以不同方式绘制视图内容时,才实现此方法。

约束条件

  • requiresConstraintBasedLayout 如果您的视图类需要约束才能正常工作,请实现此类方法。

  • updateConstraints 如果您的视图需要在子视图之间创建自定义约束,请实施此方法。

  • alignmentRectForFrame:,请frameForAlignmentRect:实现这些方法以覆盖您的视图如何与其他视图对齐。

布局

  • sizeThatFits:如果希望视图的大小与调整大小操作时通常使用的默认大小不同,请实现此方法。例如,您可以使用此方法来防止视图缩小到无法正确显示子视图的程度。

  • layoutSubviews 如果您需要对子视图的布局进行比约束或自动调整大小行为所提供的更为精确的控制,请实施此方法。

  • didAddSubview:willRemoveSubview:根据需要实施这些方法,以跟踪子视图的添加和删除。

  • willMoveToSuperview:didMoveToSuperview根据需要实施这些方法以跟踪当前视图在视图层次结构中的移动。

  • willMoveToWindow:didMoveToWindow根据需要实施这些方法,以跟踪视图向其他窗口的移动。

事件处理:

  • touchesBegan:withEvent:touchesMoved:withEvent:touchesEnded:withEvent:touchesCancelled:withEvent:如果你需要直接处理触摸事件实现这些方法。(对于基于手势的输入,请使用手势识别器。)

  • gestureRecognizerShouldBegin: 如果您的视图直接处理触摸事件,并且可能希望阻止附加的手势识别器触发其他操作,请实施此方法。


那-(void)setFrame:(CGRect)frame呢?
pfrank

好吧,您绝对可以覆盖它,但是出于什么目的呢?
加布里埃尔·彼得罗内拉2013年

随时更改框架大小或位置来更改布局/绘图
pfrank 2013年

1
layoutSubviews
加布里埃尔·彼得罗内拉

stackoverflow.com/questions/4000664/…中,“问题在于子视图不仅可以更改其大小,而且可以为该大小变化设置动画。当UIView运行动画时,它不会每次都调用layoutSubviews。” 不过还没有亲自测试过
pfrank

38

这在Google中仍然很高。以下是swift的更新示例。

didLoad函数使您可以放置​​所有自定义的初始化代码。如其他人所述,didLoad当通过编程方式创建视图init(frame:)时,或者当XIB反序列化器通过以下方式将XIB模板合并到您的视图中时,将被调用init(coder:)

除了layoutSubviewsupdateConstraints大多数视图被多次调用。当视图的边界发生变化时,这适用于高级的多遍布局和调整。就我个人而言,我尽可能避免使用多通道布局,因为它们会消耗CPU周期并使所有事情变得头疼。另外,我很少在初始化器中放入约束代码,因为我很少使它们无效。

import UIKit

class MyView: UIView {
  //-----------------------------------------------------------------------------------------------------
  //Constructors, Initializers, and UIView lifecycle
  //-----------------------------------------------------------------------------------------------------
  override init(frame: CGRect) {
      super.init(frame: frame)
      didLoad()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    didLoad()
  }

  convenience init() {
    self.init(frame: CGRectZero)
  }

  func didLoad() {
    //Place your initialization code here

    //I actually create & place constraints in here, instead of in
    //updateConstraints
  }

  override func layoutSubviews() {
     super.layoutSubviews()

     //Custom manually positioning layout goes here (auto-layout pass has already run first pass)
  }

  override func updateConstraints() {
    super.updateConstraints()

    //Disable this if you are adding constraints manually
    //or you're going to have a 'bad time'
    //self.translatesAutoresizingMaskIntoConstraints = false

    //Add custom constraint code here
  }
}

您能否解释何时/为什么要在初始化过程中调用的方法与layoutSubviews和updateConstraints之间拆分布局约束代码?似乎它们都是放置布局代码的三个可能的候选位置。那么,您如何知道何时/什么/为什么在这三个之间拆分布局代码?
克莱·埃利斯,

3
我从不使用updateConstraints;updateConstraints可能很好,因为您知道视图层次结构是在init中完全设置的,因此您不能通过不在两个层次结构中的两个视图之间添加约束来引发异常:如果在布局传递过程中约束“无效”,则会很容易引起无限递归,因为layoutSubviews被调用。手动布局设置(如直接设置框架一样,除非出于性能原因,否则几乎不需要再进行设置)会进入layoutSubviews中。我个人将约束创建放在init中
seo 2016年

对于自定义渲染代码,我们应该重写draw方法吗?
Petrus Theron

14

Apple 文档中有一个不错的摘要,iTunes上提供的免费斯坦福课程对此做了很好的介绍。我在这里介绍我的TL; DR版本:

如果您的类主要由子视图组成,则在init方法中分配它们的正确位置。对于视图,有两种不同的init方法可以调用,这取决于是从代码还是从笔尖/ storyboard实例化视图。我要做的是编写自己的setup方法,然后从initWithFrame:initWithCoder:方法中调用它。

如果要进行自定义绘图,则确实要drawRect:在视图中进行覆盖。但是,如果自定义视图主要是子视图的容器,则可能不需要这样做。

layoutSubViews在要执行添加或删除子视图之类的操作时才覆盖,这取决于您是纵向还是横向。否则,您应该可以不理会它。


我用你的答案来更改视图(这是awakeFromNib)的subView框架layoutSubViews,它确实起作用了。
飞机

1

layoutSubviews 用于在子视图上设置框架,而不是在视图本身上设置框架。

对于UIView,指定的构造函数通常是initWithFrame:(CGRect)frame,您应该在此处(或中initWithCoder:)设置框架,可能会忽略传入的框架值。您还可以提供其他构造函数,并在那里设置框架。


您能详细一点吗?我不知道你的意思。如何设置视图的subView框架?视图是awakeFromNib
飞机

快进到2016年,您可能根本不应该设置框架并使用自动布局(约束)。如果视图来自XIB(或情节提要),则应已设置子视图。
proxi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.