为UIView子类化的正确做法?

时间:2013-04-12 18:26:35

标签: ios objective-c cocoa-touch uiview

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

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

4 个答案:

答案 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 如果您的视图需要在子视图之间创建自定义约束,请实施此方法。

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

    < / LI>   
     

布局

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

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

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

    < / LI>   
  • 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:)

查看
  对于大多数视图,

除了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
  }
}

答案 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:中),可能忽略传递的帧值。您还可以提供不同的构造函数并在那里设置框架。