TheValyreanGroup
TheValyreanGroup

Reputation: 3599

Rounded rectangle with SKShapeNode

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)

Upvotes: 3

Views: 2425

Answers (1)

Ron Myschuk
Ron Myschuk

Reputation: 6061

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.

Upvotes: 4

Related Questions