Rounded rectangle with SKShapeNode - swift

I have the following code that rounds an already existing rectangle from the scene builder...
btOk.path = UIBezierPath(roundedRect: CGRect(x: -62.5,y:-25,width: 125, height: 50), cornerRadius: 10).cgPath
However, if i try to use the same init to round the corners of another rectangle that is much larger, it does not even come close to working. It just makes the width HUUUUGE (imagine Trump).
scene.enumerateChildNodes(withName: "//*"){
node,stop in
if let name = node.name{
if name.contains("round"){
if let shapeNode = node as? SKShapeNode{
print(shapeNode.frame.width) //500
shapeNode.path = UIBezierPath(roundedRect: CGRect(x: -250,y:-100,width: 500, height: 200), cornerRadius: 50).cgPath
print(shapeNode.frame.width) //5735
}
}
}
}
btOk is a SKShapeNode as well. What am i missing between the two that is so different? One thing to note is i am enumerating through the children of the scene like this because this scene is in a SKReferenceNode. Perhaps that has something to do with it?
EDIT
Taking direction from #Ron Myschuk, i've solved this and since it's such a PITA, i also created an extension. So now i can round the corners of any SKShapeNode very easily when needed. Even if it was created in the scene editor. Note, this should only be used if there are no children of the shape node. Otherwise those children will be removed also.
extension SKShapeNode {
func roundCorners(topLeft:Bool,topRight:Bool,bottomLeft:Bool,bottomRight:Bool,radius: CGFloat,parent: SKNode){
let newNode = SKShapeNode(rect: self.frame)
newNode.fillColor = self.fillColor
newNode.lineWidth = self.lineWidth
newNode.position = self.position
newNode.name = self.name
self.removeFromParent()
parent.addChild(newNode)
var corners = UIRectCorner()
if topLeft { corners = corners.union(.bottomLeft) }
if topRight { corners = corners.union(.bottomRight) }
if bottomLeft { corners = corners.union(.topLeft) }
if bottomRight { corners = corners.union(.topRight) }
newNode.path = UIBezierPath(roundedRect: CGRect(x: -(newNode.frame.width / 2),y:-(newNode.frame.height / 2),width: newNode.frame.width, height: newNode.frame.height),byRoundingCorners: corners, cornerRadii: CGSize(width:radius,height:radius)).cgPath
}
}
And use it like so...
aShapeNode.roundCorners(topLeft: true, topRight: true, bottomLeft: false, bottomRight: false, radius: 5,parent: popup)

Not what you're going to want to hear but it's because you cannot set the width of an SKShapeNode in the Scene editor (To my knowledge). In order to get that ShapeNode to have a width of 500 you would have had to adjust the xScale. The xScale then reapplies itself to the path when you adjust it (kind of growing exponentially). If you create the SKShapeNode in code there is no problem adjust the rounded corners
let round = SKShapeNode(rect: CGRect(x: 0, y: -150, width: 500, height: 200))
round.fillColor = .red
addChild(round)
print(round.frame.width)
round.path = UIBezierPath(roundedRect: CGRect(x: -250, y: -100, width: 500, height: 200), cornerRadius: 50).cgPath
print(round.frame.width)
Edit
If you have your heart set on using the Scene editor you can place your ShapeNode and stretch it to where you want it then you could just do a small conversion in code to get the results that you want
if let round = self.childNode(withName: "biground") as? SKShapeNode {
let biground = SKShapeNode(rect: round.frame)
biground.fillColor = round.fillColor
biground.position = round.position
addChild(biground)
round.removeFromParent()
print(biground.frame.width)
biground.path = UIBezierPath(roundedRect: CGRect(x: -250, y: -100, width: 500, height: 200), cornerRadius: 50).cgPath
print(biground.frame.width)
}
this just recreates the shape in code based on what you outlined in the Scene editor and rounds the edges perfectly
edit 2
I've always been under the impression that SKShapeNodes are really inefficient (i'm pretty sure they leaked memory as well). So i always setup my round rectangles as so.
let outsideTexture = SKTexture(imageNamed: "round_square_white")
let outside = SKSpriteNode(texture: outsideTexture)
let insetX: CGFloat = 20
let insetY: CGFloat = 20
let cellSize = CGSize(width: 500.0, height: 500.0)
outside.centerRect = CGRect(x: CGFloat(insetX / outside.size.width), y: CGFloat(insetY / outside.size.height), width: CGFloat((outside.size.width - insetX * 2) / outside.size.width), height: CGFloat((outside.size.height - insetY * 2) / outside.size.height))
//outside.position = CGPointMake(gameModel.gameWidth / 2, (outside.size.height) / 2);
outside.xScale = cellSize.width / outside.size.width
outside.yScale = cellSize.height / outside.size.height
outside.zPosition = 10
outside.position = CGPoint(x: CGFloat(0), y: CGFloat(-0.35 * self.size.height / 2))
self.addChild(outside)
worth noting that this lays out a rounded square/rectangle perfectly however similar to the scale issues from the scene editor you have to place an empty cell over this to add children to, otherwise they scale to the rounded square.

