我正在尝试将 UIViewRepresentable 合并到我的 SwiftUI 应用程序中。我的目标是视图以适合内容所需的尺寸(尤其是高度)显示。然而,SwiftUI 不知何故不尊重我的 UIViewRepresentable 视图的高度。它会不断扩展,直到填满整个垂直空间。
我已经做了很多研究,但我发现没有解决方案可以解决我的具体案例的问题。这个话题看起来相当复杂。
下面是一个简单的例子来演示我的问题。任何提示将非常感谢!
import SwiftUI
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
let width = geometry.size.width
let height = width / 0.9
CustomCardView {
ZStack(alignment: .bottom) {
Image(systemName: "square.and.arrow.up.circle.fill")
.resizable()
.scaledToFill()
.frame(width: width, height: height)
.foregroundStyle(.red)
/*
VStack {
Text("This is a very long test text that tells us basically nothing and only serves as an example")
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.multilineTextAlignment(.leading)
.lineLimit(2)
.font(.title)
.fontWeight(.bold)
Text("This is a very long test text that tells us basically nothing and only serves as an example")
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.multilineTextAlignment(.leading)
.lineLimit(2)
.font(.body)
}
.background(.ultraThinMaterial)
*/
CustomView()
.padding()
.background(.ultraThinMaterial)
}
}
}
.padding([.horizontal, .bottom])
}
}
struct CustomView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let rootView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = true
view.backgroundColor = .clear
return view
}()
let titleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 2
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .yellow
return label
}()
let contentView: UITextView = {
let textView = UITextView()
textView.isEditable = false
textView.isSelectable = true
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = .zero
textView.textContainer.maximumNumberOfLines = 3
textView.backgroundColor = .brown
textView.textContainer.lineBreakMode = .byTruncatingTail
return textView
}()
rootView.addSubview(titleLabel)
rootView.addSubview(contentView)
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: rootView.topAnchor),
titleLabel.leadingAnchor.constraint(equalTo: rootView.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: rootView.trailingAnchor),
contentView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
contentView.leadingAnchor.constraint(equalTo: rootView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: rootView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: rootView.bottomAnchor)
])
titleLabel.text = "This is a very long test text that tells us basically nothing and only serves as an example"
contentView.text = "This is a very long test text that tells us basically nothing and only serves as an example"
return rootView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
// not necessary
}
}
struct CustomCardView<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
content
.frame(maxWidth: .infinity)
.background(Color.green)
.cornerRadius(16)
.shadow(color: .black.opacity(0.4), radius: 8, x: 0, y: 5)
}
}
#Preview {
ContentView()
}
这是我使用 SwiftUI 文本所需行为的屏幕截图。只需删除下面代码中的注释,并注释掉 CustomView() 部分即可。
这是使用 UIViewRepresentable 时出现的不良行为的屏幕截图。即使文本非常短,卡片视图也会垂直扩展到全屏尺寸。我已经对 UIViewRepresentable 的元素应用了一些背景颜色,这样更容易看出它们太大了。
我们必须使用
sizeThatFits
函数告知 SwiftUI 有关 UIView 大小的信息,因为 SwiftUI 不知道这一点。
import SwiftUI
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
let width = geometry.size.width
let height = width / 0.9
CustomCardView {
ZStack(alignment: .bottom) {
Image(systemName: "square.and.arrow.up.circle.fill")
.resizable()
.scaledToFill()
.frame(width: width, height: height)
.foregroundStyle(.red)
CustomView()
.frame(maxWidth: .infinity)
.padding()
.background(.ultraThinMaterial)
}
}
}
.padding([.horizontal, .bottom])
}
}
class CustomUIKitView: UIView {
private var titleLabel: UILabel!
private var contentView: UITextView!
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
titleLabel = UILabel()
titleLabel.numberOfLines = 2
titleLabel.backgroundColor = .yellow
titleLabel.translatesAutoresizingMaskIntoConstraints = false
contentView = UITextView()
contentView.isEditable = false
contentView.isSelectable = true
contentView.isScrollEnabled = false
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.textContainerInset = .zero
contentView.textContainer.lineFragmentPadding = .zero
contentView.textContainer.maximumNumberOfLines = 3
contentView.backgroundColor = .brown
contentView.textContainer.lineBreakMode = .byTruncatingTail
contentView.setContentCompressionResistancePriority(.required, for: .vertical)
addSubview(titleLabel)
addSubview(contentView)
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: topAnchor),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
contentView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
titleLabel.text = "This is a very long test text that tells us basically nothing and only serves as an example"
contentView.text = "This is a very long test text that tells us basically nothing and only serves as an example"
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let titleLabelSize = titleLabel.sizeThatFits(size)
let contentViewSize = contentView.sizeThatFits(size)
let totalHeight = titleLabelSize.height + contentViewSize.height + 16
let totalWidth = min(size.width, max(titleLabelSize.width, contentViewSize.width))
return CGSize(width: totalWidth, height: totalHeight)
}
}
struct CustomView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
CustomUIKitView()
}
func updateUIView(_ uiView: UIView, context: Context) {}
func sizeThatFits(
_ proposal: ProposedViewSize,
uiView: UIView,
context: Context
) -> CGSize? {
uiView.sizeThatFits(
CGSize(
width: proposal.width ?? .infinity,
height: proposal.height ?? .infinity
)
)
}
}