Home > Software engineering >  reason: 'Modifications to the layout engine must not be performed from a background thread afte
reason: 'Modifications to the layout engine must not be performed from a background thread afte

Time:03-16

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();
            }
        }
        
        
    }
}

CodePudding user response:

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 main thread and do the following changes in your 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
                             weakSelf.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")
            }
        })
    }
}
  • Related