askit
askit

Reputation: 227

Reverse animation when button is pressed for second time in Table View

I know this has been asked before but did could not figure-out how to achieve this in the shortest possible way in my case.

When I click on "Add" button (see GIF below), the animation function animates "imageView" (in this case the galaxy image) to fly to cart i.e. "notificationButton". Also "Add" button changes to "Remove" and button color changes from black to red (see GIF below). Fine with that.

Image in table view cell animates to top of screen when Add button pressed

Now, when I click the button for the second time i.e deselect it i.e. make it to default state, everything reverses, but the image still flies to the cart !

Image in table view cell animates to top of screen when Remove button (previously Add button) pressed

Now, I want to reverse the animation flying imageView animation to its original position when I push the button back to its default position second time, and again original flying animation, if the button is pushed again as many times as I want.

Though I have added complete ProductViewController code here but you skip everything and look at the last extension ProductViewController

I know this most likely has two steps -

i) Identifying that "buttonHandlerAddToCart" button is pressed second time i.e. from selected/isselected step to default step.

ii) Reversing the animation function "func animation" in ProductViewController.

How to go about it?

Relevant code:

SSBadgeButton:-

import UIKit

class SSBadgeButton: UIButton {

  var badgeLabel = UILabel()

   var badge: String? {
    didSet {
        addBadgeToButon(badge: badge)
    }
   }

   public var badgeBackgroundColor = UIColor.red {
    didSet {
        badgeLabel.backgroundColor = badgeBackgroundColor
    }
   }

   public var badgeTextColor = UIColor.white {
    didSet {
        badgeLabel.textColor = badgeTextColor
    }
  }

public var badgeFont = UIFont.systemFont(ofSize: 12.0) {
    didSet {
        badgeLabel.font = badgeFont
    }
}

public var badgeEdgeInsets: UIEdgeInsets? {
    didSet {
        addBadgeToButon(badge: badge)
    }
}

override init(frame: CGRect) {
    super.init(frame: frame)
    addBadgeToButon(badge: nil)
}

func addBadgeToButon(badge: String?) {
    badgeLabel.text = badge
    badgeLabel.textColor = badgeTextColor
    badgeLabel.backgroundColor = badgeBackgroundColor
    badgeLabel.font = badgeFont
    badgeLabel.sizeToFit()
    badgeLabel.textAlignment = .center
    let badgeSize = badgeLabel.frame.size
    
    let height = max(18, Double(badgeSize.height) + 5.0)
    let width = max(height, Double(badgeSize.width) + 10.0)
    
    var vertical: Double?, horizontal: Double?
    if let badgeInset = self.badgeEdgeInsets {
        vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
        horizontal = Double(badgeInset.left) - Double(badgeInset.right)
        
        let x = (Double(bounds.size.width) - 10 + horizontal!)
        let y = -(Double(badgeSize.height) / 2) - 10 + vertical!
        badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height)
    } else {
        let x = self.frame.width - CGFloat((width / 2.0))
        let y = CGFloat(-(height / 2.0))
        badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height))
       }
    
    badgeLabel.layer.cornerRadius = badgeLabel.frame.height/2
    badgeLabel.layer.masksToBounds = true
    addSubview(badgeLabel)
    badgeLabel.isHidden = badge != nil ? false : true
   }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.addBadgeToButon(badge: nil)
    fatalError("init(coder:) has not been implemented")
   }
 }

The ProductViewController code:

import UIKit

