如何使SwiftUI UIViewRepresentable视图拥抱其内容?

时间:2019-10-01 12:38:06

标签: swift swiftui

我正在尝试让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

编辑: 经过进一步检查,似乎我得到了Unable to simultaneously satisfy constraints.约束,其中之一是对扩展尺寸的'UIView-Encapsulated-Layout-Height'约束。因此,我认为这可能有助于防止将这些约束强加于我的观点?不确定...

2 个答案:

答案 0 :(得分:3)

使用.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的自然大小。这适用于诸如TextImage之类的紧密视图,或包含此类紧密视图的容器。但是,我不确定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
    }
}

这里没有什么特别有趣的。我们为{strong> BOOT 和UIView托管的SwiftUI关闭了自动调整大小的掩码。我们强迫我们的视图具有与SwiftUI相同的自然尺寸。

我将在稍后讨论更新UIView

无聊的preferredContentSize

VCRep

现在进入ContentView,即外部SwiftUI:

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

    typealias UIViewControllerType = VC
}

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

注意事项

  1. 您必须指定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) } } } 才能使用自然大小。否则,视图控制器将占用尽可能多的空间,就像GeometryReader()一样。鉴于.fixedSize()的文档,这可能不足为奇,但是命名太可怕了。我从没想到.fixedSize()会这样。
  2. 您必须设置.fixedSize()并保持更新。最初,我发现它对我的代码没有视觉影响,但是水平对齐似乎是错误的,因为preferredContentSize始终将其前导锚用作VCRep的布局指南。通过VStack检查视图尺寸后,结果发现.alignmentGuide的尺寸为零。它仍然显示!由于默认情况下未启用任何内容剪切!

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

答案 1 :(得分: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)
   }
}