0

I am trying to debug a pretty difficult problem and I cannot get to the bottom of it, so I was wondering if anyone could share some suggestions.

I have a TableView that transitions to a different VC when didSelectRowAt is called, and while the tap is registered immediately, something in the background is causing the new VC to only be presented as much as 5 seconds later and I cannot figure out what is causing this.

What I tried so far: - moving the iCloud tasks to the global thread - commenting out the entirety of the iCloud functions and saving data locally - disabling the Hero pod and using a built in segue with or without an animation - commenting out the tableview.reloadData() calls - commenting out everything in viewDidAppear - running this on both iOS12 and iOS13 GM, so it's not an OS issue - profiling the app, where I couldn't see anything out of the ordinary, but then again I am not very familiar with the profiler

I apologise for the long code dump but since I'm not sure what is causing this, I want to provide as much detail as I can.

Thanks a lot for any insights you might be able to share.

The main class

import UIKit
import SPAlert
import CoreLocation
import NotificationCenter
import PullToRefreshKit


class List: UIViewController {

    // Outlets
    @IBOutlet weak var plus: UIButton!
    @IBOutlet weak var notes: UIButton!
    @IBOutlet weak var help: UIButton!
    @IBOutlet weak var tableview: UITableView!
    @IBOutlet weak var greeting: UILabel!
    @IBOutlet weak var temperature: UILabel!
    @IBOutlet weak var weatherIcon: UIImageView!
    @IBOutlet weak var weatherButton: UIButton!
    @IBOutlet weak var greetingToTableview: NSLayoutConstraint!

    let locationManager = CLLocationManager()

    @IBAction func notesTU(_ sender: Any) {
        performSegue(withIdentifier: "ToNotes", sender: nil)
    }

    @IBAction func notesTD(_ sender: Any) {
        notes.tap(shape: .square)
    }

    @IBAction func plusTU(_ sender: Any) {
        hero(destination: "SelectionScreen", type: .zoom)
    }

    @IBAction func plusTD(_ sender: Any) {
        plus.tap(shape: .square)
    }

    @IBAction func helpTU(_ sender: Any) {
        performSegue(withIdentifier: "ToHelp", sender: nil)        
    }

    @IBAction func helpTD(_ sender: Any) {
        help.tap(shape: .square)
    }

    @IBAction func weatherButtonTU(_ sender: Any) {
        performSegue(withIdentifier: "OpenModal", sender: nil)
        selectedModal = "Weather"
    }


