How to create particular animation - swift

Hi i have a question about shapes and animation of the shapes.
I would like to understand what is the best practice to create these kind of shapes (shapes that are presented on images around buttons).
Previous investigation shows that usage of the UIBezierPathis required but I am interest in how to create this kind of path and constantly animate it? Or probably should i use SpriteKit for that?

I think Spritekit is not necessary. You make this kind of animation just by acting on the CALayer masks.
If you writing a series of masks based each time on a different ellipse ( and build an animations based on it), you can re-produce this effect in your button.
Some useful code examples:
var shape = CAShapeLayer()
shape.fillColor = UIColor(white: 0.90, alpha: 1).CGColor
var image = UIImage(named: "some_image")
shape.contents = image?.CGImage
var ovalPath = UIBezierPath(ovalInRect: CGRectMake(160, 160, 240, 320))

Related

Eraser functionality for CAShapelayer drawings : SWIFT

I am working on drawing app. I want to implement eraser functionality that erases the path drawn using UIBezierPath.Is it possible??
I have tried below code but its not working
UIGraphicsBeginImageContextWithOptions(self.tempImageView.frame.size, false, 0.0)
if let context = UIGraphicsGetCurrentContext()
{
tempImageView.image?.draw(in: CGRect(x:
0, y: 0, width: self.tempImageView.frame.size.width, height: self.tempImageView.frame.size.height))
let sublayer = CAShapeLayer()
self.tempImageView.layer.addSublayer(sublayer)
context.setBlendMode(CGBlendMode.clear)
sublayer.strokeColor = editorPanelView.drawingColor.cgColor
sublayer.lineJoin = kCALineJoinRound
sublayer.lineCap = kCALineCapRound
sublayer.lineWidth = editorPanelView.brushWidth
let path = UIBezierPath()
path.move(to: CGPoint(x: mid1.x, y: mid1.y))
path.addQuadCurve(to:CGPoint(x:mid2.x, y:mid2.y) , controlPoint: CGPoint(x: prevPoint1.x, y: prevPoint1.y))
sublayer.path = path.cgPath
layerArray.append(sublayer)
UIGraphicsEndImageContext()
}
I am not able to find any solution for this. Any help (even if it's just a kick in the right direction) would be appreciated.
A CAShapeLayer is a vector drawing. It's shapes. The usual eraser tool works with raster (pixel-based) drawings. The two are different, and there isn't a straightforward way to do an eraser tool on vector drawings.
One way you could do it is to have the eraser tool edit a mask layer which is applied to your shape layer. It would have the effect of erasing the parts of the shape layer where the mask is opaque.
One tricky part to this is that once you've drawn into the mask, you'd have to erase that part of the mask in order for the user to be able to draw new content into the erased area, and that might cause the erased parts of the old drawing to show through.
Getting what you want to do to work well would be quite tricky.

Changing UIBezierPath color

I have a UIView in which I use a bezierPath to form it's shape. I want to create a duplicate view, but of a different color. The same shape is created, but the color doesn't change with the following code. Any idea why?
let whiteWave = coloredWave
let drawingLayer = CAShapeLayer()
drawingLayer.path = coloredWave.aPath.cgPath
drawingLayer.strokeColor = UIColor.white.cgColor
drawingLayer.lineWidth = audioClip.widthOfClip
drawingLayer.fillColor = UIColor.white.cgColor
whiteWave.layer.addSublayer(drawingLayer)
Edit: Okay so after some digging I found that the drawRect finishes after I set the whiteWave. I tried using a CATransactionblock and setting the new wave in the completion block. But that doesn't work either.
How can I wait for drawRect in the UIView to finish?
To the best of my knowledge it's because you are using the same names for both shapes. Try doing let whiteWave2 for the second shape.

SpriteKit. Why .strokeTexture does not work for SKShapeNode?

I can't understand why the "strokeTexture" option isn't working on a SKShapeNode. Or, how can I do border with texture? Thanks in advance!
let shape = SKShapeNode(circleOfRadius: 100)
shape.position = CGPoint(x: 200, y: 400)
shape.fillColor = SKColor.white
shape.fillTexture = SKTexture(imageNamed: "test")
shape.lineWidth = 50
shape.strokeColor = SKColor.white
shape.strokeTexture = SKTexture(imageNamed: "test(5)")
Output:
Test image:
Test(5) image:
It doesn't work in Simulator!
Try it on your device.
I get the feeling this stroke might be being done with metal, whereas the fill is somehow not being done with metal, so is visible in the simulator.
The fill also doesn't rotate with the object on the device, but the stroke/outline and its texture do.
This would tend to indicate that an SKShapeNode without a fill might have reasonable performance.
Set the width and height of the texture, that is used to render the stroke, multiple of 8 pixels. In your case Test(5) has dimensions 100x100. If you change this texture for another one with dimensions, e.g. 96x96 pixels, the stroke will be rendered correctly and displayed. I don't know, why there are no reference to this fact in the official documentation.

uibezierpath with multiple line width and a background image (swift)

I'm trying to draw several shapes (rectangle, triangle, circle, ...) in swift and for that, I use uibezierpath but I am not able to draw exactly what I want.
I need to draw for example a rectangle, but the borders of this rectangle need to have different line width.
To do that, I create different path then use the "appendpath" to merge them in one path. So that works, BUT I also need to have a background image in this rectangle.
For that, I create a layer and set it an image. The issue is that, no background image are displayed when I use "appendpath", certainly because it doesn't recognize my drawing as a rectangle.
I hope it is clear enough, but is there a way to draw a shape with a background image, and have different border width ?
Thanks for your help !!
There are two solutions I'd suggest you try:
1) Masking
Create a normal CALayer and set the image as its contents. Then create a CAShapeLayer with the path you like and use it as the first layer's mask.
E.g.:
let imageLayer = CALayer()
imageLayer.contents = UIImage(named: "yourImage")?.CGImage // Your image here
imageLayer.frame = ... // Define a frame
let maskPath = UIBezierPath(...) // Create your path here
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.CGPath
imageLayer.mask = maskLayer
Don't forget to set the right frames and paths, and you should be able to achieve the effect you wanted.
2) Fill color
Create a CAShapeLayer with the path you like, then use your image as its fillColor.
E.g.:
let path = UIBezierPath(...) // Create your path here
let layer = CAShapeLayer()
layer.path = path.CGPath
let image = UIImage(named: "yourImage") // Your image here
layer.fillColor = UIColor(patternImage: image!).CGColor
You may find this approach easier at first, but controlling the way the image fills your shape is not trivial at all.
I hope this will help.
If you'd like more details, please provide an image or a sketch of what you're trying to achieve and / or the code you've written so far. Thanks!

