无法在SwiftUI列表中更改LPLinkView的帧大小

11

我试图在SwiftUI列表中显示富链接,但无论我尝试什么,似乎都无法改变链接视图(UIViewRepresentable)在屏幕上的大小。

是否有特定链接的最小大小?如何获取它。添加.aspectRatio和clipped()将尊重大小,但链接会被大量剪切。不确定为什么链接不能调整aspectRatio以适应视图。

以下代码部分取自以下教程:https://www.appcoda.com/linkpresentation-framework/

我正在使用以下UIViewRepresentable作为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) {

    }
}

我对List的看法是:

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()
        }
    }
}

设置视图的框架,或contentMode(.fit),或填充,或任何其他我尝试过的东西都不能改变LinkViewRepresentable的框架大小。我尝试在更新中的可代表对象上使用sizeToFit,但没有成功。是否可能在这里控制可代表视图的大小?

以下是附加文件:

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)
            }
        }
    }

}

和链接类:

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")
    }
}
这是我的结果:

这就是我得到的:

在此输入图片描述


你是什么意思? - alionthego
你的代码对我有效(需要进行一些小修改以适应我的简单测试,因为并非所有信息都可用)。 例如将.frame(width: 100)更改为.frame(width: 300)就可以正常运行。 在macOS 12.1-beta上,使用xcode 13.2-beta。在真实设备,iOS-15,Catalyst-12.1上测试通过。 - workingdog support Ukraine
谢谢你的帮助。我会继续努力,直到我找到如何控制布局的方法。我已经成功地使用nslayout约束在represebtsvkr的makeview中使其工作。但是,在那里使用自动布局会导致其他问题来定位视图。 - alionthego
1
我找到的唯一解决方法是设置明确的高度和宽度,但这对我来说并不真正有效,因为我只想设置宽度。但如果这对你有用,我是通过子类化linkview并设置override intrinsicContenySize并在那里定义大小来实现的。 - alionthego
1
当然。我已经将其作为下面的答案包含了,尽管它对我来说并不真正有效,因为我需要一个动态高度。 - alionthego
显示剩余12条评论
3个回答

9
对我有效的解决方案是子类化linkView并覆盖内在内容大小。感谢user1046037的评论,使用super.intrinsicContentSize.height将使其以动态方式工作。
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


1
不错!你能做两个小改动吗?你的代码会崩溃,我必须使用URL进行初始化(以防止崩溃),并且你可以返回 CGSize(width: 0, height: super.intrinsicContentSize.height) 而不是硬编码大小。这将使其具有动态性。干得好! - user1046037
1
感谢您的评论。您真的帮助我解决了我一直苦苦挣扎的动态高度问题,现在它非常好用。我不确定您提到的使用URL初始化时是否会崩溃。我的没有崩溃。您是指使用URL而不是元数据来初始化CustomLinkView吗?我的使用元数据初始化也能正常工作。 - alionthego
哦,好的,也许我使用的方式略有不同,无论如何,授予您赏金需要24小时,我会在24小时内完成。再次做得很好。 - user1046037
1
还要感谢您帮忙完善并使其正常工作。 - alionthego
2
我刚刚遇到了你提到的关于元数据的崩溃问题。当元数据不为nil但元数据的url属性为nil时会发生这种情况。目前,我只是添加了一个条件性的nil检查来解决这个问题。 - alionthego
显示剩余3条评论

0

这是我在SwiftUI中的LinkPresentation代码示例

import SwiftUI
import LinkPresentation

自定义类,用于调整内部内容大小

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

ViewModel 类(不是 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


0
所以我与你分享我的选择。我刚刚使用LPMetadataProvider的帮助创建了可定制的、自定义的URL预览,专为SwiftUI设计。
这仅用于预览目的,我没有添加在Safari中打开的选项(否则很容易实现)。

enter image description here

我为我的视图创建了一个Observable对象,也就是视图模型。在其中,我传递了URL,并且检索所需部分的元数据(图片、标题、主机)。
final class PreviewViewModel: ObservableObject {

@Published var image: UIImage?
@Published var title: String?
@Published var url: String?

let previewURL: URL?

init(_ url: String) {
    self.previewURL = URL(string: url)
    
    initMetada()
}

private func initMetada() {
    guard let previewURL else { return }
    let provider = LPMetadataProvider()
    
    Task {
        let metadata = try await provider.startFetchingMetadata(for: previewURL)
        
        image = try await convertToImage(metadata.imageProvider)
        title = metadata.title
        
        if #available(iOS 16, *) {
            url = metadata.url?.host()
        } else {
            url = metadata.url?.host
        }
    }
}

private func convertToImage(_ imageProvider: NSItemProvider?) async throws -> UIImage? {
    var image: UIImage?
    
    if let imageProvider {
        let type = String(describing: UTType.image)
        
        if imageProvider.hasItemConformingToTypeIdentifier(type) {
            let item = try await imageProvider.loadItem(forTypeIdentifier: type)
            
            if item is UIImage {
                image = item as? UIImage
            }
            
            if item is URL {
                guard let url = item as? URL,
                      let data = try? Data(contentsOf: url) else { return nil }
                
                image = UIImage(data: data)
            }
            
            if item is Data {
                guard let data = item as? Data else { return nil }
                
                image = UIImage(data: data)
            }
        }
    }
    
    return image
}
}

根据我刚刚采用的视图模型,为了符合我的偏好,设置了图片、标题等内容。
    struct FileLinkView: View {
    
    @ObservedObject var viewModel: PreviewViewModel
    
    let action: () -> ()
    
    var body: some View {
        ZStack(alignment: .topTrailing) {
            HStack(spacing: 15) {
                if let image = viewModel.image {
                    Image(uiImage: image)
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(maxWidth: 107, maxHeight: 107)
                        .clipped()
                        .cornerRadius(16)
                }
                
                VStack(alignment: .leading, spacing: 1, content: {
                    if let title = viewModel.title {
                        Text(title)
                            .font(.CreatePost.previewTitle)
                            .foregroundColor(.codGray)
                    }
                    
                    if let url = viewModel.url {
                        Text(url)
                            .font(.smallFont)
                            .foregroundColor(.santasGray)
                    }
                })
                .padding(.top, 16)
                .padding(.bottom, 9)
                .padding(.trailing, 40)
            }
            
            Button(action: action, label: {
                Image.CreatePost.deleteIcon
            })
            .padding([.top, .trailing], 8)
        }
    }
}

    struct FileLinkView_Previews: PreviewProvider {
        static var previews: some View {
            FileLinkView(
                viewModel: PreviewViewModel("https://www.f1news.ru/news/f1-168537.html"),
                action: {}
            )
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接