Why is my SKShapeNode subclass not appearing when I run my app? - ios

I am trying to have objects fall from the top of the screen so I created a FallingBlock subclass that inherits from SKShapeNode. However, whenever I run my code, the blocks do not appear on the screen. I know these blocks are on screen because they come in contact with other objects, but they are unable to be seen. Below is my code:
import SpriteKit
class FallingBlock: SKShapeNode {
let color = [UIColor.red,UIColor.cyan,UIColor.green,UIColor.yellow,UIColor.orange,UIColor.systemPink].randomElement() as! UIColor
init(side: CGPoint) {
super.init()
self.path = CGPath(roundedRect: CGRect(x: side.x, y: side.y, width: 20, height: 20), cornerWidth: 3, cornerHeight: 3, transform: nil)
self.fillColor = color
self.strokeColor = color
self.position = side
self.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: 20))
self.physicsBody?.affectedByGravity = true
self.zPosition = 1
self.physicsBody?.categoryBitMask = PhysicsCategories.obstacleCategory
self.name = ObjectNames.obstacle
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("falling block reference deleted")
}
}
Within my GameScene I am running the following function:
func newObstacle() {
let side = [25,frame.width-25].randomElement() as! CGFloat
obstacle = FallingBlock(side: CGPoint(x: side, y: frame.maxY+15))
worldNode.addChild(obstacle)
deallocateObstacle()
}

I couldn't figure out a solution when the class inherits from an SKShapeNode; however, I was able to figure out how to do it when it inherits from an SKNode. All that is required is to initialize the SKNode and then add an SKShapeNode - with your desired attributes - as a child of the SKNode like what is done below:
class FallingBlock: SKNode {
init(side: CGPoint) {
super.init()
self.addNewBlock(side)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("falling block reference deleted")
}
private func addNewBlock(_ side: CGPoint) {
var color = [UIColor.red,UIColor.cyan,UIColor.green,UIColor.yellow,UIColor.orange,UIColor.systemPink].randomElement() as! UIColor
let obstacle = SKShapeNode(rectOf: CGSize(width: 20, height: 20), cornerRadius: 3)
obstacle.fillColor = color
obstacle.strokeColor = color
obstacle.position = side
obstacle.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: 20))
obstacle.physicsBody?.affectedByGravity = true
obstacle.zPosition = 1
obstacle.physicsBody?.categoryBitMask = PhysicsCategories.obstacleCategory
obstacle.name = ObjectNames.obstacle
self.addChild(obstacle)
}
}

Related

Creating a haptic-touch-like button in Swift/Obj-C

