Размещение UIViewRepresentable в ячейках списка в SwiftUI - PullRequest
2 голосов
/ 16 января 2020

При добавлении пользовательского UILabel к List в SwiftUI я получаю ошибки при повторном использовании ячеек, когда метка на некоторых ячейках вообще не видна, а на некоторых ячейках она размещается в верхнем левом углу без любое отношение к заполнению клетки. Он всегда отлично воспроизводится на начальных ячейках.

Проблема не возникает при использовании 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>) {}

}

enter image description here

Ответы [ 2 ]

0 голосов
/ 22 марта 2020

Существует , кажется, обходной путь для этого.

  1. Первый шаг - заставить модель сначала вернуть пустой массив элементов, а затем вернуть актуальное обновление. Это заставит представление обновить. И затем, после короткой паузы, может последовать фактическое обновление. Для этого случая этого недостаточно. Само по себе это приводит к проблемам с макетом. Каким-то образом список (предположительно подкрепленный UITableView, который агрессивно перерабатывает свои ячейки) все же удается сохранить состояние, которое каким-то образом вызывает проблемы. И так ...

  2. Второй шаг - заставить представление предложить что-то , отличное от Списка , когда нет элементов. Это делается с помощью SwiftUI if и else, чтобы использовать другое представление в зависимости от наличия каких-либо элементов. С изменениями в модели, согласно шагу 1, это происходит при каждом обновлении.

Выполнение шагов (1) и (2), кажется, решает проблему. Пример кода ниже также включает метод .animation(.none) в представлении. Это было необходимо в моем коде, но в приведенном ниже примере кода это, кажется, не нужно.

A недостаток этого обходного пути - вы потеряете анимацию. И, очевидно, это что-то вроде хака, который, если Apple внесет изменения в будущем, может не работать. (Тем не менее, возможно, к тому времени ошибка будет исправлена.)

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()
      }
}
0 голосов
/ 16 января 2020

Это не связано с ошибкой ScrollView или SwiftUI. Я думаю, что у вас есть проблема с вашим AttributedLabel классом. Я попытался использовать обычный Text, и он работает нормально.

List {
        ForEach(self.testdata, id: \.self) { text in
            Group {
                  Text(student.name)
                    .background(Color.red)
            }.padding(.vertical, 20)
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...