SwiftUI 如何在其中创建带有自定义 UIViews 的列表

Posted

技术标签:

【中文标题】SwiftUI 如何在其中创建带有自定义 UIViews 的列表【英文标题】:SwiftUI how to create List with custom UIViews inside it 【发布时间】:2020-03-24 10:58:14 【问题描述】:

我考虑如何创建以自定义 UIViews 作为其行的 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)
        ])
    


【问题讨论】:

【参考方案1】:

更新的答案 试试这个:

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()
    

【讨论】:

我在这两种情况下都添加了简化和更复杂的示例(也只留下简单的 UILabel),我在滚动列表时丢失了单元格,此外,这些单元格可以跳出屏幕。 如果我添加了 .id() 这样的 NoteView(note: note).id(i) 并将此视图放在 HStack 中,它现在不会消失,但垂直大小不合适 “没有大小”是什么意思?只需在 testview 之后设置 frame(height:blabla) ,你就可以设置任何你想要的高度 或者只是给我们一个新的可复制示例,我们可以看到它......谢谢 我希望它自动调整大小。我想要自定义视图 NoteView:UIViewRepresentable。它创建 UINoteView:UIView。并且有多行标签或多行 UITextView。我希望细胞自动获得它的高度。我事先不知道这个单元格的大小【参考方案2】:

我确实喜欢下面。即使没有 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) 
    


【讨论】:

以上是关于SwiftUI 如何在其中创建带有自定义 UIViews 的列表的主要内容,如果未能解决你的问题,请参考以下文章

带有选择器的 SwiftUI 列表:选择不适用于自定义类型(macOS)

在 SwiftUI 的自定义视图中访问图像的修饰符

SwiftUI 自定义视图接受带有 ID 的视图?

带有 UIViewRepresentable 的 SwiftUI 自定义文本字段 ObservableObject 和推送视图的问题

在 SwiftUI 中沿路径填充颜色渐变

SwiftUI - 如何在文本视图中更改自定义 SF 符号的颜色?