简体   繁体   English

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

[英]How do I make SwiftUI UIViewRepresentable view hug its content?

I am trying to get SwiftUI to recognize the intrinsic size of a UIViewRepresentable, but it seems to treat it like the frame is set to maxWidth: .infinity, maxHeight: .infinity .我试图让 SwiftUI 识别 UIViewRepresentable 的内在大小,但它似乎将其视为框架设置为maxWidth: .infinity, maxHeight: .infinity I have created a contrived example, here is my code:我创建了一个人为的示例,这是我的代码:

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

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

Is there a way for me to have it do this like I would expect?有没有办法让它像我期望的那样做到这一点? 在此处输入图像描述

EDIT: After further inspection, it seems I am getting Unable to simultaneously satisfy constraints.编辑:经过进一步检查,似乎我Unable to simultaneously satisfy constraints. and one of the constraints is a 'UIView-Encapsulated-Layout-Height' constraint to the stretched out size.其中一个约束是对拉伸尺寸的'UIView-Encapsulated-Layout-Height'约束。 So I guess it might help to prevent those constraints from being forced upon my view?所以我想这可能有助于防止这些约束被强加于我的观点? Not sure...没有把握...

Use .fixedSize() solves the problem for me.使用.fixedSize()为我解决了这个问题。

What I did uses UIViewController , so things might be a different for UIView .我所做的使用UIViewController ,因此UIView的情况可能有所不同。 What I tried to achieve is to do a sandwich structure like:我试图实现的是做一个三明治结构,如:

SwiftUI => UIKit => SwiftUI

Consider a simple SwiftUI view:考虑一个简单的 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))
    }
}

The background color is set to test whether outer SwiftUI's environment values pass to the inner SwiftUI.设置背景颜色以测试外部 SwiftUI 的环境值是否传递到内部 SwiftUI。 The general answer is yes, but caution is required if you use things like .popover .一般的答案是肯定的,但如果您使用.popover类的东西,则需要谨慎。

One nice thing about the UIHostingController is that you can get the SwiftUI's natural size using .intrinsicContentSize . UIHostingController 的一个好处是您可以使用.intrinsicContentSize获得 SwiftUI 的自然大小。 This works for tight views such as Text and Image , or a container containing such tight views.这适用于诸如TextImage之类的紧凑视图,或包含此类紧凑视图的容器。 However, I am not sure what will happen with a GeometryReader .但是,我不确定GeometryReader会发生什么。

Consider the following view controller:考虑以下视图 controller:

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
    }
}

Nothing is particularly interesting here.这里没有什么特别有趣的。 We turn off autoresizing masks for BOTH our UIView and SwiftUI's hosting UIView .我们关闭了UIView和 SwiftUI 的托管UIViewUIView调整掩码。 We force our view to have the same natural size as SwiftUI's.我们强制我们的视图具有与 SwiftUI 相同的自然大小。

I would talk about updating preferredContentSize later.稍后我将讨论更新preferredContentSize

A boring VCRep :无聊的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
}

Now comes to the ContentView, ie the outer SwiftUI:现在来到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)
        }
    }
}

And magically, everything works.神奇的是,一切正常。 You can change the horizontal alignment or add more characters to the view controller, and expect the correct layout.您可以更改水平 alignment 或向视图 controller 添加更多字符,并期望正确的布局。

Something to notice:需要注意的一点:

  1. You must specify .fixedSize() to use the natural size.您必须指定.fixedSize()以使用自然大小。 Otherwise the view controller would take as much space as possible, just like a GeometryReader().否则视图 controller 会占用尽可能多的空间,就像 GeometryReader() 一样。 This is probably unsurprising given .fixedSize() 's documentation, but the naming is horrible.鉴于.fixedSize()的文档,这可能不足为奇,但命名很糟糕。 I would never expect .fixedSize() to mean this.我永远不会期望.fixedSize()是这个意思。
  2. You have to set preferredContentSize and keep it updated.您必须设置preferredContentSize并保持更新。 Initially I found it had no visual impact on my code, but the horizontal alignment seems wrong because the VCRep always has its leading anchor used as the layout guide for the VStack .最初我发现它对我的代码没有视觉影响,但是水平的 alignment 似乎是错误的,因为VCRep总是将其前导锚用作VStack的布局指南。 After checking the view dimensions via .alignmentGuide , it turns out that VCRep has a size zero.通过.alignmentGuide检查视图尺寸后,发现VCRep的大小为零。 It still shows!它仍然显示! Since no content clipping is enabled by default!由于默认情况下不启用任何内容剪辑!

By the way, if you have a situation where view height depends on its width, and you are lucky enough to be able to obtain the width (via GeometryReader ), you could also try to do layout on your own directly in a SwiftUI view's body , and attaching your UIView as a .background to serve as a custom painter.顺便说一句,如果您遇到视图高度取决于其宽度的情况,并且您很幸运能够获得宽度(通过GeometryReader ),您也可以尝试直接在 SwiftUI 视图的body中自己进行布局,并将您的 UIView 附加为.background以用作自定义画家。 This works, for instance, if you use TextKit to compute your text layout.例如,如果您使用 TextKit 来计算文本布局,则此方法有效。

Please try below code it will help you请尝试下面的代码它会帮助你

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

Change in Example改变例子

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 让 UIStackView 拥抱它的内容 - Make UIStackView hug its content iOS:在 UIViewRepresentable 和 View 之间进行 function 调用,SwiftUI - iOS: Make function calls between UIViewRepresentable and View both ways, SwiftUI 如何使 UIViewRepresentable @ViewBuilder 与动态内容一起使用? - How to make a UIViewRepresentable @ViewBuilder work with dynamic content? 如何在 SwiftUI 中结束对 UIViewRepresentable 的编辑? - How to endEditing on UIViewRepresentable in SwiftUI? 如何将 SwiftUI 视图放置在 UIKit (UIViewRepresentable) 视图之上? (错误或错误?) - How to place a SwiftUI view on top of a UIKit (UIViewRepresentable) view? (bug or mistake?) 如何使用 SwiftUI 将视图拉伸到其父框架? - How do I stretch a View to its parent frame with SwiftUI? 如何阻止 SwiftUI 重新加载我通过 UIViewRepresentable 使用的 WKWebView 页面? - How do I stop SwiftUI from reloading WKWebView page that I am using via UIViewRepresentable? 如何制作通用 UIViewRepresentable 结构? - How can I make a generic UIViewRepresentable struct? 如何从 SwiftUI 中的 UIViewRepresentable 外部调用 UIView 上的方法? - How do you call a method on a UIView from outside the UIViewRepresentable in SwiftUI? 如何在 UIViewRepresentable SwiftUI 中重新加载 collectionview - How to reload collectionview in UIViewRepresentable SwiftUI
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM