Search code examples
swiftuiprotocols

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


I have a protocol and two different Extensions. When PoliceSignalsView is opened, when I change the shared variable in SignQuestionProvider with PoliceSignQuestion(), it returns Police related questions. If I replaced it with TrafficSignQuestion() it would return me traffic related questions. This is working properly. However, there is a problem.

Model 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?
}

Model 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:

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)")
    }
}

Extensions:

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 Service:

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):

How can I transfer the data I get from here to QuestionCardView()? I couldn't make the Generic Binding build.

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)
    }
}

SecondView(QuestionCardView):

I don't want to see the policeQuestions property while showing the data from policeSignalsView here because it can be in trafficQuestions on this page. If I write it like this I get an error. how can i fix this problem?

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())
            }
        }
    }
}

As I said, I don't want the policeQuestions property in ForEach on the QuestionCardView screen. I followed this way to remove it.

Protocol:

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

Extensions:

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 []
    }
}

PoliceSignalsView:

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

When I make changes in this way, I get an error as in the image.

enter image description here


Solution

  • I've reorganized some of the code and re-added the Question protocol from my previous answer to you. I've also slightly edited your model to make the TrafficSign and PoliceSign have non-optional id properties. Otherwise, using them in the ForEach is going to be problematic.

    
    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())
                }
            }
        }
    }