SwiftUI 的布局引擎不尊重 UIViewRepresentable 的内容大小并将大小扩展到整个屏幕

问题描述 投票:0回答:1

我正在尝试将 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 的元素应用了一些背景颜色,这样更容易看出它们太大了。

swift swiftui layout autolayout uiviewrepresentable
1个回答
0
投票

我们必须使用

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
            )
        )
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.