1

I am facing an issue where my UIView with a Lottie-Animation is not being correctly displayed. I am using a lot of Dispatch.main.async and I a pretty sure that is messing everything up.

1. User taps on a Button and instantly my UIVIew("coverView")+ Animation ("loadingAnimation") should be displayed (calling setupLoadingAnimation)

ScreenVideo

But it is not, it is only being displayed after a another function is finished.

How and where do I have to call animation.play() and view.isHidden = false so it is being displayed right after the button was tapped?

This is my structure:

@objc func addWishButtonTapped() {

    print("tapped 1")
    DispatchQueue.main.async { [weak self] in
        guard let self = self else { return }

        // ...
        self.setUpLoadingAnimation()
        // ...
        
        print("tapped 2")
        
        self.crawlWebsite {
            print("tapped 3")
            self.hideLoadingView()
            print("tapped 4")
        }

    }
}

The view is not being setup and it stops printing after print("tapped 2") and only print print(tapped 3"). That is when the view+ loadingAnimatino become visible.

Setup Loading Animation:

    //MARK: setupLoadingAnimation
func setUpLoadingAnimation(){
    
    DispatchQueue.main.async { [weak self] in
        guard let self = self else { return }
        self.view.addSubview(self.coverView)
        self.coverView.topAnchor.constraint(equalTo: self.wishView.topAnchor).isActive = true
        self.coverView.leadingAnchor.constraint(equalTo: self.wishView.leadingAnchor).isActive = true
        self.coverView.trailingAnchor.constraint(equalTo: self.wishView.trailingAnchor).isActive = true
        self.coverView.bottomAnchor.constraint(equalTo: self.wishView.bottomAnchor).isActive = true
        
        self.loadingAnimation.contentMode = .scaleAspectFit
        self.loadingAnimation.translatesAutoresizingMaskIntoConstraints = false
        self.coverView.addSubview(self.loadingAnimation)
        
        self.loadingAnimation.centerXAnchor.constraint(equalTo: self.coverView.centerXAnchor).isActive = true
        self.loadingAnimation.centerYAnchor.constraint(equalTo: self.coverView.centerYAnchor).isActive = true
        self.loadingAnimation.heightAnchor.constraint(equalToConstant: 100).isActive = true
        self.loadingAnimation.widthAnchor.constraint(equalToConstant: 100).isActive = true
        self.loadingAnimation.loopMode = .loop
        self.loadingAnimation.play()
    }
    
}

crawlWebsite:

    //MARK: crawlWebsite
func crawlWebsite(finished: @escaping () -> Void){
    
    var html: String?
    guard let url = self.url else { return }
    let directoryURL = url as NSURL
    let urlString: String = directoryURL.absoluteString! 
    // save url to wish
    self.wishView.link = urlString
 
    DispatchQueue.main.async { [weak self] in
        guard let self = self else { finished(); return }
        html = self.getHTMLfromURL(url: url)
        self.getContentFromHTML(html: html, url: url)
        self.getOpenGraphData(urlString: urlString) {
            finished()
        }
    }
}

I know this is very messy so I hope everything is clear! Let me know if you have any questions! I am very stuck here...

Chris
  • 1,828
  • 6
  • 40
  • 108

5 Answers5

1

Seems that there are redundant DispatchQueue.main.async calls

  • Better is to remove DispatchQueue.main.async in all the function and call all these functions within DispatchQueue.main.async in addWishButtonTapped()

You can try with below changes:

@objc func addWishButtonTapped() {
  print("tapped 1")
  DispatchQueue.main.async { [weak self] in
    guard let self = self else { return }

     // ...
     self.setUpLoadingAnimation()
     // ...
    
    print("tapped 2")
    
    self.crawlWebsite {
        print("tapped 3")
        self.hideLoadingView()
        print("tapped 4")
       }
    }
}

and in setUpLoadingAnimation() remove DispatchQueue.main.async as this function is already called in main thread. Also you can do view.isHidden = false in this function

//MARK: setupLoadingAnimation
func setUpLoadingAnimation(){  
    self.view.addSubview(self.coverView)
    self.coverView.topAnchor.constraint(equalTo: self.wishView.topAnchor).isActive = true
    self.coverView.leadingAnchor.constraint(equalTo: self.wishView.leadingAnchor).isActive = true
    self.coverView.trailingAnchor.constraint(equalTo: self.wishView.trailingAnchor).isActive = true
    self.coverView.bottomAnchor.constraint(equalTo: self.wishView.bottomAnchor).isActive = true
    
    self.loadingAnimation.contentMode = .scaleAspectFit
    self.loadingAnimation.translatesAutoresizingMaskIntoConstraints = false
    self.coverView.addSubview(self.loadingAnimation)
    
    self.loadingAnimation.centerXAnchor.constraint(equalTo: self.coverView.centerXAnchor).isActive = true
    self.loadingAnimation.centerYAnchor.constraint(equalTo: self.coverView.centerYAnchor).isActive = true
    self.loadingAnimation.heightAnchor.constraint(equalToConstant: 100).isActive = true
    self.loadingAnimation.widthAnchor.constraint(equalToConstant: 100).isActive = true
    self.loadingAnimation.loopMode = .loop
    self.loadingAnimation.play()

    self.view.isHidden = false
}

and crawlWebsite you can keep is as is :

//MARK: crawlWebsite
func crawlWebsite(finished: @escaping () -> Void){

var html: String?
guard let url = self.url else { return }
let directoryURL = url as NSURL
let urlString: String = directoryURL.absoluteString! 
// save url to wish
self.wishView.link = urlString

DispatchQueue.main.async { [weak self] in
    guard let self = self else { finished(); return }
    html = self.getHTMLfromURL(url: url)
    self.getContentFromHTML(html: html, url: url)
    self.getOpenGraphData(urlString: urlString) {
        finished()
    }
  }
}

I hope this could solve the issue you are facing.

Nandish
  • 1,136
  • 9
  • 16
  • your answer was definitely helpful but the issue was actually something else: https://stackoverflow.com/questions/65876079/swift-function-with-do-try-block-messes-up-uiview – Chris Jan 28 '21 at 18:19
0

I think you are using too many asyncs. You only need to stop your animation when the result view is ready. So your structure should be play the animation immediately, craw website in background, when it finishes, stops the animation and shows the result.

So there are two queues, craw in background queue, and when it is ready, run the async in main queue from the background queue to stop the animation and show the result.

Owen Zhao
  • 3,205
  • 1
  • 26
  • 43
0

Load data in background thread (so not affect UI) and send finish callback only on main queue, like

func crawlWebsite(finished: @escaping () -> Void){
    
    var html: String?
    guard let url = self.url else { return }
    let directoryURL = url as NSURL
    let urlString: String = directoryURL.absoluteString! 
    // save url to wish
    self.wishView.link = urlString
 
    DispatchQueue.global(qos: .background).async { [weak self] in
        guard let self = self else { 
            DispatchQueue.main.async {
               finished()
            }
          return
        }
        html = self.getHTMLfromURL(url: url)
        self.getContentFromHTML(html: html, url: url)

        self.getOpenGraphData(urlString: urlString) {
            DispatchQueue.main.async {
               finished()
            }
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
0

I have no experience with Lottie-Animation, but as I see there are at least 2 issues in this code.

  1. A lot of unneeded DispatchQueue.main.async calls. Remove all, and rewrite crawlWebsite function, like was suggested above with combination of async global and async main.

  2. In setUpLoadingAnimation function make sure that you add subview and setup constrains only once and for other calls use hidden property for example.

Andrew
  • 331
  • 1
  • 6
0

the last DispatchQueue.main.async is enough for this

    //MARK: crawlWebsite
func crawlWebsite(finished: @escaping () -> Void){
    
    var html: String?
    guard let url = self.url else { return }
    let directoryURL = url as NSURL
    let urlString: String = directoryURL.absoluteString! 
    // save url to wish
    self.wishView.link = urlString
 
    DispatchQueue.main.async { [weak self] in
        guard let self = self else { finished(); return }
        html = self.getHTMLfromURL(url: url)
        self.getContentFromHTML(html: html, url: url)
        self.getOpenGraphData(urlString: urlString) {
            finished()
        }
    }
}

remove all other DispatchQueue.main.async