代码之家  ›  专栏  ›  技术社区  ›  Chris

当连续两次快速设置UITextView的.attributedText时,第二个赋值不起作用

  •  0
  • Chris  · 技术社区  · 5 年前

    我有一个很简单的 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 ,灵感来自 this answer

    创建了TextViewController的实例, .htmlText 设置为“获取…”,将发出HTTP请求,并将viewcontroller推送到UINavigationController上。

    这将导致调用 updateTextView 没有效果( .textView 视图加载 再次调用以确保显示当前文本值。不久之后,HTTP请求返回一个响应,并且 设置为该响应的主体,从而导致对 更新文本视图

    所有这些代码都在主队列上运行(通过设置断点和检查堆栈跟踪进行确认),但是,除非http get中有明显的延迟,否则显示的最后文本是占位符(“Fetching…”)。在调试器中单步执行会显示序列为:

    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 似乎超过了第一个。同样奇怪的是,当 设置文本 声称它是通过“获取…”传递的,调用方认为它传递的是HTTP HTML正文。

    更改HTTP响应的接收器以执行此操作:

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { vc.htmlText = html }
    

    而不是更传统的:

    DispatchQueue.main.async { vc.htmlText = html }
    

    所有这些行为在模拟器或真实设备上都是可复制的。一个有点古怪的感觉“解决办法”是再打一个电话给 更新文本视图 在里面 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)
    

    let theVc = TextViewController()
    
    theVc.htmlText = "<i>Before...</i>"
    
    DispatchQueue.main.async {
        theVc.htmlText = "<b>After</b>"
    }
    
    navigationController.pushViewController(theVc, animated: true)
    

    这将产生一个等价的调用序列 updateTextView() 如前所述:

    1. “之前”,还没有文本视图
    2. “之后”

    但“以前”是我在屏幕上看到的。

    在开始处设置断点 设置文本 NSAttributedString(data:options:documentAttributes:) 重新进入运行循环,第二个赋值(“After”)有机会运行到完成,将其结果赋值给 .attributedText . 然后,原始的nsattributed字符串有机会完成,它立即替换 .属性文本

    这是个怪癖 s是从HTML生成的(参见 similar issues UITableView )

    0 回复  |  直到 5 年前
        1
  •  1
  •   matt    5 年前

    我解决这个问题的方法是去掉扩展名,只需编写代码,将文本视图属性文本设置为使用串行调度队列。这是我的TextViewController:

    @IBOutlet private var textView: UITextView?
    let q = DispatchQueue(label:"textview")
    var htmlText: String? {
        didSet {
            updateTextView()
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        updateTextView()
    }
    private func updateTextView() {
        guard self.isViewLoaded else {return}
        guard let s = self.self.htmlText else {return}
        let f = self.textView!.font!
        self.q.async {
            let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(f.pointSize)\">%@</span>", s)
            DispatchQueue.main.async {
                let attrStr = try! NSAttributedString(
                    data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
                    options: [.documentType: NSAttributedString.DocumentType.html],
                    documentAttributes: nil)
                self.textView!.attributedText = attrStr
            }
        }
    }
    

    添加 print 语句显示所有事情都按预期的顺序发生 htmlText

        2
  •  0
  •   E.Coms    5 年前

    这样解决问题怎么样?

      private var textView: UITextView? = UITextView()
    

    去除 updateTextView() textView = UITextView()