SpriteKit device orientation change causes SKLabelNode distortion Swift - ios

I am using SpriteKit to write a game for iOS in Swift. I am still fairly new to SpriteKit.
I would like to support both orientations for both iPhone and iPad, and have found this:
multiple orientations in SpriteKit
This works as expected in the simulator and device, however on device I notice some SKSpriteNodes distort to their new size slightly before the device rotation animation.
This is very noticeable especially with SKLabelNodes where the text distorts either slightly squashed or stretched depending on the orientation change.
I have an idea why the distortion is happening, but confirmation and a fix would be fantastic.
This occurs on device with the code described in the link, but I have updated for swift 3
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size:self.view.bounds.size)
scene.scaleMode = .resizeFill
(self.view as! SKView).presentScene(scene)
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override var prefersStatusBarHidden: Bool {
return true
}
}
class GameScene: SKScene {
var currentNode: CustomNode!
override func didMove(to view: SKView) {
self.backgroundColor = SKColor.white
transitionToScene(sceneType: .Menu)
}
override func didChangeSize(_ oldSize: CGSize) {
currentNode?.layout()
}
func transitionToScene(sceneType: SceneTransition) {
switch sceneType {
case .Menu:
currentNode?.dismissWithAnimation(animation: .Right)
currentNode = MenuNode(gameScene: self)
currentNode.presentWithAnimation(animation: .Right)
case .Scores:
currentNode?.dismissWithAnimation(animation: .Left)
currentNode = ScoresNode(gameScene: self)
currentNode.presentWithAnimation(animation: .Left)
default: fatalError("Unknown scene transition.")
}
}
}
class CustomNode: SKNode {
weak var gameScene: GameScene!
init(gameScene: GameScene) {
self.gameScene = gameScene
super.init()
}
func layout() {}
func presentWithAnimation(animation:Animation) {
layout()
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: invert*gameScene.size.width, y: 0)
gameScene.addChild(self)
let action = SKAction.move(to: CGPoint(x: 0, y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.easeInEaseOut
self.run(action)
}
func dismissWithAnimation(animation:Animation) {
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: 0, y: 0)
let action = SKAction.move(to: CGPoint(x: invert*(-gameScene.size.width), y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.easeInEaseOut
self.run(action, completion: {self.removeFromParent()})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MenuNode: CustomNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Menu Scene")
label.horizontalAlignmentMode = .center
label.verticalAlignmentMode = .center
container = SKSpriteNode(color: UIColor.black, size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.gameScene.transitionToScene(sceneType: .Scores)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ScoresNode: CustomNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Scores Scene")
label.horizontalAlignmentMode = .center
label.verticalAlignmentMode = .center
container = SKSpriteNode(color: UIColor.black, size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.gameScene.transitionToScene(sceneType: .Menu)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
enum SceneTransition{
case Menu, Scores
}
enum Animation {
case Left, Right, None
}
credit: Epic Byte
I have also tried using
viewWillTransitionToSize...
as this handles device orientation changes, I see that didChangeSize... is called multiple times in a device rotation therefore I prefer the viewWillTransitionToSize...
Thanks in advance.
Leon

Go into your Storyboard file, Select your ViewController's view and on the right hand bar, look for the slider image, it should be the 4th one from the left. This is called the attributes inspector. Change Content Mode to Center. This will give you black bars but stop the squishing.

This was the code created from the suggestion by #Knight0fDragon, producing SKLabelNodes that do not squash or stretch when rotated with scaleMode .resizeFill. Note the size of the view in this is for an iPad with 1024x768 resolution, creating a square skview of size 1024x1024. This would be easy to replicate with the largest value either width or height when the app loads depending on the orientation.
class GameViewController: UIViewController {
var scene : GameScene!
var newView: SKView!
override func viewDidLoad() {
super.viewDidLoad()
self.newView = SKView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 1024, height: 1024)))
(self.view as? SKView)?.contentMode = .center
self.newView.contentMode = .center
self.newView.autoresizesSubviews = false
self.newView.autoresizingMask = [.flexibleBottomMargin, .flexibleTopMargin, .flexibleLeftMargin, .flexibleRightMargin]
(self.view as? SKView)?.addSubview(newView)
self.newView.center = view.center
self.scene = GameScene(size: CGSize(width: 1024, height: 1024))
self.scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.scene.scaleMode = .resizeFill
self.newView.presentScene(scene)
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override var prefersStatusBarHidden: Bool {
return true
}
}
class GameScene: SKScene {
var labelNode: SKLabelNode!
var container: SKSpriteNode!
override func didMove(to view: SKView) {
self.backgroundColor = SKColor.darkGray
container = SKSpriteNode(color: .black, size: CGSize(width: 300, height: 300))
self.addChild(container)
labelNode = SKLabelNode(text: "hello")
labelNode.fontColor = .white
container.addChild(labelNode)
}
}

Related

Can a UIView assign a UITapGestureRecognizer to itself?

I'm trying to make a UIView that recognizes tap gestures, however taps are not ever correctly registered by the UIView. Here's the code for the UIView subclass itself:
import UIKit
class ActionCell: SignalTableCell, UIGestureRecognizerDelegate {
var icon: UIImageView!
var actionType: UILabel!
var actionTitle: UILabel!
var a:Action?
var tap:UITapGestureRecognizer?
required init(frame: CGRect) {
//
super.init(frame:frame)
tap = UITapGestureRecognizer(target: self, action: #selector(self.touchTapped(_:)))
tap?.delegate = self
addGestureRecognizer(tap!)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
tap = UITapGestureRecognizer(target: self, action: #selector(self.touchTapped(_:)))
tap?.delegate = self
addGestureRecognizer(tap!)
}
#objc func touchTapped(_ sender: UITapGestureRecognizer) {
print("OK")
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.isUserInteractionEnabled = true
}
override func layoutSubviews() {
if(icon == nil) {
let rect = CGRect(origin: CGPoint(x: 10,y :20), size: CGSize(width: 64, height: 64))
icon = UIImageView(frame: rect)
addSubview(icon)
}
icon.image = UIImage(named:(a?.icon)!)
if(actionType == nil) {
let rect = CGRect(origin: CGPoint(x: 100,y :20), size: CGSize(width: 200, height: 16))
actionType = UILabel(frame: rect)
addSubview(actionType)
}
actionType.text = a.type
if(actionTitle == nil) {
let rect = CGRect(origin: CGPoint(x: 100,y :80), size: CGSize(width: 200, height: 16))
actionTitle = UILabel(frame: rect)
addSubview(actionTitle)
}
actionTitle.text = a?.title
}
func configure( a:Action ) {
self.a = a
}
override func setData( type:SignalData ) {
a = (type as! Action)
}
}
I'm simply trying to make it so that this UIView can, you know, know when it's tapped. Is this possible without adding a separate UIViewController? This seems as though it should be fairly simple but it doesn't appear to be, confusingly.
I've stepped through the code and the init method is called and the Gesture Recognizer is added but it doesn't trigger.
If its a table view cell, I'd recommend not using a tap gesture, since it might interfere with the didSelectRowAtIndexPath: and other delegate methods. But if you still wanna keep the tap gesture, try adding tap?.cancelsTouchesInView = false before addGestureRecognizer(tap!) and see if that works.
If you just want to know when its tapped, you could also override the following UIResponder method:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// do your stuff
}
I think, it is easier to override touchesBegan method. Something like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched")
super.touchesBegan(touches, with: event)
}
Most likely the actionType, ActionTitle, and icon are being tapped and the tap is not falling through because user interaction is disabled by default for labels and images. Set isUserInteractionEnabled = true for each of those fields that are subviews of the main view.
override func layoutSubviews() {
if(icon == nil) {
let rect = CGRect(origin: CGPoint(x: 10,y :20), size: CGSize(width: 64, height: 64))
icon = UIImageView(frame: rect)
icon.isUserInteractionEnabled = true
addSubview(icon)
}
icon.image = UIImage(named:(a?.icon)!)
if(actionType == nil) {
let rect = CGRect(origin: CGPoint(x: 100,y :20), size: CGSize(width: 200, height: 16))
actionType = UILabel(frame: rect)
actionType.isUserInteractionEnabled = true
addSubview(actionType)
}
actionType.text = a.type
if(actionTitle == nil) {
let rect = CGRect(origin: CGPoint(x: 100,y :80), size: CGSize(width: 200, height: 16))
actionTitle = UILabel(frame: rect)
actionTitle.isUserInteractionEnabled = true
addSubview(actionTitle)
}
actionTitle.text = a?.title
}

Can't touch UIImageView that is moving in Swift

I made drop tile game that tiles fall from the top of screen to one's bottom.
This game system is when you touch a tile, the tile will be hidden.
The tiles are custom class (GameTile class), but Touches Began in GameViewController didn't work.
How can I solve it?
GameTile.swift
class GameTile: UIImageView {
init(named: String, frame: CGRect) {
super.init(frame: frame)
super.image = (UIImage(named: named))
super.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameTileNormal: GameTile {
let namedDefault: String
var frameDefault: CGRect
let isHiddenDefault: Bool
var isUserInteractionEnabledDefault: Bool
let colorName: UIColor
init(
named: String,
frame: CGRect,
isHidden: Bool = false,
isUserInteractionEnabled: Bool = true,
color: UIColor = UIColor.blue) {
namedDefault = named
isHiddenDefault = isHidden
frameDefault = frame
isUserInteractionEnabledDefault = isUserInteractionEnabled
colorName = color
super.init(named: named, frame: frame)
super.isHidden = isHiddenDefault
super.isUserInteractionEnabled = isUserInteractionEnabledDefault
super.backgroundColor = colorName
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
GameView.swift
class GameView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = true
self.backgroundColor = (UIColor.white)
self.frame = CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
//make tiles
let tileNormal = GameTileNormal.init(named: "clear",
frame: CGRect(x:0), y:-60, width:60, height:60),isUserInteractionEnabled: true)
self.addSubview(tileNormal)
//move tiles
moveTile(tile: tileNormal, lane: 1)
}
}
func moveTile(tile: GameTile, lane: Int) {
UIImageView.animate(withDuration: TimeInterval(2.0),
delay: 0.0,
options: .curveLinear,
animations: {
tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
tile.removeFromSuperview()
//make new tile
self.makeTiles(lane: lane)
})
}
GameViewController.swift
class GameViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.isUserInteractionEnabled = true
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(trapView)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
if let gameView = self.gameView {
// touchEvent.view is "gameView", not the view whose kind of class is GameTileNormal...
if let touchedGameTile = touchEvent.view as? GameTileNormal {
print("Touched normal tile")
touchEvent.view?.isHidden = true
touchEvent.view?.isUserInteractionEnabled = false
}else{
// other view
}
}
}
UPDATE
I changed how to move tiles from UIImageView.animation to Timer.
Then If I touched tiles, it didn't through after if (tile.layer.presentation()?.hitTest(location)) != nil { in touchesBegan, GameViewController.....
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
let location = touchEvent.location(in: touchEvent.view)
if let standardView = self.standardView {
for tile in standardView.tiles {
//breakpoint stops here
if (tile.layer.presentation()?.hitTest(location)) != nil {
//breakpoint doesn't through here
if tile is GameTileNormal {
//normal tile touched
}else{
}
break
}
}
}
}
moveTiles
makeTileTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(updateTilesPositionY(timer:)), userInfo: sendArray, repeats: true)
update tile position (drop tiles)
#objc func updateTilesPositionY(timer: Timer){
//tile info
let timerInfo:[Any] = timer.userInfo as! [Any]
let tile:GameTile = timerInfo[0] as! GameTile
let lane: Int = timerInfo[1] as! Int
//drop tile
tile.frame.origin.y = tile.frame.origin.y+1
//if tile reached on the bottom
if tile.frame.origin.y >= UIScreen.main.bounds.size.height {
if tile is GameTileNormal {
self.showGameOverView()
}
}else{
//drop tile
}
In UIImageView.animate add option .allowUserInteraction:
UIImageView.animate(withDuration: TimeInterval(2.0),
delay: 0.0,
options: [.curveLinear, .allowUserInteraction],
animations: {
tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
...
By default the user interaction is disallowed during animations.
UPDATE
However, to test whether the user hit a moving object, you will have a bit harder time. See for example this SO question. Basically, the UIView object does not really move, you can easily test that after firing the animation, the frame of the animated object is set straight to the end position. Just the presentation layer draws the moving view.
You will have to always go over all your moving tiles in the game and test each one if any of them has been touched (here I assume you have a reference to all the tiles in the game):
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
let location = touchEvent.location(in: touchEvent.view)
if let gameView = self.gameView {
for tile in tiles {
// go over all the moving objects in your scene and hit test all of them
if let touchedLayer = tile.layer.presentation()?.hitTest(location) {
// if a hittest returns a layer, it means that this tile was touched, we can handle it and break out of the loop
tile.isHidden = true
tile.isUserInteractionEnabled = false
tile.removeFromSuperview()
break
}
}
}
}
Use layer.presentationLayer to run a hitTest if that hitTest return a CALayer then you are touching that titleView, in fact this will only work if your titles are userInteractionEnabled = false
Full Code
import UIKit
class GameTile: UIImageView {
init(named: String, frame: CGRect) {
super.init(frame: frame)
super.image = (UIImage(named: named))
super.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameTileNormal: GameTile {
let namedDefault: String
var frameDefault: CGRect
let isHiddenDefault: Bool
var isUserInteractionEnabledDefault: Bool
let colorName: UIColor
init(
named: String,
frame: CGRect,
isHidden: Bool = false,
isUserInteractionEnabled: Bool = false,
color: UIColor = UIColor.blue) {
namedDefault = named
isHiddenDefault = isHidden
frameDefault = frame
isUserInteractionEnabledDefault = isUserInteractionEnabled
colorName = color
super.init(named: named, frame: frame)
super.isHidden = isHiddenDefault
super.isUserInteractionEnabled = isUserInteractionEnabledDefault
super.backgroundColor = colorName
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = true
self.backgroundColor = (UIColor.white)
self.frame = CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
//make tiles
let tileNormal = GameTileNormal.init(named: "clear",
frame: CGRect(x:0, y:-60, width:60, height:60),isUserInteractionEnabled: false)
self.addSubview(tileNormal)
//move tiles
moveTile(tile: tileNormal, lane: 1)
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.blue.cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveTile(tile: GameTile, lane: Int) {
UIImageView.animate(withDuration: TimeInterval(10),
delay: 0.0,
options: .curveLinear,
animations: {
tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
tile.removeFromSuperview()
//make new tile
//self.makeTiles(lane: lane)
})
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
let location = touchEvent.location(in: touchEvent.view)
for tile in self.subviews {
// go over all the moving objects in your scene and hit test all of them
if tile.layer.presentation()?.hitTest(location) != nil {
// if a hittest returns a layer, it means that this tile was touched, we can handle it and break out of the loop
tile.isHidden = true
break
}
}
}
}
class ViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.isUserInteractionEnabled = true
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(gameView)
}
}

How to recognise which image was touched

I am developing an application which the user will be able to drag and drop items on a canvas and when he releases the image it is drawn on the canvas.
This is my DragImage class which handle the touches:
class DragImages: UIImageView {
var originalPos : CGPoint!
var dropTarget: UIView?
override init (frame : CGRect){
super.init(frame: frame)
}
required init?(coder aDecoder : NSCoder){
super.init(coder : aDecoder)
}
override func touchesBegan(_ touches : Set<UITouch>,with event: UIEvent?){
originalPos = self.center
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let position = touch.location(in: self.superview)
self.center = CGPoint(x : position.x, y : position.y)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let target = dropTarget{
let position = touch.location(in: self.superview)
if target.frame.contains(position){
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "onTargetDropped"), object: nil))
}else {
self.center = originalPos
}
}
print(self.center.x, self.center.y)
self.center = originalPos
}
func getEndPosX() -> CGFloat{
return self.center.x
}
func getEndPosY() -> CGFloat {
return self.center.y
}
}
In my ViewController class I added this piece of code to handle the touches etc:
ornament1.dropTarget = xmasTree
ornament2.dropTarget = xmasTree
ornament3.dropTarget = xmasTree
ornament4.dropTarget = xmasTree
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.itemDroppedOnTree(_:)), name: NSNotification.Name(rawValue: "onTargetDropped"), object: nil)
}
func itemDroppedOnTree(_ notif : AnyObject){
}
I managed to get the X and Y position when the image is dragged on the canvas but i cant find a way to recognise which of the 4 images is being dropped in order for me to draw that specific one!
You could add the sender to your notification (and also the position):
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "onTargetDropped"), object: self, userInfo: ["position":position]))
and get it later in itemDroppedOnTree:
func itemDroppedOnTree(_ notif : NSNotification){
let position = notif.userInfo["position"]
let sender = notif.object as! DragImage
if sender === dragImage1 {
//...
} else if sender === dragImage2 {
//...
}
}
I recommend against it though and plead to use a delegate to inform the ViewController instead. (Opinion based: In general, use Notifications for to-many broadcasts only.)
The delegate function should have the sender as first parameter. According to func tableView: tableView:UITableView, cellForRowAt indexPath:IndexPath).
This way you know which image is sending its new position and can compare it to your property like in the above example:
if dragImage === dragImage1 {...
Your code plus working delegate to paste to Playground:
import UIKit
import PlaygroundSupport
protocol DragImageDelegate: class {
func dragimage(_ dragImage:DragImage, didDropAt position:CGPoint)
}
class DragImage: UIImageView {
weak var delegate: DragImageDelegate?
var originalPos : CGPoint!
var dropTarget: UIView?
override init (frame : CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches : Set<UITouch>,with event: UIEvent?){
originalPos = self.center
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let position = touch.location(in: self.superview)
self.center = CGPoint(x : position.x, y : position.y)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let target = dropTarget {
let position = touch.location(in: self.superview)
if target.frame.contains(position){
print(self.center.x, self.center.y)
guard let delegate = self.delegate else {
print("delegate not set")
return
}
print(self.center.x, self.center.y)
delegate.dragimage(self, didDropAt: position)
return
}
}
self.center = originalPos
}
}
class MyVC: UIViewController, DragImageDelegate {
let dragImage1 = DragImage(frame: CGRect(x: 0.0, y: 0.0, width: 30.0, height: 30.0))
let dragImage2 = DragImage(frame: CGRect(x: 0.0, y: 100.0, width: 30.0, height: 30.0))
override func viewDidLoad() {
let target = UIView(frame: CGRect(x: 200.0, y: 400.0, width: 30.0, height: 30.0))
target.backgroundColor = .black
view.addSubview(target)
dragImage1.backgroundColor = .white
dragImage2.backgroundColor = .white
dragImage1.dropTarget = target
dragImage2.dropTarget = target
view.addSubview(dragImage1)
view.addSubview(dragImage2)
dragImage1.delegate = self
dragImage2.delegate = self
}
private func move(_ view:UIView, to position:CGPoint) {
view.frame = CGRect(x: position.x, y: position.y, width: view.frame.size.width, height: view.frame.size.height)
}
// MARK: - DragImageDelegate
func dragimage(_ dragImage: DragImage, didDropAt position: CGPoint) {
if dragImage === dragImage1 {
move(dragImage1, to: position)
} else if dragImage === dragImage2 {
move(dragImage2, to: position)
}
}
}
var container = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 300.0, height: 600.0))
let myVc = MyVC()
myVc.view.frame = CGRect(x: 0.0, y: 0.0, width: 300.0, height: 600.0)
myVc.view.backgroundColor = .green
container.addSubview(myVc.view)
PlaygroundPage.current.liveView = container
Result:

