I have one object that collides with two other objects. I change restitution based on the collided objects in question. Whenever the restitution changes from 0.5 to 0 it isn't recognized immediately, this causes bounciness for a short while when the restitution is suppose to be zero. How can I make the change recognizable/effected immediately? Please see my code below:
func didBegin(_ contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody
var secondBody : SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}
else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.categoryBitMask == spriteCategory && secondBody.categoryBitMask == enemyCategory1 {
var spriteContactNode = firstBody.node
spriteContactNode?.physicsBody?.restitution = 0.5
self.physicsWorld.gravity = CGVector(dx: 0, dy: -2.0)
}
if firstBody.categoryBitMask == spriteCategory && secondBody.categoryBitMask == enemyCategory2 {
var spriteContactNode = firstBody.node
spriteContactNode?.physicsBody?.restitution = 0
self.physicsWorld.gravity = CGVector(dx: 0, dy: -0.5)
}
}
I don't believe what you're trying to do will work. Restitution affects a period of time, like if the node had an action that was repeated a few times.
Try changing the nodes isDynamic property to false instead.
var spriteContactNode = firstBody.node
spriteContactNode?.physicsBody?.isDynamic = false
spriteContactNode?.physicsBody?.restitution = 0
self.physicsWorld.gravity = CGVector(dx: 0, dy: -0.5)
spriteContactNode?.physicsBody?.isDynamic = true
Then setting it back to true may be enough to let bounce only once before falling with the gravity.
Related
I am having trouble figuring out how to differentiate between different bitmasks.
I want this to happen:
/*func didBegin(_ contact: SKPhysicsContact) {
if (spaceship1 collides with spaceship2) {
print("contact between 1 and 2")
}
if (spaceship1 collides with spaceship3) {
print("contact between 1 and 3")
}
}
*/
Here's the code I have tried:
func didBegin(_ contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask = enemyCategory && contact.bodyB.categoryBitMask = enemy2Category) {
print("contact between 1 and 2")
}
}
Could I get some help?
EDIT: Here's the other code:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let enemy2Category: UInt32 = 1
let enemyCategory: UInt32 = 2
let enemy3Category: UInt32 = 3
var spaceship1: SKSpriteNode!
var spaceship2: SKSpriteNode!
var spaceship3: SKSpriteNode!
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
spaceship1 = SKSpriteNode(imageNamed: "Spaceship");
spaceship1.setScale(CGFloat(0.1))
spaceship1.position = CGPoint(x: self.frame.width / 2, y: (self.frame.height / 2));
spaceship1.name = "spaceship1";
spaceship1.physicsBody = SKPhysicsBody(circleOfRadius: spaceship1.size.width / 2);
spaceship1.physicsBody?.isDynamic = true // apply gravity, friction, and collision
spaceship1.physicsBody?.affectedByGravity = true;
spaceship1.physicsBody?.allowsRotation = false
spaceship1.physicsBody = SKPhysicsBody(circleOfRadius: spaceship1.size.width / 2);
spaceship1.physicsBody?.categoryBitMask = enemy2Category
spaceship1.physicsBody?.collisionBitMask = enemyCategory
spaceship1.physicsBody?.contactTestBitMask = enemyCategory
spaceship2 = SKSpriteNode(imageNamed: "Spaceship");
spaceship2.setScale(CGFloat(0.1))
spaceship2.position = CGPoint(x: self.frame.width / 2, y: (spaceship1.position.y + 300));
spaceship2.name = "spaceship2";
spaceship2.physicsBody = SKPhysicsBody(circleOfRadius: spaceship2.size.width / 2);
spaceship2.physicsBody?.isDynamic = true // apply gravity, friction, and collision
spaceship2.physicsBody?.affectedByGravity = false;
spaceship2.physicsBody?.allowsRotation = false
spaceship2.physicsBody = SKPhysicsBody(circleOfRadius: spaceship2.size.width / 2);
spaceship2.physicsBody?.categoryBitMask = enemyCategory
spaceship2.physicsBody?.collisionBitMask = enemy2Category
spaceship2.physicsBody?.contactTestBitMask = enemy2Category
spaceship3 = SKSpriteNode(imageNamed: "Spaceship");
spaceship3.setScale(CGFloat(0.1))
spaceship3.position = CGPoint(x: self.frame.width / 2, y: (spaceship1.position.y + 300));
spaceship3.name = "spaceship3";
spaceship3.physicsBody = SKPhysicsBody(circleOfRadius: spaceship2.size.width / 2);
spaceship3.physicsBody?.isDynamic = true // apply gravity, friction, and collision
spaceship3.physicsBody?.affectedByGravity = false;
spaceship3.physicsBody?.allowsRotation = false
spaceship3.physicsBody = SKPhysicsBody(circleOfRadius: spaceship2.size.width / 2);
spaceship3.physicsBody?.categoryBitMask = enemy3Category
spaceship3.physicsBody?.collisionBitMask = enemy2Category | enemyCategory
spaceship3.physicsBody?.contactTestBitMask = enemy2Category | enemyCategory
addChild(spaceship1)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
addChild(spaceship3)
}
override func update(_ currentTime: TimeInterval) {
spaceship1.physicsBody?.affectedByGravity = false;
}
}
I'm just trying to figure out how to detect which bitmasks come into contact with each other. Like if spaceship1 comes into contact with spaceship2, it prints yay, but if spaceship1 comes into contact with spaceship 3, it prints "wow"
There is a documentation article that tries to explain bit masks as used in Sprite-Kit collision and contacts, but I can no longer link to it. Try to find the 'Documentation' page, tag 'Sprite-Kit' then the 'Manipulating contactTest and collison bitmasks to enable/disable specific contact and collisions.' article under 'SKNode Collision'.
One obvious problem is this line :
let enemy3Category: UInt32 = 3
All bitmasks have to be thought of in binary, and in binary 3 is 11. This is the '1' bit and the '2' bit both being set to 1. Category definitions should only have 1 bit set to 1 which you ensure by using exact powers of 2 for your categories - 1, 2, 4, 8, 16, 32, etc.
Objects in your scene that need to collide/contact should belong to only one category initially. For more advanced contacts and collisions, a sprite can belong to multiple categories.
You are missing an = in your if statement.
func didBegin(_ contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == enemyCategory && contact.bodyB.categoryBitMask == enemy2Category) {
print("contact between 1 and 2")
}
}
This sets firstBody to the smaller bitmask And sets secondBody to the larger one
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}else{
firstBody = contact.bodyB
secondBody = contact.bodyA
}
Assuiming bitmask1 is smaller than 2 and 3.
if firstBody.categoryBitMask == bitMask1 && secondBody.categoryBitMask == bitMask.bitmask3 {
print("a")
//If ball hits a pad, activate the pad
}else if firstBody.categoryBitMask == bitMask1 && secondBody.categoryBitMask == bitMask.bitmask2 {
print("e")
}
I'm new to Swift SpriteKit, I want to make a game like a virtual joystick and two buttons(two nodes), I've enabled the multi-touch. However, whenever I move both virtual joystick and attack Spritenode, the virtual joystick of the button seems to be Jagging. How am I gonna separate the touches of virtual joystick from touches attackbutton
class GameScene: SKScene {
var defend : Bool = false
var attack : Bool = false
var stickMove : Bool = false
var stickEnd:Bool = false
var moveattack:Bool = false
var movedefend:Bool = false
var playermovement:Bool = true
let vj1 = SKSpriteNode(imageNamed: "vj1")
let vj2 = SKSpriteNode(imageNamed: "vj2")
let player = SKSpriteNode(imageNamed: "player")
let rotationSpeed :CGFloat = CGFloat(M_PI)
let rotationOffSet : CGFloat = -CGFloat(M_PI/2.0)
let attackvj = SKSpriteNode(imageNamed: "attackvj")
let defendvj = SKSpriteNode(imageNamed: "defendvj")
private var touchPosition: CGFloat = 0
private var targetZRotation: CGFloat = 0
override func didMove(to view: SKView) {
self.view?.isMultipleTouchEnabled = true
self.backgroundColor = SKColor.black
//position of joystick
vj1.zPosition = 1
vj1.xScale = 1.5
vj1.yScale = 1.5
self.addChild(vj1)
vj1.position = CGPoint(x: self.size.width*15/100, y:self.size.height*30/100)
vj2.zPosition = 1
vj2.xScale = 1.5
vj2.yScale = 1.5
self.addChild(vj2)
vj2.position = vj1.position
player.zPosition = 0
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
player.physicsBody!.affectedByGravity = false
player.position = CGPoint(x: self.size.width/2, y:self.size.height/2)
self.addChild(player)
attackvj.anchorPoint = CGPoint(x: 0.5, y:0.5)
attackvj.position = CGPoint(x: self.size.width*80/100, y:self.size.height*30/100)
attackvj.xScale = 2.0
attackvj.yScale = 2.0
self.addChild(attackvj)
defendvj.anchorPoint = CGPoint(x: 0.5, y:0.5)
defendvj.position = CGPoint(x: self.size.width*90/100, y:self.size.height*50/100)
defendvj.xScale = 2.0
defendvj.yScale = 2.0
self.addChild(defendvj)
vj1.alpha = 0.4
vj2.alpha = 0.4
attackvj.alpha = 0.4
defendvj.alpha = 0.4
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches){
let location = touch.location(in: self)
if vj2.contains(location){
stickEnd = false
stickMove = true
}
if defendvj.contains(location){
defend = true
}
if attackvj.contains(location){
attack = true
attackvj.xScale = 2.5
attackvj.yScale = 2.5
}
if(stickMove == true && attack == true){
moveattack = true
}
if(stickMove == true && defend == true){
movedefend = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches){
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
let v = CGVector(dx: location.x - vj1.position.x, dy: location.y - vj1.position.y)
print("locationsss" , location , "previouslocationsss", previousLocation)
let angle = atan2(v.dy, v.dx)
targetZRotation = angle + rotationOffSet
let length:CGFloat = vj1.frame.size.height / 2
let xDist:CGFloat = sin(angle - 1.57079633) * length
let yDist:CGFloat = cos(angle - 1.57079633) * length
if(stickMove == true){
if(vj1.frame.contains(location)){
vj2.position = location
}
else{
vj2.position = CGPoint(x: vj1.position.x - xDist, y: vj1.position.y + yDist)
}
if(attackvj.frame.contains(location)){//How am I gonna make this location in attackvj, not to influence my joystick location?
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if(stickMove == true && attack == false && defend == false){
let move:SKAction = SKAction.move(to: vj1.position, duration: 0.2)
move.timingMode = .easeOut
vj2.run(move)
stickEnd = true
stickMove = false
}
if(attack == true){
attack = false
attackvj.xScale = 2.0
attackvj.yScale = 2.0
moveattack = false
}
if(defend == true){
defend = false
movedefend = false
}
}
override func update(_ currentTime: TimeInterval) {
//rotation
if (stickEnd == false) {
var angularDisplacement = targetZRotation - player.zRotation
if angularDisplacement > CGFloat(M_PI) {
angularDisplacement = (angularDisplacement - CGFloat(M_PI)*2)
}
else if angularDisplacement < -CGFloat(M_PI) {
angularDisplacement = (angularDisplacement + CGFloat(M_PI)*2)
}
if abs(angularDisplacement) > rotationSpeed*(1.0/60.0){
let angularVelocity = angularDisplacement < 0 ? -rotationSpeed : rotationSpeed
player.physicsBody!.angularVelocity = angularVelocity
} else {
player.physicsBody!.angularVelocity = 0
player.zPosition = targetZRotation
}
}
else{
player.physicsBody!.angularVelocity = 0
}
//movement but use attack button to testing
if (attack == true)
{
player.position = CGPoint(x:player.position.x + cos(player.zRotation + 1.57079633),y:player.position.y + sin(player.zRotation + 1.57079633))
}
}
The problem you are facing is that you are mixing the contexts for your touches. This is making things more difficult and complicated than they need to be.
The easiest thing to do would be to make your virtual joystick a separate SKSpriteNode class that tracks its own touches and reports them. Same with the buttons - they track their own touches and report their state.
But if you want to continue with your current approach of having a high-level object track multiple touches, what you want to do is capture the context that each touch is associated with in touchesBegan, and then just update things on touchesMoved as necessary, canceling the touches in touchesEnded.
For instance, you want to associate a particular touch with the virtual joystick, because you don't want weirdness if they drag their finger off of it and over to the button, say. And you want to know exactly which touch is lifted off when the user lifts a finger.
Here's some sample code that should illustrate the process:
//
// This scene lets the user drag a red and a blue box
// around the scene. In the .sks file (or in the didMove
// function), add two sprites and name them "red" and "blue".
//
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var redTouch:UITouch?
private var blueTouch:UITouch?
override func didMove(to view: SKView) {
super.didMove(to: view)
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Grab some references to the red and blue sprites.
// (They must be direct children of the scene, or the
// paths must be amended.)
guard let redBox = childNode(withName: "red") else { return }
guard let blueBox = childNode(withName: "blue") else { return }
for touch in touches {
// Get the location of the touch in SpriteKit Scene space.
let touchLocation = touch.location(in: self)
// Check to see if the user is touching one of the boxes.
if redBox.contains( touchLocation ) {
// If we already have a touch in the red box, do nothing.
// Otherwise, make this our new red touch.
redTouch = touch
} else if blueBox.contains( touchLocation ) {
// If we already have a touch in the blue box, do nothing.
// Otherwise, make this our new blue touch.
blueTouch = touch
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// We have already established which touches are active,
// and we have already tied them to the two contexts, so
// we just need to read their current location and update
// the location of the red and blue boxes for the touches
// that are active.
if let redTouch = redTouch {
guard let redBox = childNode(withName: "red") else { return }
let location = redTouch.location(in:self)
redBox.position = location
}
if let blueTouch = blueTouch {
guard let blueBox = childNode(withName: "blue") else { return }
let location = blueTouch.location(in:self)
blueBox.position = location
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// The parameter touches contains a list of ending touches,
// so we check the touches we are currently tracking to
// see if they are newly lifted. If so, we cancel them.
if let touch = redTouch {
if touches.contains( touch ) {
redTouch = nil
}
}
if let touch = blueTouch {
if touches.contains( touch ) {
blueTouch = nil
}
}
}
}
In the above code, we have separated out the touches on the red box and the blue box. We always know which touch is dragging the red box around and which touch is dragging the blue box around, if any. This is a simple example, but it's generalizable to your situation, where you'd have touches for the virtual joystick and each individual button.
Note that this approach works well for multitouch elements, too. If you have a map that you want to be zoomable, you can keep track of two touches so that you can compare them for pinch gestures. That way, if your pinch gesture accidentally strays over a button, you've already marked it as part of the pinch gesture, and know not to start triggering that button.
But again, a better approach would be to have a separate SKSpriteNode subclass that just tracks the joystick touches and reports its state to some higher-level manager class. You already know everything you need to know to do this - it's like what you have without all the extra checking to see if there are other buttons pressed. Same with the buttons. The only new part would be messaging "up the chain" to a manager, and that's pretty straightforward to deal with.
I'm trying to solving a problem where a sprite node can jump up through a platform but cannot jump back down. I tried using this code:
override func didMove(to view: SKView) {
if (thePlayer.position.y > stonePlatform1.position.y) == true {
stonePlatform1.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: stonePlatform.size.width * 0.9, height: stonePlatform.size.height * 0.75))
stonePlatform1.physicsBody!.isDynamic = false
stonePlatform1.physicsBody!.affectedByGravity = false
stonePlatform1.physicsBody!.categoryBitMask = BodyType.object.rawValue
stonePlatform1.physicsBody!.contactTestBitMask = BodyType.object.rawValue
stonePlatform1.physicsBody!.restitution = 0.4
}
}
The idea was to turn on the physics body of the platform on when the player is above the platform. However, the physics doesn't work at all when I use this code. In fact I tried using this code:
override func didMove(to view: SKView) {
if (thePlayer.position.y < stonePlatform1.position.y) == true {
stonePlatform1.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: stonePlatform.size.width * 0.9, height: stonePlatform.size.height * 0.75))
stonePlatform1.physicsBody!.isDynamic = false
stonePlatform1.physicsBody!.affectedByGravity = false
stonePlatform1.physicsBody!.categoryBitMask = BodyType.object.rawValue
stonePlatform1.physicsBody!.contactTestBitMask = BodyType.object.rawValue
stonePlatform1.physicsBody!.restitution = 0.4
}
}
and the physics doesn't turn on either. If the IF statement isn't there, the physics does work all of the time.
You can use the node velocity for this platforms, like this:
SpriteKit - Swift 3 code:
private var up1 : SKSpriteNode!
private var down1 : SKSpriteNode!
private var down2 : SKSpriteNode!
private var player : SKSpriteNode!
override func didMove(to view: SKView) {
up1 = self.childNode(withName: "up1") as! SKSpriteNode
down1 = self.childNode(withName: "down1") as! SKSpriteNode
down2 = self.childNode(withName: "down2") as! SKSpriteNode
player = self.childNode(withName: "player") as! SKSpriteNode
up1.physicsBody?.categoryBitMask = 0b0001 // Mask for UoPlatforms
down1.physicsBody?.categoryBitMask = 0b0010 // Mask for downPlatforms
down2.physicsBody?.categoryBitMask = 0b0010 // Same mask
}
override func update(_ currentTime: TimeInterval) {
player.physicsBody?.collisionBitMask = 0b0000 // Reset the mask
// For UP only Platform
if (player.physicsBody?.velocity.dy)! < CGFloat(0.0) {
player.physicsBody?.collisionBitMask |= 0b0001 // The pipe | operator adds the mask by binary operations
}
// For Down only platforms
if (player.physicsBody?.velocity.dy)! > CGFloat(0.0) {
player.physicsBody?.collisionBitMask |= 0b0010 // The pipe | operator adds the mask by binary operations
}
}
Source code with example here: https://github.com/Maetschl/SpriteKitExamples/tree/master/PlatformTest
The example show this:
Green platforms -> Down Only
Red platforms -> Up only
You could try just starting with the physics body as nil and then set the physics values to it after the player is above it. Also, this kind of code should be in the update function. Having it in didMove only lets it get called once.
override func update(_ currentTime: TimeInterval){
if (thePlayer.position.y < stonePlatform1.position.y) && stonePlatform1.physicsBody != nil {
stonePlatform1.physicsBody = nil
}else if (thePlayer.position.y > stonePlatform1.position.y) && stonePlatform1.physicsBody == nil{
setPhysicsOnPlatform(stonePlatform1)
}
}
func setPhysicsOnPlatform(_ platform: SKSpriteNode){
platform.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: stonePlatform.size.width * 0.9, height: stonePlatform.size.height * 0.75))
...
//the rest of your physics settings
}
You should also do some handling for the height of the player and your anchorPoint. Otherwise if your anchorPoint is (0,0) and the player is halfway through the platform, the physics will be applied and a undesirable result will occur.
how do we create a UICollectionViewLayout like the SnapChat's stories?
Any ideas please?
I'd like to have a solution without external library.
Based on a precedent answer adapted to your issue:
-(id)initWithSize:(CGSize)size
{
self = [super init];
if (self)
{
_unitSize = CGSizeMake(size.width/2,80);
_cellLayouts = [[NSMutableDictionary alloc] init];
}
return self;
}
-(void)prepareLayout
{
for (NSInteger aSection = 0; aSection < [[self collectionView] numberOfSections]; aSection++)
{
//Create Cells Frames
for (NSInteger aRow = 0; aRow < [[self collectionView] numberOfItemsInSection:aSection]; aRow++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:aRow inSection:aSection];
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
NSUInteger i = aRow%3;
NSUInteger j = aRow/3;
CGFloat offsetY = _unitSize.height*2*j;
CGPoint xPoint;
CGFloat height = 0;
BOOL invert = NO;
if (aRow%6 >= 3) //We need to invert Big cell and small cells => xPoint.x
{
invert = YES;
}
switch (i)
{
case 0:
xPoint = CGPointMake((invert?_unitSize.width:0), offsetY);
height = _unitSize.height;
break;
case 1:
xPoint = CGPointMake((invert?_unitSize.width:0), offsetY+_unitSize.height);
height = _unitSize.height;
break;
case 2:
xPoint = CGPointMake((invert?0:_unitSize.width), offsetY);
height = _unitSize.height*2;
break;
default:
break;
}
CGRect frame = CGRectMake(xPoint.x, xPoint.y, _unitSize.width, height);
[attributes setFrame:frame];
[_cellLayouts setObject:attributes forKey:indexPath];
}
}
}
I set the height of unitSize to 80, but you can use the size of the screen if needed, like _unitSize = CGSizeMake(size.width/2,size.height/4.);.
That render:
Side note: It's up to you to adapt the logic, or do changes, the cell frames calculation may not be the "best looking piece of code".
UICollectionViewLayout like the SnapChat's stories Like
Swift 3.2 Code
import Foundation
import UIKit
class StoryTwoColumnsLayout : UICollectionViewLayout {
fileprivate var cache = [IndexPath: UICollectionViewLayoutAttributes]()
fileprivate var cellPadding: CGFloat = 4
fileprivate var contentHeight: CGFloat = 0
var oldBound: CGRect!
let numberOfColumns:Int = 2
var cellHeight:CGFloat = 255
fileprivate var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
super.prepare()
contentHeight = 0
cache.removeAll(keepingCapacity: true)
guard cache.isEmpty == true, let collectionView = collectionView else {
return
}
if collectionView.numberOfSections == 0 {
return
}
let cellWidth = contentWidth / CGFloat(numberOfColumns)
cellHeight = cellWidth / 720 * 1220
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * cellWidth)
}
var column = 0
var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
var newheight = cellHeight
if column == 0 {
newheight = ((yOffset[column + 1] - yOffset[column]) > cellHeight * 0.3) ? cellHeight : (cellHeight * 0.90)
}
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: cellWidth, height: newheight)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache[indexPath] = (attributes)
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + newheight
if column >= (numberOfColumns - 1) {
column = 0
} else {
column = column + 1
}
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
// Loop through the cache and look for items in the rect
visibleLayoutAttributes = cache.values.filter({ (attributes) -> Bool in
return attributes.frame.intersects(rect)
})
print(visibleLayoutAttributes)
return visibleLayoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// print(cache[indexPath.item])
return cache[indexPath]
}
}
I am using transition function to have cube effect with function as shown and getting error *** Terminating app due to uncaught exception 'CALayerInvalidGeometry', reason: 'CALayer bounds contains NaN: [0 nan; 667 343]'
The above error occurs when popped among view controllers (UiNavigationcontroller with 5 view controllers in shared instance) after orientation is changed.
I am not able to fix it ,please help
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toView = toViewController.view
let fromView = fromViewController.view
let direction: CGFloat = reverse ? -1 : 1
let const: CGFloat = -0.005
toView?.layer.anchorPoint = CGPoint()
toView?.layer.anchorPoint = CGPoint(x:direction == 1 ? 0 : 1,y: 0.5)
fromView?.layer.anchorPoint = CGPoint(x:direction == 1 ? 1 : 0,y: 0.5)
var viewFromTransform: CATransform3D = CATransform3DMakeRotation(direction * CGFloat(M_PI_2), 0.0, 1.0, 0.0)
var viewToTransform: CATransform3D = CATransform3DMakeRotation(-direction * CGFloat(M_PI_2), 0.0, 1.0, 0.0)
viewFromTransform.m34 = const
viewToTransform.m34 = const
containerView.transform = CGAffineTransform(translationX: direction * containerView.frame.size.width / 2.0, y: 0)
toView?.layer.transform = viewToTransform
print("container view frame: \(containerView.frame) subviews container \(containerView.subviews)")
containerView.addSubview(toView!)
// App crashes here giving error
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
containerView.transform = CGAffineTransform(translationX: -direction * containerView.frame.size.width / 2.0, y: 0)
fromView?.layer.transform = viewFromTransform
toView?.layer.transform = CATransform3DIdentity
}, completion: {
finished in
containerView.transform = .identity
fromView?.layer.transform = CATransform3DIdentity
toView?.layer.transform = CATransform3DIdentity
fromView?.layer.anchorPoint = CGPoint(x:0.5,y:0.5)
toView?.layer.anchorPoint = CGPoint(x:0.5,y:0.5)
if (transitionContext.transitionWasCancelled) {
toView?.removeFromSuperview()
} else {
fromView?.removeFromSuperview()
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}