У меня есть очень простой подкласс UIViewController
, который настраивает его представление в viewDidLoad
:
class TextViewController: UIViewController {
private var textView: UITextView?
var htmlText: String? {
didSet {
updateTextView()
}
}
private func updateTextView() {
textView?.setHtmlText(htmlText)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
textView = UITextView()
// add as subview, set constraints etc.
updateTextView()
}
}
(.setHtmlText
- это расширение UITextView, которое превращает HTML в NSAttributedString
, вдохновленное этим ответом )
Создан экземпляр TextViewController, для .htmlText
установлено значение «Извлечение ...», выполнен HTTP-запрос, и viewcontroller помещен в UINavigationController.
Это приводит к вызову updateTextView
, который не имеет никакого эффекта (.textView
по-прежнему равен нулю), но viewDidLoad
гарантирует, что текущее текстовое значение отображается при повторном вызове. Вскоре после этого HTTP-запрос возвращает ответ, и в теле этого ответа устанавливается .htmlText
, что приводит к другому вызову updateTextView
.
Весь этот код запускается в главной очереди (подтверждается установкой точек останова и проверкой трассировки стека), и все же, если нет значительных задержек в получении HTTP, окончательный отображаемый текст является заполнителем («Выборка. .. "). Проход в отладчике показывает, что последовательность:
1. updateTextView() // htmlText = "Fetching...", textView == nil
2. updateTextView() // htmlText = "Fetching...", textView == UITextView
3. updateTextView() // htmlText = <HTTP response body>
4. setHtmlText(<HTTP response body>)
5. setHtmlText("Fetching...")
Так что последний вызов setHtmlText
как-то обгоняет первый. Точно так же, странно, просматривая стек вызовов из # 5, в то время как setHtmlText
утверждает, что ему было передано «Fetching ...», вызывающая сторона считает, что передает HTTP-тело HTML.
Изменение получателя ответа HTTP для этого:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { vc.htmlText = html }
Вместо обычного:
DispatchQueue.main.async { vc.htmlText = html }
... приводит к отображению ожидаемого окончательного текста.
Все это поведение воспроизводимо на симуляторе или реальном устройстве. Немного хакерское чувство «решения» - это сделать еще один вызов updateTextView
в viewWillAppear
, но это просто маскировка происходящего.
Отредактировано, чтобы добавить:
Мне было интересно, достаточно ли одного вызова updateTextView
в viewWillAppear
, но его нужно вызывать с viewDidLoad
И viewWillAppear
, чтобы отобразить окончательное значение.
Отредактировано для добавления запрошенного кода:
let theVc = TextViewController()
theVc.htmlText = "<i>Fetching...</i>"
service.get(from: url) { [weak theVc] (result: Result<String>) in
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
DispatchQueue.main.async {
switch result {
case .success(let html):
theVc?.htmlText = html
case .error(let err):
theVc?.htmlText = "Failed: \(err.localizedDescription)"
}
}
}
navigationController.pushViewController($0, animated: true)
Отредактировано для добавления упрощенного регистра, исключающего службу HTTP, с таким же поведением:
let theVc = TextViewController()
theVc.htmlText = "<i>Before...</i>"
DispatchQueue.main.async {
theVc.htmlText = "<b>After</b>"
}
navigationController.pushViewController(theVc, animated: true)
Это дает эквивалентную последовательность вызовов updateTextView()
, как и раньше:
- "До", пока нет текстового обзора
- "До"
- "После"
И все же «До» - это то, что я вижу на экране.
Установка точки останова в начале setHtmlText
(«До») и пошаговое выполнение показывает, что, пока первый проход находится в NSAttributedString(data:options:documentAttributes:)
, цикл выполнения повторно вводится, а второе назначение («После») дается шанс дойти до завершения, присваивая его результат .attributedText
. Затем исходному NSAttributedString дается шанс завершиться, и он немедленно заменяет .attributedText
.
Это странный способ NSAttributedString
s генерируется из HTML (см. Кто-то, имеющий схожие проблемы при заполнении UITableView
)