0

I'm developing an iOS app and at one view controller, I need to make a timer that repeats every 2 seconds and them update a stack view that is in the view controller. I made this code: viewDidLoad() Here starts my timer

override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.information.numberOfLines = 20
        configureView()
        if #available(iOS 10.0, *) {
            timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(self.reload), userInfo: nil, repeats: true)
        } else {
            self.information.isHidden = false
            self.information.text = "Esta opción solamente es válida para dispositivos con iOS 10.0 o superior"
        }
    }

This is the configureView() method:

func configureView(){
        if questionIndex != nil{
            self.titulo.text = self.titulo.text! + " " + String(self.questionIndex!)
        }
        if self.statusCode! > 0{
            if let numAns = self.numAnswers{
                self.information.isHidden = true
                for index in (0..<numAns){
                    let btn: UIButton = UIButton()
                    btn.backgroundColor = self.abcDary[self.abc[index % self.abc.count]]
                    btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 21)
                    btn.setTitle(self.abc[index % self.abc.count], for: .normal)
                    btn.addTarget(self, action: #selector(self.answerSelected(sender:)), for: .touchUpInside)
            
                    self.stackRespuestas.addArrangedSubview(btn)
                }
            }
        }
        else{
            self.information.isHidden = false
            self.information.text = "Aún no puedes resolver esta pregunta"
            
        }
    }

and this is the reload method which is called by timer every 2 seconds:

@objc func reload(){
        DispatchQueue.main.asyncAfter(deadline: .now()){
            self.getStatus()
        }
        DispatchQueue.main.asyncAfter(deadline: .now()+1){
            if self.information.isHidden == true && self.statusCode == 0{
                for view in self.stackRespuestas.subviews{
                    if view is UIButton{
                        view.removeFromSuperview()
                    }
                }
                self.information.isHidden = false
                self.information.text = "Aún no puedes contestar a esta pregunta"
            }
            else if self.information.isHidden == false && self.statusCode! > 0{
                    self.information.isHidden = true
                    self.configureView()
            }
        }
    }

and this is the error:

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

Can anyone help me with this please?

Ming Ye
  • 33
  • 3
  • 1
    The error message is self explanatory. Did you google it? https://stackoverflow.com/search?q=Modifications+to+the+layout+engine+must+not+be+performed+from+a+background+thread+after+it+has+been+accessed+from+the+main+thread. – matt Mar 27 '21 at 20:00
  • Why do you have two asynchronous dispatches? Why not just have one? Does `getStatus` itself perform asynchronous work? Such as a network request? Your problem isn't in this code. You are updating UI from another thread somewhere else. – Paulw11 Mar 27 '21 at 20:06
  • look into your code where you are dealing with UI update. please make that code inside mail thread. – Kudos Mar 27 '21 at 20:19
  • `DispatchQueue.main.async {` is the all-purpose glue here – aheze Mar 27 '21 at 21:01
  • The configureView method just update UI, and when I call it from the timer, I put it inside the main thread using DispatcheQueue.main.async{}, but it doesn't work and I don't understand why, I use two asyncAfter because getStatus make a SOAP request to a server and the second async is for wait 1 second just for making sure that the getStatus method finish. – Ming Ye Mar 27 '21 at 21:05
  • `getStatus` should accept a completion closure that it invokes when the network data has been fetched. In that closure you can dispatch your UI updates onto the main queue. Using a time delay is just a hack. You can use the [main thread checker](https://medium.com/@trivediniki94/main-thread-checker-in-xcode-8b9f3f8ce10) to identify where you are updating the UI from a background thread. See also https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function – Paulw11 Mar 27 '21 at 21:19

0 Answers0