首页/新闻资讯/正文详情

Texture 手动布局完全指南:calculateSizeThatFits 与 layout 的实战用法

发布时间:2026/9/26 3:15:01 来源:云帆数科 栏目:资讯中心
Texture 手动布局完全指南:calculateSizeThatFits 与 layout 的实战用法
移动开发UI组件【免费下载链接】TextureSmooth asynchronous user interfaces for iOS apps.项目地址https://gitcode.com/gh_mirrors/te/Texture点击查看免费下载TextureAsyncDisplayKit以自动布局Layout Specs闻名但并非所有场景都适合用它。当你的节点层级复杂、需要精确控制子节点坐标或希望保留 UIKit 时代的手写布局习惯时Texture 依然提供了完整的手动布局Manual Layout能力。本文以 layout2-manual-layout.md 为主线从 UIKit 传统布局的痛点出发逐步讲解 Texture 手动布局的核心方法calculateSizeThatFits:与layout的用法、线程模型与缓存机制并结合源码深入剖析其背后的测量缓存原理。一、为什么还要手动布局UIKit 布局方式的两大痛点在进入 Texture 的 API 之前先回顾 UIKit 中自定义视图的经典做法。一个同时容纳文本视图与图片视图的自定义UIView通常需要实现两个方法-sizeThatFits:计算视图在给定尺寸约束下应有的内容尺寸-layoutSubviews按计算好的尺寸为子视图设置 frame。一个典型的实现如下Objective-C- (CGSize)sizeThatFits:(CGSize)size { // size the image CGSize imageSize [_imageView sizeThatFits:size]; // size the text view CGSize maxTextSize CGSizeMake(size.width - imageSize.width, size.height); CGSize textSize [_textView sizeThatFits:maxTextSize]; // make sure everything fits CGFloat minHeight MAX(imageSize.height, textSize.height); return CGSizeMake(size.width, minHeight); } - (void)layoutSubviews { CGSize size self.bounds.size; // convenience // size and layout the image CGSize imageSize [_imageView sizeThatFits:size]; _imageView.frame CGRectMake(size.width - imageSize.width, 0.0f, imageSize.width, imageSize.height); // size and layout the text view CGSize maxTextSize CGSizeMake(size.width - imageSize.width, size.height); CGSize textSize [_textView sizeThatFits:maxTextSize]; _textView.frame (CGRect){ CGPointZero, textSize }; }这段代码存在两个明显问题1. 重复测量double sizing子视图在sizeThatFits:中测了一次在layoutSubviews中又测了一次。虽然这里的算术足够简单廉价但一旦换成文本排版这类昂贵操作代价会被放大。2. 主线程阻塞sizeThatFits:与layoutSubviews都运行在主线程。而 UILabel、UITextView 的尺寸计算成本很高它们会拖慢滚动帧率、造成卡顿。直观的改进方案是缓存子视图尺寸比如引入_imageSize、_textSize两个 ivar。但正如原文档指出的缓存方案很快会失控——文本内容一旦变化就必须重新计算尺寸随之而来的是大量样板代码与状态同步负担。更激进的方案是借助dispatch_async()把测量搬到后台线程。然而 UIKit 官方文档明确要求视图操作必须发生在主线程Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the UIView class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself but all other manipulations should occur on the main thread.于是有人尝试绕开 UILabel / UITextView手工搭建 TextKit 排版栈在后台完成测量。这几乎是把系统排版引擎重写一遍——工作量巨大且一旦 iOS 更新改变 UITextView 的排版行为这些手工代码就会失效。更何况TextKit 本身也不是线程安全的。这正是 Texture 手动布局 API 存在的理由。二、Texture 手动布局的核心两个方法两套线程Texture 手动布局由ASDisplayNode的两个可覆写方法构成二者分工明确分别运行在不同线程上。calculateSizeThatFits:后台线程完成昂贵测量- [ASDisplayNode calculateSizeThatFits:]这是测量阶段。子类需要基于传入的constrainedSize约束尺寸返回节点应有的固有内容尺寸。该方法在后台线程调用因此适合在其中执行昂贵的尺寸计算——这正是解决 UIKit 主线程阻塞痛点的关键。从源码注释可以确认这一点。ASDisplayNodeSubclasses.h 中明确说明Subclasses that override should expect this method to be called on a non-main thread.并且源码特别强调覆写该方法即意味着节点承诺使用手动布局因此必须同时覆写-layout来布局所有子节点或子视图。同时该方法不应被外部直接调用测量入口应使用-layoutThatFits:或-layoutThatFits:parentSize:。layout主线程只做搬运工作- [ASDisplayNode layout]这是布局阶段由视图的-layoutSubviews触发在主线程调用。按照设计意图这里应当只做最少量的工作——利用测量阶段缓存的尺寸为各个子节点设置 frame不再执行任何昂贵的测量。在 ASDisplayNodeSubclasses.h 中-layout被描述为由视图的 layoutSubviews 在主线程调用子类覆写它以布局所有子节点或子视图。原文档还补充说明在测量与布局之后可以在layout中对那些不参与自动布局系统、未被layoutSpecThatFits:引用的节点做进一步布局。线程模型小结方法运行线程职责成本calculateSizeThatFits:后台线程测量子节点、缓存中间结果昂贵操作都放这里layout主线程利用缓存尺寸设置 frame尽量轻量这套分工与 UIKit 的最大区别在于测量不再占用主线程且测量结果被缓存layout阶段可以直接复用。三、完整示例手动布局一个图片 文本节点原文档给出了一个完整的 Objective-C 示例将第一节的 UIKit 视图改写为 Texture 节点。需要引入子类扩展头文件#import AsyncDisplayKit/AsyncDisplayKitSubclasses.h节点实现如下// perform expensive sizing operations on a background thread - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { // size the image CGSize imageSize [_imageNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size; // size the text node CGSize maxTextSize CGSizeMake(constrainedSize.width - imageSize.width, constrainedSize.height); CGSize textSize [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, maxTextSize)].size; // make sure everything fits CGFloat minHeight MAX(imageSize.height, textSize.height); return CGSizeMake(constrainedSize.width, minHeight); } // do as little work as possible in main-thread layout - (void)layout { // layout the image using its cached size CGSize imageSize _imageNode.calculatedSize; _imageNode.frame CGRectMake(self.bounds.size.width - imageSize.width, 0.0f, imageSize.width, imageSize.height); // layout the text view using its cached size CGSize textSize _textNode.calculatedSize; _textNode.frame (CGRect){ CGPointZero, textSize }; }逐段拆解这段代码测量阶段后台线程通过ASSizeRangeMake(CGSizeZero, constrainedSize)构造一个从零到约束尺寸的ASSizeRange交给_imageNode layoutThatFits:用剩余宽度作为文本节点的最大约束再次调用layoutThatFits:取两者高度最大值作为整节点高度返回。布局阶段主线程直接读取_imageNode.calculatedSize与_textNode.calculatedSize——这些尺寸已经在测量阶段缓存无需再次计算镜像位于右侧文本占据左侧剩余空间与 UIKit 版本布局完全一致。关于ASSizeRange它由min与max两个CGSize组成见 ASLayout.h 与 ASLayoutElement.h用于描述至少多大、至多多大的约束区间。ASSizeRangeMake(CGSizeZero, constrainedSize)表示允许尺寸从 0 到约束上限之间自由取值。四、layoutThatFits: 与 calculatedSize测量缓存的底层原理-layoutThatFits:是理解手动布局的关键。它类似于-sizeThatFits:但带有副作用它会缓存测量结果供后续快速访问。这正是-layout可以瞬间完成的原因。缓存结果通过两个只读属性暴露calculatedSizeCGSize最近一次测量得到的尺寸calculatedLayoutASLayout最近一次测量得到的完整布局对象它对手动布局的节点包装了calculateSizeThatFits:返回的尺寸。在 ASDisplayNodeSubclasses.h 中对calculatedLayout的说明值得细读只有对节点调用过-layoutThatFits:calculatedLayout才会被设置手动布局模式下你必须在-calculateSizeThatFits:的实现里对子节点调用-layoutThatFits:对于使用自动布局layoutSpecThatFits:的节点ASLayoutSpec 会自动对所有子节点调用-layoutThatFits:基类的-layout实现也会自动利用子节点的calculatedLayout通常无需手动访问该属性。所以对图片 文本示例而言-layout中读取的_imageNode.calculatedSize、_textNode.calculatedSize正是测量阶段-layoutThatFits:写入缓存的成果。缓存触发条件的细节-layoutThatFits:机制只有在需要新的测量时才会真正调用calculateSizeThatFits:。原文档明确列举了两个条件约束尺寸constrained size发生变化节点没有实现layoutSpecThatFits:即处于手动布局模式。这一点在源码层面也有印证ASDisplayNode.mm 中calculateLayoutThatFits:restrictedToSize:relativeToParentSize:会先解析节点的style.size与父尺寸将结果与传入约束求交集ASSizeRangeIntersect再分发到实际的计算方法。而 ASDisplayNodeSubclasses.h 则说明默认的calculateLayoutThatFits:会依据子类覆写情况调用layoutSpecThatFits:或calculateSizeThatFits:二者之一。从源码结构看缓存机制由布局版本号layout version驱动ASDisplayNode.mm 中invalidateCalculatedLayout会递增_layoutVersion、清空_unflattenedLayout从而标记旧缓存失效下一次测量才会重新计算。五、失效与缓存维护invalidateCalculatedSize 的三种场景手动布局节点在测量阶段缓存了尺寸随之而来的问题是内容变了怎么办原文档给出的答案是Nodes should call[self invalidateCalculatedSize]when necessary并列举了三条编写手动布局节点时必须牢记的准则1. 递归测量所有子节点calculateSizeThatFits:必须递归地测量全部子节点通过-layoutThatFits:完成。如上文所述-layoutThatFits:机制只会在需要新测量时约束变化、未实现layoutSpecThatFits:才调用-calculateSizeThatFits:。2. 昂贵预计算放入测量阶段缓存中间结果任何其他昂贵的预布局计算都应放在-calculateSizeThatFits:中完成并把有用的中间结果缓存在 ivar 里供-layout阶段复用。3. 必要时调用失效方法当节点内容发生变化、旧尺寸不再有效时必须主动使缓存失效。原文档举例ASTextNode在其attributedString属性改变时会自动失效计算尺寸。需要指出的是源码中的实际方法名是-invalidateCalculatedLayout见 ASDisplayNodeSubclasses.h 的声明原文档写作invalidateCalculatedSize是早期版本的命名。两者含义相同使节点此前测量并缓存的布局失效强制下一次测量重新计算。其实现位于 ASDisplayNode.mm核心动作是递增布局版本号并清空未扁平化布局缓存。另外 ASDisplayNode.mm 显示setNeedsLayout内部也会调用invalidateCalculatedLayout。仓库中真实调用失效方法的例子可参考 ASEditableTextNode.mm当UITextView的文本内容变化textViewDidChange:时会调用[self invalidateCalculatedLayout]注释明确写着Invalidate, as our calculated size depends on the textviews seeded text——这正是原文档所述准则在真实组件中的落地。六、手动布局 vs 自动布局何时选择哪一种原文档在结尾明确给出立场自动布局优先应作为大多数场景的首选手动布局仅在特殊需求下使用。结合仓库源码可以补充两者的边界自动布局Layout Specs实现layoutSpecThatFits:或提供layoutSpecBlock声明式描述布局。ASLayoutSpec 自动测量子节点、基类-layout自动利用缓存布局你几乎不需要手写 frame。ASDisplayNodeSubclasses.h 明确指出自动布局节点通常无需访问calculatedLayout。手动布局Manual Layout覆写calculateSizeThatFits:与layout命令式地测量与摆放。适合精确控制坐标、需要复用既有 UIKit 布局思路、或对布局过程有特殊要求的场景。值得注意的约束子类必须在layoutSpecThatFits:与calculateSizeThatFits:中至多覆写其一。源码中有一处断言会检查这一点见 ASDisplayNodeSubclasses.h若子类两种方法都覆写会触发如下断言提示Subclass % must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits:, layoutSpecThatFits:, or calculateSizeThatFits:这意味着手动布局 自动布局两种模式是互斥的选择一种就要贯彻到底。七、总结Texture 的手动布局把 UIKit 的测量 布局两段式流程提升到了新的层次calculateSizeThatFits:在后台线程执行昂贵的测量彻底摆脱主线程阻塞layout在主线程只做最轻量的 frame 设置layoutThatFits:缓存测量结果calculatedSize/calculatedLayout让布局阶段零成本复用内容变化时通过invalidateCalculatedLayout主动失效缓存保证尺寸始终新鲜。这套 API 与自动布局Layout Specs共用同一套测量基础设施只不过把声明式描述换成了命令式计算。原文档的立场依然成立能自动布局就自动布局但当你需要精确控制、或者手头有一段成熟的 UIKit 布局逻辑需要迁移时手动布局是一条完全可行、且线程安全的后路。赞分享移动开发UI组件【免费下载链接】TextureSmooth asynchronous user interfaces for iOS apps.项目地址https://gitcode.com/gh_mirrors/te/Texture点击查看免费下载相关推荐G6 布局 API 完全指南setLayout / getLayout / layout / stopLayout 与布局配置实战G6 布局 API 完全指南setLayout / getLayout / layout / stopLayout 与布局配置实战 G6 antv/g6数据可视化前端图表库G6 布局 API 完全指南setLayout / getLayout / layout / stopLayout 与组合布局实战G6 布局 API 完全指南setLayout / getLayout / layout / stopLayout 与组合布局实战 导读 本文围绕 G6An数据可视化前端图表库Vuetify 应用布局系统Application Layout完全指南outside-in 原理、组件排序与动态布局实战Vuetify 应用布局系统Application Layout完全指南outside in 原理、组件排序与动态布局实战 Vuetify 内置了一套名为前端UI组件上一篇Beads 的 bd human 命令实战指南让人工介入点一目了然下一篇5分钟跑通eSearch离线OCR古籍竖排文字识别新手教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

Testing Classification实战复盘 多标签文本分类从基线建模到提交流程
Testing Classification实战复盘 多标签文本分类从基线建模到提交流程

这道 Kaggle 练习赛虽然题面极简,但任务指向很明确,核心是围绕多标签文本分类搭建一条完整可运行的建模链路。真正有价值的部分不在排行榜,而在于把文本字段、标签矩阵、验证方式、预测输出和提交格式衔接起来,形成可复用的分类原型。 从技术实战角度看,这类小规模赛题很… · 2026/9/26 3:15:01

医学影像多标签文本分类实战解析与 Kaggle 入门建模
医学影像多标签文本分类实战解析与 Kaggle 入门建模

这道 Kaggle 案例虽然平台元数据极少,但任务形态很适合作为多标签文本分类的完整练习。文章重点不放在题面信息本身,而是放在如何从稀缺说明中还原任务结构,识别文本字段与标签组织方式,并建立可提交、可验证、可迭代的分类流程。 多标签文本分类在医疗场景并不只是竞赛练… · 2026/9/26 3:15:01

2026知识付费工具横评:小鹅通、知识星球、小报童与小卖部,谁才是你的“变现搭子”?
2026知识付费工具横评:小鹅通、知识星球、小报童与小卖部,谁才是你的“变现搭子”?

