SwiftUI 如何创建其中包含自定义 UIView 的列表

SwiftUI how to create List with custom UIViews inside it

我考虑如何创建其行自定义 UIView 的 SwitUI 列表。

我创建列表:

List { 
   RowView()
}

RowView 是 UIRowView 的 UIViewRepresentable

struct RowView : UIViewRepresentable { 

   func makeUIView() -> UIRowView { ... }
}

UIRowView 是自定义视图

UIRowView: UIView { ... } 

当前显示了第一行,但它们通常布局不正确,并且在滚动时此视图消失而不是被回收

更新

示例 1

struct NoteView: UIViewRepresentable {

    // MARK: - Properties
    let note: Note
    let date = Date()

    func makeUIView(context: Context) -> UINoteView {

        let view = UINoteView()
        view.note = note

        return view
    }

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

        uiView.note = note
        print("View bounds: \(uiView.bounds)")
    }
}

var body: some View {
        List {
            ForEach(Array(notes.enumerated()), id: \.1) { (i, note) in
                NoteView(note: note)
                .background(Color.green)
                .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
            }
        }.background(Color.red)
    }

示例 2 - 简化版

struct TestView : UIViewRepresentable {

    let text : String

    func makeUIView(context: Context) -> UILabel {
        UILabel()
    }

    func updateUIView(_ uiView: UILabel, context: Context) {
        uiView.text = text
    }
}

  var body: some View {

    List {
        ForEach(0..<30, id: \.self) { i in
            TestView(text: "\(i)")
        }
    }
}

两者似乎都无法正常工作,因为行消失了 如果有更多内容,我还遇到了视图不保留填充和超出屏幕的问题。只有几行(最初在屏幕布局上正确可见)其他行消失或跳到某处。

更新 2

这是可自动调整大小的 UINoteView

class UINoteView: UIView {

    // MARK: - Init
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupViews()
    }

    // MARK: - Properties
    var note: Note? {
        didSet {
            textView.attributedText = note?.content?.parsedHtmlAttributedString(textStyle: .html)

            noteFooterViewModel.note = note
        }
    }

    // MARK: - Views
    lazy var textView: UITextView = {
        let textView = UITextView()
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.backgroundColor = UIColor.yellow
        textView.textContainer.lineBreakMode = .byWordWrapping
        textView.textContainerInset = .zero
        textView.textContainer.lineFragmentPadding = 0
        textView.isScrollEnabled = false
        textView.isSelectable = true
        textView.isUserInteractionEnabled = true
        textView.isEditable = false
        textView.textContainer.maximumNumberOfLines = 0

        return textView
    }()

    lazy var label: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "TEST ROW \(note?.id ?? "")"
        return label
    }()

    lazy var vStack: UIStackView = {
        let stack = UIStackView(arrangedSubviews: [
            textView,
            noteFooter
        ])
        stack.axis = .vertical
        stack.alignment = .fill
        stack.distribution = .fill
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()

    var noteFooterViewModel = NoteFooterViewModel()

    var noteFooter: UIView {
        let footer = NoteFooter(viewModel: noteFooterViewModel)
        let hosting = UIHostingController(rootView: footer)
        hosting.view.translatesAutoresizingMaskIntoConstraints = false
        return hosting.view
    }

    private func setupViews() {

        self.backgroundColor = UIColor.green
        self.addSubview(vStack)

        NSLayoutConstraint.activate([
            vStack.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            vStack.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            vStack.topAnchor.constraint(equalTo: self.topAnchor),
            vStack.bottomAnchor.constraint(equalTo: self.bottomAnchor)
        ])
    }

}

更新的答案 试试这个:

struct TestView : UIViewRepresentable {

    let text : String

    var label : UILabel = UILabel()

    func makeUIView(context: Context) -> UILabel {

        return label
    }

    func updateUIView(_ uiView: UILabel, context: Context) {
        uiView.text = text
    }
}



struct ContentView : View {

    var body: some View {

        List (0..<30, id: \.self) { i in
            TestView(text: "\(i)").id(i)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

我喜欢下面的。即使没有 UIViewRepresentable 也可以通过 getTextFrame(for note)

获取帧大小
var body: some View {
       List {
            ForEach(Array(notes.enumerated()), id: \.1) { (i, note) in
                NoteView(note: note)
                 // add this
                 .frame(width: getTextFrame(for: note).width, height: getTextFrame(for: note).height)
                .background(Color.green)
                .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
            }
        }.background(Color.red)
    }

  func getTextFrame(for text: String, maxWidth: CGFloat? = nil, maxHeight: CGFloat? = nil) -> CGSize {
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.preferredFont(forTextStyle: .body)
        ]
        let attributedText = NSAttributedString(string: text, attributes: attributes)
        let width = maxWidth != nil ? min(maxWidth!, CGFloat.greatestFiniteMagnitude) : CGFloat.greatestFiniteMagnitude
        let height = maxHeight != nil ? min(maxHeight!, CGFloat.greatestFiniteMagnitude) : CGFloat.greatestFiniteMagnitude
        let constraintBox = CGSize(width: width, height: height)
        let rect = attributedText.boundingRect(with: constraintBox, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil).integral
        print(rect.size)
        return rect.size
    }

struct NoteView: UIViewRepresentable {
    let note: String
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.font = UIFont.preferredFont(forTextStyle: .body)
        textView.isEditable = false
        textView.isSelectable = true
        textView.isUserInteractionEnabled = true
        textView.isScrollEnabled = false
        textView.backgroundColor = .clear
        textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        textView.textContainerInset = .zero
        textView.textContainer.lineFragmentPadding = 0
        textView.textContainer.lineBreakMode = .byWordWrapping
        textView.text = note
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
    }
}