在SwiftUI中,有一些.init创建图像的方法,但是它们都不允许使用块或任何其他方式从网络/缓存加载UIImage ...
我正在使用Kingfisher从网络加载图像并在列表行中缓存,但是在视图中绘制图像的方法是再次重新渲染它,我不想这样做。另外,在获取图像时,我正在创建一个假图像(仅彩色)作为占位符。另一种方法是将所有内容包装在自定义视图中,然后仅重新渲染包装器。但是我还没有尝试过。
此示例现在正在工作。任何改善当前想法的想法都会很棒
使用加载程序的某些视图
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)
}
}
Run Code Online (Sandbox Code Playgroud)
import UIKit.UIImage
import SwiftUI
import Combine
import class Kingfisher.ImageDownloader
import struct Kingfisher.DownloadTask
import class Kingfisher.ImageCache
import class Kingfisher.KingfisherManager
class ImageLoader: BindableObject {
var didChange = PassthroughSubject<ImageLoader, Never>()
private let downloader: ImageDownloader
private let cache: ImageCache
private var image: UIImage? {
didSet {
dispatchqueue.async { [weak self] in
guard let self = self else { return }
self.didChange.send(self)
}
}
}
private var task: DownloadTask?
private let dispatchqueue: DispatchQueue
init(downloader: ImageDownloader = KingfisherManager.shared.downloader,
cache: ImageCache = KingfisherManager.shared.cache,
dispatchqueue: DispatchQueue = DispatchQueue.main) {
self.downloader = downloader
self.cache = cache
self.dispatchqueue = dispatchqueue
}
deinit {
task?.cancel()
}
func image(for url: URL?) -> UIImage {
guard let targetUrl = url else {
return UIImage.from(color: .gray)
}
guard let image = image else {
load(url: targetUrl)
return UIImage.from(color: .gray)
}
return image
}
private func load(url: URL) {
let key = url.absoluteString
if cache.isCached(forKey: key) {
cache.retrieveImage(forKey: key) { [weak self] (result) in
guard let self = self else { return }
switch result {
case .success(let value):
self.image = value.image
case .failure(let error):
print(error.localizedDescription)
}
}
} else {
downloader.downloadImage(with: url, options: nil, progressBlock: nil) { [weak self] (result) in
guard let self = self else { return }
switch result {
case .success(let value):
self.cache.storeToDisk(value.originalData, forKey: url.absoluteString)
self.image = value.image
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
paw*_*222 17
从 iOS 15 开始我们现在可以使用AsyncImage:
AsyncImage(url: URL(string: "https://example.com/icon.png")) { image in
image.resizable()
} placeholder: {
ProgressView()
}
.frame(width: 50, height: 50)
Run Code Online (Sandbox Code Playgroud)
这是一个支持缓存和多种加载状态的原生 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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
(上面的代码没有使用任何第三方库,因此很容易NetworkImage以任何方式进行更改。)
演示
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
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
(我使用了一个特别大的 Stack Overflow 徽标来显示加载状态。)
将您的模型传递给包含 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()
}
}
/********************************************************************/
Run Code Online (Sandbox Code Playgroud)
在 SwiftUI 中加载图像的一种更简单、更清晰的方法是使用著名的 Kingfisher 库。
Kingfisher通过 Swift 包管理器添加选择文件 > Swift 包 > 添加包依赖项。输入 https://github.com/onevcat/Kingfisher.git
在“选择包存储库”对话框中。在下一页中,指定版本解析规则为“Up to Next Major”,其中“5.8.0”为其最早版本。
Xcode 检查源代码并解析版本后,您可以选择“KingfisherSwiftUI”库并将其添加到您的应用程序目标中。
import KingfisherSwiftUIKFImage(myUrl)完毕!就是这么简单
定义imageLoader为@ObjectBinding:
@ObjectBinding private var imageLoader: ImageLoader
Run Code Online (Sandbox Code Playgroud)
使用图像的 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)
}
}
Run Code Online (Sandbox Code Playgroud)
例如 :
//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")!
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3078 次 |
| 最近记录: |