[英]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))
}
}
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.这适用于诸如Text
和Image
之类的紧凑视图,或包含此类紧凑视图的容器。 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 的托管UIView
的UIView
调整掩码。 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:需要注意的一点:
.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()
是这个意思。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.