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)
})
}
Related
I have a ball and a cylinder. When i tap on the ball it moves in forward position. When the ball hits the cylinder they collide and the cylinder moves and changes color. Thats working fine.
I have 2 problems:
When i start the app the cylinder is constantly colliding and changes color immediately, so even when the ball is not near it. It looks like its colliding with something else.
The second thing is, there will be more cylinders in the scene, how can i make a collision event only between 2 objects. Lets say there are 2 cylinders in the scene how can ik set a filter or group.
import SwiftUI
import RealityKit
import ARKit
import FocusEntity
import Combine
struct ContentView : View {
var body: some View {
ARViewContainer()
}
}
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let view = ARView()
let session = view.session
let config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
session.run(config)
let coachingOverlay = ARCoachingOverlayView()
coachingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
coachingOverlay.session = session
coachingOverlay.goal = .horizontalPlane
view.addSubview(coachingOverlay)
context.coordinator.view = view
session.delegate = context.coordinator
view.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)))
return view
}
func updateUIView(_ uiView: ARView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator()
}
//coordinator class
class Coordinator: NSObject,ARSessionDelegate {
weak var view: ARView?
var focusEntity: FocusEntity?
#Published var sceneIsPlaced:Bool = false //is de scene reeds geplaats na openen app
#Published var subscriptions: [AnyCancellable] = []
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
guard let view = self.view else { return }
self.focusEntity = FocusEntity(on: view, style: .classic(color: .yellow))
}
//na tap op het scherm, wat te doen -> creeer locatie en plaats het object
#objc func handleTap(recognizer: UITapGestureRecognizer) {
guard let view = self.view, let focusEntity = self.focusEntity else { return }
//tap location van de tap gesture
let tapLocation = recognizer.location(in: self.view)
//Create Anchor
let anchor = AnchorEntity()
let importModel = try! Entity.load(named: "cilinder") //alle objects!
importModel.position = focusEntity.position
importModel.scale = SIMD3(repeating: 0.5)
//The cylinder
let kolomMiddleModel = importModel.findEntity(named: "cilinder")!.children[0] as! (ModelEntity & HasPhysicsBody & HasCollision)
let materialKolomMiddle = SimpleMaterial(color: .yellow, isMetallic: true)
kolomMiddleModel.model?.materials = [materialKolomMiddle]
kolomMiddleModel.generateCollisionShapes(recursive: false)
let physics = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
kolomMiddleModel.components.set(physics)
//MAKE A BALL
let materialsBall = SimpleMaterial(color: .red, isMetallic: true)
let ballModel = ModelEntity(mesh: .generateSphere(radius: 0.1),
materials: [materialsBall])
as (Entity & HasPhysicsBody & HasCollision)
ballModel.position = [-0.1, -1.0,-0.1]
ballModel.generateCollisionShapes(recursive: true)
ballModel.name = "ballModel"
//LIGHTS --> let op voor de shadow moet de plane van occlusion material met dynamical lightning op tue
let directionalLight = DirectionalLight()
directionalLight.light.color = .white
directionalLight.light.intensity = 4000
directionalLight.light.isRealWorldProxy = true
directionalLight.shadow?.maximumDistance = 1.5
directionalLight.shadow?.depthBias = 7.0
directionalLight.orientation = simd_quatf(angle: .pi/1.5, axis: [0,1,0])
//maak een anchor voor het licht
let lightAnchor = AnchorEntity(world: [0, 0, 2.5])
lightAnchor.addChild(directionalLight)
//COLLISION EVENTS
DispatchQueue.main.async {
//collision of kolomModel
view.scene.subscribe(to: CollisionEvents.Began.self,
on: kolomMiddleModel) { _ in
print("Collision kolomModel detected!")
let material = SimpleMaterial(color: .red, isMetallic: true)
kolomMiddleModel.model?.materials = [material]
}.store(in: &self.subscriptions)
}
// view.installGestures(for: ballModel)
//
//PLACE SCENE IF NOT PLACED ALREADY
if !sceneIsPlaced {
//ADD MODELS TO ANCHOR
anchor.addChild(importModel)
anchor.addChild(ballModel)
anchor.addChild(lightAnchor)
view.scene.addAnchor(anchor)
sceneIsPlaced = true
//If the scene is already placed get the taplocation
}else{
if let locationEntity = view.entity(at: tapLocation) {
let entityTapped = locationEntity as! ModelEntity
//MAKE THE BALL GO FORWARD
print("entity tapped \(entityTapped)")
entityTapped.physicsBody = .init()
entityTapped.physicsBody?.mode = .kinematic
entityTapped.physicsMotion = PhysicsMotionComponent(linearVelocity: [0, 0, -0.5],
angularVelocity: [1, 3, 5])
}
}
}
}
}
Here is an example of two objects in RealityKit that can only collide with each other:
let sphere = ModelEntity(mesh: .generateSphere(radius: 0.1), materials: [SimpleMaterial()])
sphere.collision = CollisionComponent(shapes: [ShapeResource.generateSphere(radius: 0.1)])
sphere.collision?.filter = CollisionFilter(group: 1 << 2, categories: .default, mask: .default)
let cube = ModelEntity(mesh: .generateBox(size: [0.1, 0.1, 0.1]), materials: [SimpleMaterial()])
cube.collision = CollisionComponent(shapes: [ShapeResource.generateBox(size: [0.1, 0.1, 0.1])])
cube.collision?.filter = CollisionFilter(group: 1 << 1, categories: .default, mask: .default)
sphere.collision?.filter.collisionGroup = 1 << 1
cube.collision?.filter.collisionGroup = 1 << 2
arView.scene.addAnchor(sphere)
arView.scene.addAnchor(cube)
This code creates two objects: a sphere and a cube. Each object is given a CollisionComponent with a corresponding ShapeResource and CollisionFilter. The CollisionFilter for each object specifies that only objects in a different collisionGroup can collide with it. As a result, the sphere and cube can only collide with each other, not with other objects in the scene.
See more information about CollisionGroups here:
https://developer.apple.com/documentation/realitykit/collisionfilter
I try to make an endless background through the nodes, but the background has not become infinite and is interrupted, the third background is not yet shown. After the first show, the number of nodes in the scene grows, how can this be fixed?
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var bgNode: SKNode!
var overlay: SKNode!
var overlayWidth: CGFloat!
//var viewSize: CGSize!
var levelPositionX: CGFloat = 0.0
//var speed: CGFloat = 5.5
override func didMove(to view: SKView) {
setupNode()
//viewSize = CGSize(width: frame.size.width, height:
frame.size.height )
}
func setupNode() {
let worldNode = childNode(withName: "World")!
bgNode = worldNode.childNode(withName: "Background")!
overlay = bgNode.childNode(withName: "Overlay")!.copy() as!
SKNode
overlayWidth = overlay.calculateAccumulatedFrame().width
}
func createBackgroundOverlay() {
let backgroundOverlay = overlay.copy() as! SKNode
backgroundOverlay.position = CGPoint(x: 0.0, y: 0.0)
bgNode.addChild(backgroundOverlay)
levelPositionX += overlayWidth
}
func update() {
bgNode.position.x -= 5
if bgNode.position.x <= -self.frame.size.width {
bgNode.position.x = self.frame.size.width * 2
createBackgroundOverlay()
}
}
override func update(_ currentTime: TimeInterval) {
update()
}
In my endless runner game, I have implemented an endless background and a ground(or floor) much similar to your app. Below I shall discuss the steps i have used in my game.
Step 1: In your GameScene.swift file add these variables.
var backgroundSpeed: CGFloat = 80.0 // speed may vary as you like
var deltaTime: TimeInterval = 0
var lastUpdateTimeInterval: TimeInterval = 0
Step 2: In GameScene file, make setUpBackgrouds method as follows
func setUpBackgrounds() {
//add background
for i in 0..<3 {
// add backgrounds, my images were namely, bg-0.png, bg-1.png, bg-2.png
let background = SKSpriteNode(imageNamed: "bg-\(i).png")
background.anchorPoint = CGPoint.zero
background.position = CGPoint(x: CGFloat(i) * size.width, y: 0.0)
background.size = self.size
background.zPosition = -5
background.name = "Background"
self.addChild(background)
}
for i in 0..<3 {
// I have used one ground image, you can use 3
let ground = SKSpriteNode(imageNamed: "Screen.png")
ground.anchorPoint = CGPoint(x: 0, y: 0)
ground.size = CGSize(width: self.size.width, height: ground.size.height)
ground.position = CGPoint(x: CGFloat(i) * size.width, y: 0)
ground.zPosition = 1
ground.name = "ground"
self.addChild(ground)
}
}
Step 3: Now we have to capture timeIntervals from update method
override func update(_ currentTime: TimeInterval) {
if lastUpdateTimeInterval == 0 {
lastUpdateTimeInterval = currentTime
}
deltaTime = currentTime - lastUpdateTimeInterval
lastUpdateTimeInterval = currentTime
}
Step 4: Here comes the most important part, moving our backgrounds and groungFloor by enumerating child nodes. Add these two methods in GameScene.swift file.
func updateBackground() {
self.enumerateChildNodes(withName: "Background") { (node, stop) in
if let back = node as? SKSpriteNode {
let move = CGPoint(x: -self.backgroundSpeed * CGFloat(self.deltaTime), y: 0)
back.position += move
if back.position.x < -back.size.width {
back.position += CGPoint(x: back.size.width * CGFloat(3), y: 0)
}
}
}
}
func updateGroundMovement() {
self.enumerateChildNodes(withName: "ground") { (node, stop) in
if let back = node as? SKSpriteNode {
let move = CGPoint(x: -self.backgroundSpeed * CGFloat(self.deltaTime), y: 0)
back.position += move
if back.position.x < -back.size.width {
back.position += CGPoint(x: back.size.width * CGFloat(3), y: 0)
}
}
}
}
Step 5: At this point you should get this error:"Binary operator '+=' cannot be applied to two 'CGPoint' operands" in updateBackground and updateGroundMovement methods.
Now we need to implement operator overloading to resolve this problem. Create a new Swift File and name it Extensions.swift and then implement as follows:
// Extensions.swift
import CoreGraphics
import SpriteKit
public func + (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
public func += (left: inout CGPoint, right: CGPoint) {
left = left + right
}
Step 6: call setUpBackgrounds method in didMove(toView:)
override func didMove(to view: SKView) {
setUpBackgrounds()
}
Step 7: Finally call the updateBackground and updateGroundMovement methods in update(_ currentTime) method. updated code is given below:
override func update(_ currentTime: TimeInterval) {
if lastUpdateTimeInterval == 0 {
lastUpdateTimeInterval = currentTime
}
deltaTime = currentTime - lastUpdateTimeInterval
lastUpdateTimeInterval = currentTime
//MARK:- Last step:- add these methods here
updateBackground()
updateGroundMovement()
}
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 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.
UPDATE:
After further testing and evaluation it seems that the SCNLookAtConstraint is causing the issue. Basically because it removes the 2 finger Pan Gesture it is causing some weird behavior with the pinch and scroll gesture. Does anyone have a good idea how to fix this?
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let quaternion = sceneView.pointOfView?.orientation
let position = sceneView?.pointOfView?.position
print("Orientation: ((quaternion?.x),(quaternion?.y),(quaternion?.z),(quaternion?.w)) Position: ((position?.x),(position?.y),(position?.z)")
}
but this just prints out the same values now matter the 2 finger zoom pressed.
func setupScene() {
let scene = SCNScene()
self.sceneView.scene = scene
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: -1.0, y: 0.0, z: -1.0)
cameraNode.boundingBox.max = SCNVector3(x: 2, y: 2, z: 2)
cameraNode.boundingBox.min = SCNVector3(x: 0.1, y: 0.1, z: 0.1)
let centerNode = SCNNode()
centerNode.position = SCNVector3(x: 0.0, y: 0.0, z: 0.0)
let light = SCNLight()
let lightNode = SCNNode()
light.type = .ambient
light.color = UIColor.white
lightNode.light = light
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(centerNode)
scene.rootNode.addChildNode(lightNode)
cameraNode.constraints = [SCNLookAtConstraint(target: centerNode)]
sceneView.pointOfView = cameraNode
}