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

活动指示灯显示但不旋转

  •  0
  • CharlieL  · 技术社区  · 2 年前

    我创建了一个简单的单屏幕iOS应用程序,只有两个控件,一个活动指示器和一个按钮,如下所示。当我运行代码时,我会收到“开始”消息,5秒后会收到“停止”消息,但活动指示器只是不旋转地显示。我一定错过了一些基本的东西。这已经困扰了我好几个星期了。

    import UIKit
    
    class ViewController: UIViewController { 
        @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
            
        @IBAction func startButton(_ sender: Any) {
            activityIndicator.startAnimating()
            print("starting")
            sleep(5)
            print("stopping")
            activityIndicator.stopAnimating()
        }
    }
    
    1 回复  |  直到 2 年前
        1
  •  0
  •   Matic Oblak    2 年前

    您正在阻塞主线程,并在5秒内阻止任何更新或交互。屏幕不会刷新,在生产过程中,您的应用程序会被看门狗终止。

    试试这样的方法:

    @IBAction func startButton(_ sender: Any) {
        let activityIndicator = self.activityIndicator
        
        activityIndicator.startAnimating()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            activityIndicator.stopAnimating()
        }
    }
    

    要解释一下,有多种方法可以剥这只猫的皮。但总的来说,永远不要用任何东西阻塞主线程。要么你有昂贵的操作,要么你正在等待你应该在另一个线程上做的事情。

    既然你只是在打发时间,就没有理由使用另一个线程。您可以使用计时器或调度队列。计时器如下所示:

    @IBAction func startButton(_ sender: Any) {
        let activityIndicator = self.activityIndicator
        
        activityIndicator.startAnimating()
        Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in
            activityIndicator.stopAnimating()
        }
    }
    

    但如果你想使用另一个线程,那就意味着切换到另一个螺纹,等待5秒(你可以使用睡眠),然后切换回主螺纹来停止动画。这样做很不合适。

    我希望这能让你对这个话题有更多的了解。


    编辑: From comment将等待时间替换为使用CoreData执行一些持久操作。

    核心数据有上下文。每个上下文表示数据库的内存状态,该状态是延迟加载的。 要完成繁重的工作,您需要创建一个新的背景上下文,让它执行您的所有逻辑,保存到数据库并报告完成。

    class DatabaseManager {
        
        let container: NSPersistentContainer
        
        func doHeavyLoad(onDone: (() -> Void)? = nil) {
            let context = container.newBackgroundContext()
            context.perform {
                // Do all the stuff here
                
                // Save context
                try? context.save()
                
                // Report is done on main
                if let onDone {
                    DispatchQueue.main.async(execute: onDone)
                }
            }
        }
        
    }
    

    这可以与您的逻辑一起使用,如下所示:

    @IBAction func startButton(_ sender: Any) {
        let activityIndicator = self.activityIndicator
    
        activityIndicator.startAnimating()
        databaseManager.doHeavyLoad {
            activityIndicator.stopAnimating()
        }
    }
    

    重要的是要看到,有意地说,一个上下文已经有了 perform 方法,它将在上下文设计所在的线程上执行逻辑。它可能是主线程,也可能是其他线程。因此,使用 DispatchQueue.main.async 返回到主线程,以便可以调用UI API。 还要注意,放置 sleep 在这个里面 表演 操作实际上会等待给定的时间,然后活动指示器就会正常工作,因为上下文并没有在主线程上执行睡眠。

        2
  •  0
  •   CharlieL    2 年前

    再次感谢Matic Oblak和Hangar Fox。在我的代码中,我现在使用DispatchQueue,而不是直接调用“rescoreAllDecks”:

    // rescore all decks in background
    activityIndicator.startAnimating()
    DispatchQueue.global(qos: .background).async {
        self.mydelegate.deckClass.rescoreAllDecks()
        self.stopAnimating()
    }
    

    我还创建了一个单独的函数来停止微调器:

    func stopAnimating() {
        DispatchQueue.main.async {
            self.activityIndicator.stopAnimating()
        }
    }
    

    这似乎解决了问题。马特,我会记下你最后的回复,以备将来参考。谢谢大家。