ios 子类化 UIView 的正确做法?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15978370/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 23:09:45  来源:igfitidea点击:

Proper practice for subclassing UIView?

iosobjective-ccocoa-touchuiview

提问by Moshe

I'm working on some custom UIView-based input controls, and I'm trying to ascertain proper practice for setting up the view. When working with a UIViewController, it's fairly simple to use the loadViewand related viewWill, viewDidmethods, but when subclassing a UIView, the closest methosds I have are `awakeFromNib, drawRect, and layoutSubviews. (I'm thinking in terms of setup and teardown callbacks.) In my case, I'm setting up my frame and internal views in layoutSubviews, but I'm not seeing anything onscreen.

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

What is the best way to ensure that my view has the correct height and width that I want it to have? (My question applies regardless of if I'm using autolayout, although there might be two answers.) What's the proper "best practice"?

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

回答by Gabriele Petronella

Apple defined pretty clearly how to subclass UIViewin the doc.

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

Check out the list below, especially take a look at initWithFrame:and layoutSubviews. The former is intended to setup the frame of your UIViewwhereas the latter is intended to setup the frame and the layout of its subviews.

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

Also remember that initWithFrame:is called only if you are instantiating your UIViewprogrammatically. If you are loading it from a nib file (or a storyboard), initWithCoder:will be used. And in initWithCoder:the frame hasn't been calculated yet, so you cannot modify the frame you set up in Interface Builder. As suggested in this answeryou may think of calling initWithFrame:from initWithCoder:in order to setup the frame.

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

Finally, if you load your UIViewfrom a nib (or a storyboard), you also have the awakeFromNibopportunity to perform custom frame and layout initializations, since when awakeFromNibis called it's guaranteed that every view in the hierarchy has been unarchived and initialized.

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

From the doc of NSNibAwaking(now superseded by the doc of awakeFromNib):

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

Messages to other objects can be sent safely from within awakeFromNib—by which time it's assured that all the objects are unarchived and initialized (though not necessarily awakened, of course)

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

It's also worth noting that with autolayout you shouldn't explicitly set the frame of your view. Instead you are supposed to specify a set of sufficient constraints, so that the frame is automatically calculated by the layout engine.

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

Straight from the documentation:

直接来自文档

Methods to Override

Initialization

  • initWithFrame:It is recommended that you implement this method. You can also implement custom initialization methods in addition to, or instead of, this method.

  • initWithCoder:Implement this method if you load your view from an Interface Builder nib file and your view requires custom initialization.

  • layerClassImplement this method only if you want your view to use a different Core Animation layer for its backing store. For example, if you are using OpenGL ES to do your drawing, you would want to override this method and return the CAEAGLLayer class.

Drawing and printing

  • drawRect:Implement this method if your view draws custom content. If your view does not do any custom drawing, avoid overriding this method.

  • drawRect:forViewPrintFormatter:Implement this method only if you want to draw your view's content differently during printing.

Constraints

  • requiresConstraintBasedLayoutImplement this class method if your view class requires constraints to work properly.

  • updateConstraintsImplement this method if your view needs to create custom constraints between your subviews.

  • alignmentRectForFrame:, frameForAlignmentRect:Implement these methods to override how your views are aligned to other views.

Layout

  • sizeThatFits:Implement this method if you want your view to have a different default size than it normally would during resizing operations. For example, you might use this method to prevent your view from shrinking to the point where subviews cannot be displayed correctly.

  • layoutSubviewsImplement this method if you need more precise control over the layout of your subviews than either the constraint or autoresizing behaviors provide.

  • didAddSubview:, willRemoveSubview:Implement these methods as needed to track the additions and removals of subviews.

  • willMoveToSuperview:, didMoveToSuperviewImplement these methods as needed to track the movement of the current view in your view hierarchy.

  • willMoveToWindow:, didMoveToWindowImplement these methods as needed to track the movement of your view to a different window.

Event Handling:

  • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent:Implement these methods if you need to handle touch events directly. (For gesture-based input, use gesture recognizers.)

  • gestureRecognizerShouldBegin:Implement this method if your view handles touch events directly and might want to prevent attached gesture recognizers from triggering additional actions.

覆盖的方法

初始化

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

回答by seo

This still comes up high in Google. Below is an updated example for swift.

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

The didLoadfunction lets you put all your custom initialization code. As others have mentioned, didLoadwill be called when a view is created programmatically via init(frame:)or when the XIBdeserializer merges a XIBtemplate into your view via init(coder:)

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

Aside: layoutSubviewsand updateConstraintsare called multiple times for the majority of views. This is intended for advanced multi-pass layouts and adjustments when a view's bounds changes. Personally, I avoid multi-pass layouts when possible because they burn CPU cycles and make everything a headache. Additionally, I put constraint code in the initializers themselves as I rarely invalidate them.

旁白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
  }
}

回答by dpassage

There's a decent summary in the Apple documentation, and this is covered well in the free Stanford courseavailable on iTunes. I present my TL;DR version here:

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

If your class mostly consists of subviews, the right place to allocate them is in the initmethods. For views, there are two different initmethods that could get called, depending on if your view is being instantiated from code or from a nib/storyboard. What I do is write my own setupmethod, and then call it from both the initWithFrame:and initWithCoder:methods.

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

If you're doing custom drawing, you indeed want to override drawRect:in your view. If your custom view is mostly a container for subviews, though, you probably won't need to do that.

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

Only override layoutSubViewsif you want to do something like add or remove a subview depending on if you're in portrait or landscape orientation. Otherwise, you should be able to leave it alone.

layoutSubViews当您想要执行诸如添加或删除子视图之类的操作时才覆盖,具体取决于您是纵向还是横向。否则,您应该可以不理会它。

回答by proxi

layoutSubviewsis meant to set frame on child views, not on the view itself.

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

For UIView, the designated constructor is typically initWithFrame:(CGRect)frameand you should set the frame there (or in initWithCoder:), possibly ignoring passed in frame value. You can also provide a different constructor and set the frame there.

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