ios 如何将UIImage异步加载到SwiftUI Image中?

xbp102n0  于 2023-11-19  发布在  iOS
关注(0)|答案(7)|浏览(110)

在SwiftUI中,有一些.init方法可以创建Image,但它们都不允许从网络/缓存加载UIImage的块或任何其他方式。
我使用Kingfisher从网络加载图像并缓存在列表行中,但在视图中绘制图像的方法是重新渲染它,我不希望这样做。此外,我在获取图像时创建了一个假图像(仅彩色)作为占位符。另一种方法是将所有内容 Package 在自定义视图中,只重新渲染 Package 器。但我还没有尝试过。
这个例子是工作的权利现在。任何想法,以改善目前的一个将是伟大的
使用加载器的一些视图

struct SampleView : View {

    @ObjectBinding let imageLoader: ImageLoader

    init(imageLoader: ImageLoader) {
        self.imageLoader = imageLoader
    }

    var body: some View {
       Image(uiImage: imageLoader.image(for: "https://url-for-image"))
          .frame(width: 128, height: 128)
          .aspectRatio(contentMode: ContentMode.fit)
    }

}

个字符

nfeuvbwi

nfeuvbwi1#

SwiftUI 3

从iOS 15开始,我们现在可以使用AsyncImage

AsyncImage(url: URL(string: "https://example.com/icon.png")) { image in
    image.resizable()
} placeholder: {
    ProgressView()
}
.frame(width: 50, height: 50)

字符串

SwiftUI 2

这里是一个原生的SwiftUI解决方案,支持缓存和多种加载状态:

import Combine
import SwiftUI

struct NetworkImage: View {
    @StateObject private var viewModel = ViewModel()

    let url: URL?

    var body: some View {
        Group {
            if let data = viewModel.imageData, let uiImage = UIImage(data: data) {
                Image(uiImage: uiImage)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            } else if viewModel.isLoading {
                ProgressView()
            } else {
                Image(systemName: "photo")
            }
        }
        .onAppear {
            viewModel.loadImage(from: url)
        }
    }
}
extension NetworkImage {
    class ViewModel: ObservableObject {
        @Published var imageData: Data?
        @Published var isLoading = false

        private static let cache = NSCache<NSURL, NSData>()

        private var cancellables = Set<AnyCancellable>()

        func loadImage(from url: URL?) {
            isLoading = true
            guard let url = url else {
                isLoading = false
                return
            }
            if let data = Self.cache.object(forKey: url as NSURL) {
                imageData = data as Data
                isLoading = false
                return
            }
            URLSession.shared.dataTaskPublisher(for: url)
                .map { $0.data }
                .replaceError(with: nil)
                .receive(on: DispatchQueue.main)
                .sink { [weak self] in
                    if let data = $0 {
                        Self.cache.setObject(data as NSData, forKey: url as NSURL)
                        self?.imageData = data
                    }
                    self?.isLoading = false
                }
                .store(in: &cancellables)
        }
    }
}

的数据
(The上面的代码没有使用任何第三方库,所以很容易以任何方式更改NetworkImage

Demo


的数据

import Combine
import SwiftUI

struct ContentView: View {
    @State private var showImage = false

    var body: some View {
        if showImage {
            NetworkImage(url: URL(string: "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png"))
                .frame(maxHeight: 150)
                .padding()
        } else {
            Button("Load") {
                showImage = true
            }
        }
    }
}


(我使用了一个非常大的Stack Overflow徽标来显示加载状态。

uemypmqf

uemypmqf2#

将Model传递给包含url的ImageRow结构。

import SwiftUI
import Combine

struct ContentView : View {
    var listData: Post
    var body: some View {
        List(model.post) { post in
            ImageRow(model: post) // Get image
        }
    }
}

/********************************************************************/
// Download Image

struct ImageRow: View {
    let model: Post
    var body: some View {
        VStack(alignment: .center) {
            ImageViewContainer(imageUrl: model.avatar_url)
        }
    }
}

struct ImageViewContainer: View {
    @ObjectBinding var remoteImageURL: RemoteImageURL

    init(imageUrl: String) {
        remoteImageURL = RemoteImageURL(imageURL: imageUrl)
    }

    var body: some View {
        Image(uiImage: UIImage(data: remoteImageURL.data) ?? UIImage())
            .resizable()
            .clipShape(Circle())
            .overlay(Circle().stroke(Color.black, lineWidth: 3.0))
            .frame(width: 70.0, height: 70.0)
    }
}

class RemoteImageURL: BindableObject {
    var didChange = PassthroughSubject<Data, Never>()
    var data = Data() {
        didSet {
            didChange.send(data)
        }
    }
    init(imageURL: String) {
        guard let url = URL(string: imageURL) else { return }

        URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard let data = data else { return }

            DispatchQueue.main.async { self.data = data }

            }.resume()
    }
}
/********************************************************************/

字符串

y1aodyip

y1aodyip3#

在SwiftUI中加载图像的一种更简单、更干净的方法是使用著名的Kingfisher库。
1.通过Swift Package Manager添加Kingfisher
选择File > Swift Packages > Add Package Dependent。输入https://github.com/onevcat/Kingfisher.git
在“选择包存储库”对话框中。在下一页中,指定版本解析规则为“Up to Next Major”,最早版本为“5.8.0”。
在Xcode checkout 源代码并解析版本之后,您可以选择“Kingdom SwiftUI”库并将其添加到您的应用目标。

  1. import KingfisherSwiftUI
  2. KFImage(myUrl)
    就这么简单
byqmnocz

byqmnocz4#

我会使用onAppear回调函数

import Foundation
import SwiftUI
import Combine
import UIKit
    struct ImagePreviewModel {
        var urlString : String
        var width : CGFloat = 100.0
        var height : CGFloat = 100.0
    }

    struct ImagePreview: View {
        let viewModel: ImagePreviewModel
        @State var initialImage = UIImage()
        var body: some View {
            Image(uiImage: initialImage)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: self.width, height: self.height)
                .onAppear {
                    guard let url = URL(string: self.viewModel.urlString) else { return }
                    URLSession.shared.dataTask(with: url) { (data, response, error) in
                        guard let data = data else { return }
                        guard let image = UIImage(data: data) else { return }

                        RunLoop.main.perform {
                            self.initialImage = image
                        }

                    }.resume()
                }
        }
        var width: CGFloat { return max(viewModel.width, 100.0) }
        var height: CGFloat { return max(viewModel.height, 100.0) }
    }

