Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Having trouble with Cube Animator w/ programmatic cells #61

Open
GalCohen opened this issue Oct 19, 2020 · 10 comments
Open

Having trouble with Cube Animator w/ programmatic cells #61

GalCohen opened this issue Oct 19, 2020 · 10 comments

Comments

@GalCohen
Copy link

Hi.

The layout looks broken when using a cell programmatically. What am I missing? What could be different between the configuration of the cell in the Sample project in the storyboard and my own?

I narrowed the problem down to cell configuration because if I copy the Sample project's storyboard and viewControllers, I am about to successfully display and scroll the collectionview. As soon as I replace the Sample cell with my own, it fails.

I tore down the cell to the bare minimum

class MyCustomCell: UICollectionViewCell {
  // nothing    
}

and still, I get something like:
Screen Shot 2020-10-19 at 4 00 19 PMScreen Shot 2020-10-19 at 4 00 25 PM

I'm on v 1.10, using SwiftPM, testing on iPhone SE Simulator iOS 13.5 And 14.
I'm just trying to test out a full screen collectionview exactly like the sample project, only programmatic, and using my own cell.

@GalCohen
Copy link
Author

I am also able to reproduce this by taking the sample project, removing the prototype cell, and replacing it with my own cell. As soon as I use the custom cell, like the one above, the layout breaks. Any idea what could be so different between the storyboard and programmatic setup?

@KelvinJin
Copy link
Owner

The only thing I can think of is the clipsToBounds settings on either the cell or the contentView. Can you upload a sample project to reproduce this?

@GalCohen
Copy link
Author

Hi @KelvinJin , thank you for the quick response. I've attached a project that demonstrates this. All I've done is take the sample project from the repo, remove the prototype cell from the storyboard, and modified it slightly to work programmatically instead.

programmaticCellExample.zip

@kazuteru
Copy link

Hi @KelvinJin
I am facing a similar problem
I confirmed the trouble of cube animation on iOS13.7 (iPhone11 Simulator)
It seems to occur when UIButton / UIImage is placed in cell and the value is changed every time it is reused.
Does not occur with UILabel
I also put a sample project
("iOS Example" has been changed)

Please confirm

AnimatedCollectionViewLayout-master.zip

@KelvinJin
Copy link
Owner

@GalCohen @kazuteru Sorry for the late reply guys.

This issue has been giving me some hard time 😢

What I found out is that I broke this with the latest update. I can't figure out what exactly are different between programmatically created cell and the one created in Interface Builder. But my update fixed similar issue that's happening on the IB created while introduced the issue to the programmatic one 🤯

Now the proper way to fix this is to figure out what are the differences and try to workaround both cases at the same time.

At the mean time, please either

  • downgrade to use 1.0.1 if you only have programmatically created cells.
  • or use the following modified animator for your programmatically created cells while using the normal cube animator for IB created cells.

Again, thanks for reporting this issue!

/// An animator that applies a cube transition effect when you scroll.
public struct CubeAttributesAnimatorForProgrammaticCell: LayoutAttributesAnimator {
    /// The perspective that will be applied to the cells. Must be negative. -1/500 by default.
    /// Recommended range [-1/2000, -1/200].
    public var perspective: CGFloat
    
    /// The higher the angle is, the _steeper_ the cell would be when transforming.
    public var totalAngle: CGFloat
    
    public init(perspective: CGFloat = -1 / 500, totalAngle: CGFloat = .pi / 2) {
        self.perspective = perspective
        self.totalAngle = totalAngle
    }
    
