簡體   English   中英

UIViewRepresentable 在 SwiftUI 中的列表單元格中的放置

[英]Placement of UIViewRepresentable within list cells in SwiftUI

在 SwiftUI 中將自定義UILabel添加到List時,我在單元格重用時遇到錯誤,其中某些單元格上的標簽根本不可見,而在某些單元格上,它被放置在左上角而不考慮單元格的填充。 它總是在初始單元格上完美呈現。

使用ScrollView時不會出現此問題。 這是一個已知的錯誤,是否有好的解決方法?

GeometryReader { geometry in
    List {
        ForEach(self.testdata, id: \.self) { text in
            Group {
                AttributedLabel(attributedText: NSAttributedString(string: text), maxWidth: geometry.size.width - 40)
            }.padding(.vertical, 20)
        }
    }
}

struct AttributedLabel: UIViewRepresentable {

    let attributedText: NSAttributedString
    let maxWidth: CGFloat

    func makeUIView(context: UIViewRepresentableContext<Self>) -> UILabel {
        let label = UILabel()
        label.preferredMaxLayoutWidth = maxWidth
        label.attributedText = attributedText
        label.lineBreakMode = .byWordWrapping
        label.numberOfLines = 0
        label.backgroundColor = UIColor.red

        return label
    }

    func updateUIView(_ label: UILabel, context: UIViewRepresentableContext<Self>) {}

}

在此處輸入圖片說明

它與 ScrollView 或 SwiftUI 錯誤無關。 我認為您的AttributedLabel類有問題。 我嘗試使用普通Text ,它工作正常。

List {
        ForEach(self.testdata, id: \.self) { text in
            Group {
                  Text(student.name)
                    .background(Color.red)
            }.padding(.vertical, 20)
        }
    }

似乎確實有解決方法。

  1. 第一步是讓模型首先返回一個空的項目數組,然后返回實際的更新。 這將強制視圖更新。 然后,在短暫的停頓之后,就可以進行實際的更新了。 對於這種情況,這還不夠。 單獨這樣做仍然會導致布局問題。 不知何故,列表(大概是由積極回收其單元格的UITableView支持)仍然設法保持狀態,以某種方式導致問題。 所以...

  2. 第二步是讓視圖在沒有項目時提供列表以外的內容 這是使用 SwiftUI ifelse ,根據是否有任何項目使用不同的視圖。 隨着模型的更改,按照步驟 1,每次更新都會發生這種情況。

執行步驟 (1) 和 (2) 似乎可以解決此問題。 下面的示例代碼還包括視圖上的.animation(.none)方法。 這在我的代碼中是必要的,但在下面的示例代碼中似乎不需要。

這種解決方法的缺點是您將丟失動畫。 很明顯,如果蘋果在未來做出改變,它可能不會繼續工作。 (不過,也許到那時這個錯誤已經被修復了。)

import SwiftUI

struct ContentView: View {

      @ObservedObject var model = TestData()

      var body: some View {

            VStack() {
                  GeometryReader { geometry in
                        // handle the no items case by offering up a different view
                        // this appears to be necessary to workaround the issues
                        // where table cells are re-used and the layout goes wrong
                        // Don't show the "No Data" message unless there really is no data,
                        // i.e. skip case where we're just delaying to workaround the issue.
                        if self.model.sampleList.isEmpty {
                              Text("No Data")
                                    .foregroundColor(self.model.isModelUpdating ? Color.clear : Color.secondary)
                                    .frame(width: geometry.size.width, height: geometry.size.height) // centre the text
                        }
                        else {
                              List(self.model.sampleList, id:\.self) { attributedString in
                                    AttributedLabel(attributedText: attributedString, maxWidth: geometry.size.width - 40)
                              }
                        }
                        }.animation(.none) // this MAY not be necessary for all cases
                  Spacer()
                  Button(action: { self.model.shuffle()} ) { Text("Shuffle") }.padding(20)
            }
      }
}

struct AttributedLabel: UIViewRepresentable {

      let attributedText: NSAttributedString
      let maxWidth: CGFloat

