iOS icon jiggle algorithm

前端 未结 11 864
执笔经年
执笔经年 2020-12-12 16:31

I am writing an iPad app that presents user documents similar to the way Pages presents them (as large icons of the actual document). I also want to mimic the jiggling beha

11条回答
  •  遥遥无期
    2020-12-12 17:19

    Paul Popiel gave an excellent answer to this above and I am forever indebted to him for it. There is one small problem I found with his code and that's that it doesn't work well if that routine is called multiple times - the layer animations appear to sometimes get lost or deactivated.

    Why call it more than once? I'm implementing it via a UICollectionView, and as the cells are dequeued or moved, I need to reestablish the wiggle. With Paul's original code, my cells would often stop wiggling if they scrolled off screen despite my trying to reestablish the wiggle within the dequeue and the willDisplay callback. However, by giving the two animations named keys, it always works reliably even if called twice on a cell.

    This is almost all Paul's code with the above small fix, plus I've created it as an extension to UIView and added a Swift 4 compatible stopWiggle.

    private func degreesToRadians(_ x: CGFloat) -> CGFloat {
        return .pi * x / 180.0
    }
    
    extension UIView {
        func startWiggle() {
            let duration: Double = 0.25
            let displacement: CGFloat = 1.0
            let degreesRotation: CGFloat = 2.0
            let negativeDisplacement = -1.0 * displacement
            let position = CAKeyframeAnimation.init(keyPath: "position")
            position.beginTime = 0.8
            position.duration = duration
            position.values = [
                NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)),
                NSValue(cgPoint: CGPoint(x: 0, y: 0)),
                NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)),
                NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)),
                NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement))
            ]
            position.calculationMode = "linear"
            position.isRemovedOnCompletion = false
            position.repeatCount = Float.greatestFiniteMagnitude
            position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100))
            position.isAdditive = true
    
            let transform = CAKeyframeAnimation.init(keyPath: "transform")
            transform.beginTime = 2.6
            transform.duration = duration
            transform.valueFunction = CAValueFunction(name: kCAValueFunctionRotateZ)
            transform.values = [
                degreesToRadians(-1.0 * degreesRotation),
                degreesToRadians(degreesRotation),
                degreesToRadians(-1.0 * degreesRotation)
            ]
            transform.calculationMode = "linear"
            transform.isRemovedOnCompletion = false
            transform.repeatCount = Float.greatestFiniteMagnitude
            transform.isAdditive = true
            transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100))
    
            self.layer.add(position, forKey: "bounce")
            self.layer.add(transform, forKey: "wiggle")
        }
    
        func stopWiggle() {
            self.layer.removeAllAnimations()
            self.transform = .identity
        }
    }
    

    In case it saves anyone else time implementing this in a UICollectionView, you'll need a few other places to make sure the wiggle stays during moves and scrolls. First, a routine that begins wiggling all the cells that's called at the outset:

    func wiggleAllVisibleCells() {
        if let visible = collectionView?.indexPathsForVisibleItems {
            for ip in visible {
                if let cell = collectionView!.cellForItem(at: ip) {
                    cell.startWiggle()
                }
            }
        }
    }
    

    And as new cells are displayed (from a move or scroll), I reestablish the wiggle:

    override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        // Make sure cells are all still wiggling
        if isReordering {
            cell.startWiggle()
        }
    }
    

提交回复
热议问题