    // Variables
    override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent }

    // MARK: viewDidLoad
    override func viewDidLoad() {
        super.viewDidLoad()
        tableview.estimatedRowHeight = 200
        tableview.rowHeight = UITableView.automaticDimension

        // Retrieves ideas from the JSON file and assings them to the ideas array
        ideas = readJSONIdeas()
        goals = readJSONGoals()
        ideaStats = readJSONIdeaStats()
        decisions = readJSONDecisions()

        let time = Calendar.current.component(.hour, from: Date())
        switch time {
            case 21...23: greeting.text = "Good Night"
            case 0...4: greeting.text = "Good Night"
            case 5...11: greeting.text = "Good Morning"
            case 12...17: greeting.text = "Good Afternoon"
            case 17...20: greeting.text = "Good Evening"
            default: print("Something went wrong with the time based greeting")
        }

        temperature.alpha = 0
        weatherIcon.alpha = 0

        getWeather(temperatureLabel: temperature, iconLabel: weatherIcon)

        NotificationCenter.default.addObserver(self, selector: #selector(self.replayOnboarding), name: Notification.Name(rawValue: "com.cristian-m.replayOnboarding"), object: nil)


        if iCloudIsOn() {
            NotificationCenter.default.addObserver(self, selector: #selector(self.reloadAfteriCloud), name: Notification.Name(rawValue: "com.cristian-m.iCloudDownloadFinished"), object: nil)

            tableview.configRefreshHeader(with: RefreshHeader(),container:self) {
                // After the user pulls to refresh, synciCloud is called and the pull to refresh view is left open.
                // synciCloud posts a notification for key "iCloudDownloadFinished" once it finishes downloading, which then calls reloadAfteriCloud()
                // reloadAfteriCloud() loads the newly downloaded files into memory, reloads the tableview and closes the refresher view
                if iCloudIsAvailable() { synciCloud() }
                else {
                    self.alert(title: "It looks like you're not signed into iCloud on this device",
                          message: "Turn on iCloud in Settings to use iCloud Sync",
                          actionTitles: ["Got it"],
                          actionTypes: [.regular],
                          actions: [nil])
                }
            }
            synciCloud()
        }



        // Responsive Rules
        increasePageInsetsBy(top: 10, left: 20, bottom: 20, right: 20, forDevice: .iPad)
        increasePageInsetsBy(top: 0, left: 0, bottom: 14, right: 0, forDevice: .iPhone8)

        greetingToTableview.resize(to: 80, forDevice: .iPad)

    }

    @objc func replayOnboarding(_ notification:NSNotification){
        DispatchQueue.main.asyncAfter(deadline: .now()+0.2) {
            self.hero(destination: "Onboarding1", type: .zoom)
        }
    }

    @objc func reloadAfteriCloud(_ notification:NSNotification){
        goals = readJSONGoals()
        ideas = readJSONIdeas()
        ideaStats = readJSONIdeaStats()
        decisions = readJSONDecisions()

        tableview.reloadData()
        self.tableview.switchRefreshHeader(to: .normal(.none, 0.0))

        setWeeklyNotification()
    }

    @objc func goalCategoryTapped(_ sender: UITapGestureRecognizer?) {
        hero(destination: "GoalStats", type: .pushLeft)
    }

    @objc func ideaCategoryTapped(_ sender: UITapGestureRecognizer?) {
        hero(destination: "IdeaStats", type: .pushLeft)
    }


    override func viewWillAppear(_ animated: Bool) {
        tableview.reloadData()

        if shouldDisplayGoalCompletedAlert == true {
            shouldDisplayGoalCompletedAlert = false
            SPAlert.present(title: "Goal Completed", preset: .done)
        }

        if CLLocationManager.locationServicesEnabled() {
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        }

    }

}

The tableview extension

import UIKit

extension List: UITableViewDelegate, UITableViewDataSource {

    // MARK: numberOfSections
    func numberOfSections(in tableView: UITableView) -> Int { return 3 }

    // MARK: viewForHeader
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

        let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell") as! CategoryCell

        switch section {
        case 0:
            cell.title.text = "Goals"

            if goals.count != 0 { cell.emptyText.text = "You have \(completedGoals.count) achieved goals" }
            else { cell.emptyText.text = "No goals added yet" }

            if activeGoals.count > 0 { cell.emptyText.removeFromSuperview() }

            break
        case 1:
            cell.title.text = "Ideas"
            cell.emptyText.text = "No ideas added yet"

            if ideas.count > 0 { cell.emptyText.removeFromSuperview() }

            break
        case 2:
            cell.title.text = "Decisions"
            cell.arrow.removeFromSuperview()

            cell.emptyText.text = "No decisions added yet"
            if decisions.count > 0 { cell.emptyText.removeFromSuperview() }

            break
        default: print("Something went wrong with the section Switch")
        }

        if section == 0 {
            cell.button.addTarget(self, action: #selector(goalCategoryTapped(_:)), for: .touchUpInside)
        } else if section == 1 {
            cell.button.addTarget(self, action: #selector(ideaCategoryTapped(_:)), for: .touchUpInside)
        }

        return cell.contentView
    }

    // MARK: heightForHeader
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {

        var cellHeight = CGFloat(60)

        if (activeGoals.count > 0 && section == 0) || (ideas.count > 0 && section == 1) || (decisions.count > 0 && section == 2) {
            cellHeight = CGFloat(40)
        }

        return cellHeight
    }

    // MARK: numberOfRows
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        var numberOfRows: Int = 0

        if section == 0 { numberOfRows = activeGoals.count }
        if section == 1 { numberOfRows = ideas.count }
        if section == 2 { numberOfRows = decisions.count }

        return numberOfRows
    }

    // cellForRowAt
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        if indexPath.section == 0 {
            // Goal Cell
            let cell = tableview.dequeueReusableCell(withIdentifier: "GoalCell", for: indexPath) as! GoalCell

            cell.goalTitle?.text = activeGoals[indexPath.row].title

            if activeGoals[indexPath.row].steps!.count == 1 {
                cell.goalNoOfSteps?.text = "\(activeGoals[indexPath.row].steps?.count ?? 0) Step"
            } else if activeGoals[indexPath.row].steps!.count > 0 {
                cell.goalNoOfSteps?.text = "\(activeGoals[indexPath.row].steps?.count ?? 0) Steps"
            } else {
                cell.goalNoOfSteps?.text = "No more steps"
            }

            if goals[indexPath.row].stringDate != "I'm not sure yet" {
                cell.goalDuration.text = goals[indexPath.row].timeLeft(from: Date())
            } else {
                cell.goalDuration.text = ""
            }

            cell.selectionStyle = .none

            cell.background.hero.id = "goal\(realIndexFor(activeGoalAt: indexPath))"

            // Progress Bar
            cell.progressBar.configure(goalsIndex: realIndexFor(activeGoalAt: indexPath))

            return cell

        } else if indexPath.section == 1 {
            // Idea Cell
            let cell = tableView.dequeueReusableCell(withIdentifier: "IdeaCell", for: indexPath) as! IdeaCell

            cell.ideaTitle.text = ideas[indexPath.row].title
            if cell.ideaDescription != nil {
                cell.ideaDescription.text = String(ideas[indexPath.row].description!.filter { !"\n\t".contains($0) })

                if cell.ideaDescription.text == "Notes" || cell.ideaDescription.text == "" || cell.ideaDescription.text == " " || cell.ideaDescription.text == ideaPlaceholder {
                    cell.ideaDescriptionHeight.constant = 0
                    cell.bottomConstraint.constant = 16
                } else {
                    cell.ideaDescriptionHeight.constant = 38.6
                    cell.bottomConstraint.constant = 22
                }
            }

            cell.background.hero.id = "idea\(indexPath.row)"

            let image = UIImageView(image: UIImage(named: "delete-accessory"))
            image.contentMode = .scaleAspectFit
            cell.selectionStyle = .none

            return cell
        } else {
            // Decision Cell
            let cell = tableView.dequeueReusableCell(withIdentifier: "DecisionCell", for: indexPath) as! DecisionCell
            cell.title.text = decisions[indexPath.row].title

            let image = UIImageView(image: UIImage(named: "delete-accessory"))
            image.contentMode = .scaleAspectFit
            cell.selectionStyle = .none

            return cell
        }

    }

    // MARK: didSelectRowAt
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if indexPath.section == 0 {
            selectedCell = realIndexFor(activeGoalAt: indexPath)
            performSegue(withIdentifier: "toGoalDetails", sender: nil)
        } else if indexPath.section == 1 {
            selectedCell = indexPath.row
            performSegue(withIdentifier: "toIdeaDetails", sender: nil)
        } else {
            selectedDecision = indexPath.row
            hero(destination: "DecisionDetails", type: .zoom)
        }
        print("tap")
    }

    // MARK: viewForFooter
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let cell = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 10))
        cell.backgroundColor = UIColor(named: "Dark")
        return cell
    }

    // MARK: heightForFooter
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        let height:CGFloat = 18
        return height
    }

    // MARK: canEditRowAt
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true }

    // MARK: trailingSwipeActions
    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

        let action = UIContextualAction(style: .normal, title: nil, handler: { (action,view,completionHandler ) in

            var message = "This will delete this goal and all its steps permanently"
            if indexPath.section == 1 { message = "This will delete this idea permanently" }

            self.alert(title: "Are you sure?",
                       message: message,
                       actionTitles: ["No, cancel", "Yes, delete"],
                       actionTypes: [.regular, .destructive],
                       actions: [ nil, { action1 in
                            tableView.beginUpdates()
                            switch indexPath.section {
                            case 0:
                                deleteGoal(at: realIndexFor(activeGoalAt: indexPath))
                                tableView.deleteRows(at: [indexPath], with: .fade)
                            case 1:
                                deleteIdea(at: indexPath.row)
                                tableView.deleteRows(at: [indexPath], with: .fade)
                            case 2:
                                deleteDecision(at: indexPath.row)
                                tableView.deleteRows(at: [indexPath], with: .fade)
                            default: break
                            }
                            tableView.endUpdates()
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
                                tableView.reloadData()
                            })
                        },
                ]
            )
            completionHandler(true)
        })

        action.image = UIImage(named: "delete-accessory")
        action.backgroundColor = UIColor(named: "Dark")
        let confrigation = UISwipeActionsConfiguration(actions: [action])
        confrigation.performsFirstActionWithFullSwipe = false

        return confrigation
    }

}

The VC that gets opened

import UIKit

class GoalDetails: UIViewController {

    // MARK: Variables
    var descriptionExpanded = false
    var descriptionExists = true
    var keyboardHeight = CGFloat(0)
    override var preferredStatusBarStyle: UIStatusBarStyle { if #available(iOS 13.0, *) { return .darkContent } else { return .default } }


    // MARK: Outlets
    @IBOutlet weak var background: UIView!
    @IBOutlet weak var steps: UILabel!
    @IBOutlet weak var detailsTitle: UILabel!
    @IBOutlet weak var detailsDescription: UILabel!
    @IBOutlet weak var tableview: UITableView!
    @IBOutlet weak var progressBar: UIProgressView!
    @IBOutlet weak var plusButton: UIButton!
    @IBOutlet var descriptionHeight: NSLayoutConstraint!
    @IBOutlet weak var completeGoalButton: UIButton!
    @IBOutlet weak var completeGoalButtonHeight: NSLayoutConstraint!
    @IBOutlet weak var progressBarHeight: NSLayoutConstraint!
    @IBOutlet weak var dismissButton: UIButton!
    @IBOutlet weak var editButton: UIButton!
    @IBOutlet weak var tableviewBottomConstraint: NSLayoutConstraint!
    @IBOutlet weak var topToContainer: NSLayoutConstraint!
    @IBOutlet weak var bottomToContainer: NSLayoutConstraint!
    @IBOutlet weak var rightToContainer: NSLayoutConstraint!
    @IBOutlet weak var leftToContainer: NSLayoutConstraint!
    @IBOutlet weak var leftToTableview: NSLayoutConstraint!
    @IBOutlet weak var rightToTableview: NSLayoutConstraint!
    @IBOutlet weak var leftToEdit: NSLayoutConstraint!
    @IBOutlet weak var rightToPlus: NSLayoutConstraint!


    // MARK: Outlet Functions
    @IBAction func completeThisGoal(_ sender: Any) {
        shouldDisplayGoalCompletedAlert = true
        goals[selectedCell].completed = true
        goals[selectedCell].dateAchieved = Date()
        activeGoals = goals.filter { $0.completed == false }
        completedGoals = goals.filter { $0.completed == true }
        writeJSONGoals()
        hero.dismissViewController()

        setWeeklyNotification()
    }

    @IBAction func descriptionButtonTU(_ sender: Any) {
        if descriptionExpanded == false {
            descriptionHeight.isActive = false
            descriptionExpanded = true
        } else {
            descriptionHeight.isActive = true
            descriptionExpanded = false
        }
    }

    @IBAction func swipeDown(_ sender: Any) {
        dismissButton.tap(shape: .square)
        hero.dismissViewController()
    }


    @IBAction func dismissTU(_ sender: Any) {
        hero.dismissViewController()
    }

    @IBAction func dismissTD(_ sender: Any) {
        dismissButton.tap(shape: .square)
    }

    @IBAction func plusTU(_ sender: Any) {

        goals[selectedCell].steps?.append(Step(title: ""))

        let numberOfCells = tableview.numberOfRows(inSection: 0)

        tableview.reloadData()
        tableview.layoutIfNeeded()

        DispatchQueue.main.asyncAfter(deadline: .now()+0.2) {
            let cell = self.tableview.cellForRow(at: IndexPath.init(row: numberOfCells, section: 0)) as? StepCell
            cell?.label.becomeFirstResponder()
        }

        let indexPath = IndexPath(row: goals[selectedCell].steps!.count - 1, section: 0)
        tableview.scrollToRow(at: indexPath, at: .bottom, animated: true)

        progressBar.configure(goalsIndex: selectedCell)

        configureCompleteGoalButton(buttonHeight: completeGoalButtonHeight, progressBarHeight: progressBarHeight, progressBar: progressBar)

        updateNumberofSteps()
    }

    @IBAction func plusTD(_ sender: Any) {
        plusButton.tap(shape: .square)
    }


    @IBAction func editTU(_ sender: Any) {
        performSegue(withIdentifier: "ToGoalEdit", sender: nil)
    }

    @IBAction func editTD(_ sender: Any) {
        editButton.tap(shape: .rectangle)
    }


    // MARK: Class Functions
    func updateNumberofSteps(){
        if goals[selectedCell].steps!.count > 0 {
            steps.text = "\(goals[selectedCell].steps?.count ?? 0) Steps"
        } else {
            steps.text = "No more steps"
        }
    }


    // MARK: viewDidLoad
    override func viewDidLoad() {

        background.hero.id = "goal\(selectedCell)"

        self.background.clipsToBounds = true
        background.layer.cornerRadius = 16
        background.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]

        updateNumberofSteps()

        // Progress Bar
        progressBar.configure(goalsIndex: selectedCell)

        tableview.emptyDataSetSource = self
        tableview.emptyDataSetDelegate = self

        configureCompleteGoalButton(buttonHeight: completeGoalButtonHeight, progressBarHeight: progressBarHeight, progressBar: progressBar)

        // Responsive Rules
        increasePageInsetsBy(top: 0, left: 0, bottom: 14, right: 0, forDevice: .iPad)
        if UIDevice.current.userInterfaceIdiom == .pad {
            detailsTitle.font = UIFont.boldSystemFont(ofSize: 30)
            topToContainer.constant = 20
            leftToContainer.constant = 40
            rightToContainer.constant = 40
            bottomToContainer.constant = 40

            leftToTableview.constant = 40
            rightToTableview.constant = 40
            leftToEdit.constant = 40
            rightToPlus.constant = 30
        }

        increasePageInsetsBy(top: 0, left: 0, bottom: 12, right: 0, forDevice: .iPhone8)
    }


    // MARK: viewWillAppear
    override func viewWillAppear(_ animated: Bool) {
        // Deleting a goal from the Edit page seems to also call ViewWillAppear, which causes the app to crash unless checking whether the index exists anymore
        // selectedCell already get assigned the real index of this goal
        if goals.indices.contains(selectedCell) {

            detailsTitle.text = goals[selectedCell].title
            detailsDescription.text = goals[selectedCell].description

            if goals[selectedCell].description == "Reason" || goals[selectedCell].description == "" {
                descriptionHeight.constant = 0
            } else {
                descriptionHeight.constant = 58
            }
        }
    }

}

The iCloud related functions being called

func iCloudIsAvailable() -> Bool {
    // This function checks whether iCloud is available on the device
    if FileManager.default.ubiquityIdentityToken != nil { return true }
    else { return false }
}

func iCloudIsOn() -> Bool {
    // This function checks whether the user chose to use iCloud with Thrive
    if UserDefaults.standard.url(forKey: "UDDocumentsPath")! == iCloudPath || UserDefaults.standard.url(forKey: "UDDocumentsPath") == iCloudPath {
        return true
    }
    else {
        return false
    }
}

func synciCloud(){
    if iCloudIsAvailable() {
            do { try FileManager.default.startDownloadingUbiquitousItem(at: UserDefaults.standard.url(forKey: "UDDocumentsPath")!)
                do {
                    let status = try UserDefaults.standard.url(forKey: "UDDocumentsPath")!.resourceValues(forKeys: [.ubiquitousItemDownloadingStatusKey])

                    while status.ubiquitousItemDownloadingStatus != .current {
                        DispatchQueue.global().asyncAfter(deadline: .now() + 0.5, execute: {
                            print("iCloud still downloading - \(String(describing: status.ubiquitousItemDownloadingStatus))")
                        })
                    }
                    DispatchQueue.main.async {
                        NotificationCenter.default.post(name: Notification.Name(rawValue: "com.cristian-m.iCloudDownloadFinished"), object: nil)
                    }
                    print("iCloud up to date! - \(String(describing: status.ubiquitousItemDownloadingStatus))")
                }
                catch let error { print("Failed to get status: \(error.localizedDescription)") }
            }
            catch let error { print("Failed to download iCloud Documnets Folder: \(error.localizedDescription)") }
    } else {
        // TODO: Handle use case where iCloud is not available when trying to sync
        print("iCloud is not available on this device")
    }
}

Update: based on Duncan's answer, what fixed the problem was moving the three Segues I have in didSelectRowAt to the main queue like this:

DispatchQueue.main.async {
    self.performSegue(withIdentifier: "toGoalDetails", sender: nil)
}
CristianMoisei
  • 2,071
  • 2
  • 22
  • 28
  • 3
    Usually very long lags between triggering UI code and having it take effect is a symptom of doing UIKit calls from a background thread. (That can lead to all kind of bad outcomes, but long delays in responsiveness is a common one.) I don't see anything obvious from a glance at your code, but you posted a WHOLE BUNCH of code and I don't have the time to wade through it right now. I suggest setting breakpoints at various locations where you do UIKit calls and see if they break from any thread other than thread 1 (the main thread.) – Duncan C Sep 12 '19 at 00:23
  • 1
    Hi Christian, maybe you can try inspecting your code with [Whatchdog](https://github.com/wojteklu/Watchdog). It was suggested in [this answer](https://stackoverflow.com/questions/37526616/long-delay-when-pushing-a-view-controller-from-gcd) and with it you could figure out if the issue resides in something blocking the main thread for those 5 seconds. – SwissMark Sep 12 '19 at 02:11
  • 1
    as mentioned @DuncanC this is problem related to threading. why not you perform these action in some concurrent queues? ideas = readJSONIdeas() goals = readJSONGoals() ideaStats = readJSONIdeaStats() decisions = readJSONDecisions() – Muhammad Shauket Sep 12 '19 at 03:10
  • @SwissMark, I didn’t know about it but I will definitely check it out. Thank you. – CristianMoisei Sep 12 '19 at 06:38
  • @Shauket Sheikh that’s a good suggestion, I’ll try it. Thank you. – CristianMoisei Sep 12 '19 at 06:39
  • Hey @SwissMark I tried Watchdog and what's really strange is that if I have it installed and call it the issue disappears. I haven't changed anything else, so I'm pretty confused. I'll keep trying to get to the bottom of it, but this does mean that I can't replicate the issue while Watchdog is installed. – CristianMoisei Sep 12 '19 at 17:48
  • Hey @DuncanC and thanks for the info. You are right, a UIKit call was made from the background and while I'm still not sure what caused this, moving the Segues to the main queue fixed the problem. If you'd like to post your comment as an answer, I'd be happy to accept it. I'm grateful for all suggestions but I think what you said will be the most helpful to others in the future. – CristianMoisei Sep 12 '19 at 20:00
  • 1
    @CristianMoisei can you flag the UIKit call that was causing the problem, and add a section at the bottom of your question showing the fix that you used? (I usually mark changes to my questions/answers with `##EDIT` so they stand out.) – Duncan C Sep 13 '19 at 13:11
  • Good idea @DuncanC – CristianMoisei Sep 13 '19 at 17:14

2 Answers2

2

Normally if I cannot figure out what is causing the problem, I would perform "binary search" style debugging.

You mentioned that you have commented out the whole of viewDidAppear, but I am assuming you have not tried that with viewDidLoad.

In this case, I will comment out all the code in viewDidLoad, run it and see if the delay is still around.

If the delay is gone, I will comment out half the code in viewDidLoad, rerun. Generally, once I found the half that causes the delay, I would comment out half of that "bad" code and repeat until I find the exact lines causing the problem.

Hong Wei
  • 1,397
  • 1
  • 11
  • 16
  • Thank you @Hong Wei, I will try this tonight and reply if it worked. You’re right I haven’t commented out viewDidLoad for the main vc yet. – CristianMoisei Sep 12 '19 at 07:31
1

Usually very long lags between triggering UI code and having it take effect is a symptom of doing UIKit calls from a background thread. (That can lead to all kind of bad outcomes, but long delays in responsiveness is a common one.)

I don't see anything obvious from a glance at your code, but you posted a WHOLE BUNCH of code and I don't have the time to wade through it right now. I suggest setting breakpoints at various locations where you do UIKit calls and see if they break from any thread other than thread 1 (the main thread.) – Duncan C yesterday Delete

Duncan C
  • 128,072
  • 22
  • 173
  • 272