Get a list of nodes in an specific area?

I'm working in a side-scolling game and I need to know what nodes are in an area to implement something like "line of sight". Right now I'm trying using enumerateBodyiesInRect() however it's detecting bodies that are 20px or more from the evaluated rect and I cannot figure out why it's so imprecise.
This is what I'm trying now:
import SpriteKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate
{
var player = SKShapeNode()
var world = SKShapeNode()
var rShape = SKShapeNode()
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.scaleMode = SKSceneScaleMode.AspectFit
self.size = view.bounds.size
// Add world
world = SKShapeNode(rectOfSize: view.bounds.size)
world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path)
world.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2) // Move camera
self.addChild(world)
// Add player
player = SKShapeNode(rectOfSize: CGSize(width: 25, height: 25))
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.frame.size)
player.physicsBody.dynamic = false
player.strokeColor = SKColor.blueColor()
player.fillColor = SKColor.blueColor()
player.position = CGPointMake(90, -50)
world.addChild(player)
}
override func update(currentTime: CFTimeInterval) {
// Define rect position and size (area that will be evaluated for bodies)
var r : CGRect = CGRect(x: 200, y: 200, width: 25, height: 25)
// Show rect for debug
rShape.removeFromParent()
rShape = SKShapeNode(rect: r)
rShape.strokeColor = SKColor.redColor()
self.addChild(rShape)
// Evaluate rect
rShape.fillColor = SKColor.clearColor()
self.physicsWorld.enumerateBodiesInRect(r) {
(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
self.rShape.fillColor = SKColor.redColor() // Paint the area blue if it detects a node
}
}
}
This code should show the evaluated rect and ray on the screen (for debugging purposes) and paint them red if they contact the player node. However you can see in the screenshot how it turns red when the player is 25px or more away from it, it's like if the drawing is a little bit off, or smaller than the actual area being evaluated. You can copy paste it to a project to duplicate the problem.
Could this be because this is just beta or am I doing something wrong?
You are creating a physical world where there is a specific rectangle that has 'special properties' - this is the rectangle that you use in enumerateBodiesInRect(). Why not create an invisible, inert physical body with the required rectangular dimension and then use SKPhysicsBody to check for collisions and/or contacts? You could then use allContactedBodies() or some delegate callbacks to learn what other bodies are inside your special rectangle.
Think of it like a 'tractor beam' or a 'warp rectangle'.
I believe you want SKPhysicsWorld's enumerateBodyiesInRect() instance method, which will iterate over all nodes in a given rectangle. If you're looking to get at the physics world through your scene, usage could look like this:
self.physicsWorld.enumerateBodiesInRect(CGRect(x: 0, y: 0, width: 50, height: 50)) {(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
// enumerates all nodes in given frame
}
I've experimented quite a bit with enumerateBodiesInRect now, and I've found it to be incredibly inaccurate. It seems to not have any of the claimed functionality, and instead produces random results. I honestly cannot even determine any pattern from its products.
enumerateBodiesAlongRay seems better, but still very buggy. The problem with that function seems to be the conversion between Screen and PhysicsWorld coordinates. I would avoid that one, as well.
I think your solution should simply be to use the existing contact detection system. All of your desired functionality can be written in the didBeginContact() and didEndContact() functions. This has the added benefit of allowing you to specify distinct functionality for both entering and leaving the area. You can also add particle effects, animations, and similar, as well as intentionally ignoring specific types of nodes.
The only thing to ensure success with this method is to clarify that the contact area has a unique category, that the contactTestBitMask contains all desired nodes and the collisionBitMask is set to 0.
The enumerateBodiesInRect method of SKPhysicsWorld expects the rect parameter to be in scene coordinates. This is important. If you have a scene hierarchy of nodes, you need to convert the rect you calculate from a reference node to the scene coordinates.
I faced a lot of issues with this method returning bodies that were off by values like 30px to the left etc. and finally realized the issue was because of the rect parameter not defined in scene coordinate space.
In my case, I had a worldNode inside my scene, and all objects were created in the worldNode. My camera was moving the worldNode about, and applying scaling to it for zooming out and in.
In order to use enumerateBodiesInRect correctly, I had to do something as follows:
// get your world rect based on game logic
let worldRect = getWorldRect()
// calculate the scene rect
let sceneRectOrigin = scene.convertPoint(worldRect.origin, fromNode:scene.worldNode)
let worldScale = scene.worldNode.xScale // assert this is not 0
// now to get the scene rect relative to the world rect, in scene coordinates
let sceneRect = CGRectMake( sceneRectOrigin.x, sceneRectOrigin.y, worldRect.width / worldScale, worldRect.height / worldScale)
world.physicsWorld.enumerateBodiesInRect(sceneRect) {
// your code here
}
Hope this helps.
I am not sure if this is a good practice. Correct me if not. But I am using
let shapeNode = SKShapeNode()
shapeNode.intersects(playerNode)
I checked selected nodes with simple loop if they intersect the player. Additionally I created SKShapeNodes which are drawn in front of nodes representing view sight of other actors in the game. They are moved along those actors.
There is only nodesAtPoint: method.
To achieve what you want you'd better to store all enemies in an array and have an int variable, something like nextEnemyIndex. This approach lets you to easily return the next enemy node, it's much more efficient than trying to find a node on the scene.
yes problem may occur because of your player's image, for example try to use 10px smaller body size:
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGRectMake(self.frame.origin.x, self.frame.origin.y, self.size.width-10, self.size.height-10)));