启动和停止活动指示器swift3

时间:2016-11-17 17:04:17

标签: ios swift uiactivityindicatorview

我正在学习Swift,现在正在完成我的家庭任务。 我的一个按钮调用一个函数,我重新加载一个集合视图,这需要时间。我正在尝试实施一个活动指示器来显示该过程。 问题是,如果我启动指示器,加载集合视图,然后在按钮操作中停止指示器,则指示器不会出现。 在这种情况下,实施指标的正确方法是什么? 这是代码片段:

let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.whiteLarge)
@IBOutlet weak var onCIFilterView: UICollectionView!
@IBOutlet var cifilterMenu: UIView!

@IBAction func onCIFiltersButton(_ sender: UIButton) {
    if (sender.isSelected) {
        hideCIFilters()
        sender.isSelected = false
    }
    else {
        activityIndicator.startAnimating()
        showCIFilters()  //the menu with UIView collection
        activityIndicator.stopAnimating()
        sender.isSelected = true
    }
}

func showCIFilters () {
    onCIFilterView.reloadData() // reloading collection view
    view.addSubview (cifilterMenu)
    let bottomConstraint = cifilterMenu.bottomAnchor.constraint(equalTo: mainMenu.topAnchor)
    let leftConstraint = cifilterMenu.leftAnchor.constraint(equalTo: view.leftAnchor)
    let rightConstraint = cifilterMenu.rightAnchor.constraint(equalTo: view.rightAnchor)
    let heightConstraint = cifilterMenu.heightAnchor.constraint(equalToConstant: 80)
    NSLayoutConstraint.activate([bottomConstraint, leftConstraint, rightConstraint, heightConstraint])
    view.layoutIfNeeded()
}

func hideCIFilters () {
    currentImage = myImage.image
    cifilterMenu.removeFromSuperview()

}

override func viewDidLoad() {
    super.viewDidLoad()
    //.....
    activityIndicator.center = CGPoint(x: view.bounds.size.width/2, y: view.bounds.size.height/2)
    activityIndicator.color = UIColor.lightGray
    view.addSubview(activityIndicator)
}

3 个答案:

答案 0 :(得分:11)

您面临的问题是,在您的代码返回并且应用程序为事件循环提供服务之前,UI更改(如启动活动指示符)不会发生。

您的代码是同步的,因此活动指示器永远不会被调用。

您需要启动活动指示器旋转,返回,然后在延迟后执行耗时的活动。您可以使用dispatch_after(或Swift 3中的DispatchQueue.main.asyncAfter)。

以下是点击按钮后运行活动指示器2秒的示例IBAction

@IBAction func handleButton(_ sender: UIButton) {
  activityIndicator.startAnimating()
  sender.isEnabled = false
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {

    // your time-consuming code here
    sleep(2) //Do NOT use sleep on the main thread in real code!!!
    self.activityIndicator.stopAnimating()
    sender.isEnabled = true
  }

}

编辑:

您的代码如下所示:

@IBAction func onCIFiltersButton(_ sender: UIButton) {
    if (sender.isSelected) {
        hideCIFilters()
        sender.isSelected = false
    }
    else {
        activityIndicator.startAnimating()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) {
            showCIFilters()  //the menu with UIView collection
            activityIndicator.stopAnimating()
            sender.isSelected = true
        }
    }
}

编辑#2:

同样,在您的代码RETURNS和应用程序为事件循环提供服务

之前,实际上不会发生UI更改

如果你有这样的代码:

activityIndicator.startAnimating()
sleep(2)  //This is in place of your time-consuming synchronous code.
activityIndicator.stopAnimating()

然后会发生什么:

您排队调用以在下次代码返回时启动活动指示器。 (所以它还没有发生。)

您使用一些耗时的任务来阻止主线程。在此过程中,您的活动指示器尚未开始旋转。

