【问题标题】:SwiftUI and Combine not working smoothly when downloading image asynchrously异步下载图像时SwiftUI和Combine工作不顺畅
【发布时间】:2019-07-08 15:11:02
【问题描述】:

当我尝试使用 SwiftUI & Combine 异步下载图像时,它工作正常。然后,我尝试将其实现为动态列表,我发现只有一行(最后一行)可以正确显示,其他单元格中的图像丢失。我已经用断点跟踪代码,并且我确信图像下载过程在其他人中是成功的,但只有最后一行会触发 @ObjectBinding 来更新图像。请检查我的示例代码,如果有任何错误,请告诉我。谢谢!

struct UserView: View {
    var name: String
    @ObjectBinding var loader: ImageLoader

    init(name: String, loader: ImageLoader) {
        self.name = name
        self.loader = loader
    }

    var body: some View {
        HStack {
            Image(uiImage: loader.image ?? UIImage())
                .onAppear {
                    self.loader.load()
            }
            Text("\(name)")
        }
    }
}

struct User {
    let name: String
    let imageUrl: String
}

struct ContentView : View {
    @State var users: [User] = []
    var body: some View {
        NavigationView {
            List(users.identified(by: \.name)) { user in
                UserView(name: user.name, loader: ImageLoader(with: user.imageUrl))
            }
            .navigationBarTitle(Text("Users"))
            .navigationBarItems(trailing:
                Button(action: {
                    self.didTapAddButton()
                }, label: {
                    Text("+").font(.system(size: 36.0))
                }))
        }
    }

    func didTapAddButton() {
        fetchUser()
    }

    func fetchUser() {
        API.fetchData { (user) in
            self.users.append(user)
        }
    }
}

class ImageLoader: BindableObject {

    let didChange = PassthroughSubject<UIImage?, Never>()

    var urlString: String
    var task: URLSessionDataTask?
    var image: UIImage? = UIImage(named: "user") {
        didSet {
            didChange.send(image)
        }
    }

    init(with urlString: String) {
        print("init a new loader")
        self.urlString = urlString
    }

    func load() {
        let url = URL(string: urlString)!
        let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
            if error == nil {
                DispatchQueue.main.async {
                    self.image = UIImage(data: data!)
                }
            }
        }
        task.resume()
        self.task = task
    }

    func cancel() {
        if let task = task {
            task.cancel()
        }
    }
}

class API {
    static func fetchData(completion: @escaping (User) -> Void) {
        let request = URLRequest(url: URL(string: "https://randomuser.me/api/")!)
        let task = URLSession.shared.dataTask(with: request) { (data, _, error) in
            guard error == nil else { return }

            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any]
                guard
                    let results = json!["results"] as? [[String: Any]],
                    let nameDict = results.first!["name"] as? [String: String],
                    let pictureDict = results.first!["picture"] as? [String: String]
                    else { return }

                let name = "\(nameDict["last"]!) \(nameDict["first"]!)"
                let imageUrl = pictureDict["thumbnail"]
                let user = User(name: name, imageUrl: imageUrl!)
                DispatchQueue.main.async {
                    completion(user)
                }
            } catch let error {
                print(error.localizedDescription)
            }
        }
        task.resume()
    }
}

无论列表中有多少项目,每张图片都应该成功下载。

【问题讨论】:

    标签: swift swiftui combine


    【解决方案1】:

    @ObjectBinding 中似乎存在错误。我不确定,也无法确认。我想创建一个最小的示例代码来确定,如果是这样,请向 Apple 报告错误。有时 SwiftUI 似乎不会使视图无效,即使它所基于的 @ObjectBinding 调用了它的 didChange.send() 。我发布了我自己的问题 (@BindableObject async call to didChange.send() does not invalidate its view (and never updates))

    与此同时,我会尽可能使用 EnvironmentObject,因为似乎不存在该错误。

    然后,您的代码只需进行很少的更改即可工作。不要使用 ObjectBinding,而是使用 EnvironmentObject:

    用@EnvironmentObject 替换@ObjectBinding 的代码

    
    import SwiftUI
    import Combine
    
    struct UserView: View {
        var name: String
        @EnvironmentObject var loader: ImageLoader
    
        init(name: String) {
            self.name = name
        }
    
        var body: some View {
            HStack {
                Image(uiImage: loader.image ?? UIImage())
                    .onAppear {
                        self.loader.load()
                }
                Text("\(name)")
            }
        }
    }
    
    struct User {
        let name: String
        let imageUrl: String
    }
    
    struct ContentView : View {
        @State var users: [User] = []
        var body: some View {
            NavigationView {
                List(users.identified(by: \.name)) { user in
                    UserView(name: user.name).environmentObject(ImageLoader(with: user.imageUrl))
                }
                .navigationBarTitle(Text("Users"))
                    .navigationBarItems(trailing:
                        Button(action: {
                            self.didTapAddButton()
                        }, label: {
                            Text("+").font(.system(size: 36.0))
                        }))
            }
        }
    
        func didTapAddButton() {
            fetchUser()
        }
    
        func fetchUser() {
            API.fetchData { (user) in
                self.users.append(user)
            }
        }
    }
    
    class ImageLoader: BindableObject {
    
        let didChange = PassthroughSubject<UIImage?, Never>()
    
        var urlString: String
        var task: URLSessionDataTask?
        var image: UIImage? = UIImage(named: "user") {
            didSet {
                didChange.send(image)
            }
        }
    
        init(with urlString: String) {
            print("init a new loader")
            self.urlString = urlString
        }
    
        func load() {
            let url = URL(string: urlString)!
            let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
                if error == nil {
                    DispatchQueue.main.async {
                        self.image = UIImage(data: data!)
                    }
                }
            }
            task.resume()
            self.task = task
        }
    
        func cancel() {
            if let task = task {
                task.cancel()
            }
        }
    }
    
    class API {
        static func fetchData(completion: @escaping (User) -> Void) {
            let request = URLRequest(url: URL(string: "https://randomuser.me/api/")!)
            let task = URLSession.shared.dataTask(with: request) { (data, _, error) in
                guard error == nil else { return }
    
                do {
                    let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any]
                    guard
                        let results = json!["results"] as? [[String: Any]],
                        let nameDict = results.first!["name"] as? [String: String],
                        let pictureDict = results.first!["picture"] as? [String: String]
                        else { return }
    
                    let name = "\(nameDict["last"]!) \(nameDict["first"]!)"
                    let imageUrl = pictureDict["thumbnail"]
                    let user = User(name: name, imageUrl: imageUrl!)
                    DispatchQueue.main.async {
                        completion(user)
                    }
                } catch let error {
                    print(error.localizedDescription)
                }
            }
            task.resume()
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-06-10
      • 1970-01-01
      • 2017-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多