0

I'm using the ToastView class from this answer, but I experience the same issue with an activity indicator. I have ToastViews throughout the app but have no qualms switching to an indicator if it will work

My app has a tableview that presents options. After an option is selected, it needs to load a lot of information about the option, like so

class TableController : UITableViewController
{
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        index = (indexPath as NSIndexPath).row
        super.performSegue(withIdentifier: "cellSelectedSegue", sender: self)
    }
}

class cellSelectionView : UIViewController
{
    override func viewDidLoad() {
        super.viewDidLoad()

        if let data = parser.getData(...){
            // data processing
        }
    }
}

I'd like to let the user know a loading operation is taking place when they pick a cell. Otherwise a cell gets selected, takes on the cell-selected color, and the app sits still for a second before the segue takes place.

To do so, I've tried

// In the tableview:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // Solution 1: ToastView.showInParent(parentView: self.view, text: "ayy lmao", duration: 1.0)
    // Solution 2: indicator.startAnimating()
   index = (indexPath as NSIndexPath).row
    super.performSegue(withIdentifier: "cellSelectedSegue", sender: self)
}

Neither solution works as the UI event is not fired until the cell selection UI event is complete, which doesn't complete until the data processing has taken place, entirely defeating the point. I've also tried moving the UI calls to willSelectRowAt with no difference. I'd like to know how I can let the user know of a loading task that involves a UI event.

If you're wondering why I don't do the data processing before showing the cells rather than on-selection, it's due to the large amount of processing per row. The tableview would take a very long time to appear if done this way

Jason
  • 808
  • 6
  • 25
  • I couldn't get when exactly you want to load the large amount of data, and for what. E.g do you want to load loads of data to populate the table view? Or do you want to load the large amount of data after you select the row? – Danilo Gomes Sep 28 '17 at 20:04
  • Hello Danilo. It is after the row is selected. The process is to show the table view, have the user select a cell, load the data about the user's choice, then present the choice and its information on the next view – Jason Sep 28 '17 at 20:19

2 Answers2

1

This should do what you want.

import UIKit

class ExampleTableViewController: UITableViewController {

    var names : [String] = []
    var activityIndicator : UIActivityIndicatorView?

    override func viewDidLoad() {
        super.viewDidLoad()
        configureActivityIndicator()
        retrieveTableViewData()
    }

    func configureActivityIndicator(){
        activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
        activityIndicator?.hidesWhenStopped = true
        activityIndicator?.center = self.view.center
        self.view.addSubview(activityIndicator!)
    }

    func retrieveTableViewData(){
        names = ["John", "Will", "Ana"]
        self.tableView.reloadData()
    }

    func retrieveLoadsOfDataAfterSelectingRow(indexPath: IndexPath){
        // Example of slow code. You would replace this by some background processing.
        // Then when it is finished you would call the next screen.
        activityIndicator?.startAnimating()
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) {
            self.activityIndicator?.stopAnimating()
        }
    }


    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return names.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellIdentifier = "cell"
        var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)

        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
        }
        cell!.textLabel?.text = names[indexPath.row]
        return cell!
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        retrieveLoadsOfDataAfterSelectingRow(indexPath: indexPath)
    }
}

Replace this part:

 func retrieveLoadsOfDataAfterSelectingRow(indexPath: IndexPath){
        // Example of slow code. You would replace this by some background processing.
        // Then when it is finished you would call the next screen.
        activityIndicator?.startAnimating()
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) {
            self.activityIndicator?.stopAnimating()
        }
    }

By something like:

func retrieveLoadsOfDataAfterSelectingRow(indexPath: IndexPath){

        activityIndicator?.startAnimating()

        DispatchQueue.global(qos: .background).async {

            // Heavy processing here.
            if let data = parser.getData(...){
                // data processing
            }

            DispatchQueue.main.async {
                self.activityIndicator?.stopAnimating()
               // Call what you want here, to open details screen.
            }


        }

    }
Danilo Gomes
  • 767
  • 5
  • 15
  • That was just an example. Any heavy processing should happen in the background thread to not block the ui. – Danilo Gomes Sep 28 '17 at 23:42
  • I've edit the post to match what you need. Don't use the timer, it was just an example. Use the second part. If that solves your problem, please check as solution. – Danilo Gomes Sep 28 '17 at 23:47
1

A few different options for you -

  1. On your pushed view, put your getData method inside viewDidAppear instead of viewDidLoad. The view won't push until everything's been done inside viewDidLoad. Then you can show your loading indicator on the pushed view.
  2. Put a UIActivityIndicator inside the cell and hinge whether to show/animate it on a variable that holds the index of the cell pressed, such as indexLoading. Then set indexLoading to the row of the cell pressed, and call reloadRowsAtIndexPaths on that index path. If the cell's indexPath.row matches indexLoading, show the indicator
  3. On didSelectRow... send a notification that a view will be pushed. Listen for that notification from your table view controller and show the loading indicator over your table view.
inorganik
  • 24,255
  • 17
  • 90
  • 114
  • Yup. I moved the ToastView call out of tableview didSelectRowAt and into cellSelectionView viewDidLoad. I also moved getData out of viewDidLoad and added an override for viewDidAppear. I then put getData in viewDidAppear and added a refresh of the relevant UI components that hold the data – Jason Sep 28 '17 at 22:48
  • 1
    The result: Cell is selected, segue takes place (UI is handed over!!), view controller throws Toast onto its UI stack, view appears, Toast is drawn, viewDidAppear is called. Inside viewDidAppear, the data is processed, then a call is placed for blank labels of new page to set/refresh their data. They are redrawn and bam! Thank you – Jason Sep 28 '17 at 22:51