Protocol 'GetSignQuestions' 只能用作泛型约束,因为它有 Self 或关联类型要求

Protocol 'GetSignQuestions' can only be used as a generic constraint because it has Self or associated type requirements

我有一个协议和两个不同的扩展。当打开 PoliceSignalsView 时,当我用 PoliceSignQuestion() 更改 SignQuestionProvider 中的共享变量时,它 returns 与警察相关的问题。如果我用 TrafficSignQuestion() 替换它,它会 return 我提出与交通相关的问题。这工作正常。但是,有一个问题。

模型 1:

struct TrafficSignQuestion: Codable, Hashable {
    var trafficQuestions: [TrafficSign]?
}

enum TrafficSignSectionType: String, Codable, Hashable {
    case A = "A"
    case B = "B"
}

struct TrafficSign: Codable, Hashable {
    var id: Int?
    var image: String?
    var sections: [TrafficSignSectionType.RawValue : String]?
    var correct: String?
}

模型 2:

struct PoliceSignQuestion: Codable, Hashable {
    var policeQuestions: [PoliceSign]?
}

enum PoliceSignSectionType: String, Codable, Hashable {
    case A = "A"
    case B = "B"
    case C = "C"
}

struct PoliceSign: Codable, Hashable {
    var id: Int?
    var image: String?
    var sections: [PoliceSignSectionType.RawValue : String]?
    var correct: String?
}

协议:

protocol GetSignQuestions {
    func getQuestions(jsonService: JSONService) -> [Self]?
}

**SignQuestionProvider**

class SignQuestionProvider<T: Any>: ObservableObject {
    
    var shared: GetSignQuestions?
    @Published var allQuestions: T?
    
    func getQuestions() {
        let questions = shared?.getQuestions(jsonService: JSONService())
        allQuestions = questions as? T
        print("\(allQuestions)")
    }
}

分机:

extension PoliceSign: GetSignQuestions {
    func getQuestions(jsonService: JSONService) -> [PoliceSign]? {
        if let questions = jsonService.getQuestion(fileName: "policeQuestions", using: PoliceSignQuestion.self) {
            return questions.policeQuestions ?? []
        }
        return []
    }
}

extension TrafficSign: GetSignQuestions {
    func getQuestions(jsonService: JSONService) -> [TrafficSign]? {
        if let questions = jsonService.getQuestion(fileName: "trafficQuestions", using: TrafficSignQuestion.self) {
            return questions.trafficQuestions ?? []
        }
        return []
    }
}

JSON 服务:

class JSONService: ObservableObject {
    
    func getQuestion<T: Codable>(fileName: String, using modelType: T.Type)  -> T? {
        if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
            do {
                let data = try Data(contentsOf: url)
                let decoder = JSONDecoder()
                let jsonData = try decoder.decode(modelType, from: data)
                return jsonData
            } catch {
                print("error:\(error)")
            }
        }
        return nil
    }
}

PoliceSignalsView(FirstView):

如何将从此处获取的数据传输到 QuestionCardView()?我无法构建通用绑定。

struct PoliceSignalsView: View {
    @StateObject var questionProvider = SignQuestionProvider<PoliceSignQuestion>()
    
    var body: some View {
        VStack {
            QuestionCardView()
        }
        .onAppear {
            questionProvider.shared = PoliceSignQuestion()

// When I change it to "TrafficSignQuestion", the TrafficSign function in the protocol works and returns TrafficSigns questions. I want to pass the result returned from here to QuestionCardView. How can I do that ?

            questionProvider.getQuestions()
        }
        .environmentObject(questionProvider)
    }
}

第二个视图(问题卡视图):

我不想在此处显示来自 policeSignalsView 的数据时看到 policeQuestions 属性,因为它可能位于此页面的 trafficQuestions 中。如果我这样写,我会得到一个错误。我该如何解决这个问题?

struct QuestionCardView: View {
    @EnvironmentObject var optionConfigure: OptionConfigure
    @EnvironmentObject var questionProvider: SignQuestionProvider<PoliceSignQuestion>
    var body: some View {
        VStack {
           
            
            
            ForEach(questionProvider.allQuestions?.policeQuestions?.indices ?? 0..<0, id: \.self) { item in 
// here.. I don't want the "policeQuestions" property here. Because it should be a generic construct.
                Text("")
                    .animation(.spring())
            }
        }
    }
}

如我所说,我不希望 QuestionCardView 屏幕上 ForEach 中的 policeQuestions 属性。我是按照这个方法去掉的