字符串

wbrvyc0a

wbrvyc0a5#

imageLoader定义为@ObjectBinding

@ObjectBinding private var imageLoader: ImageLoader

字符串
使用图像的url初始化视图会更有意义:

struct SampleView : View {

    var imageUrl: URL

    private var image: UIImage {
        imageLoader.image(for: imageUrl)
    }

    @ObjectBinding private var imageLoader: ImageLoader

    init(url: URL) {
        self.imageUrl = url
        self.imageLoader = ImageLoader()
    }

    var body: some View {
        Image(uiImage: image)
            .frame(width: 200, height: 300)
            .aspectRatio(contentMode: ContentMode.fit)
    }
}


举例来说,您可以:

//Create a SampleView with an initial photo
var s = SampleView(url: URL(string: "https://placebear.com/200/300")!)
//You could then update the photo by changing the imageUrl
s.imageUrl = URL(string: "https://placebear.com/200/280")!

lqfhib0f

lqfhib0f6#

import SwiftUI
struct UrlImageView: View {
@ObservedObject var urlImageModel: UrlImageModel

init(urlString: String?) {
    urlImageModel = UrlImageModel(urlString: urlString)
}

var body: some View {
    Image(uiImage: urlImageModel.image ?? UrlImageView.defaultImage!)
        .resizable()
        .scaledToFill()
}

static var defaultImage = UIImage(systemName: "photo")
}

class UrlImageModel: ObservableObject {
@Published var image: UIImage?
var urlString: String?

init(urlString: String?) {
    self.urlString = urlString
    loadImage()
}

func loadImage() {
    loadImageFromUrl()
}

func loadImageFromUrl() {
    guard let urlString = urlString else {
        return
    }
    
    let url = URL(string: urlString)!
    let task = URLSession.shared.dataTask(with: url, completionHandler: 
getImageFromResponse(data:response:error:))
    task.resume()
}

func getImageFromResponse(data: Data?, response: URLResponse?, error: Error?) 
{
    guard error == nil else {
        print("Error: \(error!)")
        return
    }
    guard let data = data else {
        print("No data found")
        return
    }
    
    DispatchQueue.main.async {
        guard let loadedImage = UIImage(data: data) else {
            return
        }
        self.image = loadedImage
    }
}
}

字符串
像这样使用:

UrlImageView(urlString: "https://developer.apple.com/assets/elements/icons/swiftui/swiftui-96x96_2x.png").frame(width:100, height:100)

qmelpv7a

qmelpv7a7#

随着iOS 15和macOS 12在2021年的发布,SwiftUI提供了原生AsyncImage视图,可以异步加载图像。请记住,您仍然需要回退到早期OS版本的自定义实现。

AsyncImage(url: URL(string: "https://example.com/tile.png"))

字符串
API本身还提供了各种方式来自定义图像或提供占位符,例如:

AsyncImage(url: URL(string: "https://example.com/tile.png")) { image in
    image.resizable(resizingMode: .tile)
} placeholder: {
    Color.green
}


更多关于Apple Developer Documentation.

相关问题