因此,我正尝试使用从我的Node JS服务器获取的数据来创建内容供稿。
我在这里从我的API中获取数据
class Webservice {
func getAllPosts(completion: @escaping ([Post]) -> ()) {
guard let url = URL(string: "http://localhost:8000/albums")
else {
fatalError("URL is not correct!")
}
URLSession.shared.dataTask(with: url) { data, _, _ in
let posts = try!
JSONDecoder().decode([Post].self, from: data!); DispatchQueue.main.async {
completion(posts)
}
}.resume()
}
}
将变量设置为从API获取的数据
final class PostListViewModel: ObservableObject {
init() {
fetchPosts()
}
@Published var posts = [Post]()
private func fetchPosts() {
Webservice().getAllPosts {
self.posts = $0
}
}
}
struct Post: Codable, Hashable, Identifiable {
let id: String
let title: String
let path: String
let description: String
}
SwiftUI
struct ContentView: View {
@ObservedObject var model = PostListViewModel()
var body: some View {
List(model.posts) { post in
HStack {
Text(post.title)
Image("http://localhost:8000/" + post.path)
Text(post.description)
}
}
}
}
来自post.title
和post.description
的文本可以正确显示,但来自Image()
的文本则不显示。如何使用服务器中的URL与图像一起显示?
答案 0 :(得分:10)
试试这个实现:
AsyncImage(url: URL(string: "http://mydomain/image.png")!,
placeholder: { Text("Loading ...") },
image: { Image(uiImage: $0).resizable() })
.frame(idealHeight: UIScreen.main.bounds.width / 2 * 3) // 2:3 aspect ratio
看起来很简单吧? 该函数可以将图像保存在缓存中,也可以发出异步图像请求。
现在,将其复制到一个新文件中:
import Foundation
import SwiftUI
import UIKit
import Combine
struct AsyncImage<Placeholder: View>: View {
@StateObject private var loader: ImageLoader
private let placeholder: Placeholder
private let image: (UIImage) -> Image
init(
url: URL,
@ViewBuilder placeholder: () -> Placeholder,
@ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
) {
self.placeholder = placeholder()
self.image = image
_loader = StateObject(wrappedValue: ImageLoader(url: url, cache: Environment(\.imageCache).wrappedValue))
}
var body: some View {
content
.onAppear(perform: loader.load)
}
private var content: some View {
Group {
if loader.image != nil {
image(loader.image!)
} else {
placeholder
}
}
}
}
protocol ImageCache {
subscript(_ url: URL) -> UIImage? { get set }
}
struct TemporaryImageCache: ImageCache {
private let cache = NSCache<NSURL, UIImage>()
subscript(_ key: URL) -> UIImage? {
get { cache.object(forKey: key as NSURL) }
set { newValue == nil ? cache.removeObject(forKey: key as NSURL) : cache.setObject(newValue!, forKey: key as NSURL) }
}
}
class ImageLoader: ObservableObject {
@Published var image: UIImage?
private(set) var isLoading = false
private let url: URL
private var cache: ImageCache?
private var cancellable: AnyCancellable?
private static let imageProcessingQueue = DispatchQueue(label: "image-processing")
init(url: URL, cache: ImageCache? = nil) {
self.url = url
self.cache = cache
}
deinit {
cancel()
}
func load() {
guard !isLoading else { return }
if let image = cache?[url] {
self.image = image
return
}
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: $0.data) }
.replaceError(with: nil)
.handleEvents(receiveSubscription: { [weak self] _ in self?.onStart() },
receiveOutput: { [weak self] in self?.cache($0) },
receiveCompletion: { [weak self] _ in self?.onFinish() },
receiveCancel: { [weak self] in self?.onFinish() })
.subscribe(on: Self.imageProcessingQueue)
.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.image = $0 }
}
func cancel() {
cancellable?.cancel()
}
private func onStart() {
isLoading = true
}
private func onFinish() {
isLoading = false
}
private func cache(_ image: UIImage?) {
image.map { cache?[url] = $0 }
}
}
struct ImageCacheKey: EnvironmentKey {
static let defaultValue: ImageCache = TemporaryImageCache()
}
extension EnvironmentValues {
var imageCache: ImageCache {
get { self[ImageCacheKey.self] }
set { self[ImageCacheKey.self] = newValue }
}
}
完成!
答案 1 :(得分:9)
首先,您需要从url获取图像:
class ImageLoader: ObservableObject {
var didChange = PassthroughSubject<Data, Never>()
var data = Data() {
didSet {
didChange.send(data)
}
}
init(urlString:String) {
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
self.data = data
}
}
task.resume()
}
}
您也可以将其作为Webservice类功能的一部分。
然后在ContentView结构中,您可以通过以下方式设置@State图片:
struct ImageView: View {
@ObservedObject var imageLoader:ImageLoader
@State var image:UIImage = UIImage()
init(withURL url:String) {
imageLoader = ImageLoader(urlString:url)
}
var body: some View {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width:100, height:100)
.onReceive(imageLoader.didChange) { data in
self.image = UIImage(data: data) ?? UIImage()
}
}
}
此外,如果您需要更多tutorial,这是一个很好的参考
答案 2 :(得分:3)
iOS 15 中的新功能,SwiftUI
有一个专用的 AsyncImage
,用于从 Internet 下载和显示远程图像。以最简单的形式,您可以只传递一个 URL,如下所示:
AsyncImage(url: URL(string: "https://www.thiscoolsite.com/img/nice.png"))
答案 3 :(得分:1)
SwiftUI
现在有一个 AsyncImage
类。AsyncImage
使我们能够从 URL 下载图像,而无需与 URLSession
交互。但是,Apple 建议在等待时使用占位符,而不是简单地下载图像并在加载时显示空图像。
如果需要,我们可以使用 transaction:
添加动画并更改基础 Image
属性。例如不同的纵横比模式或使图像可调整大小。
这是一个例子:
AsyncImage(
url: "https://dogecoin.com/assets/img/doge.png",
transaction: .init(animation: .easeInOut)
) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
}. placeholder: {
Color.gray
}
.frame(width: 500, height: 500)
.mask(RoundedRectangle(cornerRadius: 16)
要在请求失败或正在进行时显示不同的视图,我们可以使用阶段。这会在加载期间和请求完成后更新视图。
AsyncImage(url: url, transaction: .init(animation: .spring())) { phase in
switch phase {
case .empty:
randomPlaceholderColor()
.opacity(0.2)
.transition(.opacity.combined(with: .scale))
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.transition(.opacity.combined(with: .scale))
case .failure(let error):
ErrorView(error)
@unknown default:
ErrorView()
}
}
.frame(width: 400, height: 266)
.mask(RoundedRectangle(cornerRadius: 16))
对于需要从 URL 加载图像的所有实例,我们不应使用 AsyncImage
。相反,当需要根据请求下载图像时,最好使用 .refreshable
或 .task
修饰符。在这里,Apple 建议使用 await
来防止阻塞主线程 (Swift 5.5)。