Проблема с высотой кадра с помощью пользовательского UIViewRepresentable UITextView в SwiftUI - PullRequest
1 голос
/ 27 февраля 2020

Я создаю пользовательский UITextView для SwiftUI через UIViewRepresentable. Он предназначен для отображения NSAttributedString и обработки нажатий на ссылки. Все работает, но высота кадра полностью испорчена, когда я показываю этот вид внутри NavigationView со встроенным заголовком.

import SwiftUI

struct AttributedText: UIViewRepresentable {
  class Coordinator: NSObject, UITextViewDelegate {
    var parent: AttributedText

    init(_ view: AttributedText) {
      parent = view
    }

    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
      parent.linkPressed(URL)
      return false
    }
  }

  let content: NSAttributedString
  @Binding var height: CGFloat
  var linkPressed: (URL) -> Void

  public func makeUIView(context: Context) -> UITextView {
    let textView = UITextView()
    textView.backgroundColor = .clear
    textView.isEditable = false
    textView.isUserInteractionEnabled = true
    textView.delegate = context.coordinator
    textView.isScrollEnabled = false
    textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    textView.dataDetectorTypes = .link
    textView.textContainerInset = .zero
    textView.textContainer.lineFragmentPadding = 0
    return textView
  }

  public func updateUIView(_ view: UITextView, context: Context) {
    view.attributedText = content

    // Compute the desired height for the content
    let fixedWidth = view.frame.size.width
    let newSize = view.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))

    DispatchQueue.main.async {
      self.height = newSize.height
    }
  }

  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
}


struct ContentView: View {

  private var text: NSAttributedString {
    NSAttributedString(string: "Eartheart is the principal settlement for the Gold Dwarves in East Rift and it is still the cultural and spiritual center for its people. Dwarves take on pilgrimages to behold the great holy city and take their trips from other countries and the deeps to reach their goal, it use to house great temples and shrines to all the Dwarven pantheon and dwarf heroes but after the great collapse much was lost.\n\nThe lords of their old homes relocated here as well the Deep Lords. The old ways of the Deep Lords are still the same as they use intermediaries and masking themselves to undermine the attempts of assassins or drow infiltrators. The Gold Dwarves outnumber every other race in the city and therefor have full control of the city and it's communities.")
  }

  @State private var height: CGFloat = .zero

  var body: some View {
    NavigationView {
      List {
        AttributedText(content: text, height: $height, linkPressed: { url in print(url) })
          .frame(height: height)

        Text("Hello world")
      }
      .listStyle(GroupedListStyle())
      .navigationBarTitle(Text("Content"), displayMode: .inline)
    }
  }
}

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

Когда вы запустите этот код, вы увидите, что AttributedText Ячейка будет слишком маленькой, чтобы вместить ее содержимое.

enter image description here

Когда вы удаляете параметр displayMode: .inline из navigationBarTitle, он отображается нормально.

enter image description here

Но если я добавлю еще одну строку для отображения значения высоты (Text("\(height)")), она снова обрывается.

enter image description here

Может быть, это какое-то состояние гонки, вызванное обновлениями просмотра через изменения состояния? Само значение height является правильным, просто кадр на самом деле не такой высокий. Есть ли обходной путь?

Использование ScrollView с VStack решает проблему, но я бы действительно предпочел использовать List из-за способа отображения контента в реальном приложении .

1 Ответ

0 голосов
/ 27 февраля 2020

Мне удалось найти версию моего AttributedText View, которая в основном работает.

struct AttributedText: UIViewRepresentable {
  class HeightUITextView: UITextView {
    @Binding var height: CGFloat

    init(height: Binding<CGFloat>) {
      _height = height
      super.init(frame: .zero, textContainer: nil)
    }

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

    override func layoutSubviews() {
      super.layoutSubviews()
      let newSize = sizeThatFits(CGSize(width: frame.size.width, height: CGFloat.greatestFiniteMagnitude))
      if height != newSize.height {
        height = newSize.height
      }
    }
  }

  class Coordinator: NSObject, UITextViewDelegate {
    var parent: AttributedText

    init(_ view: AttributedText) {
      parent = view
    }

    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
      parent.linkPressed(URL)
      return false
    }
  }

  let content: NSAttributedString
  @Binding var height: CGFloat
  var linkPressed: (URL) -> Void

  public func makeUIView(context: Context) -> UITextView {
    let textView = HeightUITextView(height: $height)
    textView.attributedText = content
    textView.backgroundColor = .clear
    textView.isEditable = false
    textView.isUserInteractionEnabled = true
    textView.delegate = context.coordinator
    textView.isScrollEnabled = false
    textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    textView.dataDetectorTypes = .link
    textView.textContainerInset = .zero
    textView.textContainer.lineFragmentPadding = 0
    return textView
  }

  public func updateUIView(_ textView: UITextView, context: Context) {
    if textView.attributedText != content {
      textView.attributedText = content

      // Compute the desired height for the content
      let fixedWidth = textView.frame.size.width
      let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))

      DispatchQueue.main.async {
        self.height = newSize.height
      }
    }
  }

  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
}

В некоторых случаях вы можете видеть, что представление внезапно увеличивается в размере, но почти на всех моих экранах, где я нахожусь используя это, это огромное улучшение. Авторазмер UITextView в SwiftUI все еще остается большой головной болью, и любые ответы, которые улучшат это, будут с благодарностью приняты:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...