简体   繁体   English

以 SwiftUI 动态形式声明 TextField 和 Toggle

[英]Declaring TextField and Toggle in a SwiftUI dynamic form

I went back to swift programming after 4 years and everything changed. 4 年后我又回到了 Swift 编程,一切都变了。 I learn everything from scratch.我从头开始学习一切。 I'm trying to solve a variable binding problem.我正在尝试解决变量绑定问题。 I want to build a dynamic form with fields that will come back from API.我想构建一个动态表单,其中包含将从 API 返回的字段。 Code:代码:

import SwiftUI


struct Filter: Codable, Identifiable {
      var id: Int
      var name: String
      var type: String
      var defaultValue: Int
init(_ dictionary: [String: Any]) {
      self.id = dictionary["id"] as? Int ?? 0
      self.name = dictionary["name"] as? String ?? ""
      self.type = dictionary["type"] as? String ?? ""
      self.defaultValue = dictionary["defaultValue"] as? Int ?? 0
    }
}

struct ContentView: View {
    
    @State var filters:Array<Filter> = []
    
    func load(){
        guard let url = URL(string: "https://api.kierklosebastian.pl/") else {return}
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        guard let dataResponse = data,
                  error == nil else {
                  print(error?.localizedDescription ?? "Response Error")
                  return }
            do {
                let decoder = JSONDecoder()
                let model = try decoder.decode([Filter].self, from:
                             dataResponse)
                self.filters = model;
            } catch let parsingError {
                print("Error", parsingError)
            }
        }
        task.resume()
    }
    
    var body: some View {
        VStack{
            Button(action: self.load, label: {
                Text("Get json")
            })
            List{
                ForEach(self.filters) { filter in
                    HStack{
                        Text("NAME: \(filter.name)")
                        Text("TYPE: \(filter.type)")
           
                        if filter.type == "textField" {
//                            TextField("", ???????)
                        }
                        
                        if filter.type == "checkbox" {
//                            Toggle(isOn: ????????) {
//                                ""
//                            }
                        }
                    }
                }
            }
        }
    }
    
}

How do I declare the TextFiled and Toggle variables?如何声明 TextFiled 和 Toggle 变量?

Toggle needs to have a boolean binding property that determines whether the toggle is on or off. Toggle需要有一个boolean绑定属性来确定切换是打开还是关闭。 So you need to define a boolean property somehwere, for ex, for Filter .所以你需要定义一个布尔属性,例如,为Filter

struct Filter: Codable, Identifiable {
    var id: Int
    var name: String
    var type: String
    var defaultValue: Int
    var isOn: Bool = false
    init(_ dictionary: [String: Any]) {
        self.id = dictionary["id"] as? Int ?? 0
        self.name = dictionary["name"] as? String ?? ""
        self.type = dictionary["type"] as? String ?? ""
        self.defaultValue = dictionary["defaultValue"] as? Int ?? 0
        self.isOn = (dictionary["type"] as? String ?? "") ==  "checkbox"
    }
}

Create a view model that confirms to ObservableObject .创建一个确认ObservableObject的视图模型。

https://developer.apple.com/documentation/combine/observableobject https://developer.apple.com/documentation/combine/observableobject

class FilterViewModel: ObservableObject {
    @Published var filters:Array<Filter> = []
    
    func load(){
        guard let url = URL(string: "https://api.kierklosebastian.pl/") else {return}
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard let dataResponse = data,
                  error == nil else {
                print(error?.localizedDescription ?? "Response Error")
                return }
            do {
                let decoder = JSONDecoder()
                let model = try decoder.decode([Filter].self, from:
                                                dataResponse)
                self.filters = model;
            } catch let parsingError {
                print("Error", parsingError)
            }
        }
        task.resume()
    }
}

And use it instead of @State var filters:Array<Filter> = [] in ContentView .并在ContentView使用它代替@State var filters:Array<Filter> = []

struct FilterView: View {
    