class ProductViewController: UIViewController, UITableViewDataSource,
                             UITableViewDelegate {
    let notificationButton = SSBadgeButton()
    let rightbarbuttonimage = UIImage(named:"ic_cart")
    fileprivate var cart = Cart()
    let scrollView = UIScrollView()
    let sections = ["Section A", "Section B","Section C", "Section D","Section   E","Section F","Section G","Section H", "Section I","Section J","Section K","Section L"]
    let rowspersection = [2,3,1,2,2,3,3,1,4,2,1,2]
    
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        self.tableView.backgroundColor = UIColor.gray
        
        //Add and setup scroll view
        self.tableView.addSubview(self.scrollView)
        self.scrollView.translatesAutoresizingMaskIntoConstraints = false;
        
        //Constrain scroll view
        self.scrollView.leadingAnchor.constraint(equalTo: self.tableView.leadingAnchor, constant: 20).isActive = true;
        self.scrollView.topAnchor.constraint(equalTo: self.tableView.topAnchor, constant: 20).isActive = true;
        self.scrollView.trailingAnchor.constraint(equalTo: self.tableView.trailingAnchor, constant: -20).isActive = true;
        self.scrollView.bottomAnchor.constraint(equalTo: self.tableView.bottomAnchor, constant: -20).isActive = true;
        
        // customising rightBarButtonItems as notificationbutton
        notificationButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
        notificationButton.setImage(UIImage(named: "ic_cart")?.withRenderingMode(.alwaysTemplate), for: .normal)
        notificationButton.badgeEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 15)
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: notificationButton)
        
        
        
        //following register is needed because I have rightbarbuttonitem customised as   uibutton i.e.  notificationbutton
        notificationButton.addTarget(self, action: #selector(self.registerTapped(_:)), for: .touchUpInside)
    }
    @objc func registerTapped(_ sender: UIButton) {
        self.performSegue(withIdentifier: "showCart", sender: nil)
    }
    
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        
        
        
        //Workaround to avoid the fadout the right bar button item
        self.navigationItem.rightBarButtonItem?.isEnabled = false
        self.navigationItem.rightBarButtonItem?.isEnabled = true
        
        //Update cart if some items quantity is equal to 0 and reload the product table and right button bar item
        cart.updateCart()
        
        
        //self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
        notificationButton.badge = String(cart.items.count)// making badge equal to no.ofitems in cart
        
        tableView.reloadData()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // this segue to transfer data
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showCart" {
            if let cartViewController = segue.destination as? CartViewController {
                cartViewController.cart = self.cart
            }
        }
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return productMap.count
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return productMap[section]?.count ?? 0
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let product = productMap[indexPath.section]![indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "ProductTableViewCell") as! ProductTableViewCell
        cell.imageView?.image =  product.imagename
        cell.delegate = self as CartDelegate
        cell.setButton(state: self.cart.contains(product: product))
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44
    }
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        
        switch(section) {
        case 0: return "Section A"
        case 1: return "Section B"
        case 2: return "Section C"
        case 3: return "Section D"
        case 4: return "Section E"
        case 5: return "Section F"
        case 6: return "Section G"
        case 7: return "Section H"
        case 8: return "Section I"
        case 9: return "Section J"
        case 10: return "Section K"
        case 11: return "Section L"
        default: return ""
        }
    }
}

extension ProductViewController: CartDelegate {
    
    // MARK: - CartDelegate
    func updateCart(cell: ProductTableViewCell) {
        guard let indexPath = tableView.indexPath(for: cell) else { return }
        let product = productMap[indexPath.section]![indexPath.row]
        
        //Update Cart with product
        cart.updateCart(with: product)
        // self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
        notificationButton.badge = String(cart.items.count) // making badge equal to noofitems in cart
        
    }
}

***// Most relevant code begins here -***

extension ProductViewController {
    
    @IBAction func buttonHandlerAddToCart(_ sender: UIButton) {
        
        let buttonPosition : CGPoint = sender.convert(sender.bounds.origin, to: self.tableView)
        
        let indexPath = self.tableView.indexPathForRow(at: buttonPosition)!
        
        let cell = tableView.cellForRow(at: indexPath) as! ProductTableViewCell
        
        let imageViewPosition : CGPoint = cell.imageView!.convert(cell.imageView!.bounds.origin, to: self.view)
        
        
        let imgViewTemp = UIImageView(frame: CGRect(x: imageViewPosition.x, y: imageViewPosition.y, width: cell.imageView!.frame.size.width, height: cell.imageView!.frame.size.height))
        
        imgViewTemp.image = cell.imageView!.image
        
        animation(tempView: imgViewTemp)
    }
    
    func animation(tempView : UIView)  {
        self.view.addSubview(tempView)
        UIView.animate(
            withDuration: 1.0,
            animations: {
                tempView.animationZoom(scaleX: 1.5, y: 1.5)
            }, completion: { _ in
                
                UIView.animate(withDuration: 0.5, animations: {
                    
                    
                    tempView.animationZoom(scaleX: 0.2, y: 0.2)
                    tempView.animationRoted(angle: CGFloat(Double.pi))
                    
                    tempView.frame.origin.x = self.notificationButton.frame.origin.x
                    tempView.frame.origin.y = self.notificationButton.frame.origin.y
                    
                }, completion: { _ in
                    
                    tempView.removeFromSuperview()
                    
                    UIView.animate(withDuration: 1.0, animations: {
                        
                        
                        self.notificationButton.animationZoom(scaleX: 1.4, y: 1.4)
                    }, completion: {_ in
                        self.notificationButton.animationZoom(scaleX: 1.0, y: 1.0)
                    })
                    
                })
                
            }
        )
    }
}

