stu7000
stu7000

Reputation: 125

wait until scrollToRowAtIndexPath is done in swift

I have a UITableView with more cells than fit on screen. When I get a notification from my data model I want to jump to a specific row and show a very basic animation.

My code is:

 func animateBackgroundColor(indexPath: NSIndexPath) {        
    dispatch_async(dispatch_get_main_queue()) {
        NSLog("table should be at the right position")
        if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? BasicCardCell {
            var actColor = cell.backgroundColor
            self.manager.vibrate()
            UIView.animateWithDuration(0.2, animations: { cell.backgroundColor = UIColor.redColor() }, completion: {
                _ in

                UIView.animateWithDuration(0.2, animations: { cell.backgroundColor = actColor }, completion: { _ in
                        self.readNotificationCount--
                        if self.readNotificationCount >= 0 {
                            var legicCard = self.legicCards[indexPath.section]
                            legicCard.wasRead = false
                            self.reloadTableViewData()
                        } else {
                            self.animateBackgroundColor(indexPath)
                        }
                })
            })
        }
    }
}    

func cardWasRead(notification: NSNotification) {
    readNotificationCount++
    NSLog("\(readNotificationCount)")

        if let userInfo = notification.userInfo as? [String : AnyObject], let index = userInfo["Index"] as? Int {

            dispatch_sync(dispatch_get_main_queue()){
                self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: index), atScrollPosition: .None, animated: true)

                self.tableView.layoutIfNeeded()
                NSLog("table should scroll to selected row")
            }
            self.animateBackgroundColor(NSIndexPath(forRow: 0, inSection: index))
        }
}

I hoped that the dispatch_sync part would delay the execution of my animateBackgroundColor method until the scrolling is done. Unfortunately that is not the case so that animateBackgroundColor gets called when the row is not visible yet -> cellForRowAtIndexPath returns nil and my animation won't happen. If no scrolling is needed the animation works without problem.

Can anyone tell my how to delay the execution of my animateBackgroundColor function until the scrolling is done?

Thank you very much and kind regards

Upvotes: 6

Views: 7033

Answers (3)

QuangLoc
QuangLoc

Reputation: 71

Here are my solution

1) Create a .swift file, and copy code below into it:

typealias SwagScrollCallback = (_ finish: Bool) -> Void 

class UICollectionViewBase: NSObject, UICollectionViewDelegate {
    static var shared = UICollectionViewBase()

    var tempDelegate: UIScrollViewDelegate?
    var callback: SwagScrollCallback?

    func startCheckScrollAnimation(scroll: UIScrollView, callback: SwagScrollCallback?){
        if let dele = scroll.delegate {
            self.tempDelegate = dele
        }
        self.callback = callback
        scroll.delegate = self
    }
    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        callback?(true)
        if let dele = self.tempDelegate {
            scrollView.delegate = dele
            tempDelegate = nil
        }
    }
}

extension UICollectionView {

    func scrollToItem(at indexPath: IndexPath, at scrollPosition: UICollectionView.ScrollPosition, _ callback: SwagScrollCallback?){
        UICollectionViewBase.shared.startCheckScrollAnimation(scroll: self, callback: callback)
        self.scrollToItem(at: indexPath, at: scrollPosition, animated: true)
    }

}

2) Example:

@IBAction func onBtnShow(){

        let index = IndexPath.init(item: 58, section: 0)
        self.clv.scrollToItem(at: index, at: .centeredVertically) { [weak self] (finish) in

            guard let `self` = self else { return }

            // Change color temporarily
            if let cell = self.clv.cellForItem(at: index) as? MyCell {
                cell.backgroundColor = .yellow
                cell.lbl.textColor = .red
            }


            // Reset
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                self.clv.reloadData()
            }
        }
    }

3) My github code example: github here

Upvotes: 0

Cao Yong
Cao Yong

Reputation: 608

I has similar problem. I just do this.

UIView.animateWithDuration(0.2,delay:0.0,options: nil,animations:{
          self.tableView.scrollToRowAtIndexPath(self.indexPath!, atScrollPosition: .Middle, animated: true)},
                        completion: { finished in UIView.animateWithDuration(0.5, animations:{
                            self.animateBackgroundColor(self.indexPath)})})}

Upvotes: -3

Zell B.
Zell B.

Reputation: 10286

Delaying animation does not seem to be a good solution for this since scrollToRowAtIndexPath animation duration is set based on distance from current list item to specified item. To solve this you need to execute animateBackgroudColor after scrollToRowAtIndexPath animation is completed by implementing scrollViewDidEndScrollingAnimation UITableViewDelegate method. The tricky part here is to get indexPath at which tableview did scroll. A possible workaround:

var indexPath:NSIndexpath?

func cardWasRead(notification: NSNotification) {
readNotificationCount++
NSLog("\(readNotificationCount)")

    if let userInfo = notification.userInfo as? [String : AnyObject], let index = userInfo["Index"] as? Int{

        dispatch_sync(dispatch_get_main_queue()){

            self.indexPath = NSIndexPath(forRow: 0, inSection: index)
            self.tableView.scrollToRowAtIndexPath(self.indexPath, atScrollPosition: .None, animated: true)

            self.tableView.layoutIfNeeded()

            NSLog("table should scroll to selected row")
        }

    }

}

func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
    self.animateBackgroundColor(self.indexPath)
    indexPath = nil
}

Upvotes: 5

Related Questions