你如何在 SwiftUI 中实现自定义委托
How do you implement custom delegates in SwiftUI
例如,我有一个 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 调用。我为 class 创建了一个具有两个功能的委托。
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
}
}
}
这是测试模型数据class。只抓取返回的Json的文本。
struct TestModel :Decodable{
let text: String
}
如何将 TestManager 连接到视图并让视图像在故事板中那样处理委托?
关于测试模型
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
}
关于代表
在 SwiftUI 中无法使用此方法。相反,开发人员需要调整 Combine 框架的功能:属性 包装器 @ObservedObject
、@Published
和 ObservableObject
协议。
您想将您的逻辑放入某个结构中。坏消息,(目前)ObservableObject
是 AnyObject
协议(即 Class-Only Protocol)。您需要将 TestManager
重写为 class 为:
class TestManager: ObservableObject {
// ...
}
只有这样,您才能使用 @ObservedObject 属性 包装器在 CurrentView
中使用它:
struct ContentView: View {
@ObservedObject var manager = TestManager()
// ...
}
关于测试管理器
您的逻辑现在排除了 delegate
,您需要使用 TestModel
将数据传递给 CustomView
。您可以通过添加新的 属性 和 @Published 属性 包装器来修改 TestManager
:
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
}
}
}
- 为了能够访问您的模型 "from outside",在您的情况下
ContentView
。
- 对异步任务使用
DispatchQueue.main.async{ }
,因为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.
- 只需使用您解析的模型。
然后在 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
您使用 link http://numbersapi.com/2/trivia?json
即 not allowed by Apple, please, use https
instead, or add the App Transport Security Settings
key with Allow Arbitrary Loads
parameter set to YES
. But do this very carefully 因为 http link 根本无法工作。
进一步的步骤
您可以根据上面的描述自行实现错误处理。
完整代码(复制粘贴):
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
}
例如,我有一个 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 调用。我为 class 创建了一个具有两个功能的委托。
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
}
}
}
这是测试模型数据class。只抓取返回的Json的文本。
struct TestModel :Decodable{
let text: String
}
如何将 TestManager 连接到视图并让视图像在故事板中那样处理委托?
关于测试模型
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
}
关于代表
在 SwiftUI 中无法使用此方法。相反,开发人员需要调整 Combine 框架的功能:属性 包装器 @ObservedObject
、@Published
和 ObservableObject
协议。
您想将您的逻辑放入某个结构中。坏消息,(目前)ObservableObject
是 AnyObject
协议(即 Class-Only Protocol)。您需要将 TestManager
重写为 class 为:
class TestManager: ObservableObject {
// ...
}
只有这样,您才能使用 @ObservedObject 属性 包装器在 CurrentView
中使用它:
struct ContentView: View {
@ObservedObject var manager = TestManager()
// ...
}
关于测试管理器
您的逻辑现在排除了 delegate
,您需要使用 TestModel
将数据传递给 CustomView
。您可以通过添加新的 属性 和 @Published 属性 包装器来修改 TestManager
:
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
}
}
}
- 为了能够访问您的模型 "from outside",在您的情况下
ContentView
。 - 对异步任务使用
DispatchQueue.main.async{ }
,因为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.
- 只需使用您解析的模型。
然后在 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
您使用 link http://numbersapi.com/2/trivia?json
即 not allowed by Apple, please, use https
instead, or add the App Transport Security Settings
key with Allow Arbitrary Loads
parameter set to YES
进一步的步骤
您可以根据上面的描述自行实现错误处理。
完整代码(复制粘贴):
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
}