[英]How do you add more rows/items to a SwiftUI form?
I'm a newbie coder and learning SwiftUI.我是一个新手编码器,正在学习 SwiftUI。
This image will show the problem I'm having:此图像将显示我遇到的问题:
When clicking the "Add Exercise" button, I'd like the rounded rectangle and contence to be repeated below.单击“添加练习”按钮时,我希望在下面重复圆角矩形和内容。
I'm using Firebase's Cloud Firestore to store this data.我正在使用 Firebase 的 Cloud Firestore 来存储这些数据。 How would the contence of the form be structured with the changing amounts of Exercises?
随着练习数量的变化,表格的内容将如何构建?
Thanks, here's my basic code for the form setup:谢谢,这是我的表单设置基本代码:
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))
}
}
You could use a ForEach
and an array of Exercises.您可以使用
ForEach
和一组练习。
For instance, I'm gonna assume you have an Exercise
model that contains something like the following:例如,我假设您有一个
Exercise
model,其中包含以下内容:
struct Exercise: Identifiable, Hashable {
var id: String
var title: String
var description: String
}
Your form component could contain the following (I removed the irrelevant parts):您的表单组件可能包含以下内容(我删除了不相关的部分):
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
}
}
}
Upon adding a new Exercise
to your array, SwiftUI will redraw your component with the new line containing an empty Exercise
.将新的
Exercise
添加到您的数组后,SwiftUI 将使用包含空Exercise
的新行重新绘制您的组件。 Because we use indexes, we are able to get a binding for the correct Exercise
in our array.因为我们使用索引,所以我们能够在我们的数组中获得正确
Exercise
的绑定。
To achieve the look and feel you showed in your screenshot, you can use a List
with InsetGroupedListStyle
:要实现您在屏幕截图中显示的外观,您可以使用带有
InsetGroupedListStyle
的List
:
Each of the sections can then be represented using a Section
.然后可以使用
Section
来表示每个部分。 As Section
allows us to insert views into the header and footer, we can insert TextField
s into the headers to allow the user to enter the workout title, etc. Likewise, the button for adding a new exercise can go into the footer
.由于
Section
允许我们在 header 和页脚中插入视图,我们可以在页眉中插入TextField
以允许用户输入锻炼标题等。同样,添加新练习的按钮可以 go 到footer
中。
Now, making the data model editable in-place is a bit more challenging, especially if you want to connect it to a backend service like Firebase.现在,使数据 model 可就地编辑更具挑战性,特别是如果您想将其连接到像 Firebase 这样的后端服务。 We need to convert our structs into
ObservableObject
s so we can bind the TextField
s to them.我们需要将我们的结构体转换为
ObservableObject
s,以便我们可以将TextField
s 绑定到它们。
This is something I showed in my article series about MakeItSo .这是我在关于MakeItSo的文章系列中展示的内容。
For your app, this would look as follows:对于您的应用程序,这将如下所示:
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)
}
}
}
}
How top store this in Firestore depends on your overall data model.如何将其存储在 Firestore 中取决于您的整体数据 model。 Can you confirm that in your app, you're going to deal with a whole bunch of workouts?
您能否确认在您的应用程序中,您将处理一大堆锻炼?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.