知识付费工具横评:小鹅通、知识星球、小报童与小卖部,谁才是你的“变现搭子”? 做了五年自媒体商业观察,我测评过不下二十款知识付费工具。最近后台总有创作者问:“小鹅通太贵,知识星球太封闭,小… · 2026/9/26 3:14:55

优质skills推荐:用 Planning with Files 给 Claude Code 装上“记忆“,复刻 Manus 式 AI 代理
优质skills推荐:用 Planning with Files 给 Claude Code 装上“记忆“,复刻 Manus 式 AI 代理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:59:04

EchoIsland 桌面灵动岛工具:用 Tauri + Rust 给开发者做一个常驻状态栏
EchoIsland 桌面灵动岛工具:用 Tauri + Rust 给开发者做一个常驻状态栏

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:59:04

LangChain学习笔记:用TaoToken统一Key跑通Chain与Agent配置
LangChain学习笔记:用TaoToken统一Key跑通Chain与Agent配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:59:04

wfrest 01_basic 实战:用 TaoToken 统一 Key 跑通第一个 C++ HTTP 服务器
wfrest 01_basic 实战:用 TaoToken 统一 Key 跑通第一个 C++ HTTP 服务器

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:59:04

OpenManus实战:基于Ollama+qwen2.5搭建openmanus的TaoToken配置指南
OpenManus实战:基于Ollama+qwen2.5搭建openmanus的TaoToken配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:59:04

企业大数据应用20年演进:架构选型、治理与落地实践
企业大数据应用20年演进:架构选型、治理与落地实践

在2001年刚入行那会儿,我在一家传统企业做数据库管理员,每天的主要工作是写SQL、搞ETL、调报表。那会儿还没有“大数据”这个词,我们叫它“海量数据”,最头疼的是存储和IO瓶颈。到了2023年,大数据技术已经发展成完整的… · 2026/9/26 3:58:58

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码