子类化 UIView 的正确实践?

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

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

72349 次浏览

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

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

在苹果的 文件中有一个不错的总结,在 iTunes 上的免费 斯坦福大学的课程中也有很好的介绍。我在这里介绍我的 TL; DR 版本:

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

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

只有覆盖 layoutSubViews,如果你想做一些事情,如添加或删除一个子视图取决于如果你在纵向或横向方向。否则,你应该可以不管它。

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

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

还要记住,只有在以编程方式实例化 UIView时才调用 initWithFrame:。如果您从一个 nib 文件(或一个情节串连图板)加载它,将使用 initWithCoder:。在 initWithCoder:中,帧还没有被计算出来,所以你不能修改你在 Interface Builder 中设置的帧。正如建议的 在这个答案中,您可以考虑从 initWithCoder:调用 initWithFrame:,以便设置帧。

最后,如果你从一个笔尖(或故事板)加载你的 UIView,你也有机会执行自定义框架和布局初始化,因为当 awakeFromNib被调用时,它保证层次结构中的每个视图都已经被未归档和初始化。

来自 NSNibAwaking的文档(现在被 awakeFromNib的文档取代) :

可以从 awakeFromNib 内部安全地向其他对象发送消息ーー这时可以确保所有对象都未归档和初始化(当然,不一定是唤醒的)

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

来自 文件的报道:

重写的方法

初始化

  • 建议您实现此方法。除了以下方法外,您还可以实现自定义初始化方法, 或者用这种方法代替

  • 如果你从一个 Interface Builder 的 nib 文件加载你的视图并且你的视图需要自定义,那么使用这个方法 初始化

  • 只有当你希望你的视图使用不同的核心动画层作为后台存储时,才实现这个方法。比如说, 如果您正在使用 OpenGL ES 进行绘图,则需要 重写此方法并返回 CAEAGLLayer 类

绘图和印刷

  • 如果视图绘制自定义内容,则实现此方法。如果视图不执行任何自定义绘图,请避免重写此 方法

  • 只有当您想在打印过程中以不同的方式绘制视图内容时才使用此方法。

约束

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

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

  • alignmentRectForFrame: frameForAlignmentRect:实现这些方法来覆盖视图与其他视图的对齐方式。

布局

  • 如果希望视图的默认大小与调整大小时的默认大小不同,请实现此方法 例如,您可以使用此方法来防止您的 视图从收缩到无法显示子视图的点 正确

  • 如果你需要更精确的控制子视图的布局而不是约束或者 自动调整大小行为提供

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

  • willMoveToSuperview: didMoveToSuperview根据需要实现这些方法来跟踪视图中当前视图的运动 等级制度

  • willMoveToWindow: didMoveToWindow根据需要实现这些方法来跟踪视图到不同窗口的移动。

事件处理:

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

  • 如果您的视图直接处理触摸事件并且可能希望防止附加,则实现此方法 手势识别器触发附加动作

这在 Google 中仍然很常见,下面是一个更新后的例子。

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

除了 : 对于大多数视图,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
}
}