How to call a function that is defined in a different view controller?

I'm trying to call the function called "kick". Inside it is a CAAnimation that I am playing on this man I made. So usually I would just call the function in the same view controller as it was made in. But because I have a HUD/overlay scene the displays over my 3D game. And the HUD/overlayscene is made in a different view controller specifically just to set up the HUD I can't get the HUD view controller to Recognize any functions from the Main View Controller.
MainViewController:
Code:
class GameViewController: UIViewController, ADBannerViewDelegate, SKPhysicsContactDelegate, SKSceneDelegate, SCNSceneRendererDelegate, SCNPhysicsContactDelegate{
var HUDView: HUD!
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
//-----Scene-Setup----------
let scnView = self.view as! SCNView
scnView.scene = FieldScene
scnView.playing = true
scnView.loops = true
self.HUDView = HUD(size: CGSizeMake(100, 100))
scnView.overlaySKScene = self.HUDView
scnView.delegate = self
scnView.overlaySKScene!.delegate = self
scnView.overlaySKScene!.userInteractionEnabled = true
scnView.backgroundColor = UIColor.whiteColor()
scnView.allowsCameraControl = true
scnView.showsStatistics = false
let GuyScene = SCNScene(named: "art.scnassets/The1.dae")
let Guy: SCNNode = GuyScene!.rootNode.childNodeWithName("Armature", recursively: true)!
let GuyBody: SCNNode = GuyScene!.rootNode.childNodeWithName("Cube", recursively: true)!
//----Giveing it a physics---------
let collisionCapsuleRadius2 = CGFloat(0.1)
let collisionCapsuleHeight2 = CGFloat(0.1)
Guy.position = SCNVector3(x: -30.0, y: 30.0, z: 0.0)
Guy.scale = SCNVector3Make(50, 50, 50)
Guy.rotation = SCNVector4Make(0, 1, 0, 1 )
Guy.physicsBody = SCNPhysicsBody(type: .Dynamic, shape:SCNPhysicsShape(geometry: SCNCapsule(capRadius: collisionCapsuleRadius2, height: collisionCapsuleHeight2), options:nil))
Guy.physicsBody?.affectedByGravity = true
Guy.physicsBody?.friction = 0 //
Guy.physicsBody?.restitution = 1 //bounceness of the object
Guy.physicsBody?.angularDamping = 1 // rotationess
Guy.physicsBody?.mass = 1
Guy.physicsBody?.rollingFriction = 0
scnView.scene!.rootNode.addChildNode(Guy)
scnView.scene!.rootNode.addChildNode(GuyBody)
}
//Ok Kick function gets declared right here
func Kick() {
//-----Animate-Guy-----Working-Perfectly-----------
let KickAnimation = CAAnimation.animationWithSceneNamed("art.scnassets/The1Anima.dae")!
Guy.addAnimation(KickAnimation, forKey: "Go")
Guy.removeAnimationForKey("Go", fadeOutDuration: 3.0)
}
}
extension CAAnimation {
class func animationWithSceneNamed(name: String) -> CAAnimation? {
var animation: CAAnimation?
if let scene = SCNScene(named: name) {
scene.rootNode.enumerateChildNodesUsingBlock({ (child, stop) in
if child.animationKeys.count > 0 {
animation = child.animationForKey(child.animationKeys.first!)
stop.initialize(true)
}
})
}
return animation
}
}
//Ok Below is my HUD/Overlayscene which holds the button that i would like call the function in.
extension GameViewController {
// was never used
}
class HUD: SKScene {
//-----------------Controller-Buttons----------------------
var ButtonA = SKSpriteNode(imageNamed:"EnhancedAButton")
override init(size: CGSize) {
super.init(size: size)
//----A-Button--Creation -------------------
ButtonA.size = CGSize(width: 7, height: 11)
ButtonA.anchorPoint = CGPointMake(-11.35, -0.6)
ButtonA.zPosition = 0
ButtonA.alpha = 0.45
self.addChild(ButtonA)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//Ok here is the function I need to put "Kick()" inside it but when I do it doenst recognize it because the function wasnt declared in this view controller
func AButtonPressed() {
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
for touch: AnyObject in touches {
let location1 = touch.locationInNode(self)
if self.nodeAtPoint(location1) == self.ButtonA {
AButtonPressed()
print("AButtonPressed")
}
}
}
}
enter image description here
enter image description here
This is practically the best way to handle this. Other methods are messy while this is the most decoupled natural way to handle it. If you have any questions, let me know.
extension GameViewController: GameDelegate {
func Kick() {
//-----Animate-Guy-----Working-Perfectly-----------
let KickAnimation = CAAnimation.animationWithSceneNamed("art.scnassets/The1Anima.dae")!
Guy.addAnimation(KickAnimation, forKey: "Go")
Guy.removeAnimationForKey("Go", fadeOutDuration: 3.0)
}
}
protocol GameDelegate {
func Kick()
}
class HUD: SKScene {
var delegate: GameDelegate?
override init(size: CGSize) {
super.init(size: size)
//this calls Kick anywhere in the HUD class
delegate?.Kick()
//----A-Button--Creation -------------------
ButtonA.size = CGSize(width: 7, height: 11)
ButtonA.anchorPoint = CGPointMake(-11.35, -0.6)
ButtonA.zPosition = 0
ButtonA.alpha = 0.45
self.addChild(ButtonA)
}
}
after the line that is inside of GameViewController's viewDidLoad()
//below this line
self.HUDView = HUD(size: CGSizeMake(100, 100))
//add in
self.HUDView.delegate = self
After all of this, is added in, you can delete the copy of Kick that is not in the extension. Both controllers can access the function.

I can't get my ball SKSpriteNode to display on a secondary View Controller's UIImage(Xcode / Swift 2)

I have a button on my first view controller that leads to the second view controller. When the second view controller appears, a ball image is supposed to appear and it's code is supposed to run. I seem to be having a little bit of trouble figuring out why the image isn't appearing. Any help would be greatly appreciated!
import UIKit
import SpriteKit
class GameViewController: UIViewController {
#IBOutlet weak var startButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if let scene = gamePlay(fileNamed:"gamePlay") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .AllButUpsideDown
} else {
return .All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
import SceneKit
import SpriteKit
class gamePlayController: UIViewController {
}
class gamePlay: SKScene, SKPhysicsContactDelegate {
let skyColor = SKColor(red: 0, green: 191, blue: 255, alpha: 1)
var blueBall:SKSpriteNode!
override func didMoveToView(view: SKView) {
self.backgroundColor = skyColor
self.physicsWorld.gravity = CGVectorMake(0.0, -5.0)
self.physicsWorld.contactDelegate = self
blueBall = SKSpriteNode( imageNamed: "ball")
blueBall.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
blueBall.physicsBody = SKPhysicsBody(circleOfRadius: blueBall.size.width / 1.5 )
blueBall.physicsBody!.dynamic = true
blueBall.physicsBody!.allowsRotation = false
self.addChild(blueBall)
}
override func touchesBegan(touches: Set<UITouch> , withEvent event: UIEvent?) {
self.blueBall.physicsBody?.velocity = CGVectorMake(35, 0)
self.blueBall.physicsBody?.applyImpulse(CGVectorMake(4, 10))
}
}
Looks like position is wrong. Try this code
blueBall.position = CGPoint(x: CGRectGetMidX(self.bounds), y: CGRectGetMidY(self.bounds))
I fixed the issue. I needed to write my backgroundImage as an SKSpriteNode and set the zPosition accordingly.

Resources