【问题标题】:Activity Indicator is not animated when press navigationItem button按下 navigationItem 按钮时,活动指示器没有动画
【发布时间】:2020-10-15 20:10:28
【问题描述】:

我尝试在按下 navigationItem 的按钮时触发活动指示器的动画。但我发现活动指示器没有旋转。我尝试将scanerIndicator.startAnimating() 放入主线程,但没有帮助。

代码是采集路由器打开的端口,我想在按下navigationItem按钮时开始旋转,在返回openPorts时停止旋转。感谢任何关于哪里出错的线索/提示?

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start", style: .plain, target: self, action: #selector(startScan))
        ...
    }

    @objc func startScan() {
        scanerIndicator.startAnimating()
        
        if let address = serverAddress.text, !address.isEmpty {
            if let start = Int(startPort.text!) {
                if let stop = Int(stopPort.text!) {
                    if start < stop {
                        openPorts = netUtility.scanPorts(address: address, start: start, stop: stop)
                        print("Open Open: \(openPorts)")
                        if !openPorts.isEmpty {
                            scanerIndicator.stopAnimating()
                            table.reloadData()
                        } else {
                            showErrorMessage(errorTitle: "Not at all", errorMessage: "No open ports were found")
                        }
                    } else {
                        showErrorMessage(errorTitle: "Range error", errorMessage: "Start port should be smaller than stop port")
                    }
                } else {
                    showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
                }
            } else {
                showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
            }
        } else {
            showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
        }
    }

收集端口的代码:

   // MARK: - Port Scaner
    // Get number of threads for scan ports
    func getSegmentsQueues(min: Int, max: Int, maxPerSegment: Int) -> [[Int]] {
        
        var start: Int = min
        var portSegments = [[Int]]()
        
        while start <= max {
            var _portSegment = [Int]()
            
            for _ in 1...maxPerSegment {
                
                if start <= max {
                    _portSegment.append(start)
                }
                
                start += 1
            }
            
            portSegments.append(_portSegment)
        }
        
        return portSegments
    }


    // Crate queques for scan ports by segments
    func QueueDispatchPort(address: String, minPort: Int, maxPort: Int, segmentsQueues: (Int, Int, Int) -> [[Int]]) -> [Int] {
        var openPorts : [Int] = []
        let segmentPorts = segmentsQueues(minPort, maxPort, 1);
        
        let group = DispatchGroup()
        
        for segment in segmentPorts {
            group.enter()
            DispatchQueue.global().async {
                
                for port in segment {
                    let client = TCPClient(address: address, port: Int32(port))
                    switch client.connect(timeout: 2) {
                        case .success:
                            openPorts.append(port)
                        
                        case .failure(_):
                            print("port \(port) closed")
                    }
                    
                    client.close()
                }
                group.leave()
            }
        }
        
        group.wait()

        return openPorts
    }
    
    // Scans ports from an address and a range given by the user
    func scanPorts(address : String, start : Int, stop : Int) -> [Int] {
        let openPorts = QueueDispatchPort(
            address: address, minPort: start, maxPort: stop, segmentsQueues:
            getSegmentsQueues(min:max:maxPerSegment:))
        
        return openPorts
    }

代码更新,我将这段代码(扫描端口)放在主线程上,这次删除了stopAnimating()。在长时间运行代码返回(DispatchQueue.main 中的内容)后,activityIndi​​cator 会被动画化。还是不行……

@objc func startScan() {
        scanerIndicator.startAnimating()
        
        DispatchQueue.main.async { [self] in
            if let address = serverAddress.text, !address.isEmpty {
                if let start = Int(startPort.text!) {
                    if let stop = Int(stopPort.text!) {
                        if start < stop {
                            openPorts = netUtility.scanPorts(address: address, start: start, stop: stop)
                            print("Open Open: \(openPorts)")
                            if !openPorts.isEmpty {
                                table.reloadData()
                            } else {
                                showErrorMessage(errorTitle: "Not at all", errorMessage: "No open ports were found")
                            }
                        } else {
                            showErrorMessage(errorTitle: "Range error", errorMessage: "Start port should be smaller than stop port")
                        }
                    } else {
                        showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
                    }
                } else {
                    showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
                }
            } else {
                showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
            }
        }
    }

【问题讨论】:

    标签: swift uiactivityindicatorview


    【解决方案1】:

    有点难以理解您的代码在做什么,但我的猜测是,即使您使用队列进行端口扫描,因为您使用的是 DispatchGroup,代码会阻塞,直到所有端口扫描完成.

    如果您有执行以下操作的同步代码:

    1. 开始动画活动指示器
    2. 执行长时间运行的任务(在主线程上)
    3. 停止动画活动指示器

    那么你就永远看不到动画了。问题是在您的代码返回并且您的应用访问其事件循环之前,动画不会开始。

    您需要像这样编写代码:

    scanerIndicator.startAnimating()
    DispatchQueue.main.async {
       //Do long-running task
       scanerIndicator.stopAnimating()
    }
    

    这是因为在调用startAnimating() 之后,您将异步调用添加到主调度队列(在主线程上),然后返回。您的应用程序的函数调用堆栈全部返回,您的应用程序访问事件循环,活动指示器开始旋转。然后系统会选择您添加到主队列的异步任务并开始运行该任务。最后,当你的长时间运行的任务完成时,你关闭活动指示器(在里面调用的代码中 async() 电话。

    【讨论】:

    • 谢谢,我尝试将长期运行代码放在主线程上作为您的解决方案,这次指标在返回长期运行代码后动画。请查看我的相关更新。
    • 我不明白你在说什么。你说“这一次指标是在返回长期代码后动画的”。您是说在进行更改后,指示器会在扫描代码运行时旋转?”
    • 我的意思是指标在返回长期代码后旋转。好吧,我一整天都检查了这个问题,还创建了一些简单的项目进行比较,并证明你的答案是正确的。所以问题应该出在我收集开放端口的代码中。我现在还在检查。
    • 谢谢邓肯,我已经修好了!
    【解决方案2】:

    最后,我终于让这段代码工作了,DispatchQueue 真是烧了我一整天!

    我的修复流程:

        func abc() {
            activityIndicator.startAnimating()
            
            DispatchQueue.global(qos: .default).async {
                // put your heavy code here
                
                DispatchQueue.main.async {
                    // UI code must be on main thread
                    activityIndicator.stopAnimating()
                }
            }
        }
    
    

    我要做什么?

    1. 首先,我将两个端口扫描码合并为一个函数。
    2. 将耗时较长的代码放入DispatchQueue.global(qos: .default).async。我首先尝试使用主线程,但它失败了,长时间运行的代码也应该在后台线程中。 global() 是后台线程吗?我稍后会挖掘更多:)
    3. 使用@escaping completionHandler 作为回调,在完成此繁重代码时将openPorts 提供给调用者。因为在这种情况下你不能使用返回值,因为我们不知道繁重的代码何时完成,所以我们使用回调将参数传回给调用者。
        // Scans ports from an address and a range given by the user
        func scanPorts(address : String, start : Int, stop : Int, completion: @escaping ([Int]) -> ()) {
            
            DispatchQueue.global(qos: .default).async {
                for port in start...stop {
                    let client = TCPClient(address: address, port: Int32(port))
                    switch client.connect(timeout: 2) {
                        case .success:
                            self.openPorts.append(port)
                            print("HH: port: \(self.openPorts)")
                        case .failure(_):
                            print("port \(port) closed")
                    }
                    client.close()
                }
                completion(self.openPorts)
            }
        }
    

    调用者:记得把 UI 代码放在这里的主线程上。

    @objc func startScan() {
            scanerIndicator.startAnimating()
            view.endEditing(true)
            self.view.isUserInteractionEnabled = false
            
            if let address = serverAddress.text, !address.isEmpty {
                if let start = Int(startPort.text!) {
                    if let stop = Int(stopPort.text!) {
                        if start < stop {
                            netUtility.scanPorts(address: address, start: start, stop: stop) { [self] (availablePorts) in
                                openPorts = availablePorts
                                print("$Open ports: \(self.openPorts)")
                                if !openPorts.isEmpty {
                                    DispatchQueue.main.async {
                                        table.reloadData()
                                        self.scanerIndicator.stopAnimating()
                                    }
                                } else {
                                    showErrorMessage(errorTitle: "Not at all", errorMessage: "No open ports were found")
                                }
                            }
                        } else {
                            showErrorMessage(errorTitle: "Range error", errorMessage: "Start port should be smaller than stop port")
                        }
                    } else {
                        showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
                    }
                } else {
                    showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
                }
            } else {
                showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-14
      • 1970-01-01
      • 2012-06-24
      • 2021-03-09
      • 1970-01-01
      • 1970-01-01
      • 2012-07-30
      • 2020-05-14
      相关资源
      最近更新 更多