168

我正在研究一些基于 UIView 的自定义输入控件,并且正在尝试确定设置视图的正确做法。使用 UIViewController 时,使用loadView和 相关viewWillviewDid方法相当简单,但是当子类化 UIView 时,我拥有的最接近的方法是`awakeFromNibdrawRectlayoutSubviews。(我正在考虑设置和拆卸回调。)在我的情况下,我正在设置我的框架和内部视图layoutSubviews,但我没有在屏幕上看到任何东西。

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

4

4 回答 4

310

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

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

还请记住,initWithFrame:仅当您以UIView编程方式实例化时才调用它。如果您从 nib 文件(或情节提要)加载它,initWithCoder:将使用。并且initWithCoder:框架中还没有计算出来,所以你不能修改你在Interface Builder中设置的框架。正如此答案中所建议的那样,您可能会考虑调用initWithFrame:frominitWithCoder:以设置框架。

最后,如果您UIView从 nib(或故事板)加载您的,您也有awakeFromNib机会执行自定义框架和布局初始化,因为当awakeFromNib被调用时,可以保证层次结构中的每个视图都已取消存档和初始化。

来自NSNibAwaking(现在被 doc 取代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:如果您的视图直接处理触摸事件并且可能希望防止附加的手势识别器触发其他操作,请实现此方法。

于 2013-04-12T19:02:33.070 回答
41

这在谷歌中仍然很高。下面是 swift 的更新示例。

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

Aside :layoutSubviews并且updateConstraints对于大多数视图被多次调用。这适用于视图边界发生变化时的高级多通道布局和调整。就个人而言,我尽可能避免使用多通道布局,因为它们会消耗 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
  }
}
于 2015-11-15T15:54:22.447 回答
14

Apple文档中有一个不错的总结,这在 iTunes 上的免费斯坦福课程中得到了很好的介绍。我在这里展示我的 TL;DR 版本:

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

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

layoutSubViews当您想要根据您是纵向还是横向进行添加或删除子视图之类的操作时才覆盖。否则,您应该可以不理会它。

于 2013-04-12T18:59:50.793 回答
1

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

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

于 2013-04-12T18:58:12.957 回答