【问题标题】:How do I make SwiftUI UIViewRepresentable view hug its content?如何让 SwiftUI UIViewRepresentable 视图拥抱它的内容?
【发布时间】:2019-10-01 12:38:06
【问题描述】:

我试图让 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' 对拉伸尺寸的约束。所以我想这可能有助于防止这些约束被强加于我的观点?不确定...

【问题讨论】:

    标签: swift swiftui


    【解决方案1】:

    使用.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
        }
    }
    

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

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

    【讨论】:

    • 这对于 iOS 15 中的其他任何人来说似乎都坏了?突然间 UIHostingControllers 正在增长,即使它们的 SwiftUI 视图内容具有明确定义的大小......
    【解决方案2】:

    请试试下面的代码,它会对你有所帮助

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

    【讨论】:

    • 有没有更通用的方式适合任意UIViews?
    猜你喜欢
    • 2020-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多