问题描述
我是一名新手编码员,正在学习 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",style: .continuous))
}
.padding(.horizontal)
.navigationBarTitle("Add Injury Exercise")
.navigationBarHidden(true)
}
.background(
VisualEffectBlur()
.edgesIgnoringSafeArea(.all))
}
}
解决方法
您可以使用 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
获取绑定。
要实现屏幕截图中显示的外观和感觉,您可以将 List
与 InsetGroupedListStyle
结合使用:
然后可以使用 Section
表示每个部分。由于 Section
允许我们在页眉和页脚中插入视图,我们可以在页眉中插入 TextField
以允许用户输入锻炼标题等。同样,用于添加新练习的按钮可以进入footer
。
现在,使数据模型可就地编辑更具挑战性,尤其是当您想将其连接到 Firebase 等后端服务时。我们需要将我们的结构转换为 ObservableObject
以便我们可以将 TextField
绑定到它们。
这是我在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,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 中的顶部存储方式取决于您的整体数据模型。您能否确认在您的应用中,您将进行大量锻炼?