如何在自定义 UIView 中正确使用 intrincSize。在哪里/何时计算和更新尺寸?

2024-02-15

我一直在努力理解和利用intrinsicSize在定制上UIView已经好几天了。到目前为止,收效甚微。

这篇文章很长,对此感到抱歉:-) 问题是,这个主题非常复杂。虽然我知道可能还有其他解决方案,但我只是想了解如何intrinsicSize可以正确使用。

因此,当有人知道一个关于如何使用/实现的深入解释的好来源时intrinsicSize你可以跳过我所有的问题,只给我留下链接。

My goal:

创建自定义UIView它使用intrinsicSize让 AutoLayout 自动适应不同的内容。就像一个UILabel它会根据文本内容、字体、字体大小等自动调整大小。

作为一个例子,假设一个简单的视图RectsView它只是绘制给定数量、给定大小、给定间距的矩形。如果不是所有矩形都适合一行,则内容将被换行并在另一行中继续绘制。因此,视图的高度取决于不同的属性(矩形数量、矩形大小、间距等)

这很像一个UILabel但绘制的是简单的矩形,而不是单词或字母。然而,虽然UILabel效果很好,但我无法为我的产品实现同样的效果RectsView.

为什么选择内在尺寸

正如@DonMag 在他对我之前的问题的出色回答中指出的那样,我不必使用intrinsicSize实现我的目标。我还可以使用子视图并添加约束来创建这样的矩形图案。或者我可以使用UICollectionView, etc.

虽然这肯定可行,但我认为这会增加很多开销。如果目标是重新创建一个UILabel类中,人们不会使用 AutoLayout 或 CollectionView 将字母排列为单词,不是吗?相反,人们肯定会尝试手动绘制字母......尤其是在使用RectsView在 TableView 或 CollectionView 中,直接绘制的普通视图肯定比使用 AutoLayout 排列的大量子视图编译的复杂解决方案要好。

当然,这是一个极端的例子。然而,归根结底,有些情况下使用intrinsicSize肯定是更好的选择。自从UILabel和其他内置视图用途intrinsicSize完美,必须有一种方法可以让它发挥作用,我只是想知道如何:-)

我对内在尺寸的理解

@DonMag 怀疑,“我真的不明白intrinsicContentSize 做什么和不做什么”。他很可能是正确的:-)但是,问题是我没有找到真正解释它的来源......因此我花了几个小时试图理解如何正确使用intrinsicSize没有一点进展。

这就是我所学到的从文档 https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html#//apple_ref/doc/uid/TP40010853-CH9-SW1:

  • intrinsicSize是一个用于AutoLayout。提供固有高度和/或宽度的视图不需要为这些值指定约束。
  • 无法保证视图会准确地得到它的intrinsicSize。它更像是一种告诉 autoLayout 哪个尺寸最适合视图的方法,而 autoLayout 将计算实际尺寸。
  • 计算是使用intrinsicSizeCompression Resistance + Content Hugging特性。
  • 的计算intrinsicSize应该只取决于内容,而不取决于视图框架。

我不明白的是:

  • 计算如何独立于视图框架?当然UIImageView可以使用其图像的大小,但可以使用UILabel显然只能根据其内容和宽度来计算。那么我的RectsView计算其高度而不考虑框架宽度?
  • 应该什么时候计算intrinsicSize发生?在我的例子中RectsView大小取决于矩形大小、间距和数量。在一个UILabel大小还取决于文本、字体、字体大小等多个属性。如果在设置每个属性时都进行计算,则会执行多次,效率相当低。那么什么地方才是正确的做法呢?

实施示例:

这是我的一个简单实现RectsView:

@IBDesignable class RectsView: UIView {
    
    // Properties which determin the intrinsic height
    @IBInspectable public var rectSize: CGFloat = 20 {
        didSet {
            calcContent()
            setNeedsLayout()
        }
    }
    
    @IBInspectable public var rectSpacing: CGFloat = 10 {
        didSet {
            calcContent()
            setNeedsLayout()
        }
    }
    
    @IBInspectable public var rowSpacing: CGFloat = 5 {
        didSet {
            calcContent()
            setNeedsLayout()
        }
    }
    
    @IBInspectable public var rectsCount: Int = 20 {
        didSet {
            calcContent()
            setNeedsLayout()
        }
    }
    
    
    // Calculte the content and its height
    private var rects = [CGRect]()
    func calcContent() {
        var x: CGFloat = 0
        var y: CGFloat = 0
        
        rects = []
        if rectsCount > 0 {
            for _ in 0..<rectsCount {
                let rect = CGRect(x: x, y: y, width: rectSize, height: rectSize)
                rects.append(rect)
                
                x += rectSize + rectSpacing
                if x + rectSize > frame.width {
                    x = 0
                    y += rectSize + rowSpacing
                }
            }
        }
        
        height = y + rectSize
        invalidateIntrinsicContentSize()
    }
    
    
    // Intrinc height
    @IBInspectable var height: CGFloat = 50
    override var intrinsicContentSize: CGSize {
        return CGSize(width: UIView.noIntrinsicMetric, height: height)
    }
    
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        invalidateIntrinsicContentSize()
    }
    
    
    // Drawing
    override func draw(_ rect: CGRect) {
        super.draw(rect)
                
        let context = UIGraphicsGetCurrentContext()
        
        for rect in rects {
            context?.setFillColor(UIColor.red.cgColor)
            context?.fill(rect)
        }
        
        
        let attrs = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: UIFont.systemFontSize)]
        let text = "\(height)"
        
        text.draw(at: CGPoint(x: 0, y: 0), withAttributes: attrs)
    }
}

class ViewController: UITableViewController {
    let CellId = "CellId"

    // Dummy content with different values per row
    var data: [(CGFloat, CGFloat, CGFloat, Int)] = [
        (10.0, 15.0, 13.0, 35),
        (20.0, 10.0, 16.0, 28),
        (30.0, 5.0, 19.0, 21)
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UINib(nibName: "IntrinsicCell", bundle: nil), forCellReuseIdentifier: CellId)
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: CellId, for: indexPath) as? IntrinsicCell ?? IntrinsicCell()
        
        // Dummy content with different values per row
        cell.rectSize = data[indexPath.row].0     // CGFloat((indexPath.row+1) * 10)
        cell.rectSpacing = data[indexPath.row].1  // CGFloat(20 - (indexPath.row+1) * 5)
        cell.rowSpacing = data[indexPath.row].2   // CGFloat(10 + (indexPath.row+1) * 3)
        cell.rectsCount = data[indexPath.row].3   // (5 - indexPath.row) * 7
        
        return cell
    }

    // Add/remove content when tapping on a row
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        var rowData = data[indexPath.row]
        rowData.3 = (indexPath.row % 2 == 0 ? rowData.3 + 5 : rowData.3 - 5)
        data[indexPath.row] = rowData
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }
}


// Simple cell holing an intrinsic rectsView
class IntrinsicCell: UITableViewCell {
    @IBOutlet private var rectsView: RectsView!
        
    var rectSize: CGFloat {
        get { return rectsView.rectSize }
        set { rectsView.rectSize = newValue }
    }
    
    var rectSpacing: CGFloat {
        get { return rectsView.rectSpacing }
        set { rectsView.rectSpacing = newValue }
    }
    
    var rowSpacing: CGFloat {
        get { return rectsView.rowSpacing }
        set { rectsView.rowSpacing = newValue }
    }
    
    var rectsCount: Int {
        get { return rectsView.rectsCount }
        set { rectsView.rectsCount = newValue }
    }
} 

问题:

基本上是intrinsicSize工作正常:当第一次渲染 TableView 时,每行都有不同的高度,具体取决于其内在内容。但是,尺寸不正确,因为intrinsicSize在 TableView 实际布局其子视图之前计算。就这样RectsView使用默认宽度而不是实际的最终宽度来计算其内容大小。

那么:何时/何地计算初始布局?

此外,属性更新(= 点击单元格)未正确处理

那么:何时/何地计算更新的布局?

再次:很抱歉这篇文章很长,如果您能读到这里,非常感谢。我真的很感激!

我你知道任何好的来源/教程/如何解释这一切,我很高兴你能提供的任何链接!

更新:更多观察结果

我已经添加了一些调试输出到我的RectsView并到一个UILabel子类看看如何intrinsicContentSize用来。

In RectsView intrinsicContentSize在边界设置为其最终大小之前仅调用一次。由于此时我还不知道最终尺寸,因此我只能根据旧的、过时的宽度计算固有尺寸,这会导致错误的结果。

In UIView然而,intrinsicContentSize被调用多次(why?)并且在最后一次调用中,结果似乎适合即将到来的最终尺寸。此时如何知道这个尺寸?

RectsView willSet frame: (-120.0, -11.5, 240.0, 23.0)
RectsView didSet frame: (40.0, 11.0, 240.0, 23.0)
RectsView didSet rectSize: 10.0
RectsView didSet rectSpacing: 15.0
RectsView didSet rowSpacing: 20
RectsView didSet rectsCount: 35
RectsView get intrinsicContentSize: 79.0
RectsView willSet bounds: (0.0, 0.0, 240.0, 23.0)
RectsView didSet bounds: (0.0, 0.0, 350.0, 79.33333333333333)
RectsView layoutSubviews
RectsView layoutSubviews

MyLabel willSet frame: (-116.5, -9.5, 233.0, 19.0)
MyLabel didSet frame: (53.0, 13.0, 233.0, 19.0)
MyLabel willSet text: (53.0, 13.0, 233.0, 19.0)
MyLabel didSet text: (53.0, 13.0, 233.0, 19.0)
MyLabel get intrinsicContentSize: (65536.0, 20.333333333333332)
MyLabel get intrinsicContentSize: (675.0, 20.333333333333332)
MyLabel get intrinsicContentSize: (65536.0, 20.333333333333332)
MyLabel get intrinsicContentSize: (338.0, 40.666666666666664)
MyLabel get intrinsicContentSize: (65536.0, 20.333333333333332)
MyLabel get intrinsicContentSize: (65536.0, 20.333333333333332)
MyLabel get intrinsicContentSize: (338.0, 40.666666666666664)
MyLabel get intrinsicContentSize: (65536.0, 20.333333333333332)
MyLabel get intrinsicContentSize: (338.0, 40.666666666666664)
MyLabel willSet bounds: (0.0, 0.0, 233.0, 19.0)
MyLabel didSet bounds: (0.0, 0.0, 350.0, 41.0)
MyLabel layoutSubviews
MyLabel layoutSubviews

第一次打电话时UILabel返回固有宽度65536.0这是一些常数吗?我只知道UIView.noIntrinsicMetric = -1它指定视图在给定维度中没有固有大小。

Why, is intrinsicContentSize多次调用UILabel?我尝试返回相同尺寸(65536.0, 20.333333333333332) in RectsView但这没有任何区别,intrinsicContentSize仍然只被调用一次。

在最后一次通话中intrinsicContentSize of UILabel价值(338.0, 40.666666666666664)被返回。看起来UILabel此时知道,它将被重新调整为宽度350, but how?

另一个观察结果是,两种观点intrinsicContentSize不被称为after bounds.didSet。因此必须有一种方法来知道即将到来的帧变化intrinsicContentSize before bounds.didSet. How?

更新2:

我已将调试输出添加到以下内容中,其他UILabel方法也是如此,但它们是not称为(因此似乎不会影响问题):

sizeToFit()
sizeThatFits(_ :)
contentCompressionResistancePriority(for :)
contentHuggingPriority(for :)
systemLayoutSizeFitting(_ :)
systemLayoutSizeFitting(_ :, withHorizontalFittingPriority :, verticalFittingPriority:)
preferredMaxLayoutWidth  get + set

正如 @DonMag 建议的那样,我使用技术支持事件直接从 Apple 工程师那里获取反馈。花了一些时间才得到答案,但最终得到了答案:

长话短说:不使用固有尺寸

似乎内在大小更多的是一个内部功能,不打算由自定义视图使用。当然它不是私有API,可以使用。然而,如果没有(很多)与其他私有 iOS 功能一起使用,它的功能就相当有限,几乎无法使用。

