使用 SwiftUI 使自定义 UIView 方面比例适合/填充的麻烦

Posted

技术标签:

【中文标题】使用 SwiftUI 使自定义 UIView 方面比例适合/填充的麻烦【英文标题】:Trouble to make a custom UIView aspect scale fit/fill with SwiftUI 【发布时间】:2019-09-17 07:37:17 【问题描述】:

SwiftUI 中没有公共 API 来响应 View 协议的 resizable 修饰符。只有 SwiftUI 中的 Image 可以使用 .resizable()。像 UIView for GIF 这样的自定义 UIView 现在无法调整大小。

我使用 SDWebImageSwiftUI AnimatedImage,它支持 UIKit View SDAnimatedImageView。 AnimatedImage 不响应 .resizable()、.scaleToFit、.aspectRatio(contentMode: .fit) 等。WebImage 支持 SwiftUI Image,所以它工作正常。

import SwiftUI
import SDWebImageSwiftUI

struct ContentView: View 
    let url = URL(string: "https://media.giphy.com/media/H62DGtBRwgbrxWXh6t/giphy.gif")!
    var body: some View 
        VStack 
            AnimatedImage(url: url)
                .scaledToFit()
                .frame(width: 100, height: 100)
            WebImage(url: url)
                .scaledToFit()
                .frame(width: 100, height: 100)
        
    

不确定这是否是 Apple 错误。期望像 SDWebImageSwiftUI AnimatedImage 这样的自定义视图能够响应 SwiftUI 大小相关的修饰符,如 .scaledToFit()。

相关问题:https://github.com/SDWebImage/SDWebImageSwiftUI/issues/3

【问题讨论】:

您可以在View 上使用.scaledToFit,因为Image 可能嵌入在孩子的某个地方。但是ViewRepresentable 没有这个机会,因此您可能必须自己实现一个方法来设置适当的纵横比(从中返回修改后的视图)。我还没有看到任何从 EnvrionmentValues 检索纵横比的方法。 问题是 AnimatedImage(url: url) 没有 .resisable() 修饰符。 我尝试在 Rectangle 和 VStack 上使用 scaledToFit,但也没有效果。 【参考方案1】:

SwiftUI 使用抗压缩优先级和内容拥抱优先级来决定可以调整的大小。

如果您想将视图的大小调整到低于其固有内容大小,则需要降低抗压缩优先级。

例子:

func makeUIView(context: Context) -> UIView 
    let imageView = UIImageView(image: UIImage(named: "yourImage")!)
    imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
    return imageView

这将允许您将.frame(width:height:) 设置为您想要的任何大小。

【讨论】:

这和覆盖 layoutSubviews 都有效。我更喜欢这个,因为我不需要覆盖任何东西。【参考方案2】:

终于找到了解决办法。

在 SDAnimationImageView 或 UIImageView 之外创建一个 UIView 包装器,然后覆盖 layoutSubviews() 设置子视图的框架。

这是我的完整代码。

而SDWebImageSwiftUI 也发布了一个新版本,它使用 wrapper 来解决这个问题。

class ImageModel: ObservableObject 
    @Published var url: URL?
    @Published var contentMode: UIView.ContentMode = .scaleAspectFill


struct WebImage: UIViewRepresentable 
    @ObservedObject var imageModel = ImageModel()

    func makeUIView(context: UIViewRepresentableContext<WebImage>) -> ImageView 
        let uiView = ImageView(imageModel: imageModel)
        return uiView
    

    func updateUIView(_ uiView: ImageView, context: UIViewRepresentableContext<WebImage>) 
        uiView.imageView.sd_setImage(with: imageModel.url)
        uiView.imageView.contentMode = imageModel.contentMode
    

    func url(_ url: URL?) -> Self 
        imageModel.url = url
        return self
    

    func scaledToFit() -> Self 
        imageModel.contentMode = .scaleAspectFit
        return self
    

    func scaledToFill() -> Self 
        imageModel.contentMode = .scaleAspectFill
        return self
    


class ImageView: UIView 
    let imageView = UIImageView()

    init(imageModel: ImageModel) 
        super.init(frame: .zero)
        addSubview(imageView)
    

    override func layoutSubviews() 
        super.layoutSubviews()
        imageView.frame = bounds
    

    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

【讨论】:

谢谢,应用了类似的解决方案并按预期工作:)

以上是关于使用 SwiftUI 使自定义 UIView 方面比例适合/填充的麻烦的主要内容,如果未能解决你的问题,请参考以下文章

如何使自定义 UIView 可访问?

如何使自定义 UIView 与 UINavigationBar 结合使其看起来像一个

SwiftUI - 使用自定义绘图包装 UIView 时出现 UIViewRepresentable 故障

在 SwiftUI 中更改 UIView 背景颜色

如何在 SwiftUI 中使用 UIViewController 和 UIView?

如何在 SwiftUI 视图中访问 UIView 子类的属性?