【问题标题】:Proper practice for subclassing UIView?继承 UIView 的正确做法?
【发布时间】:2023-03-16 22:04:01
【问题描述】:

我正在开发一些基于 UIView 的自定义输入控件,并且正在尝试确定设置视图的正确做法。使用 UIViewController 时,使用 loadView 和相关的 viewWillviewDid 方法相当简单,但是当子类化 UIView 时,我拥有的最接近的方法是 `awakeFromNibdrawRectlayoutSubviews . (我正在考虑设置和拆卸回调。)在我的情况下,我在layoutSubviews 中设置我的框架和内部视图,但我在屏幕上看不到任何东西。

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

【问题讨论】:

    标签: ios objective-c cocoa-touch uiview


    【解决方案1】:

    Apple 非常清楚地定义了如何在文档中继承 UIView

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

    还请记住,仅当您以编程方式实例化 UIView 时才会调用 initWithFrame:。如果您从 nib 文件(或故事板)加载它,将使用 initWithCoder:。而在initWithCoder: 中还没有计算框架,所以你不能修改你在Interface Builder 中设置的框架。正如in this answer 建议的那样,您可能会考虑从initWithCoder: 调用initWithFrame: 以设置框架。

    最后,如果您从 nib(或情节提要)加载 UIView,您还可以通过 awakeFromNib 执行自定义框架和布局初始化,因为当调用 awakeFromNib 时,可以保证每个视图层次结构已被取消归档并初始化。

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

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

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

    直接来自documentation

    重写方法

    初始化

    • initWithFrame: 建议您实现此方法。除了,您还可以实现自定义初始化方法, 或代替此方法。

    • initWithCoder: 如果您从 Interface Builder nib 文件加载视图并且您的视图需要自定义 初始化。

    • layerClass 仅当您希望视图为其后备存储使用不同的核心动画层时才实施此方法。例如, 如果您使用 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: 如果您的视图直接处理触摸事件并且可能希望阻止附加,请实施此方法 手势识别器不会触发其他操作。

    【讨论】:

    • 怎么样 - (void) setFrame:(CGRect)frame?
    • 好吧,你绝对可以覆盖它,但是为了什么目的?
    • 在框架大小或位置发生变化时更改布局/绘图
    • layoutSubviews 呢?
    • 来自stackoverflow.com/questions/4000664/…,“这样做的问题是子视图不仅可以改变它们的大小,还可以动画化改变大小。当UIView运行动画时,它不会每次都调用layoutSubviews。 "虽然没有亲自测试过
    【解决方案2】:

    这在 Google 中仍然很高。下面是 swift 的更新示例。

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

    除此之外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
      }
    }
    

    【讨论】:

    • 您能解释一下何时/为什么要在从初始化进程调用的方法与 layoutSubviews 和 updateConstraints 之间拆分布局约束代码吗?似乎它们都是放置布局代码的三个可能的候选位置。那么你怎么知道什么时候/什么/为什么要在三者之间分割布局代码呢?
    • 我从不使用 updateConstraints; updateConstraints 可以很好,因为您知道您的视图层次结构完全在 init 中设置,因此您不能通过在不在层次结构中的两个视图之间添加约束来引发异常:) layoutSubviews 永远不应该有约束修改;如果在布局过程中约束“无效”,它很容易导致无限递归,因为 layoutSubviews 被调用。手动布局设置(如直接设置框架,除非出于性能原因,否则您很少需要再做)在 layoutSubviews 中。就个人而言,我将约束创建放在 init 中
    • 对于自定义渲染代码,我们是否应该重写draw方法?
    【解决方案3】:

    Apple documentation 中有一个不错的摘要,iTunes 上的免费 Stanford course 对此进行了很好的介绍。我在这里展示我的 TL;DR 版本:

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

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

    仅当您想要根据您是纵向还是横向进行添加或删除子视图之类的操作时,才覆盖 layoutSubViews。否则,您应该可以不用管它。

    【讨论】:

    • 我用你的答案来改变layoutSubViews中的视图(这是awakeFromNib)的子视图框架,它确实有效。
    【解决方案4】:

    layoutSubviews 用于在子视图上设置框架,而不是在视图本身上。

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

    【讨论】:

    • 你能更详细一点吗?我不知道你的意思。如何设置视图的子视图框架?视图是awakeFromNib
    • 快进到 2016 年,您可能根本不应该设置框架并使用自动布局(约束)。如果视图来自 XIB(或故事板),则应该已经设置了子视图。
    猜你喜欢
    • 2011-07-13
    • 2012-04-11
    • 1970-01-01
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    • 2016-11-05
    • 2011-12-30
    相关资源
    最近更新 更多