Firefox for iOS 组件库(ComponentLibrary)完全指南:从入门、组件规范到新增组件实战
2026/9/15 18:54:41 网站建设 项目流程

Firefox for iOS 组件库(ComponentLibrary)完全指南:从入门、组件规范到新增组件实战

【免费下载链接】firefox-iosFirefox for iOS项目地址: https://gitcode.com/GitHub_Trending/fi/firefox-ios

ComponentLibrary 是 Firefox for iOS 仓库中用于统一 UI 元素构建方式的 Swift 组件库,目标是消除长期积累的"UX 技术债",让开发者在多个页面间复用设计一致、可无障碍访问、支持动态字体与主题切换的标准组件。读完本文,你将掌握 ComponentLibrary 的定位与分类体系、通用组件与构建块的逐个用法(含视图模型配置项)、新增组件的完整流程与九条编码规范,以及如何通过 SampleComponentLibraryApp 示例工程快速验证组件效果。

背景:为什么 Firefox for iOS 需要一套组件库

Firefox for iOS 是一个大型应用,随着时间推移,不同页面上的 UI 元素在观感上逐渐出现差异,形成了"UX 技术债"。逐视图修复这些差异需要大量重复开发时间。组件库(Component Library)正是为解决这一问题而生的:

  • 开发者可以直接取用预构建组件,减少从零创建 UI 元素的时间和精力;
  • 组件与设计侧的 Figma components 和 Acorn(Firefox 的跨平台设计系统)保持一致,让开发者与设计师拥有"共同语言",提升协作效率;
  • 通过对照 Android 端的 Fenix 组件(firefox-android 仓库中的 Compose 组件),保证各平台外观与行为对齐;若存在分歧,应由相关方讨论后达成一致。

这套库以 Swift Package 形式存在于仓库中,源码位于 BrowserKit/Sources/ComponentLibrary,其目录结构按组件类型组织(Buttons、BottomSheet、Headers、ContextualHintView、SwiftUI 等子目录)。它同时被主应用(firefox-ios/Client)与示例应用(SampleComponentLibraryApp)依赖。

什么是组件:三类组件的划分标准

组件(UI Component)是应用中可以多处复用的 UI 片段,通常由设计师定义,命名应沿用 Figma 或 Acorn 中的组件名。从开发者视角,组件库将组件划分为三个通用类别:

通用组件(General components)

不一定是严格意义上的"UX 组件",而是开发者需要的可复用构建单元。例如:作为某个类型UITableViewcell 的基类,供多个功能复用;或者一个被多个功能组件复用的CardView。这类组件的定位是"底层可复用单元",随着库中示例增多会持续扩充。

功能组件(Feature components)

完全由设计师定义的组件,可能依赖部分通用组件。仅服务于单一功能的组件,应随功能代码一起存放,而不是放进组件库。例如"Jump back in"(跳回)cell,即使其底层复用了通用组件,由于只在首页(Homepage)使用,就应该放在首页代码中;只有当该 cell 之后被多个功能复用时,才把它移入组件库。

构建块(Building blocks)

供开发者用来支撑通用组件或功能组件的类,它们未必与 UI 直接相关,但常常是让代码组织更优雅的必要结构。例如允许把闭包当作按钮 action 的ActionButton、支持 Dynamic Type 尺寸自适应的ResizableButton、在滚动时提示更多内容的FadeScrollView

快速上手:导入组件库并运行示例应用

组件库是一个可通过 Swift Package Manager 导入的 Swift package。在你的工程中把它添加为依赖即可开始使用。入门说明详见 GettingStarted。

仓库内提供了一个示例应用 SampleComponentLibraryApp,用于演示如何导入与使用库中的各个组件,其中每个组件在 SampleComponentLibraryApp/SampleComponentLibraryApp 下都有独立的示例页面目录(如 BottomSheet、Buttons、CollapsibleCardView、Headers、ShadowCardView 等),由 RootViewController.swift 统一组织入口。注意:打开 Sample 应用前,请先关闭 Client 主应用(两者都依赖 BrowserKit,同时打开会冲突)。

使用体验建议:

  1. 用 Xcode 打开 SampleComponentLibraryApp.xcodeproj;
  2. 运行后在列表中逐个查看组件示例;
  3. 切换 Light / Dark 主题、放大动态字体,验证组件的主题适配与 Dynamic Type 表现(这也是示例应用的核心价值之一,后续可作为截图测试的基础)。

通用组件逐个拆解:用法与配置要点

以下通用组件在 ComponentLibrary.md 的 "General" 分区中列出,每个组件都在 General Components 文档目录 下有自己的说明页,且遵循"视图 + 视图模型(ViewModel)"的配置模式:组件的外观细节通常封装在内部,外部通过 ViewModel 注入标题、图标、无障碍标识等信息

BottomSheetViewController:底部弹出的模态面板

BottomSheetViewControllerUIViewController的子类,以 popover 形式从屏幕底部弹出的模态视图。其内容由另一个子视图控制器(child view controller)承载并嵌入其中,关闭方式支持:点击关闭按钮、下滑手势、点击面板外部区域,均可通过其视图模型BottomSheetViewModel配置。

关键设计点(来自源码):

  • 子控制器需遵循BottomSheetChild协议(实现willDismiss(),在面板即将关闭时被调用);
  • 面板可配置cornerRadius(默认 iOS 26 及以上为 24,其余为 8)、animationTransitionDuration(默认 0.3s)、animatesPresentation(是否带动画)、backgroundColorshouldDismissForTapOutside(点击外部是否关闭)、shadowOpacity(默认 0.3),以及关闭按钮的无障碍标签与标识closeButtonA11yLabel/closeButtonA11yIdentifier
  • 下滑手势关闭有阈值判断:拖拽位移 ≥ 200pt、或 ≥ 内容高度一半、或下滑速度 ≥ 700,满足其一即关闭,否则弹性回弹(BottomSheetViewController.swift);
  • 面板顶部内容使用FadeScrollView承载,内容超高时可滚动;
  • iOS 26 及以上使用UIGlassEffect玻璃材质效果,并支持主题切换时更新色调。

CardView 与 ShadowCardView:内容容器

CardViewUIView的子类,用于承载不同类型的内容。通过CardViewModel配置内容、无障碍标识与背景色。圆角与内边距是设计定死的,不应调整,按原样使用

需要阴影的卡片请使用ShadowCardView:同样通过ShadowCardViewModel(传入viewa11yId)配置,其内部固定了圆角(8)、阴影半径(14)、阴影偏移(0, 2)等 UX 常量,并在layoutSubviews中依据圆角路径重算shadowPath(ShadowCardView.swift),主题切换时根据 Nova 设计切换layer2/layer4背景色(ShadowCardView.swift)。

CollapsibleCardView:可折叠卡片

CollapsibleCardViewShadowCardView的子类,点击后展开/收起内容。通过CollapsibleCardViewModel配置:

  • contentView:展开后显示的内容视图;
  • title/titleA11yId:标题及其无障碍标识;
  • expandButtonA11yId与展开/收起两种状态的无障碍标签expandButtonA11yLabelExpand/expandButtonA11yLabelCollapse(收起时自动读 "展开"、展开时读 "收起");
  • expandState:初始状态(默认.collapsed);
  • telemetryCallback:展开状态变化时的遥测回调。

交互上,点击标题区域(UITapGestureRecognizer)或点击右侧展开按钮(chevronUp/chevronDown图标,来自StandardImageIdentifiers)都会触发状态切换,并通过UIAccessibility.post(.layoutChanged)通知辅助功能(CollapsibleCardView.swift)。注意:它重写了configure(_ viewModel: ShadowCardViewModel)并直接fatalError,明确要求使用CollapsibleCardViewModel完成配置。

ContextualHintView:带箭头的提示气泡

ContextualHintViewUIView的子类,本质是会被装入带箭头 popover 的内容。通过ContextualHintViewModel配置箭头方向、标题、无障碍标识与点击行为。使用时通常:

  • viewDidLayoutSubviews中设置preferredContentSize
  • 设置popoverPresentationController的 delegate 与 source view。

HeaderView 与 NavigationHeaderView:面板头部

  • HeaderViewUIView子类,用于底部面板等场景的头部,展示标题、副标题、favicon、主按钮与关闭按钮,支持无障碍与主题定制,适合网页信息、用户资料等"标题 + 副标题 + 动作按钮"的展示;
  • NavigationHeaderView:同样为UIView子类,提供标题、返回按钮与关闭按钮,适合菜单或多级模态视图内的导航。

两者源码位于 Headers。

CloseButton、LinkButton、PrimaryRoundedButton、SecondaryRoundedButton:按钮家族

  • CloseButtonUIButton子类,用于关闭视图(如底部面板)。通过CloseButtonViewModel配置标题、字体与无障碍标识,按钮尺寸不应调整,按原样使用
  • LinkButtonUIButton子类,用于类似网页链接的操作,通过LinkButtonViewModel配置标题、字体与无障碍标识,颜色与间距保持默认;
  • PrimaryRoundedButton:主操作按钮,ResizableButton子类并遵循ThemeApplicable。源码中可见:使用UIButton.Configuration.filled(),圆角 iOS 26 及以上 32、其余 12,垂直内边距 12、水平内边距 16;configure(viewModel:)中注入标题、无障碍标识与可选的imageTitlePadding(PrimaryRoundedButton.swift)。状态色完全由主题驱动:正常/高亮/禁用分别取actionPrimaryactionPrimaryHoveractionPrimaryDisabled,前景色取textInverted系列(PrimaryRoundedButton.swift);
  • SecondaryRoundedButton:次级操作按钮,使用方式与主按钮一致,通过SecondaryRoundedButtonViewModel配置。颜色、圆角、间距同样保持默认。

视图模型示例(以 PrimaryRoundedButtonViewModel 为例):

