如何使SwiftUI的UIViewRepresentable视图紧贴其内容?

21

我正在尝试让SwiftUI识别UIViewRepresentable的内在大小,但它似乎将其视为框架设置为maxWidth:.infinity,maxHeight:.infinity。 我创建了一个人为的示例,以下是我的代码:

struct Example: View {
    var body: some View {
        VStack {
            Text("Hello")
            TestView()
        }
        .background(Color.red)
    }
}

struct TestView: UIViewRepresentable {
    func makeUIView(context: UIViewRepresentableContext<TestView>) -> TestUIView {
        return TestUIView()
    }

    func updateUIView(_ uiView: TestUIView, context: UIViewRepresentableContext<TestView>) {
    }

    typealias UIViewType = TestUIView
}

class TestUIView: UIView {
    required init?(coder: NSCoder) { fatalError("-") }
    init() {
        super.init(frame: .zero)
        let label = UILabel()
        label.text = "Sed mattis urna a ipsum fermentum, non rutrum lacus finibus. Mauris vel augue lorem. Donec malesuada non est nec fermentum. Integer at interdum nibh. Nunc nec arcu mauris. Suspendisse efficitur iaculis erat, ultrices auctor magna."
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        label.backgroundColor = .purple
        addSubview(label)
        translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            widthAnchor.constraint(equalToConstant: 200),
            label.leadingAnchor.constraint(equalTo: leadingAnchor),
            label.trailingAnchor.constraint(equalTo: trailingAnchor),
            label.topAnchor.constraint(equalTo: topAnchor),
            label.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    }
}

struct Example_Previews: PreviewProvider {
    static var previews: some View {
        Example()
            .previewDevice(PreviewDevice(rawValue: "iPhone 8"))
            .previewLayout(PreviewLayout.fixed(width: 300, height: 300))
    }
}

我所理解的是: enter image description here 我是否能像预期的那样使它这样做? enter image description here 编辑: 经过进一步检查,似乎我得到了“无法同时满足约束条件”的提示,并且其中一个约束条件是一个“UIView-Encapsulated-Layout-Height”约束条件,其大小被拉伸。所以我想防止这些约束强加给我的视图会有所帮助?不确定...
2个回答

13

使用.fixedSize()解决了我的问题。

我使用的是UIViewController,所以对于UIView可能会有所不同。我试图实现一个类似三明治结构的东西:

SwiftUI => UIKit => SwiftUI

考虑一个简单的SwiftUI视图:
struct Block: View {
    var text: String
    init(_ text: String = "1, 2, 3") {
        self.text = text
    }
    var body: some View {
        Text(text)
            .padding()
            .background(Color(UIColor.systemBackground))
    }
}

本文旨在测试外部SwiftUI环境值是否传递到内部SwiftUI,并设置背景颜色进行验证。一般来说,答案是肯定的,但如果您使用了像.popover这样的东西,则需要注意。
UIHostingController的一个好处是您可以使用.intrinsicContentSize获取SwiftUI的自然大小。这适用于紧凑视图,例如Text和Image,或包含此类紧凑视图的容器。但是,我不确定使用GeometryReader会发生什么。
考虑以下视图控制器:
class VC: UIViewController {
    let hostingController = UIHostingController(rootView: Block("Inside VC"))
    
    var text: String = "" {
        didSet {
            hostingController.rootView = Block("Inside VC: \(text).")
        }
    }

    override func viewDidLoad() {
        addChild(hostingController)
        view.addSubview(hostingController.view)
        
        view.topAnchor.constraint(equalTo: hostingController.view.topAnchor).isActive = true
        view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor).isActive = true
        view.leadingAnchor.constraint(equalTo: hostingController.view.leadingAnchor).isActive = true
        view.trailingAnchor.constraint(equalTo: hostingController.view.trailingAnchor).isActive = true

        view.translatesAutoresizingMaskIntoConstraints = false
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
    }
    
    override func viewDidLayoutSubviews() {
        preferredContentSize = view.bounds.size
    }
}

