skip to Main Content

I am trying to display rich links in a SwiftUI List and no matter what I try, I can’t seem to be able to change the size of the link view (UIViewRepresentable) on screen.

Is there a minimum size for a particular link? And how can I get it. Adding .aspectRatio and clipped() will respect size but the link is heavily clipped. Not sure why the link will not adjust aspectRatio to fit view.

Some of the following code is sourced from the following tutorial:
https://www.appcoda.com/linkpresentation-framework/

I am using the following UIViewRepresentable for the LinkView:

import SwiftUI
import LinkPresentation

struct LinkViewRepresentable: UIViewRepresentable {
 
    typealias UIViewType = LPLinkView
    
    var metadata: LPLinkMetadata?
 
    func makeUIView(context: Context) -> LPLinkView {
        guard let metadata = metadata else { return LPLinkView() }
        let linkView = LPLinkView(metadata: metadata)
        return linkView
    }
 
    func updateUIView(_ uiView: LPLinkView, context: Context) {

    }
}

And my view with List is:

import SwiftUI
import LinkPresentation

struct ContentView: View {
    
    @ObservedObject var linksViewModel = LinksViewModel()
    
    var links: [(String, String)] = [("https://www.apple.com", "1"), ("https://www.stackoverflow.com", "2")]
    
    var body: some View {
        ScrollView(.vertical) {
            LazyVStack {
                ForEach(links, id: .self.1) { link in
                    VStack {
                        Text(link.0)
                            .onAppear {
                                linksViewModel.getLinkMetadata(link: link)
                            }
                        if let richLink = linksViewModel.links.first(where: { $0.id == link.1 }) {
                            if let metadata = richLink.metadata {
                                if metadata.url != nil {
                                    LinkViewRepresentable(metadata: metadata)
                                        .frame(width: 200)  // setting frame dimensions here has no effect
                                }
                            }
                        }
                    }
                }
            }
            .padding()
        }
    }
}

Setting the frame of the view or contentMode(.fit) or padding or anything else I’ve tried does not change the size of the frame of the LinkViewRepresentable. I have tried sizeToFit in the representable on update and no luck. Is it possible to control the size of the representable view here?

Here are additional Files:

import Foundation
import LinkPresentation

class LinksViewModel: ObservableObject {
    
    @Published var links = [Link]()
    
    init() {
        loadLinks()
    }
    
