96SEO 2026-08-03 15:00 4
使用者痛点:在建立可复用的 SwiftUI 视图时开发者往往要面对 API 与程序原生组件不统一、学习成本高、交互行为不一致等问题。话说回来,

在建立可复用的 SwiftUI 视图时我们面临的主要挑战是:如何让自定义视图的 API 尽可能接近 Apple 原生视图的 API? 这缩小了自定义视图与原生视图之间的“距离”。开发者无需学习新的交互模式,就能快速上手使用。
这篇文章从组件分类、语义建模、API 设计、自定义机制到预览策略,全面剖析一个可复用 SwiftUI 视图的诞生过程。
使用者痛点:混淆两类组件导致 API 过度膨胀或缺少必要定制能力。
在动手实现之前,先将组件分为两类。这决定了后续的自定义策略:
语义组件描述的是一个概念而非固定的视觉形态。说到例如,
Button 描述了“可以被按下来执行动作”的概念,但没有预定义的 UIToggle 描述了“启用/禁用某物”的概念。视觉表现完全开放策略:对于语义组件,几乎总是提供自定义样式的能力,因为它的视觉表现不应被限定。其实,
规定性组件已经创造了视觉预期。按理说,至于例如,
BarChart 组件尽管可以自定义颜色、间距等。但渲染时可能会显示多个柱状条tag 组件始终会渲染一个胶囊形状的标签策略:对于视觉较简单的规定性组件,通常不允许自定义样式,而是通过环境值提供外观微调。
User Pain Point: 缺乏明确业务需求导致 UI 实现反复迭代。以 Rating 评分组件为例,在考虑星星怎么画之前。先回答这些问题:
This is a classic Swift API design principle. If multiple values always go toger or must satisfy invariants,introduce dedicated model types instead of scattering unrelated parameters.
// ❌ Bad design: many loose parameters
Rating
// ✅ Good design: use ClosedRange for range and Binding for editability
Rating
The naming conventions used by system components—a selectionvalue\`label\`etc—are already well understood.
// ✅ Use system‑familiar naming
Rating
Rating }。value: $rating)
// ❌ Invent custom words → higher cognitive load
Rating
BindingThis separation keeps public APIs small while allowing internal implementation freedom.
// Public state – exposed via binding
@Binding var value : Double
// Public configuration – immutable
let range : ClosedRange
// Private state – internal only
@State private var isHighlighted : Bool
@State private var isPressed : Bool
选中评分属于公共 API;
老实说,星星是否被按下/悬停/动画是内部实现细节,不应泄露给调用方。
优先组合而非配置标志
Avoid flag explosion that leads to unclear combinatorial possibilities.
// ❌ Flag bloat
Tag
// ✅ Composition via ViewBuilder
Tag // default simple tag
Tag // with icon
渐进式披露
Keep common path short while still offering advanced overloads.
// Simple most‑common usage
Rating
// With title
Rating
// Fully custom label
Rating},value:$rating)
四、保留原生行为
优先使用原生控件而非原始手势
// ❌ Raw gesture – loses keyboard focus,disabled state。accessibility etc.
Image
.onTapGesture{ selectRating }
// ✅ Button – gets all system behaviours automatically
Button{
selectRating
} label:{
Image
}
automatically participates in focus handling,disabled state propagation and VoiceOver actions.
无障碍是 API 的一部分
Expose appropriate accessibility labels,values and adjustable actions directly on component.
accessibilityElement
.accessibilityValue)
.accessibilityAdjustableAction{ direction in
switch direction {
case .increment:
value = min
case .decrement:
value = max
@unknown default:
break
}
}
关键:无障碍行为属于 Rating 本身,而不是各个样式实现。
尊重周围环境
-
isEnabled — disabled state
-
controlSize — size hint
-
dynamicTypeSize — Dynamic Type support
-
layoutDirection — LTR/RTL awareness
-
tint — me colour
-
accessibilityReduceMotion — respect motion reduction
-
accessibilityDifferentiateWithoutColor — colour‑blind support
五、API 设计实践
容器视图初始化器设计
Start with most flexible initializer that accepts @ViewBuilder closures.
// Most flexible base initializer
public struct Card : View {
let header : Header
let content : Content
public init(
@ViewBuilder header : -> Header = { EmptyView }。@ViewBuilder content : -> Content ) {
self.header = header
self.content = content
}
public var body : some View {
VStack{
header
content
}
}
}
// Usage
Card{
Text
} content:{
Text
}
渐进式披露这方面,便利初始化器
// Image‑only header shortcut
extension Card where Header == Image {
public init(image:Image,@ViewBuilder content: -> Content ){
self.header = image
self.content = content
}
}
// Full featured header with title
extension Card where Header == ProminentCardHeader {
public init(image:Image,title:LocalizedStringResource,@ViewBuilder content: -> Content){
self.header = ProminentCardHeader
self.content = content
}
}
Three call styles from simple to fully custom illustrate progressive disclosure.
非容器视图初始化器设计
public struct Tag : View {
private let title : LocalizedStringResource
private let image : Image?
// Plain text tag
public init{
self.title = title;按理说,self.image = nil
}
// With SF Symbol
public init(_ title : LocalizedStringResource。systemImage:String){
self.title = title;self.image = Image
}
// Type‑safe custom asset
public init(_ title : LocalizedStringResource,image resource : ImageResource){
self.title=title;self.image=Image
}
public var body : some View {
HStack{
if let image { image }
Text
}
}
}
// Calls
Tag
Tag
Tag // type‑safe asset reference
Technical note: Using LocalizedStringResource and ImageResource aligns component with Apple’s localisation and asset pipelines.
六、自定义机制
从方案一来看,视图样式协议
The style protocol lets you swap entire visual representation while keeping a stable semantic API.
Step 1 – Configuration struct
public struct RatingStyleConfiguration{
public let value : Binding
public let range : ClosedRange
public let label : Label
public struct Label : View{
let underlyingLabel : AnyView
init{ underlyingLabel=AnyView }
var body:some View{ underlyingLabel }
}
}
Step 2 – Style protocol
public protocol RatingStyle : DynamicProperty{
associatedtype Body : View
@ViewBuilder func makeBody->Body
typealias Configuration=RatingStyleConfiguration
}
*DynamicProperty* lets style read `@Environment`。`@State`,etc.,automatically.
Step 3 – Resolution layer
struct ResolvedRatingStyle:View{
var style : Style
var configuration : RatingStyleConfiguration
var body : some View{ style.makeBody }
}
extension RatingStyle{
func resolve->some View{
ResolvedRatingStyle
}
}
The double type‑erasure boundary isolates generic `Body` from outer component while preserving identity.
Step 4 – Environment key
extension EnvironmentValues{
@Entry var ratingStyle:any RatingStyle = .star
}
extension View{
func ratingStyle->some View{
environment
}
}
Step 5 – Component definition
public struct Rating:View{
@Environment private var style
@Binding private var value : Double
private var label : LabelView
private let range : ClosedRange
public init->LabelView,value:Binding,in range:C=…){
self.label=label
_value=value;self.range=range
}
public var body:som e View{
let config=RatingStyleConfiguration(
说到label,.init,value:$value。range:range)
AnyView(style.resolve(configuration:
config))
.accessibilityElement
.accessibilityValue)
.accessibilityAdjustableAction{direction in…}
}
}
// Convenience when label is just Text
extension Rating where LabelView==Text{
public init(title:L ocalizedStringResource。value:B inding<Doub le>,in r ange:C=…){
self.init},value:value,in:rang e)
}
}
Default star style
swift
public struct StarRatingSt yle: RatingSt yle{
@Environment private var cs
func makeBody->some Vi ew{
HStack{
configuration.label
HStack{
ForEach...Int,id:\.self){r in
Button{conf.value.wrappedValue=Double}label:{
Image(systemName:
Double<=conf.value.wrappedValue?"star.fill":"star")
.foregroundSty le
}.buttonS tyle
}
}.accessibilityHidden
}
}
}
extension RatingSt yle where Self==StarRati ngSt yle{ static var star:self{Self}}
Numeric style
func makeBody->some Vie w{
HStack{
conf iguration.label
HStack{
Text(conf iguration.value.wrappedValue。format:.number.precision))
.fontWeight
Text)")
.foregroundSty le
}.opacity
}
}
Usage
swift
@State var rating=0.0
// Default star style
Rating
// Custom label + star style
Rating},value:$rating)
// Numeric representation
Rating},value:$rating).ratingSty le
Environment integration: each style only reads what it needs .
再看方案二,环境值
If visual skeleton stays constant but certain aspects should be tweakable by consumer,expose those knobs as environment values.
Define an environment key
swift
extension EnvironmentValues{
@Entry var tagBackgroundSty le:?=nil
}
extension View{
func tagBackgroundStyl e->s ome View{
environment
}
}
Choosing ShapeStyle over a concrete type gives you colour gradients,materials or custom shapes.
Component consumes it
swift
public struct Tag:UIView{
@Environmentprivate var bg
private let title:L ocalizedStringResource
private let image:I mage?老实说,public var body:s om e View{
HStack{
if let img=image{img}
Text
}.padding
.padding
.background
}
private var background:s om e View{
let sty le = bg.map{AnyShapeStyl e}?,AnyShapeS tyle)
return Capsule.fill
}
}
Consumer side
swift
Tag // default material
Tag.tagBackgroundSty le
Tag.tagBackgroundStyl e(
LinearGradient(colors:,startPoint:.bottom,endPoint:.top))
Priority rule: nearest modifier wins → aligns with normal SwiftUI environment behaviour.
七、预览与测试
swift
private struct ComponentPreview:UIVew{
@State private var rating=0.0
var body:s om e Vie w{
VStack{
Rating
Rating.disabled
Tag
.tagBackgroundStyl e(
LinearGradient(colors:。startPoint:.bottom,endPoint:.top))
}.padding
}
ComponentPreview.preferredColorScheme
A reusable component should be preview‑friendly across colour schemes,Dynamic Type sizes,layout directions and localisation.
八、保持组件可预测性
-
明确优先级 :显式修饰符覆盖环境默认值,样式自行管理视觉细节。*
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback