Start and stop swift3 activity indicator

I am learning Swift and am now working on my homework. One of my buttons calls a function in which I reload the collection view, and this takes time. I am trying to implement an activity indicator to show this process. The problem is that if I run the indicator, load the collection view, and then stop the indicator in the action of the button, the indicator will not appear. What will be the correct way to implement the indicator in this case? Here is the code snippet:

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) } 
+7
source share
3 answers

The problem you are facing is that the user interface changes, like the start of the activity indicator, it does not happen until your code returns, and the application maintains an event loop.

Your code is synchronous, so the activity indicator is never called.

You need to start the activity indicator, return, and then perform your laborious activity after the delay. You can use dispatch_after for this (or DispatchQueue.main.asyncAfter in Swift 3).

Here is an example IBAction that launches an activity indicator within 2 seconds after clicking a button:

 @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 } } 

Edit:

Your code will look like this:

 @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 } } } 

Edit # 2:

Again, user interface changes do not actually take place until your RETURNS code and applications use the event loop

If you have a code like this:

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

Then the following happens:

You queue a call to start the activity indicator the next time you return the code. (So ​​this is not the case.)

You are blocking the main thread with some kind of time-consuming task. As this continued, the activity indicator did not begin to rotate.

Finally, your laborious task is completed. Your code now calls activityIndicator.stopAnimating() . Finally, your code returns, your application services event loop, as well as the calls you need to start, and then immediately stop the activity indicator call. As a result, the activity indicator does not actually rotate.

This code does it differently:

 //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 } 

Since the closure passed to the DispatchQueue.main.asyncAfter call does not start immediately, this call returns immediately.

The application returns to service the event loop, and the activity indicator starts spinning. Shortly after this, the Grand Central Dispatch calls the block of code that you passed to it, which starts to perform your laborious operation (in the main thread), and then does cal to stop the activity indicator after the completion of the laborious task,

+11
source

Duncan With the rights, you must perform your laborious activities asynchronously. However, the best way to implement this is to add the onCIFilterView.reloadData() function to onCIFilterView.reloadData() . Thus, you can stop the activity indicator at the time the data download is complete. There's a good tutorial on completing the Handlers here: https://grokswift.com/completion-handlers-in-swift/

In addition, I think there is something else going on here. If you do not see this, it is because your CIFilterView is on top of it in the user interface view stack. You will need to do this. To do this, you need to call something like:

 view.bringSubview(toFront: activityIndicator) 

or

 view.superclass?.bringSubview(toFront: activityIndicator) 
0
source

Here is one thing you can try: - I created a custom boot mode and showed and hid it when I clicked the button. You can show and hide based on your specific needs. For a full working demo, you can see this repo on github - NKLoadingView in quick

 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) } } } 
0
source

Source: https://habr.com/ru/post/1012420/


All Articles