首页 > 解决方案 > 如何从字符串数组生成自定义文本字段,每个字符串都有自己的编辑/删除控件?

问题描述

我的 IOS 应用程序有一个用户表单生成器。用户可以创建表单、向其中添加问题以及更改问题类型,包括文本响应、多项选择和复选框。对于多项选择和复选框类型,用户必须提供 2-5 个选项。

在我的QuestionManager(视图模型)中,我将这些选项存储为字符串数组。在视图中,我正在OptionCell使用ForEach循环生成 s。每个选项OptionCell都有一个TextField和控制功能来删除、编辑和确认每个选项。问题是我不确定我的方法,因为我认为有一些错误是由于我设置它的方式造成的。

例如,这确实是唯一的问题,我有一个未更新的@State var isEditing: Bool变量OptionCell,我认为这与我@Published var options: [String]QuestionManager. 或者它可能@State var option: StringOptionCell. (我尝试将其设置为@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)
    }
}

标签: swiftswiftui

解决方案


推荐阅读