繁体   English   中英

SwiftUI 为什么不能在视图之间传递发布者?

[英]SwiftUI Why Can't pass a publisher between Views?

我只是想做一些这样的测试↓

  1. 从第一个视图创建一个发布者
  2. 将其传递给第二个视图
  3. 在第二个视图中将发布者与某些属性绑定并尝试将其显示在屏幕上

代码是↓(第一次查看)

struct ContentView: View {
    
    let publisher = URLSession(configuration: URLSessionConfiguration.default)
        .dataTaskPublisher(for: URLRequest(url: URL(string: "https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8")!))
        .map(\.data.description)
        .replaceError(with: "Error!")
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
        
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: ResponseView(publisher: publisher)) {
                    Text("Hello, World!")
                }
            }.navigationBarTitle("Title", displayMode: .inline)
        }
    }
}

(第二视图)

struct ResponseView: View {
    
    let publisher: AnyPublisher<String, Never>
    @State var content: String = ""
    
    var body: some View {
        HStack {
            VStack {
                Text(content)
                    .font(.system(size: 12))
                    .onAppear { _ = self.publisher.assign(to: \.content, on: self) }
                Spacer()
            }
            Spacer()
        }
        
    }
}

但是代码不起作用。 请求失败,消息失败↓

2020-11-11 11:08:04.657375+0800 PandaServiceDemo[83721:1275181] Task <6B53516E-5127-4C5E-AD5F-893F1AEE77E8>.<1> finished with error [-999] Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8, NSLocalizedDescription=cancelled, NSErrorFailingURLKey=https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8}

有人可以告诉我发生了什么以及这样做的正确方法是什么?

您需要存储订阅,否则会被取消初始化并自动取消。

通常,这是这样做的:

var cancellables: Set<AnyCancellable> = []
// ...
publisher
  .sink {...}
  .store(in: &cancellables)

所以,你可以像上面那样创建一个@State属性,或者你可以使用.onReceive

let publisher: AnyPublisher<String, Never>

var body: some View {
   HStack {
     // ...
   }
   .onReceive(publisher) {
      content = $0
   }
}

你应该小心上面的方法,因为如果ResponseView被重新初始化,它会得到一个发布者的副本(大多数发布者是值类型),所以它会开始一个新的请求。

为避免这种情况,请将.share()添加到发布者:

let publisher = URLSession(configuration: URLSessionConfiguration.default)
        .dataTaskPublisher(...)
        //...
        .share()
        .eraseToAnyPublisher()

这里的问题是,订阅没有存储在任何地方。 您必须将其存储在AnyCancellable并保留订阅。

每当您调试组合相关问题时,请使用.print()运算符。 我觉得它真的很有用。


正确的做法是将发布者和订阅者提取到一个ObservableObject并将其注入到View或使用@StateObject

class DataProvider: ObservableObject {
    @Published var content: String = ""
    private var bag = Set<AnyCancellable>()
    private let publisher: AnyPublisher<String, Never>
    init() {
        publisher = URLSession(configuration: URLSessionConfiguration.default)
            .dataTaskPublisher(for: URLRequest(url: URL(string: "https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8")!))
            .map(\.data.description)
            .print()
            .replaceError(with: "Error!")
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
    
    func loadData() {
        publisher.assign(to: \.content, on: self).store(in: &bag)
    }
}

struct ContentView: View {
    @StateObject var dataProvider = DataProvider()
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: ResponseView(dataProvider: dataProvider)) {
                    Text("Hello, World!")
                }
            }.navigationBarTitle("Title", displayMode: .inline)
        }
    }
}

struct ResponseView: View {
    let dataProvider: DataProvider
    
    var body: some View {
        HStack {
            VStack {
                Text(dataProvider.content)
                    .font(.system(size: 12))
                    .onAppear {
                        self.dataProvider.loadData()
                    }
                Spacer()
            }
            Spacer()
        }
        
    }
}

请注意,我们使用@StateObject来确保DataProvider实例不会在视图更新时被破坏。

就 SwiftUI 而言,您在做一些根本错误的事情:从视图创建发布者。 这意味着每次实例化ContentView都会创建一个新的发布者,并且出于所有方式和目的,这可能会发生很多次,SwiftUI 不保证 View 只会实例化一次。

您需要做的是将发布的内容提取到某个对象中,该对象可以从上游注入,也可以通过@StateObject由 SwiftUI 管理。

好吧,这项工作有两种方法:一种方法更好

方式一:

    import SwiftUI




struct ContentView: View {
    
    @State var urlForPublicsh: URL?
    
    
    var body: some View {
        
        VStack
        {
            Text(urlForPublicsh?.absoluteString ?? "nil")
                .padding()
            
            Button("Change the Publisher") {urlForPublicsh = URL(string: "https://stackoverflow.com")}
                .padding()
            
            
            SecondView(urlForPublicsh: $urlForPublicsh)
            
        }
        .onAppear()
        {
            urlForPublicsh = URL(string: "https://www.apple.com")
        }
  
    }
}
struct SecondView: View {
    @Binding var urlForPublicsh: URL?
    
    var body: some View {
        
        Text(urlForPublicsh?.absoluteString ?? "nil")
            .padding()
    }
}

方式二:

    import SwiftUI

class UrlForPublicshModel: ObservableObject
{
    static let shared = UrlForPublicshModel()
    @Published var url: URL?
}



struct ContentView: View {
    

    @StateObject var urlForPublicshModel = UrlForPublicshModel.shared
    
    var body: some View {
        
        VStack
        {
            Text(urlForPublicshModel.url?.absoluteString ?? "nil")
                .padding()
            
            Button("Change the Publisher") {urlForPublicshModel.url = URL(string: "https://stackoverflow.com")}
                .padding()
            
            
            SecondView()
            
        }
        .onAppear()
        {
            urlForPublicshModel.url = URL(string: "https://www.apple.com")
        }
  
    }
}


struct SecondView: View {

    @ObservedObject var urlForPublicshModel = UrlForPublicshModel.shared
    
    var body: some View {
        
        Text(urlForPublicshModel.url?.absoluteString ?? "nil")
            .padding()

    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM