My SKScene will show sized correctly in the Simulator, before rotation, but not after? - swift

My SKScene will show sized correctly in the Simulator, before rotation, but not after?
If the Simulator is preset to Portrait, my SKScene is sized correctly .. and ditto if preset to Landscape.
But, not after rotation-in-place.
Here are some code snippets …
class GameViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// thisScene = a String name, e.g. “CreditsScene” or “AboutScene”
showScene(theScene: thisScene!)
} // viewDidAppear
func showScene(theScene: String) {
view = SKView()
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: theScene) {
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
} // showScene
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
if fromInterfaceOrientation.isLandscape {
let width = UIScreen.main.bounds.width
let height = UIScreen.main.bounds.height
if let scene = SKScene(fileNamed: thisScene!) {
print("\(scene)")
scene.size = CGSize(width: width, height: height)
print("\(scene)")
}
}
else {
let width = UIScreen.main.bounds.width
let height = UIScreen.main.bounds.height
if let scene = SKScene(fileNamed: thisScene!) {
print("\(scene)")
scene.size = CGSize(width: width, height: height)
print("\(scene)")
}
}
showScene(theScene: thisScene!)
} // didRotate
}
Here’s the Console output after rotation from Portrait to Landscape:
name:'(null)' frame:{{-375, -667}, {750, 1334}} anchor:{0.5, 0.5} // before Landscape
name:'(null)' frame:{{-590, -410}, {1180, 820}} anchor:{0.5, 0.5} // after Landscape
Then, after rotation back to Portrait:
name:'(null)' frame:{{-375, -667}, {750, 1334}} anchor:{0.5, 0.5} // before Portrait
name:'(null)' frame:{{-410, -590}, {820, 1180}} anchor:{0.5, 0.5} // after Portrait
The 2nd lines, “after ..” reverse as they should.
For the life of me, I do not understand the 1st lines, “before”?
and for both lines what is name:'(null)' .. doesn't it have the name fileNamed:thisScene!
It’s almost as if
if let scene = SKScene(fileNamed: theScene) { .. }
doesn’t call view.presentScene( .. )
To complete the diagnostics, I have one GameViewController and 3 GameScenes (.sks files). I am successful in switching scenes .. but not in rotating them to size.
Any help is very, very appreciated!

SOLVED
See the above code for showScene, in particular the 1st line in the func showScene:
view = SKView()
Eliminate it.
Found this jewel here at SO:
The size specified in the .sks file is the size of the scene not the view. Set your scene's scale mode to .resizeFill to force the scene size to always match your view size.
scene.scaleMode = .resizeFill
BOOM!
DONE!
I'm trying to be a competent iOS Programmer ... I have no aspirations to be a Movie Producer, but:
lovesongforever.com/monsterpaddle

Related

Background is not filling the whole view SpriteKit

