首页 > 解决方案 > 协议“GetSignQuestions”只能用作通用约束,因为它具有 Self 或关联的类型要求

问题描述

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

型号 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
    }
}

警察信号视图(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)
    }
}

当我以这种方式进行更改时,我会收到图像中的错误。

在此处输入图像描述

标签: swiftuiprotocols

解决方案


我已经重新组织了一些代码,并重新添加了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())
            }
        }
    }
}


推荐阅读