I've been playing around with an idea for a button which, when held, expands to reveal other buttons (like the FAB in Android). Without releasing, sliding one's finger down should highlight other buttons, similar to haptic touch's behaviour.
Here's a crude mockup I've created using Drama: https://youtu.be/Iam8Gjv3gqM.
There should also be an option to have a label horizontally next to each button, and the order of buttons should be as shown (with the selected button at the top instead of its usual position).
I already have a class for each button (seen below), but do not know how to achieve this layout/behaviour.
class iDUButton: UIButton {
init(image: UIImage? = nil) {
super.init(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
if let image = image {
setImage(image, for: .normal)
}
backgroundColor = .secondarySystemFill
layer.cornerRadius = 15
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
class iDUBadgeButton: iDUButton {
var badgeLabel = UILabel()
var badgeCount = 0
override init(image: UIImage? = nil) {
super.init(image: image)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
badgeLabel.textColor = .white
badgeLabel.backgroundColor = .systemRed
badgeLabel.textAlignment = .center
badgeLabel.font = .preferredFont(forTextStyle: .caption1)
badgeLabel.alpha = 0
updateBadge()
addSubview(badgeLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
badgeLabel.sizeToFit()
let height = max(18, badgeLabel.frame.height + 5.0)
let width = max(height, badgeLabel.frame.width + 10.0)
badgeLabel.frame = CGRect(x: frame.width - 5, y: -badgeLabel.frame.height / 2, width: width, height: height);
badgeLabel.layer.cornerRadius = badgeLabel.frame.height / 2;
badgeLabel.layer.masksToBounds = true;
}
func setBadgeCount(badgeCount: Int) {
self.badgeCount = badgeCount;
self.updateBadge()
}
func updateBadge() {
if badgeCount != 0 {
badgeLabel.text = "\(badgeCount)"
}
UIView.animate(withDuration: 0.25, animations: {
self.badgeLabel.alpha = self.badgeCount == 0 ? 0 : 1
})
layoutSubviews()
}
}
If anyone could help with achieving this layout, I'd be very grateful! Thanks in advance.
PS: I'm developing the UI in Swift, but will be converting it to Objective-C once complete. If you're interested, the use case is to adapt the button in the top-left of this to offer a selection of different tags.

How to create a SKSpriteNode with Custom GKComponent?

So im currently working on a game with spriteKit and gameplayKit. I've created a custom GKComponent class that I'm giving to certain SKSpriteNode via the scene inspector in Xcode (image). Now, when I want to re-create random SKSpritenodes with the same GKComponent, it doesn't seems to work because the overridden update method of my custom GKComponent is not working. Any clues?
I've tried to give the node a new entity object (since it was returning nil) and I gave that entity my custom GKComponent but nothing is working
// In scene
let node = SKSpriteNode(texture: "coin", color: UIColor.clear, size: CGSize(width: 60, height: 60))
let entities: GKEntity! = GKEntity()
let component: CollectableComponent = CollectableComponent()
component.itemType = 1
node.anchorPoint = CGPoint(x: 0.5, y: 0.5)
node.zPosition = 6
node.setScale(0.5)
node.size = CGSize(width: 60, height: 60)
node.position = CGPoint(x: 90, y: 90)
entities.addComponent(component)
node.entity = entities
self.scene?.addChild(node)
// GKComponent
class CustomComponent: GKComponent {
var node: SKSpriteNode?
#GKInspectable var itemType: Int = 0
#GKInspectable var isStatic: Bool = false
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func update(deltaTime seconds: TimeInterval) {
if node == nil {
if let nodeComponent = self.entity?.component(ofType: GKSKNodeComponent.self){
node = nodeComponent.node as? SKSpriteNode
}
}
}
}
Desired behaviour: if the nodeComponent variable in CustomComponent is not nil, I would like to make it run an animation like this:
node = nodeComponent.node as? SKSpriteNode; node.run(SKAction.moveBy(x: 0, y: 15, duration: 0.3));
but with the code, the action would not be running
This is the way you want to be doing this.
Take note that you need to retain your entities, so a global variable is needed.
Also, when you add a node to GKSKNodeComponent, the node.entity variable is set automatically.
class GameScene : SKScene{
var entities: [GKEntity] = [GKEntity]()
func doSomething(){
let node = SKSpriteNode(texture: "coin", color: UIColor.clear, size: CGSize(width: 60, height: 60))
node.anchorPoint = CGPoint(x: 0.5, y: 0.5)
node.zPosition = 6
node.setScale(0.5) //<--- YUCK!!!!!
node.size = CGSize(width: 60, height: 60)
node.position = CGPoint(x: 90, y: 90)
self.scene?.addChild(node)
let entity = GKEntity()
let nodeComponent : GKSKNodeComponent = GKSKNodeComponent(node:node)
let component: CollectableComponent = CollectableComponent()
component.itemType = 1
entity.addComponent(nodeComponent)
entity.addComponent(component)
entities.append(entity)
}
}
// GKComponent
class CustomComponent: GKComponent {
//We want this line to crash if node is empty, so do not use ?
lazy var node: SKSpriteNode! = {return self.entity!.component(ofType: GKSKNodeComponent.self).node as? SKSpriteNode! }
#GKInspectable var itemType: Int = 0
#GKInspectable var isStatic: Bool = false
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func update(deltaTime seconds: TimeInterval) {
}
}

Swift: UILabel has weird outlines?

Ok, I have never experienced this but don't know how to make it go away: I have programmatically created several UILabels that have a certain background color. On some, however, on the upper and right borders this a weird darker line visible (see 2nd row of labels):
I don't know what is causing this.
Here is how I made them:
for i in 0...featureLabels.count-1
{
featureLabels[i] = RSSLinkLabel()
featureLabels[i]?.frame = CGRect(x: 0, y: 0, width: (overallWidth-(imgSpace))/3, height: ((overallWidth-(imgSpace))/3)/contentRatios[2])
featureLabels[i]?.text = "Hello label \(i)"
featureLabels[i]?.backgroundColor = UIColor(hexString: secondColorStr)
featureLabels[i]?.textAlignment = .left
featureLabels[i]?.font = iphoneFont
var pt = CGPoint()
switch i {
case 0:
pt = CGPoint(x: sideDiff + ((featureLabels[i]?.bounds.width)!/2), y: subContView2.bounds.height - ((featureLabels[i]?.bounds.height)!/2))
case 1:
pt = CGPoint(x: subContView2.bounds.width/2, y: subContView2.bounds.height - ((featureLabels[i]?.bounds.height)!/2))
case 2:
pt = CGPoint(x: sideDiff + (overallWidth - ((featureLabels[i]?.bounds.width)!/2)), y: subContView2.bounds.height - ((featureLabels[i]?.bounds.height)!/2))
default:
break
}
featureLabels[i]?.center = pt
subContView2.addSubview(featureLabels[i]!)
}
They are custom labels that I have subclassed here:
import UIKit
class RSSLinkLabel: UILabel {
var id = String()
override init (frame : CGRect) {
super.init(frame : frame)
}
func customInit()
{
self.text = txtSources[id]
}
required public init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
convenience init () {
self.init(frame:CGRect.zero)
}
override public func layoutSubviews() {
customInit()
}
}
I have tried doing featureLabels[i]?.layer.borderColor = UIColor.clear.cgColor but nothing happens.
How can I make that outline go away?

Subview of Custom UIView has wrong x frame position

I have a custom UIView like so:
import UIKit
class MainLogoAnimationView: UIView {
#IBOutlet var customView: UIView!
var turquoiseCircle: AnimationCircleView!
var redCircle: AnimationCircleView!
var blueCircle: AnimationCircleView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
_ = Bundle.main.loadNibNamed("MainLogoAnimationView", owner: self, options: nil)?[0] as! UIView
self.addSubview(customView)
customView.frame = self.bounds
setupAnimation()
}
let initialWidthScaleFactor: CGFloat = 0.06
func setupAnimation() {
let circleWidth = frame.size.width*initialWidthScaleFactor
let yPos = frame.size.height/2 - circleWidth/2
turquoiseCircle = AnimationCircleView(frame: CGRect(x: 0, y: yPos, width: circleWidth, height: circleWidth))
turquoiseCircle.circleColor = UIColor(red: 137/255.0, green: 203/255.0, blue: 225/255.0, alpha: 1.0).cgColor
turquoiseCircle.backgroundColor = UIColor.lightGray
addSubview(turquoiseCircle)
redCircle = AnimationCircleView(frame: CGRect(x: 100, y: yPos, width: circleWidth, height: circleWidth))
addSubview(redCircle)
blueCircle = AnimationCircleView(frame: CGRect(x: 200, y: yPos, width: circleWidth, height: circleWidth))
blueCircle.circleColor = UIColor(red: 93/255.0, green: 165/255.0, blue: 213/255.0, alpha: 1.0).cgColor
addSubview(blueCircle)
}
}
I created a light blue colored UIView in interface builder and set the above view MainLogoAnimationView as its subclass. If I run the app I get this:
It seems that for the turquoiseCircle the frame hasn't been placed at x = 0 as I set it. Not sure what I am doing wrong here. Is it because it is placed before the MainLogoAnimationView is fully initialized so being placed incorrectly? I have tried setting the frames of the circles in layoutSubviews() and this also didn't make any difference. I seem to run into the problem a lot with views being misplaced and think I am missing something fundamental here. What am I doing wrong?
UPDATE:
import UIKit
class MainLogoAnimationView: UIView {
#IBOutlet var customView: UIView!
var turquoiseCircle: AnimationCircleView!
var redCircle: AnimationCircleView!
var blueCircle: AnimationCircleView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
_ = Bundle.main.loadNibNamed("MainLogoAnimationView", owner: self, options: nil)?[0] as! UIView
self.addSubview(customView)
customView.frame = self.bounds
setupAnimation()
}
let initialWidthScaleFactor: CGFloat = 0.2
func setupAnimation() {
blueCircle = AnimationCircleView()
blueCircle.translatesAutoresizingMaskIntoConstraints = false
blueCircle.backgroundColor = UIColor.lightGray
blueCircle.circleColor = UIColor.blue.cgColor
addSubview(blueCircle)
redCircle = AnimationCircleView()
redCircle.translatesAutoresizingMaskIntoConstraints = false
redCircle.backgroundColor = UIColor.lightGray
addSubview(redCircle)
}
override func layoutSubviews() {
super.layoutSubviews()
let circleWidth = bounds.size.width*initialWidthScaleFactor
let yPos = frame.size.height/2 - circleWidth/2
blueCircle.frame = CGRect(x: 0, y: yPos, width: circleWidth, height: circleWidth)
redCircle.frame = CGRect(x: frame.size.width/2 - circleWidth/2, y: yPos, width: circleWidth, height: circleWidth)
}
}
and :
import UIKit
class AnimationCircleView: UIView {
var circleColor = UIColor(red: 226/255.0, green: 131/255.0, blue: 125/255.0, alpha: 1.0).cgColor {
didSet {
shapeLayer.fillColor = circleColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
let shapeLayer = CAShapeLayer()
func setup() {
let circlePath = UIBezierPath(ovalIn: self.bounds)
shapeLayer.path = circlePath.cgPath
shapeLayer.fillColor = circleColor
shapeLayer.frame = self.bounds
layer.addSublayer(shapeLayer)
}
}
It's definitely the case that the setting up in init will provide self.frame/self.bounds values for whatever the view's initialized values are and not the correct final values when laid out in the view hierarchy. Probably the best way to handle this is like you tried, to add the views in init, and use property to keep a reference to them (you already are doing this) and then set the frame in -layoutSubviews().
The reason it probably didn't work in -layoutSubviews() is for each view after creation you need to call .translatesAutoresizingMaskIntoConstraints = false. Otherwise with programmatically-created views the system will create constraints for the values when added as a subview, overriding any setting of frames. Try setting that and see how it goes.
EDIT: For resizing the CAShapeLayer, instead of adding as a sublayer (which would require manually resizing) since AnimationCirleView view is specialized the view can be backed by CAShapeLayer. See the answer at Overriding default layer type in UIView in swift

SpriteKit - Adding enemy causes frame drop

My game is a side scroller using SpriteKit. The player stays at the same x-position and the obstacles are moving towards him. But whenever I add an enemy the frame rate drops for a short moment. I tried to change many things, like removing the enemy's dynamics or animation. But nothing works.
Here's some code:
func addZombie() {
let zombie = Zombie()
zombie.position = CGPoint(x: screenWidth + zombie.size.width * 0.5, y: screenHeight * 0.5)
zombies.append(zombie)
addChild(zombie)
}
The above method creates the enemies. It's called by using:
func generateZombies() {
let wait = SKAction.waitForDuration(2.5)
let run = SKAction.runBlock {
self.addZombie()
}
runAction(SKAction.repeatActionForever(SKAction.sequence([wait, run])))
}
I also tried NSTimer, but nothing works. Please help me.
Zombie():
class Zombie: SKSpriteNode {
var textureArrayWalking = [SKTexture]()
init() {
let mySize = CGSize(width: Player().size.width, height: Player().size.height * (4/3))
super.init(texture: nil, color: UIColor.greenColor(), size: mySize)
orderTextureAtlases()
texture = textureArrayWalking[0]
zPosition = 1
physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: (2/3) * self.size.width, height: self.size.height))
physicsBody?.restitution = 0.0
physicsBody?.categoryBitMask = enemyCategory
physicsBody?.collisionBitMask = groundCategory
physicsBody?.contactTestBitMask = playerCategory | bulletCategory | groundCategory
physicsBody!.allowsRotation = false;
walk()
}
func walk() {
runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(textureArrayWalking, timePerFrame: 0.07)), withKey: walkingActionKeyZombie)
}
func orderTextureAtlases() {
let textureAtlasWalking = SKTextureAtlas(named: "ZombieWalking")
for index in 0..<textureAtlasWalking.textureNames.count {
let name = "ZombieWalking_JeromeWalk_\(index)"
let tex = SKTexture(imageNamed: name)
tex.filteringMode = .Nearest
textureArrayWalking.append(tex)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Resources