    public func animate(collectionView: UICollectionView, attributes: AnimatedCollectionViewLayoutAttributes) {
        let position = attributes.middleOffset
        if abs(position) >= 1 {
            attributes.contentView?.layer.transform = CATransform3DIdentity
            attributes.contentView?.keepCenterAndApplyAnchorPoint(CGPoint(x: 0.5, y: 0.5))
        } else if attributes.scrollDirection == .horizontal {
            let rotateAngle = totalAngle * position
            var transform = CATransform3DIdentity
            transform.m34 = perspective
            transform = CATransform3DRotate(transform, rotateAngle, 0, 1, 0)
            
            attributes.contentView?.layer.transform = transform
            attributes.contentView?.keepCenterAndApplyAnchorPoint(CGPoint(x: position > 0 ? 0 : 1, y: 0.5))
        } else {
            let rotateAngle = totalAngle * position
            var transform = CATransform3DIdentity
            transform.m34 = perspective
            transform = CATransform3DRotate(transform, rotateAngle, -1, 0, 0)
            
            attributes.contentView?.layer.transform = transform
            attributes.contentView?.keepCenterAndApplyAnchorPoint(CGPoint(x: 0.5, y: position > 0 ? 0 : 1))
        }
    }
}

extension UIView {
     func keepCenterAndApplyAnchorPoint(_ point: CGPoint) {

         guard layer.anchorPoint != point else { return }

         var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y)
         var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y)

         newPoint = newPoint.applying(transform)
         oldPoint = oldPoint.applying(transform)

         var c = layer.position
         c.x -= oldPoint.x
         c.x += newPoint.x

         c.y -= oldPoint.y
         c.y += newPoint.y

         layer.position = c
         layer.anchorPoint = point
     }
 }

@KelvinJin KelvinJin pinned this issue Nov 2, 2020
@GalCohen
Copy link
Author

GalCohen commented Nov 2, 2020

I spent a few hours on this with no luck. Thanks for taking the time to investigate and suggesting some fixes. Really appreciate the work!

@HimmaHorde
Copy link

@GalCohen @KelvinJin

use autoLayout

    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        contentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
        contentView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
    }

@HimmaHorde
Copy link

@KelvinJin
iOS 14 programmatically created cells will reset Origin (changed by anchor)
You can try to optimize your code in the following ways

else if attributes.scrollDirection == .horizontal {
            let rotateAngle = totalAngle * position
            let anchorPoint = CGPoint(x: position > 0 ? 0 : 1, y: 0.5)
            
            // As soon as we changed anchor point, we'll need to either update frame/position
            // or transform to offset the position change. frame doesn't work for iOS 14 any
            // more so we'll use transform.
            let anchorPointOffsetValue = contentView.layer.bounds.width / 2
            let anchorPointOffset = position > 0 ? -anchorPointOffsetValue : anchorPointOffsetValue
            var transform = CATransform3DMakeTranslation(anchorPointOffset, 0, 0)
            contentView.layer.anchorPoint = anchorPoint
            
            if #available(iOS 14, *) {
                if contentView.translatesAutoresizingMaskIntoConstraints == true {
                    // not use transformX/Y
                    transform = CATransform3DMakeTranslation(0, 0, 0)
                    // reset origin
                    var frame = attributes.frame
                    frame.origin = .zero
                    contentView.frame = frame
                }
            }
            transform.m34 = perspective
            
            transform = CATransform3DRotate(transform, rotateAngle, 0, 1, 0)
            contentView.layer.transform = transform
            
        }

@rome753
Copy link

rome753 commented Oct 21, 2022

extension UIView {
func keepCenterAndApplyAnchorPoint(_ point: CGPoint) {

     guard layer.anchorPoint != point else { return }

     var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y)
     var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y)

     newPoint = newPoint.applying(transform)
     oldPoint = oldPoint.applying(transform)

     var c = layer.position
     c.x -= oldPoint.x
     c.x += newPoint.x

     c.y -= oldPoint.y
     c.y += newPoint.y

     layer.position = c
     layer.anchorPoint = point
 }

}

Works for most devices, but not work for iPhone 7 Plus(iOS 13.6), or iPhone 11 sometimes.

@VladimirMoor
Copy link

VladimirMoor commented Nov 15, 2023

try to add checking for rotateAngle in this place:

if abs(position) >= 1 || rotateAngle == 0 {
contentView.layer.transform = CATransform3DIdentity
contentView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
} else { ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants