如何用SwiftUI实现苹果健康APP同款悬浮滚轮日期选择器?
复刻苹果健康APP表单的简化方案
一、基础表单结构简化
别再手动写HStack+Text+Spacer了,SwiftUI原生的LabeledContent就是专门干这个的,配合Form和Section,自动实现标签左对齐、值右对齐的布局,直接砍掉一半冗余代码:
struct HealthFormView: View { @Environment(\.editMode) private var editMode @State private var selectedDate = Date() @State private var showDatePicker = false var body: some View { NavigationStack { Form { Section { // 替代手写布局的LabeledContent LabeledContent("出生日期") { if editMode?.wrappedValue == .active { Button(action: { showDatePicker = true }) { Text(selectedDate.formatted(date: .long, time: .omitted)) .foregroundColor(.blue) } } else { Text(selectedDate.formatted(date: .long, time: .omitted)) } } // 其他字段同理 LabeledContent("身高") { if editMode?.wrappedValue == .active { Button("175cm") { /* 处理身高编辑逻辑 */ } .foregroundColor(.blue) } else { Text("175cm") } } } } .navigationTitle("个人信息") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() // 原生Edit按钮,自动切换编辑模式 } } .sheet(isPresented: $showDatePicker) { DatePickerView(selectedDate: $selectedDate, isPresented: $showDatePicker) } } } }
二、批量处理Edit模式切换
如果字段多,每个都写if editMode判断太繁琐,封装一个复用组件就行:
struct EditToggleRow<Content: View>: View { let label: String let content: Content let action: () -> Void @Environment(\.editMode) private var editMode init(label: String, @ViewBuilder content: () -> Content, action: @escaping () -> Void) { self.label = label self.content = content() self.action = action } var body: some View { LabeledContent(label) { if editMode?.wrappedValue == .active { Button(action: action) { content .foregroundColor(.blue) } } else { content } } } } // 使用时一行搞定 EditToggleRow(label: "出生日期", content: { Text(selectedDate.formatted(date: .long, time: .omitted)) }) { showDatePicker = true }
三、DatePicker弹窗样式修复(iOS 16+)
要实现健康APP那种只能通过Cancel/Done关闭、深色无圆角底部弹窗,用这几个关键API就能搞定:
struct DatePickerView: View { @Binding var selectedDate: Date @Binding var isPresented: Bool var body: some View { VStack(spacing: 0) { // 顶部操作栏 HStack { Button("Cancel") { isPresented = false } Spacer() Button("Done") { isPresented = false } .bold() } .padding(.horizontal) .padding(.vertical, 12) .background(Color(.systemGray6)) // 滚轮式选择器 DatePicker("", selection: $selectedDate, displayedComponents: .date) .datePickerStyle(.wheel) .padding() } .presentationDetents([.medium]) // 固定高度的底部弹窗 .presentationDragIndicator(.hidden) // 隐藏默认拖动条 .interactiveDismissDisabled() // 禁止点击外部关闭,必须点按钮 .background(Color(.systemBackground)) .onAppear { // 强制去掉弹窗圆角 if let sheetController = UIApplication.shared.windows.first?.rootViewController?.presentedViewController as? UISheetPresentationController { sheetController.preferredCornerRadius = 0 } } } }
四、你之前代码复杂的原因
你手动实现了原生组件已经支持的布局,又单独处理每个字段的编辑模式切换,没有做组件复用,导致代码冗余。上面的方案用原生API替代手写布局,把重复逻辑封装成组件,代码量至少减少三分之一。
内容的提问来源于stack exchange,提问作者Apollo
相关产品推荐
相关产品推荐

