Как реализовать настраиваемые делегаты в SwiftUI - PullRequest
1 голос
/ 19 июня 2020

В качестве примера у меня есть SwitUI ContentView. Тот, который появляется, когда вы впервые делаете проект.

import SwiftUI

struct ContentView: View {
   var manager = TestManager()
   var body: some View {
    ZStack{
        Color(.green)
            .edgesIgnoringSafeArea(.all)
        VStack {
            Text("Test Text")

            Button(action:{}) {
                Text("Get number 2")
                    .font(.title)
                    .foregroundColor(.white)
                .padding()
                .overlay(RoundedRectangle(cornerRadius: 30)
                .stroke(Color.white, lineWidth: 5))
                }
           }
       }
   }
}

У меня есть TestManager, который будет обрабатывать вызовы Api. Я сделал делегата для класса, который имеет две функции.

protocol TestManagerDelegate {
    func didCorrectlyComplete(_ testName: TestManager, model: TestModel)
    func didFailWithError(_ error: Error)
}

struct TestManager {

    var delegate: TestManagerDelegate?
    let urlString = "http://numbersapi.com/2/trivia?json"

    func Get(){
        if let url = URL(string: urlString){

            let session = URLSession(configuration: .default)

            let task = session.dataTask(with: url) { (data, response, error) in
                if error != nil{
                    self.delegate?.didFailWithError(error!)
                    return
                }

                if let safeData = data{
                    if let parsedData = self.parseJson(safeData){
                        self.delegate?.didCorrectlyComplete(self, model: parsedData)
                    }
                }
            }
            task.resume()
        }
    }

   func parseJson(_ jsonData: Data) -> TestModel?{
       let decoder = JSONDecoder()
       do {
           let decodedData = try decoder.decode(TestModel.self,  from: jsonData)
           let mes = decodedData.message
           let model = TestModel(message: mes)
           return model

       } catch {
           delegate?.didFailWithError(error)
           return nil
       }
     }

  }

Это класс данных testModel. Получение только текста возвращаемого Json.

struct TestModel :Decodable{
    let text: String
}

Как мне подключить TestManager к представлению и заставить представление обрабатывать делегата, как мы могли бы это сделать в раскадровках?

1 Ответ

2 голосов
/ 19 июня 2020

Что касается TestModel

Decodable протокола (в вашем контексте) предполагает, что вы создаете структуру модели со всеми свойствами, которые вы получаете через JSON. При запросе http://numbersapi.com/2/trivia?json вы получите что-то вроде:

{
 "text": "2 is the number of stars in a binary star system (a stellar system consisting of two stars orbiting around their center of mass).",
 "number": 2,
 "found": true,
 "type": "trivia"
}

Это означает, что ваша модель должна выглядеть следующим образом:

struct TestModel: Decodable {
    let text: String
    let number: Int
    let found: Bool
    let type: String
}

Что касается делегатов

In SwiftUI этот подход недостижим. Вместо этого разработчикам необходимо адаптировать функции платформы Combine: обертки свойств @ObservedObject, @Published и ObservableObject протокол. Вы хотите поместить свой logi c в некую структуру. Плохая новость, что (в настоящее время) ObservableObject является протоколом AnyObject (т.е. Протокол только для классов ). Вам нужно будет переписать свой TestManager как класс как:

class TestManager: ObservableObject {
   // ...
}

Только тогда вы сможете использовать его в своем CurrentView, используя @ ObservedObject обертку свойств:

struct ContentView: View {
    @ObservedObject var manager = TestManager()
    // ...
}

Что касается TestManager

Ваш logi c теперь исключает delegate как таковой, и вам нужно использовать TestModel для передачи данных на CustomView. Вы можете изменить TestManager, добавив новое свойство с помощью @ Published property wrapper:

class TestManager: ObservableObject {
    let urlString = "http://numbersapi.com/2/trivia?json"
    // 1
    @Published var model: TestModel?

    func get(){
        if let url = URL(string: urlString){
            let session = URLSession(configuration: .default)
            let task = session.dataTask(with: url) { [weak self] (data, response, error) in
                // 2
                DispatchQueue.main.async { 
                    if let safeData = data {
                        if let parsedData = self?.parseJson(safeData) {
                            // 3
                            self?.model = parsedData
                        }
                    }
                }
            }
            task.resume()
        }
    }

    private func parseJson(_ jsonData: Data) -> TestModel? {
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(TestModel.self, from: jsonData)
            return decodedData
        } catch {
            return nil
        }
    }
}
  1. Чтобы иметь возможность доступа к вашей модели «извне», в вашем случае ContentView.
  2. Используйте DispatchQueue.main.async{ } для асинхронных c задач, потому что Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
  3. Просто используйте вашу проанализированную модель.

Затем в ContentView используйте свой TestManager вот так:

struct ContentView: View {
    @ObservedObject var manager = TestManager()
    var body: some View {
        ZStack{
            Color(.green)
                .edgesIgnoringSafeArea(.all)
            VStack {
                Text("Trivia is: \(self.manager.model?.text ?? "Unknown")")
                Button(action:{ self.manager.get() }) {
                    Text("Get number 2")
                        .font(.title)
                        .foregroundColor(.white)
                        .padding()
                        .overlay(RoundedRectangle(cornerRadius: 30)
                            .stroke(Color.white, lineWidth: 5))
                }
            }
        }
    }
}

Что касается HTTP

Вы используете ссылку http://numbersapi.com/2/trivia?json, которая не разрешена Apple , пожалуйста, используйте https или добавьте ключ App Transport Security Settings с параметром Allow Arbitrary Loads, установленным на YES , в свой Info.Plist . Но делайте это очень осторожно , так как http-ссылка просто не будет работать.

Дальнейшие действия

Вы можете реализовать обработку ошибок самостоятельно, основываясь на описании выше.

Полный код (копипаст и go):

import SwiftUI

struct ContentView: View {
    @ObservedObject var manager = TestManager()
    var body: some View {
        ZStack{
            Color(.green)
                .edgesIgnoringSafeArea(.all)
            VStack {
                Text("Trivia is: \(self.manager.model?.text ?? "Unknown")")
                Button(action:{ self.manager.get() }) {
                    Text("Get number 2")
                        .font(.title)
                        .foregroundColor(.white)
                        .padding()
                        .overlay(RoundedRectangle(cornerRadius: 30)
                            .stroke(Color.white, lineWidth: 5))
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

class TestManager: ObservableObject {
    let urlString = "http://numbersapi.com/2/trivia?json"
    @Published var model: TestModel?

    func get(){
        if let url = URL(string: urlString){
            let session = URLSession(configuration: .default)
            let task = session.dataTask(with: url) { [weak self] (data, response, error) in
                DispatchQueue.main.async {
                    if let safeData = data {
                        if let parsedData = self?.parseJson(safeData) {
                            self?.model = parsedData
                        }
                    }
                }
            }
            task.resume()
        }
    }

    private func parseJson(_ jsonData: Data) -> TestModel? {
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(TestModel.self, from: jsonData)
            return decodedData
        } catch {
            return nil
        }
    }
}

struct TestModel: Decodable {
    let text: String
    let number: Int
    let found: Bool
    let type: String
}
...