如何在SwiftUI中的小部件中使用LinkPresentation框架来显示URL图像预览?

如何在SwiftUI中的小部件中使用LinkPresentation框架来显示URL图像预览?,swift,swiftui,Swift,Swiftui,下面是WidgetExtension的示例代码,它应该显示使用LinkPresentation检索到的链接预览图像。我猜这些小部件不会直接加载url数据,但需要使用geTimeline以某种方式获取并在那里定义?怎么做的 在Xcode中使用标准小部件模板只是添加元数据 import WidgetKit import SwiftUI import LinkPresentation struct SimpleEntry: TimelineEntry { let date: Date }

下面是WidgetExtension的示例代码,它应该显示使用LinkPresentation检索到的链接预览图像。我猜这些小部件不会直接加载url数据,但需要使用geTimeline以某种方式获取并在那里定义?怎么做的

在Xcode中使用标准小部件模板只是添加元数据

import WidgetKit
import SwiftUI
import LinkPresentation

struct SimpleEntry: TimelineEntry {
    let date: Date
}

struct LinkPresentationWidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        //Text(entry.date, style: .time)
        VStack{
            MetadataView(vm: LinkViewModel(link: "https://www.wsj.com/articles/global-stock-markets-dow-update-03-03-2021-11614761029?mod=markets_lead_pos1"))
        }
    }
}




class LinkViewModel : ObservableObject {
    let metadataProvider = LPMetadataProvider()
    
    @Published var metadata: LPLinkMetadata?
    @Published var image: UIImage?
    
    init(link : String) {
        guard let url = URL(string: link) else {
            return
        }
        metadataProvider.startFetchingMetadata(for: url) { (metadata, error) in
            guard error == nil else {
                assertionFailure("Error")
                return
            }
            DispatchQueue.main.async {
                self.metadata = metadata
            }
            guard let imageProvider = metadata?.imageProvider else { return }
            imageProvider.loadObject(ofClass: UIImage.self) { (image, error) in
                guard error == nil else {
                    // handle error
                    return
                }
                if let image = image as? UIImage {
                    // do something with image
                    DispatchQueue.main.async {
                        self.image = image
                    }
                } else {
                    print("no image available")
                }
            }
        }
    }
}

struct MetadataView : View {
    @StateObject var vm : LinkViewModel
    
    var body: some View {
        VStack {
            if let uiImage = vm.image {
                Image(uiImage: uiImage)
                    .resizable()
                    //.scaledToFill()
                    //.clipped()
                    .frame(width: 60, height: 60)
            }
        }
    }
}

AFAIK WidgetKit不观察
视图
s,只渲染一次,然后丢弃。因此,使用
@StateObject
的方法不起作用,因为当请求返回时,WidgetKit已经放弃了视图

正如您已经猜到的,您必须在
getSnapshot
getTimeline
中执行每个异步任务

下面是一个重用部分代码的最小工作示例。当然,您可能希望根据需要调整url的配置、错误处理和时间线策略

请注意,我不知道这是否被视为最佳实践……我只知道它有效:)

导入WidgetKit
导入快捷键
进口意向
导入链接演示文稿
结构提供程序:IntentTimelineProvider{
func占位符(在上下文中:context)->SimpleEntry{
SimpleEntry(日期:date(),配置:ConfigurationContent(),图像:UIImage(系统名:“wifi.slash”))
}
func getSnapshot(对于配置:configurationcontent,在上下文:context中,完成:@escaping(SimpleEntry)->()){
获取图像(用于:https://www.wsj.com/articles/global-stock-markets-dow-update-03-03-2021-11614761029?mod=markets_lead_pos1,调用:{image in
let entry=SimpleEntry(日期:date(),配置:配置,映像:映像)
完成(进入)
})
}
func getTimeline(对于配置:configurationcontent,在上下文:context中,完成:@escaping(Timeline)->()){
getSnapshot(对于:配置,在:上下文中,完成:{entry in
let timeline=timeline(条目:[entry],策略:。在(Date().advanced(by:3600))之后)
完成(时间表)
})
}
}
结构SimpleEntry:TimelineEntry{
日期:日期
let配置:配置内容
让图像:UIImage?
}
结构WidgetEntryView:视图{
var条目:Provider.entry
var body:一些观点{
如果let image=entry.image{
Image(uiImage:Image).resizeable().scaledToFill()
}否则{
文本(“无法加载图像…”)
}
}
}
func fetchImage(对于url:String,调用回调:@escaping(UIImage)->Void={uin}){
让metadataProvider=LPMetadataProvider()
guard let url=url(字符串:url)else{
返回
}
metadataProvider.startFetchingMetadata(for:url){(元数据,错误)位于
保护错误==nil else{
断言失败(“错误”)
返回
}
guard let imageProvider=元数据?.imageProvider else{return}
装入对象(类:UIImage.self){(图像,错误)在
保护错误==nil else{
//处理错误
返回
}
如果让图像=图像为UIImage{
//用图像做点什么
DispatchQueue.main.async{
回调(图像)
}
}否则{
打印(“无可用图像”)
}
}
}
}
@主要
struct MyWidget:Widget{
让种类:String=“Widget”
变量主体:一些WidgetConfiguration{
IntentConfiguration(种类:种类,意图:ConfigurationContent.self,提供程序:提供程序()){中的条目
WidgetEntryView(条目:条目)
}
.configurationDisplayName(“我的小部件”)
.description(“这是一个示例小部件”)
}
}

这很有效!如果您的模型是一个多维模型,比如JSON数组,它有一个URL列表,您希望从中获取图像,那么您知道如何修改这个模型吗?struct Model:TimelineEntry{var date:date var widgetData:[JSONModel]}我不太确定我是否理解正确……您想获取一个常量URL,该URL用JSON响应,其中包含指向某些图像的n个URL列表,并想在小部件上显示这n个图像吗?在这种情况下,您仍然必须在
getSnapshot
getTimeline
中完成所有工作。首先使用类似于
fetchJSON(url,onComplete:{imageURLs in…})的内容获取json。在回调中,您必须使用
fetchImage`并计算已经触发了多少次回调。一旦您的计数器到达imageURLs.count,您就完成了,可以调用
getSnapshot
completion
回调。是的,这就是我正在尝试做的。我在getTimeline中获取了包含JSON中URL的数据,对于这些URL,我需要图像。我已将var images:[UIImage]添加到我的模型/时间线条目中,在将数据发布到时间线完成中之前,我正在尝试更新每个url的模型以获取图像。您能帮我查看我的代码并修复它吗?您可以通过twitter(at)a3igner的一些代码与我取得联系,这些代码应该适合您的需求,但您必须自己调整它以适应您的业务逻辑。