最后,您的耗时任务完成。现在您的代码调用activityIndicator.stopAnimating()。最后,您的代码返回,您的应用程序服务事件循环,以及启动调用,然后立即停止调用活动指示器。因此,活动指标实际上并没有旋转。

此代码的表现方式不同:

//Queue up a call to start the activity indicator
activityIndicator.startAnimating()

//Queue up the code in the braces to be called after a delay.
//This call returns immediately, before the code in the braces is run.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
    showCIFilters()  //the menu with UIView collection
    activityIndicator.stopAnimating()
    sender.isSelected = true
}

由于传递给DispatchQueue.main.asyncAfter的调用没有立即运行,因此该调用立即返回。

应用程序返回服务事件循环,活动指示器开始旋转。不久之后,Grand Central Dispatch会调用您传递给它的代码块,它会开始执行耗时的操作(在主线程上),然后在耗时的任务完成后生成一个cal来停止活动指示器

答案 1 :(得分:1)

这是您可以尝试的一件事: -我创建了一个自定义加载视图,并在单击按钮时将其显示和隐藏。您可以根据自己的特定需求显示和隐藏。有关完整的演示示例,您可以在github-NKLoadingView in swift

上看到此仓库。
import UIKit

class NKLoadingView: UIView {

let width: CGFloat = 150
let height: CGFloat = 60
let cornerRadius: CGFloat = 15
let spacing: CGFloat = 20
var label = UILabel()
var fontSize: CGFloat = 20
var isVisible: Bool = false


override init(frame: CGRect) {
    let window = UIApplication.shared.keyWindow!
    let viewFrame = CGRect(x: 50, y: window.frame.height / 2 - height / 2, width: window.frame.width - 2 * 50, height: height)
    super.init(frame: viewFrame)
    setupView()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

func setupView(){
    self.layer.cornerRadius = cornerRadius
    self.layer.backgroundColor = UIColor.black.withAlphaComponent(0.65).cgColor
    let spinner = UIActivityIndicatorView(activityIndicatorStyle: .white)
    spinner.startAnimating()
    spinner.frame = CGRect(x: CGFloat(20), y: CGFloat(10), width: 40, height: 40)

    label = UILabel(frame: CGRect(x: spinner.frame.origin.x + spinner.frame.width + spacing, y:  spinner.frame.origin.y, width: self.frame.width - spacing - spinner.frame.width - 20, height: spinner.frame.height))
    label.text  = "Please Wait..."
    label.textColor = UIColor.white
    label.font = UIFont.systemFont(ofSize: fontSize)

    self.addSubview(spinner)
    self.addSubview(label)
}

func stopAnimating(){
    isVisible = !isVisible
    self.removeFromSuperview()
}

func startAnimating(message: String){
    label.text = message
    isVisible = !isVisible
    UIApplication.shared.keyWindow!.addSubview(self);

 }
}

//View Controller code
class ViewController: UIViewController {

var loading: NKLoadingView?
override func viewDidLoad() {
    super.viewDidLoad()

    loading = NKLoadingView()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()

}

@IBAction func startEndAnimation(_ sender: Any) {
    if !loading!.isVisible {
        loading?.startAnimating(message: "Please Wait....")
        (sender as! UIButton).setTitle("Stop Animation", for: .normal)
    }else{
        loading?.stopAnimating()
        (sender as! UIButton).setTitle("Start Animation", for: .normal)
    }
}

}

答案 2 :(得分:0)

Duncan C是对的,你应该异步进行耗时的活动。但是,实现它的最佳方法是将completionHandler添加到onCIFilterView.reloadData()函数中。这样,您可以在数据重新加载完成时停止活动指示器。这里有一个关于completionHandlers的好教程:https://grokswift.com/completion-handlers-in-swift/

另外,我认为这里还有其他的东西。如果你看不到它,那是因为你的CIFilterView位于UI视图堆栈的顶部。你需要把它推向前进。要做到这一点,你需要调用类似的东西:

view.bringSubview(toFront: activityIndicator)

view.superclass?.bringSubview(toFront: activityIndicator)