我正在研究一些基于UIView的自定义输入控件,我正在尝试确定设置视图的正确做法。使用UIViewController时,使用loadView
和相关的viewWill
,viewDid
方法相当简单,但在对UIView进行子类化时,我所拥有的最接近的方法是`awakeFromNib
, drawRect
和layoutSubviews
。 (我正在考虑设置和拆解回调。)在我的情况下,我在layoutSubviews
设置我的框架和内部视图,但我没有在屏幕上看到任何内容。
确保我的视图具有我想要的正确高度和宽度的最佳方法是什么? (无论我是否使用autolayout,我的问题都适用,尽管可能有两个答案。)什么是正确的“最佳实践”?
答案 0 :(得分:285)
Apple非常明确地定义了如何在文档中继承UIView
。
查看下面的列表,特别是查看initWithFrame:
和layoutSubviews
。前者旨在设置UIView
的框架,而后者则用于设置框架及其子视图的布局。
另请注意,只有在以编程方式实例化initWithFrame:
时才会调用UIView
。如果从nib文件(或故事板)加载它,将使用initWithCoder:
。并且在initWithCoder:
中尚未计算帧,因此您无法修改在Interface Builder中设置的帧。根据建议in this answer,您可以考虑从initWithFrame:
调用initWithCoder:
以设置框架。
最后,如果您从笔尖(或故事板)加载UIView
,那么您还有awakeFromNib
机会执行自定义框架和布局初始化,因为调用awakeFromNib
时保证层次结构中的每个视图都已归档并初始化。
来自NSNibAwaking
的文件(现已由awakeFromNib
的文件取代):
可以从awakeFromNib中安全地发送到其他对象的消息 - 这时确保所有对象都被取消存档并初始化(当然不一定被唤醒)
值得注意的是,使用自动布局时,您不应该明确设置视图的框架。相反,您应该指定一组足够的约束,以便框架由布局引擎自动计算。
直接来自documentation:
覆盖的方法
初始化
initWithFrame:
建议您实施此方法。您还可以实现自定义初始化方法, 或者代替,这种方法。
initWithCoder:
如果从Interface Builder nib文件加载视图并且视图需要自定义,请实现此方法 初始化。
layerClass
仅当您希望视图为其后备存储使用不同的Core Animation层时,才实现此方法。例如, 如果你使用OpenGL ES进行绘图,你会想要 覆盖此方法并返回CAEAGLLayer类。绘图和打印
drawRect:
如果您的视图绘制自定义内容,请实施此方法。如果您的视图没有进行任何自定义绘图,请避免覆盖它 方法
drawRect:forViewPrintFormatter:
仅当您希望在打印期间以不同方式绘制视图内容时才实施此方法。约束
requiresConstraintBasedLayout
如果您的视图类需要约束才能正常工作,请实现此类方法。
updateConstraints
如果您的视图需要在子视图之间创建自定义约束,请实施此方法。< / LI>
alignmentRectForFrame:
,frameForAlignmentRect:
实施这些方法以覆盖视图与其他视图的对齐方式。布局
sizeThatFits:
如果您希望视图的默认大小与调整大小时的默认大小不同,请执行此方法 操作。例如,您可以使用此方法来阻止您的 从缩小到无法显示子视图的视图 正确。
layoutSubviews
如果您需要更精确地控制子视图的布局,请执行此方法,而不是约束或 自动化行为提供。< / LI>
didAddSubview:
,willRemoveSubview:
根据需要实施这些方法,以跟踪子视图的添加和删除。
willMoveToSuperview:
,didMoveToSuperview
根据需要实施这些方法,以跟踪视图中当前视图的移动 层次结构。
willMoveToWindow:
,didMoveToWindow
根据需要实施这些方法,以跟踪视图移动到其他窗口。事件处理:
touchesBegan:withEvent:
,touchesMoved:withEvent:
,touchesEnded:withEvent:
,touchesCancelled:withEvent:
实行 如果您需要直接处理触摸事件,请使用以下方法。 (对于 基于手势的输入,使用手势识别器。)
gestureRecognizerShouldBegin:
如果您的视图直接处理触摸事件并且可能希望阻止附加,请实现此方法 手势识别器触发其他操作。
答案 1 :(得分:35)
Google的这一点仍然很高。以下是swift的更新示例。
didLoad
函数允许您放置所有自定义初始化代码。正如其他人所提到的,当通过didLoad
以编程方式创建视图或 XIB 反序列化器将 XIB 模板合并到您的视图中时,将调用init(frame:)
通过init(coder:)
对于大多数视图,除了:
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
}
}
答案 2 :(得分:14)
Apple documentation中有一个不错的摘要,这在iTunes上提供的免费Stanford course中得到了很好的体现。我在这里展示我的TL; DR版本:
如果您的课程主要由子视图组成,则分配它们的正确位置在init
方法中。对于视图,可以调用两种不同的init
方法,具体取决于您的视图是从代码还是从nib / storyboard实例化。我所做的是编写自己的setup
方法,然后使用initWithFrame:
和initWithCoder:
方法调用它。
如果您正在进行自定义绘图,则确实要在视图中覆盖drawRect:
。但是,如果您的自定义视图主要是子视图的容器,则可能不需要这样做。
如果您想要执行添加或删除子视图等操作,请仅覆盖layoutSubViews
,具体取决于您是纵向还是横向。否则,你应该可以不管它。
答案 3 :(得分:1)
layoutSubviews
用于在子视图上设置框架,而不是在视图本身上设置。
对于UIView
,指定的构造函数通常为initWithFrame:(CGRect)frame
,您应该在那里设置框架(或在initWithCoder:
中),可能忽略传递的帧值。您还可以提供不同的构造函数并在那里设置框架。