    func createLink(with metadata: LPLinkMetadata, id: String) {
        let link = Link()
        link.id = id
        link.metadata = metadata
        links.append(link)
        saveLinks()
    }
    
    
    fileprivate func saveLinks() {
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: links, requiringSecureCoding: true)
            guard let docDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
            try data.write(to: docDirURL.appendingPathComponent("links"))
            print(docDirURL.appendingPathComponent("links"))
        } catch {
            print(error.localizedDescription)
        }
    }
    
    
    fileprivate func loadLinks() {
        guard let docDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
        let linksURL = docDirURL.appendingPathComponent("links")
        
        if FileManager.default.fileExists(atPath: linksURL.path) {
            do {
                let data = try Data(contentsOf: linksURL)
                guard let unarchived = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Link] else { return }
                links = unarchived
            } catch {
                print(error.localizedDescription)
            }
        }
    }
    
    func fetchMetadata(for link: String, completion: @escaping (Result<LPLinkMetadata, Error>) -> Void) {

        guard let uRL = URL(string: link) else { return }
        let metadataProvider = LPMetadataProvider()
        metadataProvider.startFetchingMetadata(for: uRL) { (metadata, error) in
            if let error = error {
                print(error)
                completion(.failure(error))
                return
            }
            if let metadata = metadata {
                completion(.success(metadata))
            }
        }
    }
    
    func getLinkMetadata(link: (String, String)) {
        for storedLink in self.links {
            if storedLink.id != link.1 {
                return
            }
        }
        do {
            let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
            let matches = detector.matches(in: link.0, options: [], range: NSRange(location: 0, length: link.0.utf16.count))
            if let match = matches.first {
                guard let range = Range(match.range, in: link.0) else { return }
                let uRLString = link.0[range]
                
                self.fetchMetadata(for: String(uRLString)) { result in
                    self.handleLinkFetchResult(result, link: link)
                }
            }
        } catch {
            print(error)
        }
    }
    
    private func handleLinkFetchResult(_ result: Result<LPLinkMetadata, Error>, link: (String, String)) {
        DispatchQueue.main.async {
            switch result {
                case .success(let metadata):
                self.createLink(with: metadata, id: link.1)
                case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }

}

And Link Class:

import Foundation
import LinkPresentation

class Link: NSObject, NSSecureCoding, Identifiable {
    
    var id: String?
    var metadata: LPLinkMetadata?
    
    override init() {
        super.init()
    }
    
    // MARK: - NSSecureCoding Requirements
    
    static var supportsSecureCoding = true

    func encode(with coder: NSCoder) {
        guard let id = id, let metadata = metadata else { return }
        coder.encode(id, forKey: "id")
        coder.encode(metadata as NSObject, forKey: "metadata")
    }
 
    required init?(coder: NSCoder) {
        id = coder.decodeObject(forKey: "id") as? String
        metadata = coder.decodeObject(of: LPLinkMetadata.self, forKey: "metadata")
    }
}

This is what I get:

enter image description here

2

Answers


  1. Chosen as BEST ANSWER

    The solution that worked for me was subclassing the linkView overriding the intrinsic content size. Thanks to user1046037's comment, using super.intrinsicContentSize.height will enable it to work dynamically.

    import SwiftUI
    import LinkPresentation
    
    class CustomLinkView: LPLinkView {
        override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
    }
    
    struct LinkViewRepresentable: UIViewRepresentable {
     
        typealias UIViewType = CustomLinkView
        
        var metadata: LPLinkMetadata?
     
        func makeUIView(context: Context) -> CustomLinkView {
            guard let metadata = metadata else { return CustomLinkView() }
            let linkView = CustomLinkView(metadata: metadata)
            return linkView
        }
     
        func updateUIView(_ uiView: CustomLinkView, context: Context) {
        }
    }
    

    enter image description here


  2. Here is my LinkPresentation code example in SwiftUI

    import SwiftUI
    import LinkPresentation
    

    Custom Class, to resize internal content

    class CustomLinkView: LPLinkView {
        override var intrinsicContentSize: CGSize {
            CGSize(width: super.intrinsicContentSize.width,
                   height: super.intrinsicContentSize.height)
        }
    }
    

    ViewModel Class (not the best approach for SwiftUI)

    class LPMeatDataProvider_VM: ObservableObject {
    
        private let stringURLs: [String] = [
            "https://medium.com", "https://apple.com",
            "https://yahoo.com", "https://stackoverflow.com"
        ]
    
        @Published var URLs: [URL] = []
    
        init() { getURLs() }
    
        func getURLs() {
            stringURLs.forEach { string in
                guard let url = URL(string: string) else { return }
                URLs.append(url)
           }
        }
    }
    

    View & UIViewRepresentable

    struct URLPreviewContainer: UIViewRepresentable {
    
        @Binding var togglePreview: Bool
    
        var previewURL: URL
    
        func makeUIView(context: Context) -> CustomLinkView {
            let view = CustomLinkView(url: previewURL)
            let provider = LPMetadataProvider()
            provider.startFetchingMetadata(for: previewURL) { metadata, error in
                if error == nil, let metadata = metadata {
                    DispatchQueue.main.async {
                        view.metadata = metadata
                        togglePreview.toggle()
                    }
                }
            }
            return view
        }
    
        func updateUIView(_ uiView: CustomLinkView, context: Context) {}
    }
    
    struct LPMeatDataProvider_: View {
    
        @State var togglePreview = false
        @StateObject var vm = LPMeatDataProvider_VM()
    
        var body: some View {
            ScrollView {
                LazyVStack {
                    ForEach(vm.URLs, id: .self) { _url in
                        URLPreviewContainer(togglePreview: $togglePreview, previewURL: _url)
                            .padding()
                            .padding(.horizontal)
                    }
                }
            }
        }
    }
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search