Related

Programmatically drawing custom oval shape on screen swift

How can i draw custom oval shape like in below image in swift (not swiftUI).
Thank you in advance
I have tried to clone similar view using UIView and was able to create similar UI as you have stated on screenshot
And here is my code snippet
let width: CGFloat = view.frame.size.width
let height: CGFloat = view.frame.size.height
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height), cornerRadius: 0)
let rect = CGRect(x: width / 2 - 150, y: height / 2.5 - 100, width: 300, height: 400)
let circlePath = UIBezierPath(ovalIn: rect)
path.append(circlePath)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = .evenOdd
fillLayer.fillColor = UIColor.red.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)
Hope this helps you. Good day.
You can draw oval path like this
class CustomOval: UView {
override func drawRect(rect: CGRect) {
var ovalPath = UIBezierPath(ovalIn: rect)
UIColor.gray.setFill()
ovalPath.fill()
}
}

SkShapeNode physicsbody weird behaviour

I created a subclass of SKNode:
class SquareDrop: SKNode {
init(image: SKSpriteNode) {
super.init()
self.setScale(0.3
//Set the starting position of the node
self.position = CGPoint(x: 0.5, y: UIScreen.main.bounds.height/2)
//Apply a physics body to the node
self.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "square"), size: CGSize(width: image.size.width * 0.3, height: image.size.height * 0.3))
self.addChild(image)
}
I created an instance in GameScene class and I set up it:
func spawnRain()
squareDrop = SquareDrop(image: SKSpriteNode(imageNamed: "square"))
squareDrop?.physicsBody?.linearDamping = 5
squareDrop?.physicsBody?.restitution = 0
self.addChild(squareDrop!)
}
It is the result:
It works like aspected, but instead of using an image I wanted to draw a square:
func spawnRain() {
//Here I did not use the class that I had created previously
let shape = SKShapeNode()
shape.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 100, height: 100), cornerRadius: 5).cgPath
shape.position = CGPoint(x: frame.midX - shape.frame.width/2 , y: UIScreen.main.bounds.height/2)
shape.fillColor = UIColor.red
shape.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 100, height: 100))
addChild(shape)
}
It doesn't seem like I aspected (the squares that I created have a strange behaviour). Any hints?
I am sorry for the question, but I am a noob of SpriteKit.

Centering a UIBezierPath