extension UIView{
    func animationZoom(scaleX: CGFloat, y: CGFloat) {
        self.transform = CGAffineTransform(scaleX: scaleX, y: y)
    }
    
    func animationRoted(angle : CGFloat) {
        self.transform = self.transform.rotated(by: angle)
    }
}

I have also included ProductTableViewCell code, just in case:

import UIKit

protocol CartDelegate {
    func updateCart(cell: ProductTableViewCell)
}

class ProductTableViewCell: UITableViewCell {
    
    weak var myParent:ProductViewController?
    
    @IBOutlet weak var imagename: UIImageView!
    @IBOutlet weak var addToCartButton: UIButton!
    
    var delegate: CartDelegate?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        
        addToCartButton.layer.cornerRadius = 5
        addToCartButton.clipsToBounds = true
    }
    
    func setButton(state: Bool) {
        addToCartButton.isUserInteractionEnabled = true
        addToCartButton.isSelected = state
        addToCartButton.backgroundColor = (!addToCartButton.isSelected) ? .blue : .red
    }
    
    @IBAction func addToCart(_ sender: Any) {
        setButton(state: !addToCartButton.isSelected)
        self.delegate?.updateCart(cell: self)
    }
}

Edit: on @aheze's request :

struct Product: Equatable {
    let imagename: UIImage
}

var productMap = [
    0: [ Product(imagename:#imageLiteral(resourceName: "blue")), Product( imagename:#imageLiteral(resourceName: "CakeImage")) ]
    1: [ Product(imagename:#imageLiteral(resourceName: "vectorlogo")), Product(imagename:#imageLiteral(resourceName: "PeasImge")), Product(imagename:#imageLiteral(resourceName: "castle"))],
    2: [ Product( imagename:#imageLiteral(resourceName: "scoobydoo")),Product(imagename:#imageLiteral(resourceName: "ufo"))] ,
    3: [ Product( imagename:#imageLiteral(resourceName: "wolfsky")),Product( imagename:#imageLiteral(resourceName: "universe")) ],
    4: [ Product(imagename:#imageLiteral(resourceName: "werewolf")),Product(  imagename:#imageLiteral(resourceName: "galaxy")) ]
]

Edit 2: class Cart, on @aheze's request:

import Foundation

class Cart {
    var items : [CartItem] = []
}

extension Cart {
    
    var totalQuantity : Int {
        get { return items.reduce(0) { value, item in
            value + item.quantity
        }
        }
    }
    func updateCart(with product: Product) {
        if !self.contains(product: product) {
            self.add(product: product)
        } else {
            self.remove(product: product)
        }
    }
    func updateCart() {
        
        for item in self.items {
            if item.quantity == 0 {
                updateCart(with: item.product)
            }
        }
    }
    
    func add(product: Product) {
        let item = items.filter { $0.product == product }
        
        if item.first != nil {
            item.first!.quantity += 1
        } else {
            items.append(CartItem(product: product))
        }
    }
    
    func remove(product: Product) {
        guard let index = items.firstIndex(where: { $0.product == product  })     else { return}
        items.remove(at: index)
    }
    
    
    func contains(product: Product) -> Bool {
        let item = items.filter { $0.product == product }
        return item.first != nil
    }
}

Further information you need, feel free...

Upvotes: 4

Views: 1062

Answers (1)

aheze
aheze

Reputation: 30336

Does this work? (the gif was too big) https://i.sstatic.net/bzYFM.jpg

I made separate functions for adding and removing from the cart.

extension ProductViewController: CartDelegate {
    
    // MARK: - CartDelegate
    func updateCart(cell: ProductTableViewCell) {
        guard let indexPath = tableView.indexPath(for: cell) else { return }
        let product = productMap[indexPath.section]![indexPath.row]
        
/// `var selectedIndexPaths = [IndexPath]()` defined inside `ProductViewController`, to keep track of the selected products
        if selectedIndexPaths.contains(indexPath) {
            if let index = selectedIndexPaths.firstIndex(of: indexPath) {
                selectedIndexPaths.remove(at: index)
                removeProductFromCart(indexPath: indexPath)
            }
        } else {
            selectedIndexPaths.append(indexPath)
            addProductToCart(indexPath: indexPath)
        }
        
//        addProductToCart(indexPath: indexPath)
        ///  **I commented this out because I don't have the code for `Cart`**
        //Update Cart with product
//        cart.updateCart(with: product)
        // self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
//        notificationButton.badge = String(cart.items.count) // making badge equal to noofitems in cart
        
    }
}

func addProductToCart(indexPath: IndexPath) {
    if let cell = tableView.cellForRow(at: indexPath) as? ProductTableViewCell {
        if let imageView = cell.imagename {
            
            let initialImageViewFrame = imageView.convert(imageView.frame, to: self.view)
            let targetImageViewFrame = self.notificationButton.frame
            
            let imgViewTemp = UIImageView(frame: initialImageViewFrame)
            imgViewTemp.clipsToBounds = true
            imgViewTemp.contentMode = .scaleAspectFill
            imgViewTemp.image = imageView.image
            
            self.view.addSubview(imgViewTemp)
            
            UIView.animate(withDuration: 1.0, animations: {
                imgViewTemp.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
            }) {  _ in
                UIView.animate(withDuration: 0.5, animations: {
                    imgViewTemp.transform = CGAffineTransform(scaleX: 0.2, y: 0.2).rotated(by: CGFloat(Double.pi))
                    imgViewTemp.frame = targetImageViewFrame
                }) { _ in
                    imgViewTemp.removeFromSuperview()

                    UIView.animate(withDuration: 1.0, animations: {
                        self.notificationButton.transform = CGAffineTransform(scaleX: 1.4, y: 1.4)
                    }, completion: {_ in
                        self.notificationButton.transform = CGAffineTransform.identity
                    })
                }
            }
        }
    }
}

func removeProductFromCart(indexPath: IndexPath) {
    if let cell = tableView.cellForRow(at: indexPath) as? ProductTableViewCell {
        if let imageView = cell.imagename {
            
            let initialImageViewFrame = self.notificationButton.frame
            let targetImageViewFrame = imageView.convert(imageView.frame, to: self.view)
            
            let imgViewTemp = UIImageView(frame: initialImageViewFrame)
            imgViewTemp.clipsToBounds = true
            imgViewTemp.contentMode = .scaleAspectFill
            imgViewTemp.image = imageView.image
            
            self.view.addSubview(imgViewTemp)
            
            var initialTransform = CGAffineTransform.identity
            initialTransform = initialTransform.scaledBy(x: 0.2, y: 0.2)
            initialTransform = initialTransform.rotated(by: CGFloat(Double.pi))
            
            UIView.animate(withDuration: 0.5, animations: {
                self.notificationButton.animationZoom(scaleX: 1.4, y: 1.4)
                imgViewTemp.transform = initialTransform
            }) {  _ in
                UIView.animate(withDuration: 1, animations: {
                    self.notificationButton.animationZoom(scaleX: 1, y: 1)
                    imgViewTemp.transform = CGAffineTransform.identity
                    imgViewTemp.frame = targetImageViewFrame
                }) { _ in
                    imgViewTemp.removeFromSuperview()
                }
            }
        }
    }
}
        

Some things that you should fix:

  • Instead of using imagename (the image view that you added to your table view cell), you used cell.imageView! which is the built-in image view that all cells have. Don't use this.
  • Inside ProductTableViewCell, you should make a separate property for keeping track of selected/not selected state instead of using UIButton's isSelected. This way, you won't run into unwanted behavior when changing the button color (currently, a red rectangle will appear behind the button's text for a moment)
  • If you're combining transforms, you should do this:
var initialTransform = CGAffineTransform.identity
initialTransform = initialTransform.scaledBy(x: 0.2, y: 0.2)
initialTransform = initialTransform.rotated(by: CGFloat(Double.pi))
tempView.transform = initialTransform

instead of:

tempView.animationZoom(scaleX: 0.2, y: 0.2)
tempView.animationRoted(angle: CGFloat(Double.pi))

Here's the full project (added some more comments too).

Upvotes: 4

Related Questions