问题描述
我正在尝试通过 swiftUI 构建一个演示应用程序,它从用户那里获取多文本并将它们添加到列表中,下面,每次用户按下 AddListView 的加号按钮时,都会有一个应用程序的图像显示给用户,用户可以在列表中添加多文本。我有一个问题,通过新的 switUI 数据流将它们添加到列表中我不知道如何传递数据。(我评论更多信息) 谢谢? 这是我的 AddListView 代码:
import SwiftUI
struct AddListView: View {
@State var numberOfTextFiled = 1
@Binding var showAddListView : Bool
var body: some View {
ZStack {
Title(numberOfTextFiled: $numberOfTextFiled)
vstack {
ScrollView {
ForEach(0 ..< numberOfTextFiled,id: \.self) { item in
PreAddTextField()
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView)
}
.frame(width: 300,height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3),radius: 10,x: 0,y: 10)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
AddListView(showAddListView: .constant(false))
}
}
struct PreAddTextField: View {
// I made this standalone struct and use @State to every TextField text be independent
// if i use @Binding to pass data all Texfield have the same text value
@State var textInTextField = ""
var body: some View {
vstack {
TextField("Enter text",text: $textInTextField)
}
}
}
struct Buttons: View {
@Binding var showAddListView : Bool
var body: some View {
vstack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
// What should happen here to add Text to List???
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
@Binding var numberOfTextFiled : Int
var body: some View {
vstack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
numberOfTextFiled += 1
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
对于数据模型:
import SwiftUI
struct Text1 : Identifiable,Hashable{
var id = UUID()
var text : String
}
var textData = [
Text1(text: "SwiftUI"),Text1(text: "Data flow?"),]
最后:
import SwiftUI
struct ListView: View {
@State var showAddListView = false
var body: some View {
NavigationView {
vstack {
ZStack {
List(textData,id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
解决方法
由于问题的多个项目部分,这变得不那么微不足道了。但是,结合使用 ObservableObject
和回调函数,绝对可行。查看代码中的内联注释以了解正在发生的事情:
struct Text1 : Identifiable,Hashable{
var id = UUID()
var text : String
}
//Store the items in an ObservableObject instead of just in @State
class AppState : ObservableObject {
@Published var textData : [Text1] = [.init(text: "Item 1"),.init(text: "Item 2")]
}
//This view model stores data about all of the new items that are going to be added
class AddListViewViewModel : ObservableObject {
@Published var textItemsToAdd : [Text1] = [.init(text: "")] //start with one empty item
//save all of the new items -- don't save anything that is empty
func saveToAppState(appState: AppState) {
appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
}
//these Bindings get used for the TextFields -- they're attached to the item IDs
func bindingForId(id: UUID) -> Binding<String> {
.init { () -> String in
self.textItemsToAdd.first(where: { $0.id == id })?.text ?? ""
} set: { (newValue) in
self.textItemsToAdd = self.textItemsToAdd.map {
guard $0.id == id else {
return $0
}
return .init(id: id,text: newValue)
}
}
}
}
struct AddListView: View {
@Binding var showAddListView : Bool
@ObservedObject var appState : AppState
@StateObject private var viewModel = AddListViewViewModel()
var body: some View {
ZStack {
Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "")) })
VStack {
ScrollView {
ForEach(viewModel.textItemsToAdd,id: \.id) { item in //note this is id: \.id and not \.self
PreAddTextField(textInTextField: viewModel.bindingForId(id: item.id))
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView,save: {
viewModel.saveToAppState(appState: appState)
})
}
.frame(width: 300,height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3),radius: 10,x: 0,y: 10)
}
}
struct PreAddTextField: View {
@Binding var textInTextField : String //this takes a binding to the view model now
var body: some View {
VStack {
TextField("Enter text",text: $textInTextField)
}
}
}
struct Buttons: View {
@Binding var showAddListView : Bool
var save : () -> Void //callback function for what happens when "Add" gets pressed
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
save()
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
var addItem : () -> Void //callback function for what happens when the plus button is hit
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
addItem()
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
struct ListView: View {
@StateObject var appState = AppState() //store the AppState here
@State private var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(appState.textData,id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView,appState: appState)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}