协议:

protocol GetSignQuestions {
    func getQuestions(jsonService: JSONService) -> [Self] // Array
}

分机:

extension PoliceSign: GetSignQuestions {
    
    func getQuestions(jsonService: JSONService) -> [PoliceSign] {
        if let questions = jsonService.getQuestion(fileName: "policeQuestions", using: PoliceSignQuestion.self) {
            return questions.policeQuestions ?? []
        }
        return []
    }
}

extension TrafficSign: GetSignQuestions {
    func getQuestions(jsonService: JSONService) -> [TrafficSign] {
        if let questions = jsonService.getQuestion(fileName: "trafficQuestions", using: TrafficSignQuestion.self) {
            return questions.trafficQuestions ?? []
        }
        return []
    }
}

警察信号视图:

struct PoliceSignalsView: View {
    @StateObject var questionProvider = SignQuestionProvider<PoliceSign>()
    
    var body: some View {
        VStack {
            QuestionCardView()
        }
        .onAppear {
            questionProvider.shared = PoliceSign()
            questionProvider.getQuestions()
        }
        .environmentObject(questionProvider)
    }
}

当我以这种方式进行更改时,出现如图所示的错误。

我重新组织了一些代码并重新添加了我之前给您的回答中的 Question 协议。我还稍微编辑了您的模型,使 TrafficSignPoliceSign 具有非可选的 id 属性。否则,在 ForEach 中使用它们会出问题。


struct TrafficSignQuestion: Codable, Hashable {
    var trafficQuestions: [TrafficSign]?
}

enum TrafficSignSectionType: String, Codable, Hashable {
    case A = "A"
    case B = "B"
}

struct TrafficSign: Codable, Hashable, Question {
    var id: Int
    var image: String?
    var sections: [TrafficSignSectionType.RawValue : String]?
    var correct: String?
}

struct PoliceSignQuestion: Codable, Hashable {
    var policeQuestions: [PoliceSign]?
}

enum PoliceSignSectionType: String, Codable, Hashable {
    case A = "A"
    case B = "B"
    case C = "C"
}

struct PoliceSign: Codable, Hashable, Question {
    var id: Int
    var image: String?
    var sections: [PoliceSignSectionType.RawValue : String]?
    var correct: String?
}

protocol Question {
    var id: Int { get set }
    var image: String? { get set }
}

protocol GetSignQuestions {
    associatedtype QuestionType : Question
    func getQuestions(jsonService: JSONService) -> [QuestionType]?
}

class SignQuestionProvider<T: GetSignQuestions>: ObservableObject {
    @Published var allQuestions : [T.QuestionType] = []
    
    func getQuestions(getter: T) {
        if let questions = getter.getQuestions(jsonService: JSONService()) {
            self.allQuestions = questions
        }
        print("\(allQuestions)")
    }
}

struct PoliceSignGetter : GetSignQuestions {
    func getQuestions(jsonService: JSONService) -> [PoliceSign]? {
        if let questions = jsonService.getQuestion(fileName: "policeQuestions", using: PoliceSignQuestion.self) {
            return questions.policeQuestions ?? []
        }
        return []
    }
}

struct TrafficSignGetter: GetSignQuestions {
    func getQuestions(jsonService: JSONService) -> [TrafficSign]? {
        if let questions = jsonService.getQuestion(fileName: "trafficQuestions", using: TrafficSignQuestion.self) {
            return questions.trafficQuestions ?? []
        }
        return []
    }
}

class JSONService: ObservableObject {
    
    func getQuestion<T: Codable>(fileName: String, using modelType: T.Type)  -> T? {
        if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
            do {
                let data = try Data(contentsOf: url)
                let decoder = JSONDecoder()
                let jsonData = try decoder.decode(modelType, from: data)
                return jsonData
            } catch {
                print("error:\(error)")
            }
        }
        return nil
    }
}

struct PoliceSignalsView: View {
    @StateObject var questionProvider = SignQuestionProvider<PoliceSignGetter>()
    
    var body: some View {
        VStack {
            QuestionCardView<PoliceSign>(questions: questionProvider.allQuestions)
        }
        .onAppear {
            questionProvider.getQuestions(getter: PoliceSignGetter())
        }
    }
}

struct QuestionCardView<T: Question>: View {
    var questions: [Question]
    
    var body: some View {
        VStack {
            ForEach(questions, id: \.id) { item in
                Text("")
                    .animation(.spring())
            }
        }
    }
}