为UIView子类化的正确做法?

Mos*_*she 154 cocoa-touch objective-c uiview ios

我正在研究一些基于UIView的自定义输入控件,我正在尝试确定设置视图的正确做法.当一个UIViewController的工作,这是相当简单的使用loadView和相关viewWill,viewDid方法,但是继承一个UIView的时候,我有最接近methosds是`awakeFromNib,drawRectlayoutSubviews.(我正在考虑设置和拆卸回调.)在我的情况下,我正在设置框架和内部视图layoutSubviews,但我没有在屏幕上看到任何内容.

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

Gab*_*lla 288

Apple非常明确地定义了如何UIView在doc中进行子类化.

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

还要记住initWithFrame:只有在以UIView编程方式实例化时才会调用它.如果从nib文件(或故事板)加载它,initWithCoder:将被使用.并且initWithCoder:尚未计算帧,因此您无法修改在Interface Builder中设置的帧.如您在本回答中所建议的那样,您可以考虑initWithFrame:从中调用initWithCoder:以设置框架.

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

来自博士 NSNibAwaking

可以从awakeFromNib中安全地发送到其他对象的消息 - 这时确保所有对象都被取消归档并初始化(当然,当然不一定被唤醒)

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

直接来自文档:

覆盖的方法

初始化

  • awakeFromNib建议您实现此方法.除了此方法之外,您还可以实现自定义初始化方法,或者代替此方法.

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

  • initWithCoder:仅当您希望视图为其后备存储使用不同的Core Animation层时,才实现此方法.例如,如果您使用OpenGL ES进行绘图,则需要覆盖此方法并返回CAEAGLLayer类.

绘图和打印

  • layerClass如果视图绘制自定义内容,请实现此方法.如果您的视图没有执行任何自定义绘图,请避免覆盖此方法.

  • drawRect: 仅当您希望在打印期间以不同方式绘制视图的内容时才实现此方法.

约束

  • drawRect:forViewPrintFormatter: 如果视图类需要约束才能正常工作,请实现此类方法.

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

  • updateConstraints,alignmentRectForFrame:实现这些方法以覆盖视图与其他视图的对齐方式.

布局

  • frameForAlignmentRect:如果希望视图具有与调整大小操作期间通常不同的默认大小,请实现此方法.例如,您可以使用此方法来防止视图缩小到无法正确显示子视图的位置.

  • sizeThatFits: 如果您需要更精确地控制子视图的布局,请执行此方法,而不是约束或自动调整行为提供.

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

  • willRemoveSubview:,willMoveToSuperview:根据需要实现这些方法,以跟踪视图层次结构中当前视图的移动.

  • didMoveToSuperview,willMoveToWindow:根据需要实施这些方法,以跟踪视图移动到不同的窗口.

事件处理:

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

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


seo*_*seo 37

谷歌的这一点仍然很高.以下是swift的更新示例.

didLoad函数允许您放置所有自定义初始化代码.正如其他人所提到的,didLoad当通过编程方式创建视图时init(frame:)或者当XIB反序列化器将XIB模板合并到视图中时,将调用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
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我从不使用updateConstraints; updateConstraints可以很好,因为您知道您的视图层次结构是在init中完全设置的,因此您不能通过在不在层次结构中的两个视图之间添加约束来引发异常:) layoutSubviews应该永远不会有约束修改; 如果在布局过程中约束"无效",则调用layoutSubviews会很容易导致无限递归.手动布局设置(如直接设置框架,除非出于性能原因,您很少需要这样做)在layoutSubviews中进行.就个人而言,我在init中设置了约束创建 (3认同)

dpa*_*age 14

Apple 文档中有一个不错的摘要,这在iTunes上提供的免费斯坦福课程中得到了很好的体现.我在这里展示我的TL; DR版本:

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

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

layoutSubViews如果您想要执行添加或删除子视图等操作,则仅覆盖,具体取决于您是纵向还是横向.否则,你应该可以不管它.