繁体   English   中英

如何在 SwiftUI 的另一个视图/结构中调用 func?

[英]How to call a func in another view/struct in SwiftUI?

我有一个由 3 层视图组成的应用程序。 该应用程序的功能是让学生看问题,选择答案,并让应用程序检查答案是否正确(这是我现在正在苦苦挣扎的功能)

  1. ContentView - 生成一个随机 id (ranId) 并将其传递给 ChoiceView。 ChoiceView 是这个视图中的一个组件。

  2. ChoiceView* - 在 ForEach 循环之后组合不同的 ChoiceRowView 并将字符串作为标签从 ranId 传递给 Choice RowView

  3. ChoiceRowView* - 控制 ChoiceView 中每一行的外观并检查每个答案的答案。

我尝试使用 Protocol-delegate 方法,但我不知道如何将它与 ForEach 循环一起使用。 我知道还有其他方法可以做到这一点,比如 didSet,但我现在更愿意使用协议委托方法。 谢谢!!

ChoiceView.swift 中的代码

import SwiftUI   

struct ChoiceView: View {
        
        var ranId_CV : Int // random id pass from ContentView
        
        var delegate : ChoiceRowView?
        
        func test_CV() {
            
            delegate?.checkAnswer()
        }
        
        var body: some View {
            
            ScrollView {
            HStack{
                VStack(alignment: .leading){
                ForEach (qandaData[ranId_CV].choices.components(separatedBy: "\n"), id: \.self){ item in
                    ChoiceRowView(label: String(item), answer: String(qandaData[self.ranId_CV].answer))
                    }
                }
                .fixedSize(horizontal: false, vertical: true)
                Spacer()
            }
            .padding([.top, .leading, .bottom, .trailing])
            Button(action: test_CV) { Text("Test_CV") }   // this should work, but it doesn't
            }
        }
    }

struct ChoiceView_Previews: PreviewProvider {
    static var previews: some View {
        ChoiceView(ranId_CV: 125)
    }
}

ChoiceRowView.swift 中的代码


    import SwiftUI

    protocol checkAnswerProtocol {
        func checkAnswer()
    }

    struct ChoiceRowView: View, checkAnswerProtocol {    
        
        var label: String
        var answer: String
        @State var isChecked : Bool = false
        @State var fgColor : String = "black"
        
        func buttonPress() {

            isChecked = !isChecked
                
            if isChecked == false {
                
                fgColor = "black"
                
            } else {
                
                fgColor = "blue"
                
            }
            
    //            selectedAnswer = selectedAnswer.filter { $0 != item_BP.prefix(1) }
        }
        
        func checkAnswer() {
            
            print("executed checkAnswer")
            
            switch (isChecked, answer.contains(String(label.prefix(1)))) {
            
            //selected = isChecked = true, qandaData.answer contain prefix(1)  => change to green
            case (true, true) : fgColor = "green"
            
            //selected = isChecked = true, incorrect => change to red
            case (true, false) : fgColor = "red"
                
            //unselected = isChecked = false, correct => change to red
            case (false, true) : fgColor = "red"
            //unselected = isChecked = false, incorrect => remain unchange
            default: print("do nothing")
            }

        }

        
        var body: some View {
            Button(action: buttonPress) {
                    HStack{
                        Image(systemName: isChecked ? "checkmark.square": "square")
                        Text(label)
                    }
                    .foregroundColor(Color(String(fgColor)))
                Button(action: checkAnswer) { Text("Test") }
            }
        }
            
    }
        

    struct ChoiceRow_Previews: PreviewProvider {
        static var previews: some View {
            ChoiceRowView(label: "label", answer: "AB")
        }
    }

qandaData[ranId_CV] 样本


    "id": 1,
    "question": "Which ones are odd number?",
    "choices": "A. 1\nB. 2\nC. 3\nD. 4",
    "answer": "AC"

在这篇文章中,我将尝试解释一些我用来解决您的问题的 SwiftUI 的基础知识。 首先,SwiftUI 与 UIKit 有点不同,它是一种声明式编程,状态驱动,因此 UI 组件不会被手动引用以进行操作或更新,每个视图都有不同的状态,并且会在状态更改时相应地呈现。

在 SwiftUI 中,Apple 为我们提供了内置的状态管理属性包装器 @State 和 @Binding。 SwiftUI 将存储它们并更新组件链接到的每个 UI。

您可以很好地阅读有关 Swift UI 的信息:此处此处


我尽量不改变很多东西,并保持属性和类名相同。

ChoiceView 中的代码

import SwiftUI

