Swift SKShapeNode not accepting initialiser - swift

I can't get the a sub class of SKShapeNode to accept an initialiser. To work around this I've tried to go by the example posted here Adding Convenience Initializers in Swift Subclass. The code i'm using is below.
class Ground1 : SKShapeNode {
override init() {
super.init()
print("Hello1")
}
convenience init(width: CGFloat, point: CGPoint) {
var points = [CGPoint(x: -900, y: -300),
CGPoint(x: -600, y: -100),
CGPoint(x: -100, y: -300),
CGPoint(x: 2, y: 150),
CGPoint(x: 100, y: -300),
CGPoint(x: 600, y: -100),
CGPoint(x: 900, y: -300)]
self.init(splinePoints: &points, count: points.count)
lineWidth = 5
strokeColor = UIColor.orange
physicsBody = SKPhysicsBody(edgeChainFrom: path!)
physicsBody?.restitution = 0.75
physicsBody?.isDynamic = false
print("Hello2")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
When initialised the convenience init() is ignored and not used. Resulting in no shape being added to the scene. What am I doing wrong ?

Thanks to 0x141E, who roundabout pointed me in the right direction. I found this worked but the code looks messy. The reasons for going about the initialisation this way is explained in detail here Adding Convenience Initializers in Swift Subclass. Although I was thrown out of finding a solution because of the use of splinePoints and assigning to a path.
class Ground1 : SKShapeNode {
override init() {
super.init()
}
convenience init(name: String) {
var points = [CGPoint(x: -900, y: -300),
CGPoint(x: -600, y: -100),
CGPoint(x: -100, y: -300),
CGPoint(x: 2, y: 150),
CGPoint(x: 100, y: -300),
CGPoint(x: 600, y: -100),
CGPoint(x: 900, y: -300)]
self.init(splinePoints: &points, count: points.count)
lineWidth = 5
strokeColor = UIColor.orange
physicsBody = SKPhysicsBody(edgeChainFrom: self.path!)
physicsBody?.restitution = 0.75
physicsBody?.isDynamic = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Related

How to sort correct CAShapeLayers in UIView and child button?

I create a parent view with CAShapeLayer, and create a child button with CAShapeLayer.
class TestButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: CGPoint(x: 60, y: 100))
path.addLine(to: CGPoint(x: 0, y: 100))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 100, startAngle: .pi, endAngle: .pi*1.5, clockwise: true)
path.addLine(to: CGPoint(x: 100, y: 60))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 40, startAngle: .pi*1.5, endAngle: .pi, clockwise: false)
path.close()
let drawLayer = CAShapeLayer.init()
drawLayer.path = self.path.cgPath
drawLayer.fillColor = UIColor.green.cgColor
self.layer.addSublayer(drawLayer)
}
}
class TestView:UIView{
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: rect.height-20))
path.addLine(to: CGPoint(x: rect.width/2, y: rect.height))
path.addLine(to: CGPoint(x: rect.width, y: rect.height-20))
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
let drawLayer = CAShapeLayer.init()
drawLayer.path = self.path.cgPath
drawLayer.fillColor = UIColor.red.cgColor
self.layer.addSublayer(drawLayer)
}
}
createView
func createView() {
let announce = TestAnnotationview.init(frame: CGRect.init(x: 50, y: 400, width: 150, height: 150))
announce.backgroundColor = .blue
self.view.addSubview(announce)
let btn = TestButton2.init(frame: CGRect.init(x: 0, y: 0, width: 150, height: 150))
btn.backgroundColor = .yellow
announce.addSubview(btn)
}
It run at simular like this, parent view's CAShapeLayers cover its child button CAShapeLayers.
but it show in Debug View Hierarchy like this.
How do i sort them like Debug View Hierarchy?
Thanks for answer!!
Don't create and add a new sublayer in draw(_ rect:). You don't control how often that method is called, and you are adding a new sublayer every time it is.
Overriding drawRect is generally not a great idea, since it can affect the drawing of other parts of the view and performance during animations.
Create and add the sublayer once, in your init method, for example, and position it if you need to in layoutSubviews. Don't override draw(_ rect:).

Slant one edge of rectangle UIBezierPath

I'm trying to slant one edge of a UIBezierPath in Swift. I believe you do this by using move on one of the edges. Similar to the below -
let offset: CGFloat = 60.0;
let path = UIBezierPath()
let width = self.bounds.width - offset
let upperLeftPoint = CGPoint(x: self.bounds.origin.x + width + offset, y: self.bounds.origin.y)
let upperRightPoint = CGPoint(x: self.bounds.origin.x, y: self.bounds.origin.y)
let lowerRightPoint = CGPoint(x: self.bounds.origin.x, y: self.bounds.size.height)
let lowerLeftPoint = CGPoint(x: width - offset, y: self.bounds.size.height)
path.move(to: upperLeftPoint)
path.addLine(to: upperRightPoint)
path.addLine(to: lowerRightPoint)
path.addLine(to: lowerLeftPoint)
path.addLine(to: upperLeftPoint)
// Close the path. This will create the last line automatically.
path.close()
UIColor.red.setFill()
path.fill()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
self.layer.mask = shapeLayer;
However this isn't quite what I'm trying to achieve. Below is what I'm attempting to achieve.
I achieved this shape by doing the following -
class Header: UIView {
var path: UIBezierPath!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func createHeader() {
// Drawing code
// Get Height and Width
let layerHeight = layer.frame.height
let layerWidth = layer.frame.width
// Create Path
let bezierPath = UIBezierPath()
// Points
let pointA = CGPoint(x: 0, y: 0)
let pointB = CGPoint(x: layerWidth, y: 0)
let pointC = CGPoint(x: layerWidth, y: layerHeight*2/3)
let pointD = CGPoint(x: 0, y: layerHeight)
// Draw the path
bezierPath.move(to: pointA)
bezierPath.addLine(to: pointB)
bezierPath.addLine(to: pointC)
bezierPath.addLine(to: pointD)
bezierPath.close()
// Mask to Path
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
layer.mask = shapeLayer
}
override func draw(_ rect: CGRect) {
// self.createRectangle()
self.createHeader()
}
}

