带有 UIViewRepresentable 的多行标签

1 uikit ios swift swiftui

我正在尝试使用在 SwiftUI 中使用 UIKit 创建的组件UIViewRepresentable. The result I want to achieve is to have a textfield on top of my view and my UIKit view stacked at the bottom with an automatic height.

问题是这个组件包含一个多行标签,据我所知,它使得自动高度很难正常工作,所以我的 UIKit 组件占用了 VStack 中的所有可用空间。

预期结果 Actual result

这是我的代码,包含在游乐场中以对其进行测试。我尝试着拥抱优先级,但没有任何效果对我来说,如果我使用快速 UI 视图进行测试,它可以正常工作..有什么想法吗?

import UIKit
import Foundation
import SwiftUI
import PlaygroundSupport

class InformationView: UIView {
    lazy var label = UILabel()
    lazy var button = UIButton(type: .custom)

    override init(frame: CGRect) {
        super.init(frame: frame)

        backgroundColor = .red

        addSubview(label)
        addSubview(button)

        label.text = "zaeiof azoeinf aozienf oaizenf oazeinf oaziefj oazeijf oaziejf aozijf oaizje foazeafjoj"
        label.numberOfLines = 0
        label.setContentHuggingPriority(.required, for: .vertical)

        label.translatesAutoresizingMaskIntoConstraints = false
        button.translatesAutoresizingMaskIntoConstraints = false

        button.setTitle("My button title", for: .normal)
        button.setContentCompressionResistancePriority(.required, for: .horizontal)

        addConstraints([
            label.leadingAnchor.constraint(equalTo: leadingAnchor),
            label.topAnchor.constraint(equalTo: topAnchor),
            label.bottomAnchor.constraint(equalTo: bottomAnchor),
            label.trailingAnchor.constraint(equalTo: button.leadingAnchor, constant: -10),
            button.trailingAnchor.constraint(equalTo: trailingAnchor),
            button.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

struct InformationRepresenter: UIViewRepresentable {
    func makeUIView(context: Context) -> InformationView {
        let view = InformationView(frame: .zero)
        view.setContentHuggingPriority(.required, for: .vertical)

        return view
    }

    func updateUIView(_ uiView: InformationView, context: Context) {

    }
}

struct Info: View {
    var body: some View {
        return HStack {
            Text("zaeiof azoeinf aozienf oaizenf oazeinf oaziefj oazeijf oaziejf aozijf oaizje foazeafjoj")

            Button("My button title", action: {
                print("test")
            })
        }
    }
}

struct ContentView: View {
    @State var text = ""

    var body: some View {
        VStack {
            TextField("Field", text: $text)
                .padding()

            Spacer()

            // SwiftUI works
            // Info().background(Color.red).padding()

            // UIViewRepresentable doesn't
            InformationRepresenter().padding()
        }
    }
}

PlaygroundPage.current.setLiveView(ContentView().frame(width: 400, height: 800, alignment: .top))

Run Code Online (Sandbox Code Playgroud)

Mat*_*omi 10

这里发生了一些事情。

  • InformationView没有intrinsicContentSize. SwiftUI 依赖此属性来确定 aUIView的理想大小。
  • 没有fixedSize修饰符。此修饰符强制视图保持其理想大小,而不是增长以填充其父级。

您无法添加 an intrinsicContentSize,因为内容大小是动态的;它基于标签中的行数。

您无法添加fixedSize修饰符,因为如果没有intrinsicContentSize,这会将大小设置为 (0,0)。

一种解决方案是包装InformationView一个UIView测量尺寸的InformationView变量,并intrinsicContentSize根据该测量值更新它自己的尺寸。

InformationView应该填满屏幕的宽度;它没有固有宽度。另一方面,固有高度应等于压缩后的系统布局尺寸的高度。这是“基于其约束的视图的最佳大小”。

这是包装器:

class IntrinsicHeightView<ContentView: UIView>: UIView {
  var contentView: ContentView

  init(contentView: ContentView) {
    self.contentView = contentView
    super.init(frame: .zero)
    backgroundColor = .clear
    addSubview(contentView)
  }

  @available(*, unavailable) required init?(coder _: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  private var contentHeight: CGFloat = .zero {
    didSet { invalidateIntrinsicContentSize() }
  }

  override var intrinsicContentSize: CGSize {
    .init(width: UIView.noIntrinsicMetric, height: contentHeight)
  }

  override var frame: CGRect {
    didSet {
      guard frame != oldValue else { return }

      contentView.frame = self.bounds
      contentView.layoutIfNeeded()

      let targetSize = CGSize(width: frame.width, height: UIView.layoutFittingCompressedSize.height)

      contentHeight = contentView.systemLayoutSizeFitting(
        targetSize,
        withHorizontalFittingPriority: .required,
        verticalFittingPriority: .fittingSizeLevel).height
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

IntrinsicHeightView是一个通用的,当其框架发生变化时,根据其内容视图的压缩系统布局高度UIView重新计算其高度。intrinsicContentSize

以下是更新InformationRepresenter

struct InformationRepresenter: UIViewRepresentable {
    func makeUIView(context: Context) -> IntrinsicHeightView<InformationView> {
        return IntrinsicHeightView(contentView: InformationView())
    }

    func updateUIView(_ uiView: IntrinsicHeightView<InformationView>, context: Context) {
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,在 SwiftUI 中使用它时:

InformationRepresenter()
  .padding()
  .fixedSize(horizontal: false, vertical: true)
Run Code Online (Sandbox Code Playgroud)

fixedSize以这种方式使用修改器可以UIView填充父级,但用作intrinsicContentSize.height其高度。这是 最终结果。祝你好运!