Обратная анимация при повторном нажатии кнопки в табличном представлении

#ios #swift #uitableview

#iOS #swift #uitableview

Вопрос:

Я знаю, что об этом спрашивали раньше, но не мог понять, как добиться этого как можно быстрее в моем случае.

Когда я нажимаю кнопку «Добавить» (см. GIF ниже), функция анимации анимирует «Просмотр изображения» (в данном случае изображение галактики), чтобы перейти в корзину, т.Е. «notificationButton». Также кнопка «Добавить» меняется на «Удалить», а цвет кнопки меняется с черного на красный (см. GIF ниже). С этим все в порядке.

Изображение в ячейке табличного представления анимируется в верхней части экрана при нажатии кнопки добавления

Теперь, когда я нажимаю кнопку во второй раз, т.Е. отменяю ее выбор, т.Е. Переводю ее в состояние по умолчанию, все меняется на противоположное, но изображение все равно отправляется в корзину!

Изображение в ячейке табличного представления анимируется в верхней части экрана при нажатии кнопки удаления (ранее кнопки добавления)

Теперь я хочу изменить анимацию анимации полета ImageView в исходное положение, когда я нажимаю кнопку обратно в положение по умолчанию во второй раз, и снова оригинальную анимацию полета, если кнопка нажимается столько раз, сколько я хочу.

Хотя я добавил сюда полный код ProductViewController, но вы все пропускаете и смотрите на последнее расширение ProductViewController

Я знаю, что это, скорее всего, состоит из двух шагов —

i) Определение того, что кнопка «buttonHandlerAddToCart» нажата во второй раз, т.е. с выбранного / выбранного шага на шаг по умолчанию.

ii) Изменение функции анимации «func animation» в ProductViewController.

Как это сделать?

Соответствующий код:

SSBadgeButton:-

импорт 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")
   }
 }
  

Код ProductViewController :

 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)
    }
}
  

Редактировать: по запросу @aheze :

 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")) ]
]
  

Редактировать 2: класс Cart , по запросу @aheze:

 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
    }
}
  

Дополнительная информация, которая вам нужна, не стесняйтесь…

Комментарии:

1. Как вы можете видеть, я немного в бедной части города, что касается репутации, но все же я поставил награду, потому что у меня мало времени. Пожалуйста, разберитесь в этом вопросе.

2. Можете ли вы показать, где productMap определено?

3. Извините за поздний ответ. Пожалуйста, смотрите Редактирование.

4. но, насколько я могу понять, это не имеет ничего общего с productMap. Это связано с тегированием кнопки в ProductTableViewCell и / или ProductViewController и обратным завершением-hanslers в ProductViewController.

5. Спасибо. Чтобы отслеживать, какой продукт выбран, вы должны создать новый массив, selectedProducts , и добавлять продукты к нему при их выборе. Затем вы можете проверить, какие продукты там есть, и настроить анимацию. Для обратного я рекомендую просто создать новую функцию анимации. Сейчас мне нужно идти, но я постараюсь опубликовать некоторый код завтра

Ответ №1:

Работает ли это? (gif был слишком большим) https://imgur.com/a/jrcwEWv

Я создал отдельные функции для adding корзины и removing из нее.

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

Некоторые вещи, которые вы должны исправить:

  • Вместо того, чтобы использовать imagename (представление изображения, которое вы добавили в ячейку представления таблицы), вы использовали cell.imageView! встроенное представление изображения, которое есть во всех ячейках. Не используйте это.
  • Внутри ProductTableViewCell вы должны создать отдельное свойство для отслеживания выбранного / не выбранного состояния вместо использования UIButton ‘s isSelected . Таким образом, вы не столкнетесь с нежелательным поведением при изменении цвета кнопки (в настоящее время за текстом кнопки на мгновение появляется красный прямоугольник)
  • Если вы комбинируете преобразования, вы должны сделать это:
 var initialTransform = CGAffineTransform.identity
initialTransform = initialTransform.scaledBy(x: 0.2, y: 0.2)
initialTransform = initialTransform.rotated(by: CGFloat(Double.pi))
tempView.transform = initialTransform
  

вместо:

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

Вот полный проект (добавлены еще несколько комментариев).

Комментарии:

1. да, я запускаю ваш ProductViewController из github после тщательного сопоставления и исправления, у него по-прежнему нет обратной анимации, только прямая анимация. Кроме того, если я скопирую код ProductTableViewcell из вашего в мой, он показывает ошибку sigbart в AppDelegate и эту ошибку в консоли — «Завершение работы приложения из-за неперехваченного исключения ‘NSUnknownKeyException’, причина: ‘[<PracticeCart.ProductTableViewCell 0x7fbe720af000> setValue:forUndefinedKey:]: этот класс не совместим с кодированием значений ключадля имени ключа.’*** Первый стек вызовов throw:»

2. Добавлена «корзина классов», но у меня все еще есть ощущение, что это связано только с переключением кнопки добавления в ProductTableViewCell и изменением анимации.

3. @askit Terminating app due to uncaught exception вероятно, это связано с тем, что вам нужно установить модуль ячейки. В раскадровке просто удалите текущий класс и вставьте его снова (он должен быть автозаполнен).

4. @askit По-прежнему нет обратной анимации? Убедитесь, что вы selectedIndexPaths определили

5. Да, сегодня я с вами в Сети. Хорошо, теперь вы имеете в виду » var selectedIndexPaths = [indexPath]()» в ProductViewcontroller? Да, это там. по-прежнему нет обратной анимации. Кроме того, он печатает «Внутри» в консоли каждый раз, когда я нажимаю на кнопку «Добавить». Остальное в порядке, т.Е. Его добавление и удаление из корзины.