    @ObservedObject var viewModel = FilterViewModel()
   
    -----
}

Create a new view for the content of the ForEach and pass viewModel and filter to it.ForEach的内容创建一个新视图,并将 viewModel 和过滤器传递给它。 In the new view, find index of the current filter.在新视图中,查找当前过滤器的索引。

struct FilterDetailView: View {
    
    @ObservedObject var viewModel: FilterViewModel
    var filter: Filter
    
    
    var indexOfFilter: Int {
        self.viewModel.filters.firstIndex { $0 == filter}!
    }
    var body: some View {
        
        HStack{
            Text("NAME: \(filter.name)")
            Text("TYPE: \(filter.type)")
            
            if filter.type == "textField" {
                TextField("TextField", text: self.$viewModel.filters[indexOfFilter].name)
            }
            
            if filter.type == "checkbox" {
                Toggle(isOn: self.$viewModel.filters[indexOfFilter].isOn) {
                    Text("Toggle")
                }
            }
        }
    }
}

You need to confirm to Equatable too.您也需要向Equatable确认。 Oterwise, you can not use firstIndex { $0 == filter}!否则,您不能使用firstIndex { $0 == filter}!

struct Filter: Codable, Identifiable, Equatable {
}

Complete code完整代码


struct Filter: Codable, Identifiable, Equatable {
    var id: Int
    var name: String
    var type: String
    var defaultValue: Int
    var isOn: Bool = false
    init(_ dictionary: [String: Any]) {
        self.id = dictionary["id"] as? Int ?? 0
        self.name = dictionary["name"] as? String ?? ""
        self.type = dictionary["type"] as? String ?? ""
        self.defaultValue = dictionary["defaultValue"] as? Int ?? 0
        self.isOn = (dictionary["type"] as? String ?? "") ==  "checkbox"
    }
}


class FilterViewModel: ObservableObject {
    @Published var filters:Array<Filter> = []
    
    func load(){
        guard let url = URL(string: "https://api.kierklosebastian.pl/") else {return}
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard let dataResponse = data,
                  error == nil else {
                print(error?.localizedDescription ?? "Response Error")
                return }
            do {
                let decoder = JSONDecoder()
                let model = try decoder.decode([Filter].self, from:
                                                dataResponse)
                self.filters = model;
            } catch let parsingError {
                print("Error", parsingError)
            }
        }
        task.resume()
    }
}

struct FilterView: View {
    
    @ObservedObject var viewModel = FilterViewModel()
    
    var body: some View {
        VStack{
            Button(action: self.viewModel.load, label: {
                Text("Get json")
            })
            List{
                ForEach(self.viewModel.filters) { filter in
                    FilterDetailView(viewModel: viewModel, filter: filter)
                }
            }
        }
    }
    
}


struct FilterDetailView: View {
    
    @ObservedObject var viewModel: FilterViewModel
    var filter: Filter
    
    
    var indexOfFilter: Int {
        self.viewModel.filters.firstIndex { $0 == filter}!
    }
    var body: some View {
        
        HStack{
            Text("NAME: \(filter.name)")
            Text("TYPE: \(filter.type)")
            
            if filter.type == "textField" {
                TextField("TextField", text: self.$viewModel.filters[indexOfFilter].name)
            }
            
            if filter.type == "checkbox" {
                Toggle(isOn: self.$viewModel.filters[indexOfFilter].isOn) {
                    Text("Toggle")
                }
            }
        }
    }
}


iOS 15+ iOS 15+

ForEach(self.viewModel.filters) { $filter in
  -----
}

Toggle(isOn: $filter.isOn) {
   Text("Toggle")
}

If you are interested, watch What's new in SwiftUI - WWDC21 (08:00) and follow Apple's SwiftUI Tutorials如果您有兴趣,请观看 SwiftUI 中的新增功能 - WWDC21 (08:00) 并关注 Apple 的 SwiftUI 教程

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM