reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'

My app worked fine before adding the check notification allow function. and when i add that function, I got this weird crash that gave an error of 'Modifications to layout engine must not be performed from a background thread after it has been accessed from the main thread'. The code that pops up in debugging is this. Any thoughts?

My Code

import UIKit
import GoogleMaps

class HomeViewController: FHBaseViewController {
    var devices : [Device]?
    var firstLoad = true;
    let defaults = UserDefaults.standard;
    var icons: [String:UIImage] = [:];
    
    
    
    
    @IBOutlet weak var movingView : UIView!;
    @IBOutlet weak var stoppedView : UIView!;
    @IBOutlet weak var inYardView : UIView!;
    @IBOutlet weak var offlineView : UIView!;
    @IBOutlet weak var statsView : UIView!;
    @IBOutlet weak var mapBed : UIView!;
    @IBOutlet weak var mapView : GMSMapView!;
    
    
    @IBOutlet weak var morningLabel : UILabel!;

    @IBOutlet weak var movingLabel : UILabel!;
    @IBOutlet weak var stoppedLabel : UILabel!;
    @IBOutlet weak var inYardLabel : UILabel!;
    @IBOutlet weak var offlineLabel : UILabel!;
    
    @IBOutlet weak var nameLabel : UILabel!;
    
    override func viewDidLoad() {
        super.viewDidLoad();
        
        self.reloadData();
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated);
        self.tabBarController?.tabBar.isHidden = false;
        if firstLoad{
            firstLoad = false;
        }else{
            if UserService.userInfo != nil{
                self.reloadData();
            }
        }
    
    }
    
    override func connectionResume() {
        self.reloadData()
    }
    
    
    func checkNotificationAllowed(){
        let data = defaults.object(forKey:"mute") as? Bool
        print(data!)
        if (data != nil) == true {
            let current = UNUserNotificationCenter.current()
            current.getNotificationSettings(completionHandler: { permission in
                switch permission.authorizationStatus  {
                case .authorized:
                    print("User granted permission for notification")
                case .denied:
                    print("User denied notification permission")
                    
                    let alert = UIAlertController(title: "Turn On Notifications".localized(), message: "Notifications are disabled. Please turn on app notifications to get device alerts.".localized(), preferredStyle: .alert)
                    alert.addAction(UIAlertAction(title: "Don't Allow".localized(), style: .cancel, handler: { action in
                        self.dismiss(animated: true, completion: nil)
                    }))
                    alert.addAction(UIAlertAction(title: "Allow".localized(), style: .destructive, handler: { action in
                        self.dismiss(animated: true, completion: nil)
                    }))
                    self.present(alert, animated: true, completion: nil)
                    
                case .notDetermined:
                    print("Notification permission haven't been asked yet")
                case .provisional:
                    // @available(iOS 12.0, *)
                    print("The application is authorized to post non-interruptive user notifications.")
                case .ephemeral:
                    // @available(iOS 14.0, *)
                    print("The application is temporarily authorized to post notifications. Only available to app clips.")
                @unknown default:
                    print("Unknow Status")
                }
            })
        }
    }
    
    func showLoginVC(){
        UserService.clearUser();
        UserService.clearLocalCacheUser();
        self.firstLoad = true;
        if buildType == "GPSTracker" {
            let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MarocLoginViewController") as! MarocLoginViewController;
            loginVC.successBlock = { (resp, password) in
                self.loginSuccess(resp, password: password);
            }
            loginVC.modalPresentationStyle = .fullScreen
            self.present(loginVC, animated: true, completion: {});
        } else {
            let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController;
            loginVC.successBlock = { (resp, password) in
                self.loginSuccess(resp, password: password);
            }
            loginVC.modalPresentationStyle = .fullScreen
            self.present(loginVC, animated: true, completion: {});
        }
        
    }
    
    func loginSuccess(_ resp: LoginResponse?, password: String){
        if let un = resp?.userName, let name = resp?.name, let apiToken = resp?.apiToken{
            let u = User(username: un, name: name, password: password, apiToken: apiToken, isActive: true, baseUrl: Configuration.getBaseUrl());
            UserService.setUser(user: u);
        }
        
        self.reloadData();
    }
    
    func reloadData(){
        self.nameLabel.text = UserService.userInfo?.name;
        self.mapView.clear();
        let update = GMSCameraUpdate.zoom(to: 1);
        self.mapView.moveCamera(update);
        self.showHud();
        DashBoardService.getDashBoard { (resp) in
            self.hideHud();
            if resp?.status == 1{
                self.movingLabel.text = "\(resp?.summary?.moving ?? 0)";
                self.stoppedLabel.text = "\(resp?.summary?.stopped ?? 0)";
                self.inYardLabel.text = "\(resp?.summary?.inyard ?? 0)";
                self.offlineLabel.text = "\(resp?.summary?.offline ?? 0)";
                
                DispatchQueue.main.async {
                    self.hideHud();
                    self.checkNotificationAllowed()
                }
            }
        }
        
        DeviceService.getDevice { (resp) in
            if resp?.status == 1{
                self.devices = resp?.devices;
                self.reloadMap();
            }
        }
        
        
    }
}
Answered by GursharanSachDeva in 707531022

