[英]How to call a func in another view/struct in SwiftUI?
我有一个由 3 层视图组成的应用程序。 该应用程序的功能是让学生看问题,选择答案,并让应用程序检查答案是否正确(这是我现在正在苦苦挣扎的功能) 。
ContentView - 生成一个随机 id (ranId) 并将其传递给 ChoiceView。 ChoiceView 是这个视图中的一个组件。
ChoiceView* - 在 ForEach 循环之后组合不同的 ChoiceRowView 并将字符串作为标签从 ranId 传递给 Choice RowView
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。
我尽量不改变很多东西,并保持属性和类名相同。
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 部分:
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 部分:
使用包装器@Binding 将告诉视图该属性确实来自调用者或父级,并且每当它的值发生变化时,视图将相应地更新。
使用 enum 来存储视图的状态是一种很好的做法,可以使您的代码更具可读性。
使用了实体 Question,因此我们可以正确操作数据并避免处理原始字符串。
由于代码中没有视图引用,委托不再是这种编程范式的一部分。
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
}
最后这是项目结构
随时问我任何问题,我很乐意为您提供帮助,如果我回答了您的问题,请不要忘记 +1。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.