      func makeUIView(context: UIViewRepresentableContext<Self>) -> UILabel {
            let label = UILabel()
            label.preferredMaxLayoutWidth = maxWidth
            label.attributedText = attributedText
            label.lineBreakMode = .byWordWrapping
            label.numberOfLines = 0
            label.backgroundColor = UIColor.red
            return label
      }

      func updateUIView(_ label: UILabel, context: UIViewRepresentableContext<Self>) {
            // function required by protoocol - NO OP
      }

}

class TestData : ObservableObject {

      @Published var sampleList = [NSAttributedString]()
      @Published var isModelUpdating = false
      private var allSamples = [NSAttributedString]()


      func shuffle() {

            let filtered = allSamples.filter{ _ in Bool.random() }
            let shuffled = filtered.shuffled()

            // empty the sampleList - this will trigger the View that is
            // observing the model to update and handle the no items case
            self.sampleList = [NSAttributedString]()
            self.isModelUpdating = true

            // after a short delay update the sampleList - this will trigger
            // the view that is observing the model to update
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
                  self.sampleList = shuffled
                  self.isModelUpdating = false
            }
      }

      init() {
            generateSamples()
            shuffle()
      }

      func generateSamples() {

            DispatchQueue.main.async {
                  var samples = [NSAttributedString]()

                  samples.append("The <em>quick</em> brown fox <strong>boldly</strong> jumped over the <em>lazy</em> dog.".fromHTML)
                  samples.append("<h1>SwiftUI</h1><p>At the time of writing, still very much a <em>work in progress</em>. Normal and <em>italic</em>. And <strong>strong</strong> too.</p><p>At the time of writing, still very much a <em>work in progress</em>. Normal and <em>italic</em>. And <strong>strong</strong> too.</p><p>At the time of writing, still very much a <em>work in progress</em>. Normal and <em>italic</em>. And <strong>strong</strong> too.</p><p>At the time of writing, still very much a <em>work in progress</em>. Normal and <em>italic</em>. And <strong>strong</strong> too.</p><p>At the time of writing, still very much a <em>work in progress</em>. Normal and <em>italic</em>. And <strong>strong</strong> too.</p>".fromHTML)
                  samples.append("<h1>Test Cells</h1><p>Include cells that have different heights to demonstrate what is going on. Make some of them really quite long. If they are all showing the list is going to need to scroll at least on smaller devices.</p><p>Include cells that have different heights to demonstrate what is going on. Make some of them really quite long. If they are all showing the list is going to need to scroll at least on smaller devices.</p><p>Include cells that have different heights to demonstrate what is going on. Make some of them really quite long. If they are all showing the list is going to need to scroll at least on smaller devices.</p> ".fromHTML)
                  samples.append("<h3>List of the day</h3><p>And he said:<ul><li>Expect the unexpected</li><li>The sheep is not a creature of the air</li><li>Chance favours the prepared observer</li></ul>And now, maybe, some commentary on that quote.".fromHTML)
                  samples.append("Something that is quite short but that is more than just one line long on a phone maybe. This might do it.".fromHTML)

                  self.allSamples = samples
            }
      }
}

extension String {
      var fromHTML : NSAttributedString {
            do {
                  return try NSAttributedString(data: Data(self.utf8), options: [
                        .documentType: NSAttributedString.DocumentType.html,
                        .characterEncoding: String.Encoding.utf8.rawValue
                  ], documentAttributes: nil)
            }
            catch {
                  return NSAttributedString(string: self)
            }
      }
}

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

我遇到了類似的問題,並通過使用getTextFrame(text)向 UIViewRepresentable 添加框架來解決它,

GeometryReader { geometry in
    List {
        ForEach(self.testdata, id: \.self) { text in
            Group {
                AttributedLabel(attributedText: NSAttributedString(string: text), maxWidth: geometry.size.width - 40)
                // add this frame
                .frame(width: getTextFrame(text).width height: getTextFrame(text).height)
            }.padding(.vertical, 20)
        }
    }
}
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
        return rect.size
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM