3

According to WWDC20 and other articles it seems its quite easy to fetch the image url from a given url. Below is my starter code. That just lists a random list of urls and is supposed to fetch the imageurl for the rich link previews of the urls. One simply fetches the metadata using the LPMetadataProvider. But i can't get it to show the image url. Does someone know how its done in SwiftUI?

import SwiftUI
import LinkPresentation

struct ExampleHTTPLinks: View {
    var links = [ "https://www.google.com", "https://www.hotmail.com"]
    let metadataProvider = LPMetadataProvider()
    
    
    var body: some View {
        
        
        List(links, id:\.self) { item in
            HStack {
                Text(item)
                Image(systemName: "heart.fill")
                metadataProvider.startFetchingMetadata(for: URL(string: item)!) { metadata, error in
                  if error != nil {
                    // The fetch failed; handle the error.
                    // Examples: server doesn't respond, is too slow, user doesn't have network.
                    return
                  }
                 let linkView = LPLinkView(metadata: metadata)
                    Image(linkView.image)
                  // Make use of the fetched metadata.
                }
            }
        }
    }
}

struct ExampleHTTPLinks_Previews: PreviewProvider {
    static var previews: some View {
        ExampleHTTPLinks()
    }
}
AndiAna
  • 854
  • 6
  • 26

1 Answers1

4

Here's a working version:


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 metadata = vm.metadata {
                Text(metadata.title ?? "")
            }
            if let uiImage = vm.image {
                Image(uiImage: uiImage)
                    .resizable()
                    .frame(width: 100, height: 100)
            }
        }
    }
}

struct ContentView: View {
    var links = [ "https://www.google.com", "https://www.hotmail.com"]
    let metadataProvider = LPMetadataProvider()
    
    var body: some View {
        List(links, id:\.self) { item in
            HStack {
                Text(item)
                Image(systemName: "heart.fill")
                MetadataView(vm: LinkViewModel(link: item))
            }
        }
    }
}

LPMetadataProvider complains if you try to use it for multiple calls, so I've moved it to a view model.

The image is vended by an NSImageProvider -- you can see the loadObject call is what gets the UIImage out of it.

Note that you could use LPLinkView if you wanted to use the out-of-the-box presentation that Apple gives you. Because it's a UIView, to use it in SwiftUI, you'd have to wrap it with UIViewRepresentable:

struct LPLinkViewRepresented: UIViewRepresentable {
    var metadata: LPLinkMetadata
    
    func makeUIView(context: Context) -> LPLinkView {
        return LPLinkView(metadata: metadata)
    }
    
    func updateUIView(_ uiView: LPLinkView, context: Context) {
        
    }
}

struct MetadataView : View {
    @StateObject var vm : LinkViewModel
    
    var body: some View {
        if let metadata = vm.metadata {
            LPLinkViewRepresented(metadata: metadata)
        } else {
            EmptyView()
        }
    }
}

class LinkViewModel : ObservableObject {
    let metadataProvider = LPMetadataProvider()
    
    @Published var metadata: LPLinkMetadata?
    
    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
            }
        }
    }
}
jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • 1
    Great. This was a good thing to learn about -- I need to implement it in one of my projects as well. – jnpdx Mar 02 '21 at 07:02
  • do you know how to print the imageurl? Text(metadata.imageurl ?? "") doesnt work. – AndiAna Mar 02 '21 at 07:08
  • 1
    I looked at documentation for `LPLinkMetadata`, `LPLinkView`, `NSItemProvider`, etc, and didn't see anything obvious for actually getting the URL. My sense is you're probably going to have to do the http request yourself and parse out the image url if you need that specifically. Otherwise, `NSItemProvider` does the work behind the scenes to get the image without giving you access to that info. – jnpdx Mar 02 '21 at 07:13
  • Ok i guess so. But does this maybe help? metaData.imageProvider = NSItemProvider(contentsOf: URL(fileURLWithPath: path ?? "")) as described in this article? https://betterprogramming.pub/linkpresentation-in-ios-13-bbb6007818c6 – AndiAna Mar 02 '21 at 07:17
  • 1
    No, that’s kind of going backwards. That’s if you want to revise your own image for the link. – jnpdx Mar 02 '21 at 07:18
  • yeah right i see. its when you want to set it manually. – AndiAna Mar 02 '21 at 07:19
  • I am trying this code using it in a widget. But its not working there. Do you have any idea how to get it to work in a widget? – AndiAna Mar 03 '21 at 09:54
  • see this thread i opened : https://stackoverflow.com/questions/66455246/how-to-use-the-linkpresentation-framework-inside-a-widget-in-swiftui-to-show-an – AndiAna Mar 03 '21 at 10:33
  • Sorry -- I have no experience with Widgets. – jnpdx Mar 03 '21 at 17:47
  • @jnpdx thank for this answer. When i using your code, i am getting the following errors in my console: `com.apple.WebKit.WebContent: 113: Could not find specified service` How can i solve this? – Niklas Sep 16 '21 at 13:06
  • @Niklas I’m just using Apple APIs, so there’s not much that can be changed. Googling that message came up with this: https://stackoverflow.com/questions/44585980/com-apple-webkit-webcontent-drops-113-error-could-not-find-specified-service Try everything there — is start with the app transport settings. – jnpdx Sep 16 '21 at 14:16