I have a UIBezierPath thats a rounded square. It somewhat looks like this:
Here's my code for the shape:
let shape = SKShapeNode()
shape.path = UIBezierPath(roundedRect: CGRect(x:(0), y: (0), width: (250), height: (400)), cornerRadius: 64).cgPath
shape.position = CGPoint(x: 0, y: 0)
print(shape.position)
shape.fillColor = UIColor.white
shape.strokeColor = UIColor.white
shape.lineWidth = 5
addChild(shape)
I want to center it in the middle of the screen, but using
shape.position = CGPoint(x: self.frame.width, y: self.frame.height)
doesn't work. Thanks!
I suggest that you center the UIBezierPath within the shape node. For example,
let width:CGFloat = 250
let height:CGFloat = 400
shape.path = UIBezierPath(roundedRect: CGRect(x:-width/2, y: -height/2, width: width, height: height), cornerRadius: 64).cgPath
You can center the shape in the scene by setting its position to (0, 0).

Different CAAnimation on iphone 6 and iphone 5 with the same code

My problem is that I created simple path with CABasicAnimation from circle to roundedRect. I simply use two cashapelayers for each shape with uibezierpath, and because circle can be showed as roundedrect I used it twice and added them onto view, and it is working like a charm on iphone 6 and above, but strange things happen with edges when im trying launch it on iphone 5 and below (see gif). Im struggling with it almost 2 days and have no clue. Btw. red circle is a button and it's above those shapes. Here is code:
class Shapes: CAShapeLayer {
var circleLayer : UIBezierPath {
return UIBezierPath(roundedRect: CGRect(x: -(buttonWidth/2), y: -(buttonHeight/2), width: buttonWidth, height: buttonHeight), cornerRadius: 15)
}
var roundedRectLayer: UIBezierPath {
return UIBezierPath(roundedRect: CGRect(x: -(frame.width/2), y: -(frame.height/2), width: frame.width, height: frame.height), cornerRadius: 20)
}
func openMenu(){
let openMenu: CABasicAnimation = CABasicAnimation(keyPath: "path")
openMenu.fromValue = circleLayer.cgPath
openMenu.toValue = roundedRectLayer.cgPath
openMenu.duration = 0.4
openMenu.beginTime = CACurrentMediaTime() + 0.2
openMenu.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
openMenu.fillMode = kCAFillModeForwards
openMenu.isRemovedOnCompletion = false
add(openMenu, forKey: "openMenu")
}
}
and this is instantiated in another class of uiview which is that white rect below, this way:
let shapes = Shapes()
layer.addSublayer(shapes)
shapes.frame = CGRect(x: redButton.frame.midX, y: redButton.frame.midY, width: 320, height: redButton.frame.height + 10)
iPhone 5:
iPhone 6:

Add SKShapeNode in another one

I want to draw a square in another square; but I can't position the second one in the right way.
What can I do for positioning based on first square coordinates?
var tileBackground = SKShapeNode(rectOfSize: CGSize(width: screenWidth, height: screenWidth))
tileBackground.name = "tileBackground"
tileBackground.fillColor = SKColor.whiteColor()
tileBackground.position = CGPoint(x: frame.midX, y: frame.midY);
self.addChild(tileBackground)
var tile = SKShapeNode(rectOfSize: CGSize(width: tileSize, height: tileSize))
tile.name = "tile1"
tile.fillColor = SKColor.redColor()
tile.position = CGPointMake(100,100)
tileBackground.addChild(tile)
You should use SKSpriteNode instead. That way you can add it easily and without setting the position manually. Because in an SKSpriteNode, the center is always the center of the node:
var tileBackground = SKSpriteNode(color: UIColor.whiteColor(), size: CGSizeMake(width: screenWidth, height: screenWidth))
tileBackground.name = "tileBackground"
tileBackground.position = CGPoint(x: frame.midX, y: frame.midY);
self.addChild(tileBackground)
var tile = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: tileSize, height: tileSize))
tile.name = "tile1"
tileBackground.addChild(tile)
A node's position is relative to its parent position.
If you want the tile node to be in the center of its parent then set its position as:
tile.position = CGPoint(x: tileBackground.size.width / 2.0, y: tileBackground.size.height / 2.0)
which will place the tile in center of the tileBackground node.
also note that you do not need to update the tile position to "follow" the tileBackground node. Updating the tileBackground position will automatically keep the tile in its center