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