Why is SKSpriteNode not working with SKAction?

I'm working on a project and tried to reset the position with an SKAction, but the SKSpriteNode didn't move! Can anyone help?
import SpriteKit
import Foundation
enum Difficulty: TimeInterval {
case easy = 8.5
case normal = 6.0
case hard = 4.0
case extraHard = 2.0
}
class Note: SKSpriteNode {
init() {
super.init(texture: SKTexture(imageNamed: "dropNotes"), color: .clear, size: CGSize(width: 50, height: 50))
self.position = CGPoint(x: 375, y: 999)
}
func fall(difficulty: Difficulty) {
let fallAction = SKAction.move(
to: CGPoint(
x: 375,
y: 180
),
duration: difficulty.rawValue
)
let resetAction = SKAction.move(to: CGPoint(x: 375, y: 700), duration: 0.01)
run(fallAction)
run(resetAction)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Does anyone know why this happpened? Please answer if you do!
The issue
Look at this part of your code
run(fallAction)
run(resetAction)
You are running these 2 actions at the same time. Instead I guess you want to run the fallAction and then, AFTER it finishes, the resetAction right?
The fix
In this case you need to create a sequence and run it.
let sequence = SKAction.sequence([fallAction, resetAction])
run(sequence)
Full code
Here's the full fall method updated
func fall(difficulty: Difficulty) {
let fallAction = SKAction.move(
to: CGPoint(
x: 375,
y: 180
),
duration: difficulty.rawValue
)
let resetAction = SKAction.move(to: CGPoint(x: 375, y: 700), duration: 0.01)
let sequence = SKAction.sequence([fallAction, resetAction])
run(sequence)
}

How to add curved view top left cornor in swift

How to add uiview to top-left corner with curves.
I have done somethings like this
class curvedView: UIView {
override func draw(_ rect: CGRect) {
let color = UIColor.blue
let y:CGFloat = 0
let myBezier = UIBezierPath()
myBezier.move(to: CGPoint(x: 0, y: y))
myBezier.addQuadCurve(to: CGPoint(x: rect.width, y: y), controlPoint: CGPoint(x: rect.width / 2, y: rect.height * 2))
myBezier.addLine(to: CGPoint(x: rect.width, y: rect.height))
myBezier.addLine(to: CGPoint(x: 0, y: rect.height))
myBezier.close()
color.setFill()
myBezier.fill()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.clear
}
}
But this create a curve in U shape but i want curve from x position 180 to Y position 250.
Any help would be so thankful.
you can try these codes.it will help you
let path = UIBezierPath(roundedRect:viewToRound.bounds, byRoundingCorners:[.TopRight, .BottomLeft], cornerRadii: CGSizeMake(20, 20))
let maskLayer = CAShapeLayer()
maskLayer.path = path.CGPath
viewToRound.layer.mask = maskLayer

Add gradient color to a custom UIView

I realized a custom UIView class to a realize a sort of trapeze; following code I had used to realize the class:
import UIKit
import ChameleonFramework
class PolygonalView: UIView
{
override init(frame: CGRect)
{
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.beginPath()
context.move(to: CGPoint(x: rect.minX, y: rect.minY))
context.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
context.addLine(to: CGPoint(x: rect.maxX, y: 171))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
context.closePath()
context.setFillColor(FlatSkyBlue().cgColor)
context.fillPath()
}
}
Then in my UIViewController I had create my view:
let pol = PolygonalView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 196))
pol.backgroundColor = UIColor.clear
self.view.addSubview(pol)
I achieved my goal, yep! But a problem has arisen: if I set a gradient, this gradient cover all the rectangular frame of the view.
Following codes and image...
let gradientColor = CAGradientLayer()
gradientColor.frame = pol.bounds
gradientColor.colors = [FlatSkyBlue().cgColor, FlatSkyBlueDark().cgColor, FlatBlue().cgColor]
gradientColor.startPoint = CGPoint(x: 0, y: 0)
gradientColor.endPoint = CGPoint(x: 1, y: 1)
pol.layer.addSublayer(gradientColor)
Any solutions to adapt the gradient to the real shape of my View?
Set the gradientColor layer's mask property to CAShapeLayer() that has the correct shape filled with black
let mask = CAShapeLayer()
mask.frame = // to whatever size it should be
mask.path = yourShapeBezierHERE.cgPath
mask.fillColor = UIColor.black.cgColor
gradientColor.mask = mask