如何向 SwiftUI 表单添加更多行/项目?
Posted
技术标签:
【中文标题】如何向 SwiftUI 表单添加更多行/项目?【英文标题】:How do you add more rows/items to a SwiftUI form? 【发布时间】:2021-01-25 10:48:56 【问题描述】:我是一名新手程序员,正在学习 SwiftUI。
这张图片将显示我遇到的问题:
点击“添加练习”按钮时,我希望圆角矩形和内容在下面重复。
我正在使用 Firebase 的 Cloud Firestore 来存储这些数据。随着习题数量的变化,表格的内容如何构成?
谢谢,这是我设置表单的基本代码:
var body: some View
ScrollView
VStack (alignment: .leading, spacing: 4)
Text("Injury Exercises")
.font(.largeTitle)
.bold()
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
VStack (spacing: 16)
TextField("Workout Title (optional)", text: $text)
.autocapitalization(.words)
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
.lineLimit(1)
TextField("Add Warmup", text: $text)
.font(.subheadline)
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
VStack
TextField("Exercise Title (required)", text: $text)
.autocapitalization(.words)
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
.lineLimit(1)
TextField("Sets, Reps, Tempo, Rest etc.", text: $text)
.font(.subheadline)
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
.padding(8)
.foregroundColor(Color("card4"))
.background(Color.white).opacity(0.8)
.clipShape(RoundedRectangle(cornerRadius: 16, style: /*@START_MENU_TOKEN@*/.continuous/*@END_MENU_TOKEN@*/))
HStack
Image(systemName: "plus")
Text("Exercise")
.font(.subheadline)
.padding(8)
.foregroundColor(Color("card4"))
.background(Color.white).opacity(0.8)
.clipShape(Capsule())
TextField("Add Cooldown", text: $text)
.font(.subheadline)
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
.padding(.horizontal)
.navigationBarTitle("Add Injury Exercise")
.navigationBarHidden(true)
.background(
VisualEffectBlur()
.edgesIgnoringSafeArea(.all))
【问题讨论】:
【参考方案1】:要实现您在屏幕截图中显示的外观,您可以使用List
和InsetGroupedListStyle
:
然后可以使用Section
表示每个部分。由于Section
允许我们在页眉和页脚中插入视图,我们可以在页眉中插入TextField
s 以允许用户输入锻炼标题等。同样,添加新练习的按钮可以进入footer
.
现在,使数据模型可就地编辑更具挑战性,尤其是当您想将其连接到 Firebase 等后端服务时。我们需要将我们的结构体转换为ObservableObject
s,以便我们可以将TextField
s 绑定到它们。
这是我在article series 中展示的关于MakeItSo 的内容。
对于您的应用,如下所示:
应用程序
import SwiftUI
@main
struct SO65883241App: App
var viewModel = InjuryExercisesViewModel(exercises: sampleExercises)
var body: some Scene
WindowGroup
InjuryExercisesScreen(viewModel: viewModel)
模型
struct Exercise: Identifiable
var id = UUID().uuidString
var title: String
var details: String
let sampleExercises = [
Exercise(title: "Deadlift", details: "1-3-2"),
Exercise(title: "Squats", details: "3-3-2"),
Exercise(title: "Push-ups", details: "20-10-10"),
]
视图模型
class InjuryExercisesViewModel: ObservableObject
@Published var title: String = ""
@Published var warmUp: String = ""
@Published var coolDown: String = ""
@Published private var exercises = [Exercise]()
@Published var exerciseViewModels = [ExerciseViewModel]()
private var cancellables = Set<AnyCancellable>()
init(exercises: [Exercise])
$exercises.map exercises in
exercises.map exercise in
ExerciseViewModel(id: exercise.id, title: exercise.title, details: exercise.details)
.assign(to: \.exerciseViewModels, on: self)
.store(in: &cancellables)
self.exercises = exercises
func addNewExercise()
exercises.append(Exercise(title: "", details: ""))
class ExerciseViewModel: ObservableObject, Identifiable
var id: String
@Published var title: String
@Published var details: String
init(id: String = UUID().uuidString, title: String, details: String)
self.id = id
self.title = title
self.details = details
意见
import SwiftUI
import Combine
struct ExerciseRow: View
@ObservedObject var exerciseViewModel: ExerciseViewModel
var body: some View
TextField("Exercise Title (required)", text: $exerciseViewModel.title)
TextField("Sets, Reps, Tempo, Rest, etc.", text: $exerciseViewModel.details)
struct InjuryExercisesScreen: View
@Environment(\.presentationMode) var presentationMode
@StateObject var viewModel: InjuryExercisesViewModel
var addExerciseButton: some View
HStack
Spacer()
HStack
Image(systemName: "plus")
Text("Exercise")
.onTapGesture viewModel.addNewExercise()
.font(.headline)
.padding(12)
.foregroundColor(Color(UIColor.systemPurple))
.background(Color(UIColor.secondarySystemGroupedBackground))
.clipShape(Capsule())
Spacer()
.padding()
func dismiss()
self.presentationMode.wrappedValue.dismiss()
func save()
dump(self.viewModel.exerciseViewModels[0])
self.presentationMode.wrappedValue.dismiss()
var cancelButton: some View
Button(action: dismiss)
Text("Cancel")
var doneButton: some View
Button(action: save)
Text("Done")
var body: some View
NavigationView
List
Section(header: TextField("Workout Title (optional)", text: $viewModel.title))
Section(header: TextField("Add Warmup", text: $viewModel.warmUp))
Section(header: Text("Exercises"), footer: addExerciseButton)
ForEach(viewModel.exerciseViewModels) exerciseViewModel in
ExerciseRow(exerciseViewModel: exerciseViewModel)
Section(header: TextField("Add Cooldown", text: $viewModel.coolDown))
.listStyle(InsetGroupedListStyle())
.navigationTitle("Injury Exercises")
.navigationBarItems(leading: cancelButton, trailing: doneButton)
struct InjuryExercisesScreen_Previews: PreviewProvider
static var viewModel = InjuryExercisesViewModel(exercises: sampleExercises)
static var previews: some View
Group
InjuryExercisesScreen(viewModel: viewModel)
.preferredColorScheme(.dark)
InjuryExercisesScreen(viewModel: viewModel)
.preferredColorScheme(.light)
Text("Parent View")
.sheet(isPresented: .constant(true))
InjuryExercisesScreen(viewModel: viewModel)
如何将其存储在 Firestore 中取决于您的整体数据模型。您能否确认在您的应用中,您将处理一大堆锻炼?
【讨论】:
【参考方案2】:您可以使用 ForEach
和一组练习。
例如,我假设您有一个 Exercise
模型,其中包含以下内容:
struct Exercise: Identifiable, Hashable
var id: String
var title: String
var description: String
您的表单组件可能包含以下内容(我删除了不相关的部分):
struct Formmm: View
@State private var exercises: [Exercise]
var body: some View
ScrollView
// snip
ForEach(exercises.indices, id: \.self) index in
VStack
TextField("Exercise Title (required)", text: $exercises[index].title)
.autocapitalization(.words)
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
.lineLimit(1)
TextField("Sets, Reps, Tempo, Rest etc.", text: $exercises[index].description)
.font(.subheadline)
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
Button
exercises.append(Exercise(title: "", description: ""))
label:
HStack
Image(systemName: "plus")
Text("Exercise")
.font(.subheadline)
.padding(8)
.foregroundColor(Color("card4"))
.background(Color.white).opacity(0.8)
.clipShape(Capsule())
// snip
在向您的数组添加新的Exercise
后,SwiftUI 将使用包含空Exercise
的新行重绘您的组件。因为我们使用索引,所以我们能够在我们的数组中获得正确的Exercise
的绑定。
【讨论】:
嗨,非常有用,谢谢。我能够实现您将空“练习”添加到“练习”数组的方法。但是,我正在努力从 Firebase 保存和查询。我可以将单个“练习”添加到 Firebase 集合中,然后查询“练习”集合。我不确定如何将包含多个“练习”的数组添加到 Firebase 集合,然后查询整个数组。如果您可以对此进行扩展,将不胜感激。 我以前从未使用过 Firebase,但快速浏览一下文档,我最好的猜测是您应该拥有一个exercises
集合并获得对该集合的引用。要阅读所有练习(未过滤),您可以使用 reference.getDocuments()
,要编写多个元素,您可能需要查看批处理操作(来源:firebase.google.com/docs/firestore/query-data/get-data 和 firebase.google.com/docs/firestore/manage-data/transactions)以上是关于如何向 SwiftUI 表单添加更多行/项目?的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI:我可以向 TabView 添加更多视图,然后是选项卡项吗?
如何添加“加载更多”单元格,按下该单元格时会向表格视图添加更多行?