首页 > 解决方案 > 如何让 SwiftUI UIViewRepresentable 视图拥抱它的内容?

问题描述

我试图让 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))
    }
}

我得到了什么: 在此处输入图像描述

有没有办法让它像我期望的那样做到这一点? 在此处输入图像描述

编辑:经过进一步检查,似乎我得到了Unable to simultaneously satisfy constraints.,其中一个约束是'UIView-Encapsulated-Layout-Height'对拉伸尺寸的约束。所以我想这可能有助于防止这些约束被强加于我的观点?没有把握...

标签: swiftswiftui

解决方案


使用.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. 这适用于诸如Textand之类的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
    }
}

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

稍后我会谈到更新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,即外部 SwiftUI:

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() 一样。鉴于 's 的文档,这可能不足为奇.fixedSize(),但命名很可怕。我从没想过会.fixedSize()是这个意思。
  2. 您必须设置preferredContentSize并保持更新。最初我发现它对我的代码没有视觉影响,但水平对齐似乎是错误的,因为它VCRep总是将其前导锚用作VStack. 通过 检查视图尺寸后.alignmentGuide,发现VCRep尺寸为零。它仍然显示!由于默认情况下不启用任何内容剪辑!

顺便说一句,如果您遇到视图高度取决于其宽度的情况,并且您很幸运能够获得宽度(通过GeometryReader),您也可以尝试直接在 SwiftUI 视图中自己进行布局body,并且将您的 UIView 附加.background为用作自定义画家。例如,如果您使用 TextKit 来计算文本布局,则此方法有效。


推荐阅读