let viewModel = PrimaryRoundedButtonViewModel( title: "保存", a11yIdentifier: "saveButton", imageTitlePadding: 8 // 可选:图标与标题的间距 ) button.configure(viewModel: viewModel)

PaddedSwitch:开关控件

PaddedSwitchUIView子类,内部包含一个ThemedSwitch,为该开关增加了宽度方向的内边距。通过PaddedSwitchViewModel配置。

其他相关组件

文档目录中还包含 ActionFooterView(标题 + 链接按钮形式的底部信息区)与 RoundedButtonWithImage;此外 SwiftUI 目录下还有一批 SwiftUI 实现(PrimaryButtonStyleSecondaryButtonStylePagingCarouselAttributedLinkText等),供 SwiftUI 场景复用同一套设计语言。

构建块拆解:开发者侧的支撑类

ActionButton:以闭包代替 selector

ActionButtonResizableButton的子类,唯一目的是让按钮动作可以用闭包表达,比 selector 更便捷。通过ActionButtonViewModel注入titletouchUpAction闭包、内边距与无障碍标识;内部通过addTarget(_:action:for:)桥接到touchUpInside(sender:)再回调闭包(ActionButton.swift)。

ResizableButton:Dynamic Type 自适应按钮

ResizableButtonUIButton的子类,启用动态字体的尺寸自适应:它重写intrinsicContentSize,依据标题在当前可用宽度下的排版结果计算按钮固有尺寸,并在layoutSubviews中同步preferredMaxLayoutWidth使标题自动换行(ResizableButton.swift)。buttonEdgeSpacing(默认水平 8、垂直 0)不应随意调整。注意:这是纯开发用途的构建块,并非设计组件;设计师定义的按钮请看PrimaryRoundedButton/SecondaryRoundedButton

FadeScrollView:滚动渐隐提示

FadeScrollViewUIScrollView的子类,通过CAGradientLayer在顶部/底部添加渐隐遮罩:当内容未超出视口或已滚到顶/底时遮罩透明(透明度 1),反之对应区域渐隐(透明度 0),提示用户"还有更多内容"(FadeScrollView.swift)。它对 Dynamic Type 尤其有价值——大字号下内容常常溢出,此控件能在不配置任何参数的情况下直接作为普通滚动视图使用。BottomSheet 的内容区正是复用了它。

如何新增一个组件:完整流程与九条规范

新增组件分为两大步:先把组件加入BrowserKit中的组件库 package,再把示例加入 Sample 应用。

第一步:判断是否值得进入组件库

如果 UI 组件只服务于单个功能,就让它留在功能代码中;如果其中有可复用的 UI 元素(某种特定容器、某种特定按钮),才值得移入组件库。若存在歧义,应在 #firefox-ios-dev Slack 频道或周工程例会上与团队讨论确认。

第二步:在 ComponentLibrary 中添加组件

将代码放到 BrowserKit/Sources/ComponentLibrary 下的相应文件夹(Buttons、Headers、BottomSheet 等),并严格遵守以下规范:

  1. 图片标识必须来自StandardImageIndentifiers(见 StandardImageIdentifiers 相关定义)。如果图片不是标准的,那它大概率是功能组件,不该放进组件库;
  2. **无障碍标识(a11y identifiers)**应由 Client 应用注入并可自定义;组件被复用时每个标识必须唯一,注入通过视图模型完成;
  3. 本地化字符串与无障碍标签应由 Client 应用注入(翻译资源在 Client 侧),注入同样通过视图模型完成;
  4. 优先提供configure方法而非暴露公共属性,形成统一的使用方式,让代码更易维护和理解;
  5. 必须良好适配 Dynamic Type:组件应能随内容尺寸动态调整自身大小,任何情况下都不能使用固定高度;四向(top、bottom、trailing、leading)都应做约束;若使用居中约束,需确认确实必要且工作正常(仅居中会导致适配异常);
  6. 必须良好适配 RTL 语言:使用 trailing/leading 约束,需要时对图片做镜像翻转(参考 UIKit 的imageFlippedForRightToLeftLayoutDirection);
  7. 必须良好适配 VoiceOver:为图片注入可朗读标识,保证用户听到的上下文正确;约束要做对,使 VoiceOver 高亮的区域与实际朗读内容一致;
  8. 必须遵循ThemeApplicable,能正确响应主题管理器(ThemeManager)的切换——主题色通过applyTheme(theme:)注入(如PrimaryRoundedButtontheme.colors.actionPrimary的用法);
  9. 必须补充文档:组件要登记在 ComponentLibrary.md 落地页的 Topics 列表中,并拥有自己的独立文档页(参考 General Components 与 Building Blocks 目录下的写法)。

第三步:在 Sample 应用中添加示例

提示:打开 Sample app xcodeproj 前需关闭 Client 主应用,才能在 Sample 应用中正常浏览BrowserKit

为组件添加示例是强制步骤,用于持续跟踪组件、演示用法并保持其相关性,未来还可基于示例应用开展截图测试(不同设备、不同字号、不同主题下的外观回归)。具体步骤:

  1. 新增一个展示该组件的UIViewController,约束要保证 Dynamic Type 正常工作;控制器需遵循ThemeablelistenForThemeChange,确保组件主题化;若组件有多个状态,示例控制器要展示全部状态(可放置多个组件实例);
  2. 新增一个遵循ComponentViewModel协议的组件视图模型(协议与示例结构见 SampleComponentLibraryApp/SampleComponentLibraryApp/Protocol);
  3. 将新视图模型加入ComponentData数据数组(组织入口见 RootViewController.swift);
  4. 完成!随后即可按组件规范测试无障碍与主题切换表现。

小结与建议

ComponentLibrary 通过"通用组件 / 功能组件 / 构建块"三层分类和"视图 + 视图模型 + 主题 + 无障碍 + Dynamic Type + RTL"六位一体的约束,把 Firefox for iOS 的 UI 建设从"各自为政"收敛为"一套设计语言"。开发者实践中应注意:

  • 复用优先:按钮、卡片、头部等通用元素一律从库中取用,不要重复造轮子;
  • 注入而非硬编码:文案、无障碍标识通过 ViewModel 从 Client 注入,保证唯一性与本地化;
  • 示例先行:新增组件必须同步补 Sample 示例与文档,保持库的可维护性;
  • 多端对齐:与 Figma / Acorn 设计稿及 Android 端组件对照,发现不一致时及时推动对齐讨论。

相关材料可进一步阅读:GettingStarted、HowToAddNewComponent、组件源码目录 BrowserKit/Sources/ComponentLibrary 及示例工程 SampleComponentLibraryApp。

【免费下载链接】firefox-iosFirefox for iOS项目地址: https://gitcode.com/GitHub_Trending/fi/firefox-ios

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询