swift - 如何从字符串数组生成自定义文本字段,每个字符串都有自己的编辑/删除控件?
问题描述
我的 IOS 应用程序有一个用户表单生成器。用户可以创建表单、向其中添加问题以及更改问题类型,包括文本响应、多项选择和复选框。对于多项选择和复选框类型,用户必须提供 2-5 个选项。
在我的QuestionManager
(视图模型)中,我将这些选项存储为字符串数组。在视图中,我正在OptionCell
使用ForEach
循环生成 s。每个选项OptionCell
都有一个TextField
和控制功能来删除、编辑和确认每个选项。问题是我不确定我的方法,因为我认为有一些错误是由于我设置它的方式造成的。
例如,这确实是唯一的问题,我有一个未更新的@State var isEditing: Bool
变量OptionCell
,我认为这与我@Published var options: [String]
在QuestionManager
. 或者它可能@State var option: String
与OptionCell
. (我尝试将其设置为@Binding
,但由于某种原因不起作用。)但这对我来说没有任何意义,因为isEditing
变量设置为在点击 View 时更新,尽管我已将问题缩小到什么我最不确定,我相信它与我上面描述的有某种关系。有什么建议么?
struct OptionCell: View {
@ObservedObject var manager: QuestionManager
@State var option: String
@State var isEditing: Bool = false
var optIndex: Int
init(_ manager: QuestionManager, option: String) {
self.manager = manager
self.option = option
self.optIndex = manager.options!.firstIndex(of: option)!
}
var body: some View {
HStack {
MyCustomTextField(text: $option)
.keyboardType(.default)
.onTapGesture {
option = ""
isEditing = true // The main issue -> Isnt updating the view
}
// ActionIcon is just a Button with a SFLabels string input
ActionIcon(isEditing ? "checkmark" : "xmark", size: 10) { // No changes are recognized
isEditing ? saveOption() : deleteOption()
}
.padding(.horizontal)
}
}
func saveOption() {
if option == "" {
manager.error = "An option must not be empty"
} else {
manager.options![optIndex] = option
isEditing = false
}
}
func deleteOption() {
if manager.options!.count == 2 {
manager.error = "You must have at least two options"
} else {
manager.deleteOption(option: option)
}
}
}
struct QuestionView: View {
@Environment(\.presentationMode) var mode
@ObservedObject var manager: QuestionManager
init(_ manager: QuestionManager) { self.manager = manager }
var body: some View {
ZStack {
MyCustomViewWrapper {
// ^ Reskins the background, adds padding, etc.
ScrollView {
VStack(spacing: 20) {
ConstantTitledTextField("Type", manager.type.rawValue)
// ^ Reskinned TextField
.overlay {
HStack {
Spacer()
self.changeTypeMenu
}
.padding(.horizontal)
.padding(.top, 30)
}
TitledTextField("Prompt", $manager.prompt)
// ^ Another reskinned TextField
.keyboardType(.default)
.onTapGesture { manager.prompt = "" }
MyCustomDivider()
if manager.options != nil {
self.optionsSection
}
}
}
}
.alert(isPresented: $manager.error.isNotNil()) {
MyCustomAlert(manager.error!) { manager.error = nil }
}
VStack {
Spacer()
MyCustomButton("Save") { checkAndSave() }
}
.hiddenIfKeyboardActive()
}
}
var changeTypeMenu: some View {
Image(systemName: "chevron.down").icon(15)
.menuOnPress {
Button(action: { self.manager.changeType(.text) } ) { Text("Text Response") }
Button(action: { self.manager.changeType(.multiple) } ) { Text("Multiple Choice") }
Button(action: { self.manager.changeType(.checkbox) } ) { Text("Checkbox") }
}
}
var optionsSection: some View {
VStack(spacing: 15) {
HStack {
Text("Options")
.font(.title3)
.padding(.horizontal)
Spacer()
ActionIcon("plus", size: 15) { manager.addOption() }
.padding(.horizontal)
}
ForEach(manager.options!, id: \.self) { opt in
OptionCell(manager, option: opt)
}
}
}
private func checkAndSave() {
if manager.checkQuestion() {
manager.saveQuestion()
self.mode.wrappedValue.dismiss()
}
}
}
class QuestionManager: ObservableObject {
@Published var question: Question
@Published var type: Question.QuestionType
@Published var prompt: String
@Published var options: [String]?
@Published var error: String? = nil
@Published var id = ""
init(_ question: Question) {
self.question = question
self.type = question.type
self.prompt = question.prompt
self.options = question.options
self.id = question.id
}
func saveQuestion() {
self.question.prompt = prompt
self.question.type = type
self.question.options = options
}
func checkQuestion() -> Bool {
if type == .checkbox || type == .multiple {
if options!.count < 2 {
error = "You must have at least two options"
return false
}
else if options!.contains("") {
error = "Options must not be empty"
return false
}
else if !options!.elementsAreUnique() {
error = "Options must be unique"
return false
}
}
if prompt.isEmpty {
error = "You must include a prompt for your question"
return false
}
return true
}
func changeType(_ type: Question.QuestionType) {
if type == .text {
options = nil
}
else {
options = ["Option 1", "Option 2", "Option 3"]
}
self.type = type
}
func addOption() {
guard options != nil else { return }
if options!.count == 5 {
error = "No more than 5 options are permitted"
}
else {
let count = options!.count
let newOption = "Option \(count + 1)"
options!.append(newOption)
}
}
func deleteOption(option: String) {
guard options != nil else { return }
guard let index = self.options!.firstIndex(of: option) else {
self.error = "An error occured during deleting ... sorry you're screwed"
return
}
self.options!.remove(at: index)
}
}