Can't change frame size of LPLinkView in a SwiftUI List

Viewed 665

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

1 Answers

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

Related