Apple 建议改用 AutoLayout 和约束。即使对于像自定义 UILabel 克隆这样的基本示例也是如此。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在自定义 UIView 中正确使用 intrincSize。在哪里/何时计算和更新尺寸? 的相关文章

  • iOS WKWebView 处理文件下载

    我面临以下问题 在 Web 界面中 文件下载是通过锚标记触发的 如下所示 a href bla blabla a 虽然 Safari 浏览器可以处理此请求并打开一个对话框来处理文件 但 WKWebView 将此视为普通链接并且不对其执行任何
  • 在回调函数中调用目标c函数

    如何在回调函数中调用目标c函数 回调函数 static OSStatus inputRenderCallback void inRefCon AudioUnitRenderActionFlags ioActionFlags const Au
  • iPhone SQLite页面缓存不断增长

    I use sqlite数据库用于存储 还有许多数据库事务 我的问题是 sqlite 页面缓存的内存使用量快速增长 在instruments我可以找到这条线 Graph Category Live Bytes Living Transien
  • IDFA 使用不当,您的应用不遵守 ios 中的限制广告跟踪设置

    I have checked the iTC settings I have uploaded the same app 2 days ago and it works fine but when today I uploaded the
  • 使用自动布局、IB 和字体大小时表头视图高度错误

    我正在尝试为我的 uiTableView 创建一个标题视图 不是节标题 我已经有了 我已经在界面生成器中设置了一个 XIB 所有的连接都已连接好并且运行良好 除了桌子没有给它足够的空间 我的问题是表格顶部与表格标题有一点重叠 我的 XIB
  • 使用 Quartz 创建 PDF 注释 (iOS)

    有人设法使用 Quartz 在现有 PDF 中编写自定义注释吗 我已经使用 CGPDFDocumentRef 等渲染了 PDF 现在工作正常 我成功地阅读了 Annots 字典 if CGPDFDictionaryGetArray page
  • 如何解决 iOS 6 SDK 中的 hidesBottomBarWhenPushed 行为异常的问题?

    我遇到了中描述的相同问题这个 OpenRadar 问题 http www openradar me 14670329 正如那里所说 摘要 UIViewController的hidesBottomBarWhenPushed属性 对于使用 iO
  • iOS Swift 在后台下载大量小文件

    在我的应用程序中 我需要下载具有以下要求的文件 下载大量 例如 3000 个 小 PNG 文件 例如 5KB 逐个 如果应用程序在后台继续下载 如果图像下载失败 通常是因为互联网连接丢失 请等待 X 秒然后重试 如果失败Y次 则认为下载失败
  • Apple Developer 应用程序门户不再可以生成新的 Bundle Seed ID

    iOS 开发者门户中的新界面不再为您的应用程序 ID 提供 生成新的 按钮 取而代之的是 使用团队 ID 这将导致使用相同的种子 ID 任何人都知道为什么要进行更改以及您应该如何使用新的捆绑包种子 ID 随意补一些 不再可能生成新的种子 I
  • 在 UITableViewController 中重新排序行后 UI 更新不正确

    因此 我对表中的行重新排序 用户界面最终结果不正确 场景如下 表内容原文 a b c d e 如果我移动第 0 行 当前a 到第 4 行 当前e 我看到的最终结果是 c d e a a 一些背景 该表正在读取 Realm 对象的列表 我确认
  • 在情节提要中将 Segue 拖至自身

    我想将一个 Segue 从我的视图控制器拖到其自身 所以我可以推送该特定视图控制器的 无限 实例 我知道如何在代码中执行此操作 即以编程方式实例化视图控制器 但是 我想尽可能使用 segues 我发现了一些在故事板中进行自我延续的 技巧 但
  • 将 Armadillo C++ 库导入 Xcode

    我是 Mac 用户 正在尝试安装和导入 C Armadillo 库 以下是我到目前为止所采取的步骤 1 我从其网站下载了犰狳库 2 我仔细阅读了下载文件中的 Readme txt 文件 解释了如何安装它 3 我使用CMake将犰狳下载文件制
  • 如何将CIFilter应用到UIView上?

    根据Apple docs 过滤属性CALayer不支持iOS 当我使用正在申请的应用程序之一时CIFilter to UIView即 Splice Funimate 和 Artisto 的视频编辑器 Videoshow FX 这意味着我们可
  • TTTAttributedLabel 可点击截断标记

    我有一个 TTTAttributedLabel 并为其指定了一个自定义属性截断标记 NSAttributedString atributedTruncationToken NSAttributedString alloc initWithS
  • 循环多个 UIAlertController

    在某些情况下 我的应用程序需要显示多个警报消息 错误消息在启动时收集 并且需要一次向用户显示一条 当第一个被确认后 应该呈现下一个 问题在于 显然 它们都试图同时执行 有没有一种聪明的方法可以同步执行此操作 这是一些简单描述我想要做的事情的
  • iOS Swift 和 reloadRowsAtIndexPaths 编译错误

    我与 xCode Swift 陷入僵局并刷新 UITableView 的单行 这条线有效 self tableView reloadData 而这条线没有 self tableView reloadRowsAtIndexPaths curr
  • 如何在 iOS 上固定证书的公钥

    在提高我们正在开发的 iOS 应用程序的安全性时 我们发现需要对服务器的 SSL 证书 全部或部分 进行 PIN 操作以防止中间人攻击 尽管有多种方法可以做到这一点 但当您搜索此内容时 我只找到了固定整个证书的示例 这种做法会带来一个问题
  • UILabel UILongPressGestureRecognizer 不起作用?

    我怎样才能得到UILongPressGestureRecognizer在 uilabel 当我实现以下代码时 它不会调用该函数 那么请告诉我我做错了什么 UILongPressGestureRecognizer longPress UILo
  • WKWebview 中的 iCLoud 文档选择器关闭容器视图

    我有一个 WKWebview 加载基于 Web 的 UI 我希望用户能够从其 iCloud 文档上传文件 我已授予正确的权限 并且可以浏览 iCloud 文档 但是 当我选择文件或单击取消按钮时 文档选择器视图也会关闭 WKWebview
  • 如何观察UserDefaults的变化?

    我有一个 ObservedObject在我看来 struct HomeView View ObservedObject var station Station var body some View Text self station sta

随机推荐