The issue is caused because the alert is being shown from the background thread as the completion handler in getNotificationSettings is being run in the background thread. To prevent this crash present alert in the main thread am done the following changes in my code.

func checkNotificationAllowed(){
    let data = defaults.object(forKey:"mute") as? Bool
    print(data!)
    if (data != nil) == true {
        let current = UNUserNotificationCenter.current()
        current.getNotificationSettings(completionHandler: { permission in
            switch permission.authorizationStatus  {
            case .authorized:
                print("User granted permission for notification")
            case .denied:
                print("User denied notification permission")
                DispatchQueue.main.async {[weak self] in
                      guard let weakSelf = self else {return}

                      let alert = UIAlertController(title: "Turn On Notifications".localized(), message: "Notifications are disabled. Please turn on app notifications to get device alerts.".localized(), preferredStyle: .alert)
                      alert.addAction(UIAlertAction(title: "Don't Allow".localized(), style: .cancel, handler: { action in
                      self.dismiss(animated: true, completion: nil)
                      }))
                      alert.addAction(UIAlertAction(title: "Allow".localized(), style: .destructive, handler: { action in
                      weakSelf.dismiss(animated: true, completion: nil)
                      }))
                      weakSelf.present(alert, animated: true, completion: nil)
                }
            case .notDetermined:
                print("Notification permission haven't been asked yet")
            case .provisional:
                // @available(iOS 12.0, *)
                print("The application is authorized to post non-interruptive user notifications.")
            case .ephemeral:
                // @available(iOS 14.0, *)
                print("The application is temporarily authorized to post notifications. Only available to app clips.")
            @unknown default:
                print("Unknow Status")
            }
        })
    }
}

Could you show all the logs you get from the prints you have in the func ?

Please, use normal fonts to post your question, no need to have such a large font which makes it hard to read.

Note: you should replace

        if (data != nil) == true {

with a simpler

        if data != nil {
Accepted Answer

The issue is caused because the alert is being shown from the background thread as the completion handler in getNotificationSettings is being run in the background thread. To prevent this crash present alert in the main thread am done the following changes in my code.

func checkNotificationAllowed(){
    let data = defaults.object(forKey:"mute") as? Bool
    print(data!)
    if (data != nil) == true {
        let current = UNUserNotificationCenter.current()
        current.getNotificationSettings(completionHandler: { permission in
            switch permission.authorizationStatus  {
            case .authorized:
                print("User granted permission for notification")
            case .denied:
                print("User denied notification permission")
                DispatchQueue.main.async {[weak self] in
                      guard let weakSelf = self else {return}

                      let alert = UIAlertController(title: "Turn On Notifications".localized(), message: "Notifications are disabled. Please turn on app notifications to get device alerts.".localized(), preferredStyle: .alert)
                      alert.addAction(UIAlertAction(title: "Don't Allow".localized(), style: .cancel, handler: { action in
                      self.dismiss(animated: true, completion: nil)
                      }))
                      alert.addAction(UIAlertAction(title: "Allow".localized(), style: .destructive, handler: { action in
                      weakSelf.dismiss(animated: true, completion: nil)
                      }))
                      weakSelf.present(alert, animated: true, completion: nil)
                }
            case .notDetermined:
                print("Notification permission haven't been asked yet")
            case .provisional:
                // @available(iOS 12.0, *)
                print("The application is authorized to post non-interruptive user notifications.")
            case .ephemeral:
                // @available(iOS 14.0, *)
                print("The application is temporarily authorized to post notifications. Only available to app clips.")
            @unknown default:
                print("Unknow Status")
            }
        })
    }
}
reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'
 
 
Q