For some reason my code will not fill the whole SKScene. Here is the code that I am using on Xcode 12 Beta 5.
GameScene.swift
class GameScene: SKScene {
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: "space")
background.zPosition = 0
background.anchorPoint = CGPoint(x: 0.5, y: 0.5) // default
background.position = CGPoint(x: frame.midX, y: frame.midY)
print("frame.size \(frame.size)")
print("self.size \(self.size)")
print("view \(view.frame.size)")
background.size = CGSize(width: self.size.width, height: self.size.height)
self.addChild(background)
}
}
GameViewController.swift
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GKScene(fileNamed: "GameScene") {
if let sceneNode = scene.rootNode as! GameScene? {
// Present the scene
if let view = self.view as! SKView? {
sceneNode.size = view.bounds.size
sceneNode.anchorPoint = CGPoint.zero
sceneNode.scaleMode = .aspectFit
print("view.bounds.size \(view.bounds.size)")
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
return true
}
}
Also for some reason, my view size is reporting ->
frame.size (320.0, 480.0) self.size (320.0, 480.0) view (320.0,
480.0)
But my GameScene.sks is set to -> w750, h1336
Why is my code cutting off the tops and the bottoms of the background?
This is going to sound dumb, but do you have a launch Screen? I was having the same problem which would only happen in Xcode 12 and not 11, but the main difference I found was Xcode 11 has a launch screen. Once I added the launch screen storyboard and added it to my plist my SCNView would fill the screen. I am not sure why this would cause the view not to follow the constraints, but after adding it to my other projects it fixes the issue.
Edit:
You do not need to have a launch screen storyboard, you can just add your main storyboard that displays you scene in the plist under "Launch screen interface file base name".
This is definitely caused by not having a launch screen assigned. It's very odd that Xcode 12 has this behavior for SpriteKit by default. I'm sure many many people will be stumped and confused. I noticed that the main visible difference to earlier versions was the lack of a launch screen.
You can either create a launch screen and assign it or define Main.storyboard as the launch screen, as proposed earlier. Probably the easiest thing to just get it working is to go to the project target, then General and choose "Main" where it says "Launch Screen File". This will make it work as expected.
Update: This is still happening in Xcode 12.4 (12D4e). Surprisingly, the launch screen will be missing in a brand-new iOS-only project, whereas it's there in a multi-platform project. It seems like this is an oversight on Apple's part.
How to select Main as your project's Launch Screen File:

How to fix the wrong frame after device rotation?

I use custom alert view in my application. When called, its frame completely fills the screen. However, after the device rotates, it does not look right.
Then I try to change my alert frame and assign it a new screen size in viewWillTransition
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: nil) { _ in UIView.setAnimationsEnabled(true) }
UIView.setAnimationsEnabled(false)
super.viewWillTransition(to: size, with: coordinator)
if let _ = alert {
alert?.rotateAlert(to: size)
Logger.Log("after size = \(alert?.backgroundView.frame.size)")
Logger.Log("after frame = \(alert?.frame)")
Logger.Log("after bounds = \(alert?.bounds)")
}
I tried to do the same in viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let _ = alert {
Logger.Log("viewDidLayoutSubviews before rotation")
alert?.rotateAlert(to: self.view.frame.size)
Logger.Log("after size = \(alert?.backgroundView.frame.size)")
Logger.Log("after frame = \(alert?.frame)")
Logger.Log("after bounds = \(alert?.bounds)")
}
}
func rotateAlert(to size: CGSize) {
self.frame.size = size
backgroundView.frame = frame
}
Thus the frame of my alert changes. But its size is still smaller than the screen in height, thus forming an empty area.
Why is this happening? And how do I fix this? (I use Swift4)
To place your alert in the center of the screen, assuming that it is an instance of UIView, do this:
alert.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
alert.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// Or
NSLayoutConstraint.activate([
alert.centerXAnchor.constraint(equalTo: view.centerXAnchor),
alert.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
This is using auto layout to position your views, and the views will properly update when the trait collection changes.
When adding a view programmatically the view doesn't adjust according to the screen rotation because you have fixed the view height, width so after rotation you need to again give its frame.
This doesn't happen when you use constraints to add a view either programmatically or from the storyboard
For your alert to resize you have to again give its frame in viewDidLayoutSubviews()
override func viewDidLayoutSubviews() {
YourAlertView.frame = CGRect(x:0 , y: 0,width: self.view.frame.width, height: self.view.frame.height )
}
Works like a charm for me.

How to create a playable rectangle in SpriteKit on iPhone X

I'm a newbie in Swift and SpriteKit. I'm trying to develop a simple 2D game for iPhone X, so I want to draw a playable area where I can put game's sprites. The problem is that when I draw the rectangle it fits the screen width, but not the screen height.
The GameScene size is 2048x1536, with anchor point in (0.5, 0.5).
The default screen orientation is landscape.
This is what I did at the moment:
GameViewController.swift
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
GameScene.swift
class GameScene: SKScene {
override func didMove(to view: SKView) {
let rectangle = SKShapeNode(rect: self.frame)
rectangle.strokeColor = .green
rectangle.lineWidth = 20
rectangle.zPosition = 1
addChild(rectangle)
}
}
When I run this code, the rectangle green borders fit the width of the screen (in landscape), so I can see them, but the top and bottom borders are off-screen.
My purpose is to have a playable rect as high as the phone screen, and his width should be screen width - left safe area insets (to exclude the notch from the playable area).
Thank you.

How to change SKScene position before presenting?

I am trying to adjust SKScene position based on the device the game is running. For example due to 'top notch' area of iPhone XS Max, I need to subtract that height from SKScene height and position the scene accordingly.
So here is how I calculate the height aka safeAreaHeight in the AppDelegate and subtract from scene height aka sceneHeight:
safeAreaHeight = UIApplication.shared.statusBarFrame.size.height
sceneHeight -= safeAreaHeight
In GameViewController I try to re-position the SKScene like below:
if let view = self.view as! SKView? {
// Load the SKScene from 'MainMenuScene.sks'
if let scene = SKScene(fileNamed: "MainMenuScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFit
scene.size.height = sceneHeight
scene.size.width = sceneWidth
scene.position.y -= safeAreaHeight
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
However, I am getting below error:
SKScene: Setting the position of a SKScene has no effect.
Additionally, I tried to use SKAction to move position of the SKScene as well, then I received below error:
SKScene: Animating the position of a SKScene has no effect.
Thank you for the help.
Well I figured how to achieve this, I am sharing in case it would help someone else.
Inside AppDelegate you get the correct safe area values and store it in a singleton (in my case: safeAreaHeight) for access throughout the app. Subtract the amount from scene height (sceneHeight).
let kWindow = UIApplication.shared.windows[0]
safeAreaHeight = kWindow.safeAreaInsets.top + kWindow.safeAreaInsets.bottom
screenHeight = screenRect.size.height - safeAreaHeight
Don't forget the adjust the sceneHeight in GameViewController before presenting it:
if let view = self.view as! SKView? {
// Load the SKScene from 'MainMenuScene.sks'
if let scene = SKScene(fileNamed: "MainMenuScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFit
scene.size.height = sceneHeight
scene.size.width = sceneWidth
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
Inside loaded scene, in this case it is MainMenuScene, you simply find subview of SKScene and adjust its Y position by subtracting the safeAreaHeight at its center
guard let subView = self.view else { return }
subView.center.y -= safeAreaHeight
PS.: Do not adjust all the views in different scenes (ie.: GameScene, GameOverScene etc.) Once you adjust, it remains throughout the app.

self.size is (320,480) even if my app is in landscape

If I print self.size i get (320,480) but it should be (480,320) because my app is in landscape this is how I go to my main view:
var playScene = Menu(size: self.size)
let skView = self.view! as SKView
skView.ignoresSiblingOrder = true
playScene.scaleMode = .ResizeFill
playScene.size = skView.bounds.size
skView.presentScene(playScene)
Edit: I've found that on an Iphone 5s the self.size is (568, 320) so the problem is only on iphone 4s or on IOS 7
The sizes of views aren't updated until UIKit runs layout. That happens after viewDidLoad returns. So if that code is in viewDidLoad, you're checking too soon. Look at the size in viewDidLayoutSubviews or viewDidAppear:.
I've had to use this function to get the right screen size for the device i am using:
func screenSize() -> CGSize {
var screenSize: CGSize = UIScreen.mainScreen().bounds.size
if ((NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) && UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation)) {//If ios 7
return CGSizeMake(screenSize.height, screenSize.width);
}
return screenSize;
}
And i replace my code to change the view to:
var playScene = Menu(size: self.size)
let skView = self.view! as SKView
skView.ignoresSiblingOrder = true
playScene.scaleMode = .ResizeFill
playScene.size = screenSize()
skView.presentScene(playScene)