1

I am trying to create a material ripple effect for a UICollectioView cell. For Android, there are several material design options to do so, but for iOS that does not appear to be the case. Below is my custom cell I am using as the prototype to populate the UICollectioView:

import UIKit

class PollCell: UICollectionViewCell {


    @IBOutlet weak var imageView: UIImageView!

    @IBOutlet weak var pollQuestion: UILabel!

}

Where I initialize the CollectioViewCell:

    override func viewDidLoad() {
    super.viewDidLoad()

    ref = FIRDatabase.database().reference()
    prepareMenuButton()


    // Uncomment the following line to preserve selection between presentations
    // self.clearsSelectionOnViewWillAppear = false

    // Register cell classes

    self.dataSource = self.collectionView?.bind(to: self.ref.child("Polls")) { collectionView, indexPath, snap in
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PollCell
        //Here is where I am having issues
        cell.pulseAnimation
        /* populate cell */
        cell.pollQuestion.text = snap.childSnapshot(forPath: "question").value as! String?
        let urlPollImage = snap.childSnapshot(forPath: "image_URL").value as! String?

        cell.imageView.sd_setImage(with: URL(string: urlPollImage!), placeholderImage: UIImage(named: "Fan_Polls_Logo.png"))
        //Comment
        return cell
    }

Here is an image of one of the cells on a device:

enter image description here

tccpg288
  • 3,242
  • 5
  • 35
  • 80

2 Answers2

5

Use a CATransition

func ripple(view:UIView){
    let ripple = CATransition()
    ripple.type = "rippleEffect"
    ripple.duration = 0.5
    view.layer.add(ripple, forKey: nil)
}

you can pass whatever you want to ripple and it will. Example

self.ripple(view: imageView)

or you could pass the cell itself on didselect,touches began or whatever you are using to trigger the ripple.

But based on what you are telling me you want a circular pulse to cover the view so I tried this. I put some libraries to consider in the comments but here is my quick attempt at what you are wanting.

var scaleFactor : CGFloat = 0.6
    var animationColor : UIColor = UIColor.green
    var animationDuration : Double = 0.4
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {.  
        super.touchesBegan(touches, with: event)
        let coverView = UIView(frame: bounds)
        coverView.autoresizingMask = [.flexibleWidth,.flexibleHeight]
        coverView.backgroundColor = UIColor.clear
        self.addSubview(coverView)

        let touch = touches.first!
        let point = touch.location(in: self)

        let ourTouchView = UIView(frame: CGRect(x: point.x - 5, y: point.y - 5, width: 10, height: 10))
        print(ourTouchView)
        print(point)


        let circleMaskPathInitial = UIBezierPath(ovalIn: ourTouchView.frame)
        let radius = max((self.bounds.width * scaleFactor) , (self.bounds.height * scaleFactor))
        let circleMaskPathFinal = UIBezierPath(ovalIn: ourTouchView.frame.insetBy(dx: -radius, dy: -radius))


        let rippleLayer = CAShapeLayer()
        rippleLayer.opacity = 0.4
        rippleLayer.fillColor = animationColor.cgColor
        rippleLayer.path = circleMaskPathFinal.cgPath
        coverView.layer.addSublayer(rippleLayer)

        //fade up
        let fadeUp = CABasicAnimation(keyPath: "opacity")
        fadeUp.beginTime = CACurrentMediaTime()
        fadeUp.duration = animationDuration * 0.6
        fadeUp.toValue = 0.6
        fadeUp.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        fadeUp.fillMode = kCAFillModeForwards
        fadeUp.isRemovedOnCompletion = false
        rippleLayer.add(fadeUp, forKey: nil)

        //fade down
        let fade = CABasicAnimation(keyPath: "opacity")
        fade.beginTime = CACurrentMediaTime() + animationDuration * 0.60
        fade.duration = animationDuration * 0.40
        fade.toValue = 0
        fade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        fade.fillMode = kCAFillModeForwards
        fade.isRemovedOnCompletion = false
        rippleLayer.add(fade, forKey: nil)

        //change path
        CATransaction.begin()
        let maskLayerAnimation = CABasicAnimation(keyPath: "path")
        maskLayerAnimation.fromValue = circleMaskPathInitial.cgPath
        maskLayerAnimation.toValue = circleMaskPathFinal.cgPath
        maskLayerAnimation.beginTime = CACurrentMediaTime()
        maskLayerAnimation.duration = animationDuration
        maskLayerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        CATransaction.setCompletionBlock({
            coverView.removeFromSuperview()
        })
        rippleLayer.add(maskLayerAnimation, forKey: "path")
        CATransaction.commit()
    }

I probably would not execute this in touches began and instead use a tap gesture but you could do this.

agibson007
  • 4,173
  • 2
  • 19
  • 24
  • How would I set a color that matches the black of the cell? I am thinking a lighter grey, and are you using a third-party library? – tccpg288 Mar 10 '17 at 04:18
  • No third party library. This just makes it look like a water drop. This is just old school. Are you wanting a color burst from touch event on the location? – agibson007 Mar 10 '17 at 04:20
  • Yes I should have been more specific. I don't even have it wired for touches yet – tccpg288 Mar 10 '17 at 04:21
  • So check out that animation and get back to me but to do what you are asking if it is different would be to get the location of the touch and animate a circular view scaling out to completely scale the width and height of the view. You can use CAShapelayer. https://www.raywenderlich.com/86521/how-to-make-a-view-controller-transition-animation-like-in-the-ping-app or material kit https://github.com/nghialv/MaterialKit – agibson007 Mar 10 '17 at 04:28
  • Check it out now. I added an animation of a circular pulse using no outside libraries that could be dropped into a collection view cell or any view for that matter. This might be more what you are looking for – agibson007 Mar 12 '17 at 00:12
2

If you use Material it has a CollectionViewCell that has the pulse animation built in. You can set it with the pulseAnimation property. Hope this helps.

CosmicMind
  • 1,499
  • 1
  • 10
  • 6
  • I have updated with material - can you point out how I should correct my code? Appreciate the assistance. – tccpg288 Mar 26 '17 at 00:50
  • 1
    `PollCell` should subclass Material's `CollectionViewCell`, which will enable the pulse animations by default. Here is a sample project, [CollectionView](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/CollectionView/CollectionView) to help get you going. – CosmicMind Mar 27 '17 at 00:23
  • I got it to work, much appreciated! Now I am simply having difficulty making the cells clickable.... – tccpg288 Mar 27 '17 at 00:24
  • Are you setting the delegation methods correctly? You should be setting `CollectionVeiwDelegate` and using the `didSelectItemAt` delegation method. – CosmicMind Mar 27 '17 at 00:28
  • I am trying to learn all of it. Not sure if it goes in the viewDidLoad() method or not. Additionally, I am using the FirebaseUI which adds another layer of complexity. – tccpg288 Mar 27 '17 at 12:45
  • 1
    If you look at that sample I sent you, you can see how I set it up, which should be helpful to learn where to put things. Let me know how that goes, and we can go from there :) – CosmicMind Mar 27 '17 at 17:11