这里没有特别有趣的东西。我们关闭了UIView和SwiftUI托管UIView的自动调整大小掩码。我们强制我们的视图具有与SwiftUI相同的自然大小。

稍后会讨论更新preferredContentSize

一个无聊的VCRep:

struct VCRep: UIViewControllerRepresentable {
    var text: String
    
    func makeUIViewController(context: Context) -> VC {
        VC()
    }
    
    func updateUIViewController(_ vc: VC, context: Context) {
        vc.text = text
    }

    typealias UIViewControllerType = VC
}


现在转向 ContentView,也就是 Swift UI 的外层:
struct ContentView: View {
    @State var alignmentIndex = 0
    @State var additionalCharacterCount = 0
    
    var alignment: HorizontalAlignment {
        [HorizontalAlignment.leading, HorizontalAlignment.center, HorizontalAlignment.trailing][alignmentIndex % 3]
    }
    
    var additionalCharacters: String {
        Array(repeating: "x", count: additionalCharacterCount).joined(separator: "")
    }
    
    var body: some View {
        VStack(alignment: alignment, spacing: 0) {
            Button {
                self.alignmentIndex += 1
            } label: {
                Text("Change Alignment")
            }
            Button {
                self.additionalCharacterCount += 1
            } label: {
                Text("Add characters")
            }
            Block()
            Block().environment(\.colorScheme, .dark)
            VCRep(text: additionalCharacters)
                .fixedSize()
                .environment(\.colorScheme, .dark)
        }
    }
}

神奇地,一切都正常工作了。您可以更改水平对齐方式或向视图控制器添加更多字符,并期望正确的布局。

需要注意的是:

  1. 您必须指定 .fixedSize() 以使用自然大小。否则,视图控制器会尽可能占用空间,就像 GeometryReader() 一样。考虑到 .fixedSize() 的文档,这可能并不令人惊讶,但命名方式很糟糕。我从不希望 .fixedSize() 意味着这个。
  2. 您必须设置 preferredContentSize 并保持更新。最初我发现它对我的代码没有视觉影响,但水平对齐似乎有问题,因为 VCRep 始终将其前导锚点用作 VStack 的布局指南。通过 .alignmentGuide 检查视图尺寸后,结果表明 VCRep 的大小为零。它仍然显示!因为默认情况下未启用任何内容剪裁!

顺便说一下,如果您遇到视图高度取决于其宽度的情况,并且您有幸能够通过 GeometryReader 获得宽度,则也可以尝试在 SwiftUI 视图的 body 中直接进行布局,并将您的 UIView 作为 .background 附加以充当自定义绘图器。例如,如果您使用 TextKit 来计算文本布局,则可以这样做。


1
在iOS 15中,是否有其他人发现UIHostingControllers突然变大了,尽管它们的SwiftUI视图内容已经有明确定义的大小? - Trev14
preferredContentSize 这个技巧真是太妙了,谢谢! - Greg de J

1
请尝试以下代码,它会帮助您。
// **** For getting label height ****
struct LabelInfo {

   func heightForLabel(text: String, font: UIFont, width: CGFloat) -> CGFloat {

       let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
       label.numberOfLines = 0
       label.lineBreakMode = NSLineBreakMode.byWordWrapping
       label.font = font
       label.text = text
       label.sizeToFit()
       return label.frame.height
   }
}

示例中的更改

struct Example: View {

   // Note : Width is fixed 200 at both place
   //      : use same font at both 

   let height = LabelInfo().heightForLabel(text: "Sed mattis urna a ipsum fermentum, non rutrum lacus finibus. Mauris vel augue lorem. Donec malesuada non est nec fermentum. Integer at interdum nibh. Nunc nec arcu mauris. Suspendisse efficitur iaculis erat, ultrices auctor magna.", font: UIFont.systemFont(ofSize: 17), width: 200) 

   var body: some View {
       VStack {
           Text("Hello")
           TestView().frame(width: 200, height: height, alignment: .center) //You need to do change here
       }
       .background(Color.red)
   }
}

10
有没有一种更通用的方法适用于任意的UIView? - eagle.dan.1349

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