整理并总结 SwiftUI 开发中常用的系统组件及其常用修饰符组合,作为开发参考指南。
Control 键并点击 组件/修饰符的名词,即可在 Xcode 中查看官方说明、参数细节与示例。Shift + Cmd + L),可以查看和拖动组件直接生成代码。Cmd + Option + Enter:显示 / 隐藏 Xcode Canvas 画布。Cmd + Option + P:重新编译并刷新 Preview 预览引擎。Cmd + 点击 Canvas 元素:弹窗呼出属性面板(Inspector),在面板中修改尺寸或字体时,Xcode 会瞬间自动同步生成代码修饰符。文本 (Text):
Text("Hello")
.font(.system(size: 20, weight: .bold)) // 精确字号与字重
.fontWidth(.expanded) // iOS 16+ 物理字宽拉伸 (.standard/.expanded/.condensed/.compressed)
.frame(maxWidth: .infinity, alignment: .leading) // 文本框强撑满整行并靠左对齐
// 导入第三方字体(需在 Info.plist 中声明 Fonts provided by application)
Text("Custom Font")
.font(.custom("Inter-Bold", size: 28))
// 格式化输出
Text(price, format: .currency(code: "CNY")) // 格式化为人民币货币:¥100.00
Text(score, format: .number) // 格式化为本地数值显示
图像 (Image):
// 1. 加载系统图标库 SF Symbols 与多色渲染
Image(systemName: "star.fill")
.imageScale(.large) // 调整图标尺寸比例 (.small/.medium/.large)
.foregroundStyle(.tint) // 采用当前主题色染色
.symbolRenderingMode(.multicolor) // 激活系统原生多色渲染模式
// 动态变量控制图标外观 (variableValue: 0.0 ~ 1.0)
Image(systemName: "timelapse", variableValue: 0.4)
// 2. 自定义静态资源的适配与瓦片平铺规则
Image("myImage")
.resizable() // 使图片能够缩放适应容器
.aspectRatio(contentMode: .fit) // 保持比例缩放适应容器
.frame(width: 200, height: 150)
Image("pattern")
.resizable(resizingMode: .tile) // 瓦片平铺纹理背景
进度指示器 (ProgressView):
ProgressView(value: 0.65, total: 1.0) // 进度条形态
.progressViewStyle(LinearProgressViewStyle())
.tint(.orange)
基础 Shape 与描边规范:
Shape(如 Circle、RoundedRectangle)默认会占满父容器分配的最大空间。 Shape 上色使用 .fill() 或 .foregroundStyle(),而 View 背景使用 .background()。
// 虚线描边 (StrokeStyle)
RoundedRectangle(cornerRadius: 12)
.stroke(
Color.orange,
style: StrokeStyle(lineWidth: 2, lineCap: .round, dash: [8, 4])
)
.frame(width: 200, height: 60)
骨架屏占位与远程网络图像 (Skeleton & AsyncImage):
.redacted):SwiftUI 提供了自动占位化修饰符 .redacted(reason:),会将文本与图片一键替换为灰色呼吸占位块:StatCardView()
.redacted(reason: isLoading ? .placeholder : [])
AsyncImage Phase):通过 Phase 分状态处理加载中(ProgressView)、成功(图片渲染)与失败(警告占位图):AsyncImage(url: URL(string: "https://example.com/banner.png")) { phase in
switch phase {
case .empty:
ProgressView().frame(width: 300, height: 180)
case .success(let image):
image.resizable().aspectRatio(contentMode: .fill).frame(width: 300, height: 180).clipShape(RoundedRectangle(cornerRadius: 12))
case .failure:
Image(systemName: "photo.fill").foregroundColor(.gray).frame(width: 300, height: 180).background(Color.gray.opacity(0.2)).clipShape(RoundedRectangle(cornerRadius: 12))
@unknown default:
EmptyView()
}
}
VStack & HStack (垂直与水平堆叠):
VStack(alignment: .leading, spacing: 12) { // 子视图左对齐,子视图间距 12px
Text("主标题")
Text("副标题")
}
ZStack (深度叠加) 与悬浮定位:让子视图沿 Z 轴方向堆叠,常用于制作卡片衬底和带阴影的悬浮层。
ZStack 悬浮定位与 CSS position: fixed 对比:
在 Web 中,若要将浮动操作按钮 (Floating Action Button, FAB) 悬浮在屏幕右下角,通常使用 position: fixed; bottom: 16px; right: 16px;。而在 SwiftUI 声明式布局中,没有脱离文档流的概念。所有视图遵循流式排版,悬浮通常是通过在 ZStack 中使用 Spacer 布局,将交互元素对齐到屏幕角落:
ZStack {
ScrollView {
// 主内容区域
}
VStack {
Spacer() // 将内容按需推送至底部
HStack {
Spacer() // 将内容按需推送至右侧
FloatingActionButton()
.padding(.trailing, 16)
.padding(.bottom, 16)
}
}
}
(或者可以直接在最外层容器上使用 .overlay(alignment: .bottomTrailing) { FloatingActionButton() })
Spacer (弹性占位符):在主轴方向上填满所有剩余的可用空间,将其他组件推向屏幕边缘。
网格 LazyVGrid 与 Size Class 跨端自适应 (Bento Grid):
在 Web 中,可以使用 display: grid 或 CSS 媒体查询来实现网格与响应式自适应。在 SwiftUI 中,网格是由 网格列定义(GridItem) 与 LazyVGrid 容器 共同决定的。
结合 iOS 原生的 Size Class (环境尺寸类) 机制,可以根据当前屏幕宽度(iPhone 窄屏 .compact vs. iPad 宽屏 .regular),实现响应式 Bento Grid 排版:
struct BentoGridView: View {
// 提取系统当前的水平尺寸类环境值
@Environment(\.horizontalSizeClass) private var sizeClass
// 声明双栏自适应网格定义
let columns = [
GridItem(.flexible(), spacing: 16),
GridItem(.flexible(), spacing: 16)
]
var body: some View {
Group {
if sizeClass == .regular {
// iPad 大屏设备:等宽三列横向排列
HStack(spacing: 16) {
MetricCard(title: "今日待办", value: "5")
MetricCard(title: "已完成", value: "12")
MetricCard(title: "LeetCode", value: "3")
}
} else {
// iPhone 手机端:首行突出展示,次行双卡并排展示(VStack 嵌套 HStack)
VStack(spacing: 16) {
MetricCard(title: "今日待办", value: "5") // 核心卡片全宽
HStack(spacing: 16) {
MetricCard(title: "已完成", value: "12")
MetricCard(title: "LeetCode", value: "3")
}
}
}
}
.padding(.horizontal, 16)
}
}
GridItem 提供了三种尺寸约束模式:
.fixed(size):固定像素大小。.flexible(min:max:):弹性等分排布。.adaptive(minimum:maximum:):自适应多栏。在满足设定的最小宽度下,在一行/列中自动平铺塞入尽可能多的元素。GeometryReader 比例计算与注意事项:
若需要获取父容器分配给当前视图的具体尺寸以进行比例布局,可以使用 GeometryReader:
GeometryReader { geometry in
HStack {
LeftCard().frame(width: geometry.size.width * 0.6) // 占 60% 宽度
RightCard().frame(width: geometry.size.width * 0.4) // 占 40% 宽度
}
}
注意事项:GeometryReader 会填满父视图提供的所有可用空间,这可能会影响原本的自适应排版。在现代 SwiftUI 架构中,建议优先使用 Grid、LazyVGrid 或 Size Class,仅在需要获取具体尺寸或计算滚动视差偏移的场景下使用。
按需加载 (Lazy) 与 Cell 复用机制说明:
在需要横向/纵向长滚动布局时,了解底层的渲染差异:
ScrollView + LazyVStack / LazyVGrid:具备**按需实例化(Lazy loading)**特性。仅在滚动到屏幕可见区域时才创建对应的视图节点。然而,已经滑出屏幕的节点不会被自动销毁或复用,它们依然保留在内存中。适用于页面布局定制化高、或数据量较少的卡片流场景。List:封装了 iOS 经典的 UITableView,实现了原生的 单元格复用 (Cell Reuse) 机制。滑出屏幕外的视图行会被回收并进行重绘。对于单列长列表,建议优先选用 List。List (可滚动列表) 样式控制:具备视图复用机制。
默认情况下,List 带有系统的灰底、分割线和边距(Plain/InsetGrouped 样式)。可以通过以下修饰符组合自定义样式:
List(tasks) { task in
Text(task.title)
.listRowSeparator(.hidden) // 1. 隐藏系统分割线
.listRowBackground(Color.clear) // 2. 将行背景设为透明
.listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16)) // 3. 设置自定义间距
// 列表手势:滑动操作
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
delete(task)
} label: {
Label("删除", systemImage: "trash.fill")
}
.tint(Color.Sahara.tertiary)
}
}
.listStyle(.plain) // 4. 使用平铺样式
.scrollContentBackground(.hidden) // 5. 隐藏系统默认背景
.background(Color.Sahara.background)
(注:.swipeActions 支持 allowsFullSwipe: true 划到底自动触发删除,并在滑至临界点时触发系统的触觉马达反馈。)
编程式定位:ScrollViewReader:
在声明式架构下,要实现自动定位或平滑滚动到某个特定项,可以使用 ScrollViewReader:
ScrollViewReader { proxy in
ScrollView {
VStack {
ForEach(items) { item in
CardRow(item: item)
.id(item.id) // 1. 挂载唯一标识 ID
}
}
}
.onChange(of: activeItemId) { _, newId in
// 2. 监听外部状态,执行平滑自动滚动
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
proxy.scrollTo(newId, anchor: .center) // anchor: .center 滚动到屏幕正中
}
}
}
下拉刷新与网络异步流:.refreshable:
在 iOS 15+ 中,可以直接在 List 或 ScrollView 上挂载 .refreshable,支持 Swift 异步交互(async/await):
List(items) { item in
ItemRow(item: item)
}
.refreshable {
// 下拉时系统自动显示刷新指示器,await 任务执行完毕后指示器自动收回。
await viewModel.refreshData()
}
长列表性能优化建议:
在处理长列表时,注意以下优化要求:
避免使用 \.self 作为长列表的唯一 ID 标识:List 依赖唯一 ID 进行虚拟化渲染与 Diff 计算。如果使用 \.self(以整个数据对象或字符串作为 ID),数据发生微小变化时,SwiftUI 为了对比 Diff 会对对象执行哈希对比,导致整张列表所有 Cell 重新绘制。
Identifiable 协议,并在内部声明一个轻量的唯一标识(如 let id = UUID() 或唯一的 id: Int),使系统能够高效处理增量动画。网络图片需异步加载并缓存:
避免在 Cell 渲染中同步下载图片,这会阻塞主线程导致 UI 响应卡顿。
列表内避免嵌套非 Lazy 容器:
在 List 单元格中或 ScrollView 内部直接包裹常规的 VStack / HStack(而非 LazyVStack),会导致视图初次载入时一次性实例化隐藏在屏幕外的全部子视图节点,使复用机制失效。
长列表数据预加载 (Pre-fetching):
推荐在 ForEach 渲染时,监听倒数第 N 行(如倒数第 3 行)的 .onAppear。在用户滑到底部前触发下一页数据请求,实现无限滚动体验:
ForEach(Array(viewModel.items.enumerated()), id: \.element.id) { index, item in
ItemRowView(item: item)
.onAppear {
// 预加载策略:距离底端剩余 3 条数据时,提前请求下一页
let threshold = 3
if index == viewModel.items.count - threshold {
Task {
await viewModel.fetchNextPage()
}
}
}
}
Form & Section (表单与分区):表单会自动对输入控件进行流式排版,配合 Section 实现分组视觉效果:
Form {
Section(header: Text("任务配置")) {
TextField("任务名称", text: $taskName)
Toggle("开启提醒", isOn: $isReminderEnabled)
}
}
TextField (文本输入框):
利用 .overlay() 挂载清除按钮、限制输入长度、设置键盘与完成按钮的示例:
struct CustomInputView: View {
@State private var projectName = ""
@FocusState private var isNameFocused: Bool
var body: some View {
TextField("请输入项目名称", text: $projectName)
.padding()
.background(Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: 10))
.focused($isNameFocused) // 绑定焦点
.keyboardType(.default) // 键盘类型 (.numberPad / .emailAddress / .decimalPad)
.submitLabel(.done) // 键盘右下角动作按钮样式 (.done / .search / .go)
.onSubmit {
print("用户点击了键盘右下角完成键")
}
.overlay(
// 1. 悬浮清除按钮
HStack {
Spacer()
if !projectName.isEmpty {
Button {
projectName = ""
} label: {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.gray)
}
.padding(.trailing, 12)
}
}
)
// 2. 限制最大输入长度为 10 个字符
.onChange(of: projectName) { oldValue, newValue in
if newValue.count > 10 {
projectName = String(newValue.prefix(10))
}
}
}
}
SecureField (安全输入框):对应 HTML 的 <input type="password">。它会自动对输入内容进行掩码遮蔽,并关联系统密码管理器(Keychain):
SecureField("请输入密码", text: $password)
.textContentType(.password) // 引导系统密码填充
Picker (下拉/胶囊选择器):
Picker("选择优先级", selection: $priority) {
Text("高").tag(1)
Text("中").tag(2)
Text("低").tag(3)
}
.pickerStyle(.segmented) // 分段胶囊风格
// .pickerStyle(.navigationLink) // 在 NavigationStack 中使用,点击推入二级列表页选择
DatePicker (日期/时间选择器):
DatePicker("时间选择", selection: $reminderTime, displayedComponents: .hourAndMinute) // 仅限时分选择
TextEditor (多行文本框) 样式自定义:
在 iOS 中,TextEditor 默认带有系统的背景色。从 iOS 16 开始,应用自定义卡片背景前,需通过 .scrollContentBackground(.hidden) 隐藏系统默认背景:
TextEditor(text: $description)
.scrollContentBackground(.hidden) // 必须隐藏默认背景,避免自定义背景被掩盖
.background(Color.Sahara.surfaceContainerLow)
.clipShape(RoundedRectangle(cornerRadius: 12))
焦点状态控制与软键盘收起:
在移动端表单中,TextEditor 激活时软键盘右下角按键是“换行”,可以通过焦点管理提供显式收起交互。
处理方案(@FocusState + 键盘 Toolbar):
Field 枚举统一管理输入框焦点:private enum Field: Hashable {
case title
case desc
}
@FocusState private var focusedField: Field?
TextField("输入标题", text: $title)
.focused($focusedField, equals: .title)
TextEditor(text: $description)
.focused($focusedField, equals: .desc)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("完成") {
focusedField = nil // 主动解绑焦点,收起软键盘
}
}
}
Color.Sahara.background
.ignoresSafeArea()
.onTapGesture {
focusedField = nil // 点击空白处收起键盘
}
Button (按钮点击热区设置):
.frame() 和 .border(),仅会放大外框视觉,点击边框空白区域可能不会触发点击响应。label 属性传入 Text,将 frame 与样式施加在 Text 上,使可点击区域覆盖整行宽度:Button {
print("点击生效")
} label: {
Text("确认提交")
.padding()
.frame(maxWidth: .infinity) // 可点击区域覆盖整行宽度
.background(Color.blue)
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
NavigationStack & NavigationLink (页面栈导航):
在 iOS 中,页面跳转基于堆栈(Push / Pop)的 NavigationStack:
Link 网页外部跳转与 Text 内联 Markdown 链接:
// 1. 原生 Link 控件(调用 Safari 唤起外部网页)
Link("访问官方网站", destination: URL(string: "https://example.com")!)
.font(.headline)
// 2. Text 内联 Markdown 链接
Text("请仔细阅读 [服务协议](https://example.com/terms) 与 [隐私政策](https://example.com/privacy)。")
.tint(.orange) // 修改内联链接点击高亮颜色
Toolbar 工具栏与 Placement 精准布局:
跨平台推荐使用 .topBarLeading 与 .topBarTrailing 替代废弃的 .navigationBarLeading:
NavigationStack {
Text("内容区")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("取消") { }
}
ToolbarItem(placement: .topBarTrailing) {
Button("保存") { }
}
// 底部工具栏与 Spacer 布局
ToolbarItemGroup(placement: .bottomBar) {
Button(action: {}) { Image(systemName: "folder") }
Spacer()
Button(action: {}) { Image(systemName: "trash") }
}
}
}
在移动端交互设计中,模态弹窗(Modal)与系统对话框(Dialog)用于显示特定交互流。在 SwiftUI 声明式架构下,模态呈现由状态驱动。
底层机制与 Web 对比:
- 渲染树与层级托管 (React Portal vs
.sheet):Web 中使用ReactDOM.createPortal将模态节点挂载在<body>下以逃逸 CSS 层级限制。SwiftUI 的.sheet属于系统级声明式修饰符,由系统宿主容器统一托管生命周期,原生具备向下拖拽手势阻尼(Swipe-to-Dismiss)及三维微缩缩放效果。$双向绑定指针魔法:当使用.sheet(isPresented: $isShowingSheet)或TextField("...", text: $title)时,$前缀访问的是属性包装器的projectedValue(投影属性),返回Binding<T>类型,省去手动编写onChange监听闭包。
.sheet 与气泡 .popover.sheet(isPresented:content:):卡片式模态半屏/全屏浮层。.sheet(isPresented: $isShowingAddTaskSheet) {
AddTaskSheetView()
// 控制弹窗为半屏(medium)或全屏(large)两档
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
.presentationCornerRadius(30) // 定制弹出卡片圆角
.presentationBackground(.ultraThinMaterial) // 毛玻璃背景效果
}
.popover(isPresented:content:):气泡式弹出框。在 iPhone 窄屏下默认退化为普通 Sheet,在 iPadOS 宽屏设备下,会自动渲染为指向触发源的气泡框。.popover(isPresented: $isShowingAddTaskSheet) {
AddTaskSheetView()
.frame(width: 420, height: 580) // iPad 上限制气泡框的显示尺寸
.presentationCompactAdaptation(.popover) // 强制适配气泡
}
.fullScreenCover用于视频播放、图表全屏分析或登录页面等场景,需要提供显式的关闭/返回按钮:
.fullScreenCover(isPresented: $showFullScreenModal) {
FullScreenAnalysisView()
}
@Environment(\.dismiss) 环境退出机制在 SwiftUI 中,子视图可以通过环境依赖注入向系统申请退出凭证,无需父视图传递闭包:
struct AddTaskSheetView: View {
@Environment(\.dismiss) private var dismiss // 声明向系统申请 dismiss 凭证
var body: some View {
Button("取消") {
dismiss() // 触发关闭当前 Sheet
}
}
}
两者的唤起与 Sheet 机制保持一致:
.alert("确定要删除吗?", isPresented: $showingAlert) {
Button("取消", role: .cancel) { }
Button("重置", role: .destructive) { deleteData() }
} message: {
Text("此操作不可逆,请谨慎选择。")
}
.confirmationDialog("删除任务", isPresented: $showingDeleteConfirm, titleVisibility: .visible) {
Button("永久删除", role: .destructive) { delete() }
Button("取消", role: .cancel) { }
} message: {
Text("任务删除后将无法找回。")
}
.frame() (约束宽高):.frame(width: 100, height: 50):限制具体宽高。.frame(maxWidth: .infinity):获取当前父容器的分配空间,撑满宽度。.padding() (内边距控制):.padding():默认四周留出边距。.padding(.horizontal, 12):在水平左右两侧添加 12px 边距。.overlay() (视图上方悬浮叠加):Rectangle()
.fill(Color.green)
.frame(width: 200, height: 150)
.overlay(
Text("右上角标签"),
alignment: .topTrailing // 将文本定位在右上角
)
.tint() (主题色修饰):改变当前视图树下所有可交互控件(Button, Toggle, TabBar, Picker)的主题强调色。.background() (背景修饰):.background(Color.yellow)。.background(.ultraThinMaterial)。.background(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
.shadow(color: .gray, radius: 5, x: 2, y: 2):加设阴影效果。.disabled() (禁用状态):.disabled(taskName.isEmpty):如果条件满足,禁用按钮的交互与响应。.animation() (响应式动画绑定):struct AnimatedView: View {
@State private var isAnimating = false
var body: some View {
VStack {
Circle()
.fill(isAnimating ? Color.blue : Color.red)
.frame(width: isAnimating ? 200 : 100)
// 绑定 isAnimating 改变,并在 1 秒内执行平滑缓动动画
.animation(.easeInOut(duration: 1.0), value: isAnimating)
Button("开始动画") {
isAnimating.toggle()
}
}
}
}
.transition() (过渡效果):控制视图在被插入/移除时的动画轨迹(如 .transition(.slide) 或 .transition(.scale))。在 SwiftUI 中可以根据环境自动处理明暗色彩转换:
Text("自适应面板")
.padding()
// Color(.systemBackground) 在浅色模式下为白色,深色模式下为黑色
.background(Color(.systemBackground))
// Color(.label) 对应文本标签色,浅色模式为黑色,深色模式为白色
.foregroundColor(Color(.label))
@Environment 提取当前颜色模式:struct ThemeAdaptiveView: View {
// 提取底层 Light/Dark 模式环境值
@Environment(\.colorScheme) private var colorScheme
var body: some View {
RoundedRectangle(cornerRadius: 15)
.fill(colorScheme == .dark ? Color.black : Color.white)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(colorScheme == .dark ? Color.orange : Color.blue, lineWidth: 1)
)
.frame(width: 200, height: 100)
}
}
在小屏幕或大字号模式下,单纯限制 .lineLimit(1) 会将超出的文字截断显示为“...”。
利用 .minimumScaleFactor 可以实现文本防截断处理:
Text("This is an extremely long title for developer task that might overflow")
.font(.headline)
.lineLimit(1) // 限制单行
// 当空间受限时,允许文字大小自动收缩,支持缩小至 80%
.minimumScaleFactor(0.8)
这能保证在大字号环境下,文字内容依然可完整被读取。
Text("渐变标题")
.font(.system(size: 36, weight: .black))
.foregroundStyle(
LinearGradient(colors: [.blue, .purple], startPoint: .leading, endPoint: .trailing)
)
RoundedRectangle(cornerRadius: 12)
.fill(
LinearGradient(
colors: [Color.orange.opacity(0.8), Color.red.opacity(0.8)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 300, height: 100)
MeshGradient(
width: 3,
height: 3,
points: [
.init(0, 0), .init(0.5, 0), .init(1, 0),
.init(0, 0.5), .init(0.4, 0.5), .init(1, 0.5),
.init(0, 1), .init(0.5, 1), .init(1, 1)
],
colors: [
.red, .purple, .indigo,
.orange, .white, .blue,
.yellow, .black, .mint
]
)
// 1. 原生内阴影 (.shadow(.inner(...)))
Circle()
.foregroundStyle(Color.gray.opacity(0.2))
.shadow(.inner(color: .black.opacity(0.6), radius: 5, x: 2, y: 2))
.frame(width: 80, height: 80)
// 2. iOS 17 不对称异形圆角剪裁 (UnevenRoundedRectangle)
CardView()
.clipShape(.rect(
topLeadingRadius: 24,
bottomLeadingRadius: 0,
bottomTrailingRadius: 24,
topTrailingRadius: 0
))
通过 .symbolEffect 赋予系统图标动画:
Image(systemName: "bell.fill")
.symbolEffect(.bounce, value: unreadCount) // unreadCount 改变时弹跳
常用效果包含 .bounce(弹跳)、.pulse(呼吸/脉冲)、.variableColor(多色闪烁)。
Text("全屏沉浸页")
.statusBarHidden(isFullscreen) // 动态隐藏状态栏
在 Web 中习惯将常用的样式类(如 CSS/Tailwind)提取为复用类,在 SwiftUI 中可以通过实现 ViewModifier 协议配合 extension View 将一长串修饰符封装为类型安全的链式调用:
// 1. 实现 ViewModifier 协议
struct SaharaCardModifier: ViewModifier {
let cornerRadius: CGFloat
func body(content: Content) -> some View {
content
.padding()
.background(Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
.shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4)
}
}
// 2. 扩展 View 协议暴露链式方法
extension View {
func saharaCardStyle(cornerRadius: CGFloat = 16) -> some View {
modifier(SaharaCardModifier(cornerRadius: cornerRadius))
}
}
// 3. 在视图层直接链式调用
VStack { Text("卡片内容") }
.saharaCardStyle()
利用原生 Swift Charts 构建图表,并通过 Catmull-Rom 插值平滑曲线及 iOS 17 手势悬停捕获:
import SwiftUI
import Charts
struct InteractiveChartView: View {
let data = [
(month: "Jan", revenue: 200.0),
(month: "Feb", revenue: 96.0),
(month: "Mar", revenue: 312.0)
]
@State private var selectedMonth: String? = nil
var body: some View {
Chart {
ForEach(data, id: \.month) { item in
LineMark(
x: .value("月份", item.month),
y: .value("收益", item.revenue)
)
.interpolationMethod(.catmullRom) // Catmull-Rom 高阶平滑插值
}
if let selectedMonth {
RuleMark(x: .value("选中", selectedMonth))
.foregroundStyle(.blue.opacity(0.3))
}
}
.frame(height: 200)
.chartXSelection(value: $selectedMonth) // iOS 17 手势触控悬停捕获
}
}
| 梯度 | 代表组件 | 渲染机制 | 推荐应用场景 |
|---|---|---|---|
| 基础 Shape 积木 | Rectangle, Circle |
声明式 View 节点 | 规则布局卡片、热力瓷砖墙等常规 UI |
| 矢量路径 (Path) | Path |
矢量控制点连线 | 自定义折线图、不规则多边形 |
| 即时渲染画布 (Canvas) | Canvas |
直接模式图形上下文绘制 | 高频物理动画、海量粒子特效、密集数据可视化 |
AttributedString 富文本安全处理在 SwiftUI 中使用 AttributedString 处理局部高亮与范围切片:
var attrStr = AttributedString("提示:已成功同步 SwiftData 数据。")
if let range = attrStr.range(of: "SwiftData") {
attrStr[range].foregroundColor = .orange
attrStr[range].backgroundColor = .orange.opacity(0.1)
}
Text(attrStr) // Text 直接渲染富文本