struct ChoiceView: View {

var ranId_CV : Int // random id pass from ContentView

var delegate : ChoiceRowView? // delegates can't be used in SwiftUI

@State var qandaData : [Question] = [
    Question(id : 1 , question : "Which ones are odd number?", rawChoices : "A. 1\nB. 2\nC. 3\nD. 4" , answer : "AC"),
    Question(id : 2 , question : "This is another question", rawChoices : "A. 1\nB. 2\nC. 3\nD. 4" , answer : "B")
]

// Array of Line objects containing all the Questions and their states
@State var lines : [Line] = []

var body: some View {
    ScrollView {
        Text(self.qandaData[ranId_CV].question)
        HStack{
            VStack(alignment: .leading){
                ForEach (self.lines.indices, id: \.self){ index in
                    ChoiceRowView(line: self.$lines[index])
                }
            }
            .fixedSize(horizontal: false, vertical: true)
            Spacer()
        }
        .padding([.top, .leading, .bottom, .trailing])
        .onAppear(perform: {
            self.qandaData[self.ranId_CV].choices.forEach { (label) in
                self.lines.append(Line(label: label , answer: self.qandaData[self.ranId_CV].answer))
            }
        })
        
        Button(action: {
            self.test_CV()
        }, label: {
            Text("Test_CV")
        })
    }
}

func test_CV() {
    //        delegate?.checkAnswer()
    
    self.lines.indices.forEach { (index) in
        self.lines[index].state = .none // restore correction state
    }
    
    self.lines.indices.forEach { (index) in
        if self.lines[index].selection == .selected {
            self.qandaData[self.ranId_CV].answer.forEach({ (char) in
                if self.lines[index].label.prefix(1).contains(char) && self.lines[index].selection == .selected {
                    self.lines[index].state = .valid
                }else{
                    self.lines[index].state = .invalid
                }
                
            })
        }
    }
}

}

演练第 1 部分:

  1. 当视图出现时,由随机的 Int Question 命题所选择的每一个都会被转换成一个 Line 对象。
  2. ForEach 将遍历 Lines 数组并根据它们的每个状态渲染它们。
  3. Line 的状态存储在一个枚举中,这使得它们易于触发。
  4. 使用 @State 包装器将告诉 SwiftUI 在该对象数组发生更改时更新相关视图。

ChoiceRowView 中的代码

import SwiftUI

protocol checkAnswerProtocol {
    func checkAnswer()
}

struct ChoiceRowView: View {

@Binding var line : Line

//    var label: String
//    var answer: String
@State var isChecked : Bool = false
@State var fgColor : Color = .black


func buttonPress() {

    self.line.selection = isChecked ? .selected : .unselected
    
    isChecked = !isChecked
   
    
    if isChecked == false {
        fgColor = .black
    } else {
        fgColor = .blue
    }
    
    //            selectedAnswer = selectedAnswer.filter { $0 != item_BP.prefix(1) }
}

func checkAnswer() {
    
    print("executed checkAnswer")
    
    switch (isChecked, self.line.label.contains(String(self.line.label.prefix(1)))) {
        
    //selected = isChecked = true, qandaData.answer contain prefix(1)  => change to green
    case (true, true) : fgColor = .green
        
    //selected = isChecked = true, incorrect => change to red
    case (true, false) : fgColor = .red
        
    //unselected = isChecked = false, correct => change to red
    case (false, true) : fgColor = .red
    //unselected = isChecked = false, incorrect => remain unchange
    default: print("do nothing")
    }
    
}


var body: some View {
    HStack{
        Button(action: {
            self.buttonPress()
            
        }) {
            
            Image(systemName: isChecked ? "checkmark.square": "square")
            Text(self.line.label)
        }
        .foregroundColor(self.line.state != .none  ? (self.line.state == .valid ? .green : .red) : .blue )
    }
}

}

演练第 2 部分:

  1. 使用包装器@Binding 将告诉视图该属性确实来自调用者或父级,并且每当它的值发生变化时,视图将相应地更新。

  2. 使用 enum 来存储视图的状态是一种很好的做法,可以使您的代码更具可读性。

  3. 使用了实体 Question,因此我们可以正确操作数据并避免处理原始字符串。

  4. 由于代码中没有视图引用,委托不再是这种编程范式的一部分。

     import Foundation struct Question { let id: Int let question, answer: String let choices : [String] init(id : Int , question : String, rawChoices : String , answer : String) { self.id = id self.question = question self.choices = rawChoices.components(separatedBy: "\\n") self.answer = answer } }

和 UI 对象

import Foundation
struct Line {
        let id = UUID()
        let label : String
        let answer : String
        var state : QState = .none
        var selection : Selecction = .unselected
        
    }
    
    enum QState {
        case valid,invalid, none
    }
    
    enum Selecction {
        case selected, unselected
    }

最后这是项目结构

在此处输入图片说明

或者从我的 Github 克隆项目

随时问我任何问题,我很乐意为您提供帮助,如果我回答了您的问题,请不要忘记 +1。

暂无
暂无

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

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