I have a question about using self.view.safeAreaInsets in GameViewController

I am currently using this code in my GameViewController file to retrieve the boundaries of the safeArea

Code Block
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let safeAreaInsets = self.view.safeAreaInsets
}

My concern is that if I were using a UIView (called myView below), I could automatically apply it with this code block and never think of it again.
Code Block
var constraints = [NSLayoutConstraint]()
constraints.append(myView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor))
constraints.append(myView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor))
constraints.append(myView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor))
constraints.append(myView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor))
}

Is there an equivalent call when I'm doing that for a UIViewController?

Also, from all my reading online many posts have said to use trailing and leading rather than top and bottom. However self.view.safeAreaInsets appears to only have top and bottom.


Answered by OOPer in 644454022

SKView appears to have no process to do that.

Have you ever tried? There's no difficulties to set constraints between the safeArea edges of the main view and SKView.
(code at the bottom)

 it's still there and Main storyboard filename is set to Main within info.plist. 

Then you ARE using storyboard and instantiating GameViewController through storyboard.
I just wanted to clarify how you instantiate GameViewController.
(By the way, why do you insist on not using storyboard? It seems to be the harder way for you.)
Fortunately, if you add right constraints for myView properly, they work as expected whether you set them with code or in storyboard.

Am only using myView as a UIView to set safeAreaLayout.

You can set safeAreaLayout even if myView is aSKView.

Code Block
import UIKit
import SpriteKit
class GameViewController: UIViewController {
//### Change the type of `myView` to `SKView`.
public let myView : SKView = {
let myView = SKView()
//### Needed to make added constraints to work properly
myView.translatesAutoresizingMaskIntoConstraints = false
return myView
}()
private func addConstraints(){
var constraints = [NSLayoutConstraint]()
//add
constraints.append(myView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor))
constraints.append(myView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor))
constraints.append(myView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor))
constraints.append(myView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor))
//activate
NSLayoutConstraint.activate(constraints)
}
override func viewDidLoad() {
super.viewDidLoad()
//### Change the type of `view` to `UIView` (in Main.storyboard)
//if let view = self.view as! SKView? {
view.addSubview(myView)
//### Add constraints in `viewDidLoad()`
addConstraints()
//### You have no need to delay these settings
//var scene : GameScene!
//DispatchQueue.main.async {
let scene = GameScene(size: myView.frame.size)
// let scene = GameScene(size: CGSize(width: screenDims.widthPts - self.view.safeAreaInsets.left - self.view.safeAreaInsets.right, height: screenDims.heightPts-self.view.safeAreaInsets.top-self.view.safeAreaInsets.bottom))
//### Do not get screen metrics in `viewDidLoad()`
// myGlobalVars.widthPoints = screenDims.widthPts - self.view.safeAreaInsets.left - self.view.safeAreaInsets.right
// myGlobalVars.heightPoints = screenDims.heightPts - self.view.safeAreaInsets.top - self.view.safeAreaInsets.bottom
#if DEBUG
//print("SIZE \(screenDims.heightPts)")
print("SIZE \(self.view.safeAreaInsets.top)")
print("SIZE \(self.view.safeAreaInsets.bottom)")
#endif
scene.anchorPoint = CGPoint(x: 0.0, y: 0.0)
scene.backgroundColor = .brown//.clear //### for debugging
scene.scaleMode = .aspectFit
//### Use `myView` instead of `view` to show the GameScene.
myView.isHidden = false
myView.presentScene(scene)
//}
myView.ignoresSiblingOrder = true
myView.showsFPS = true
myView.showsNodeCount = true
myView.showsPhysics = true
//}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
#if DEBUG
print ("GVC viewWillLayoutSubviews")
#endif
//### Remove these ↓
// let constraints = [
// view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// view.centerYAnchor.constraint(equalTo: view.centerYAnchor),
// view.widthAnchor.constraint(equalToConstant: 100),
// view.heightAnchor.constraint(equalTo: view.widthAnchor)]
// NSLayoutConstraint.activate(constraints)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
getScreenDimensions(screen: &screenDims)
if myGlobalVars.safeSceneRect == .zero {
//### Do not add constraints in `viewDidLayoutSubviews()`
// addConstraints()
// createConstraints(view: myView)
myGlobalVars.sceneRect = view.frame
}
if #available(iOS 11.0, *) {
myGlobalVars.topSafeArea = view.safeAreaInsets.top
myGlobalVars.bottomSafeArea = view.safeAreaInsets.bottom
} else {
myGlobalVars.topSafeArea = topLayoutGuide.length
myGlobalVars.bottomSafeArea = bottomLayoutGuide.length
}
//### Change only `size` of the `scene` if you need
myView.scene?.size = myView.frame.size
}
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .portraitUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
#if DEBUG
print ("GVC prefersStatusBarHidden")
#endif
return false
}
}

(Some lines kept there, to clarify which lines should be removed. Please read comments with ###.)




Is there an equivalent call when I'm doing that for a UIViewController?

NO.
If you add right constraints between the main view of the UIViewController and a view (myView), it should work.
So, you have no need to search for equivalent.

Also, from all my reading online many posts have said to use trailing and leading rather than top and bottom.

Rather than left and right.
That's a problem of localization. When people use right-to-left languages, the leading is the right and the trailing is the left.

You can use left and right if you do not care about localization. But generally, you have no need to access such raw values in properly written codes.

NO.
If you add right constraints between the main view of the UIViewController and a view (myView), it should work.
So, you have no need to search for equivalent.

This is what I was using...found it from posts. And someone told me that that was absolutely bad, and that it would eventually ** up in my face. What I did was:

GameViewController (viewWillLayoutSubviews)
Created an UIView
Made it a subView of GameViewController
Got all constraints, from UIView, and applied them

Then still within GVC**
I created GameScene using the values I got from constraints
I noticed tho, I don't add the GameScene to any view.

Is that wrong?




GameViewController (viewWillLayoutSubviews) 
Created an UIView
Made it a subView of GameViewController
Got all constraints, from UIView, and applied them

Is that wrong?

Wrong. viewWillLayoutSubviews is called every time before iOS layouts subviews of the main view of the view controller.
If you add a subview in viewWillLayoutSubviews, the subview may be added repeatedly.

You usually add a subview and add constraints to it inside viewDidLoad().

In SpriteKit games, you usually create a fixed size GameScene (SKScene) and make iOS scale it for the view.

If you want a pixel-wise fit for your GameScene, change only size of the GameScene in viewDidLayoutSubviews().

Anyway, to make things work well, you need to show the GameScene in the right view.
You need to change the type of myView from UIView to SKView, as I wrote once in another thread of yours.

You usually add a subview and add constraints to it inside viewDidLoad().

Yikes! Yup, didn't take that into account...that would be recursivly bad. Thanks.

You need to change the type of myView from UIView to 
SKView, as I wrote once in another thread of yours.

Except that there is no restraint for SKViews...can't find it, and have been told in more than one post there is no way to set restraints for SKViews.

In SpriteKit games, you usually create a fixed size GameScene (SKScene) and make iOS scale it for the view.

And there's the bottom line. I have found no way to create the GameScene size fixed other than getting constraints from myView(UIView). And remember, am not using storyboards at all.

there is no way to set restraints for SKViews.

Sorry, but I do not understand what you mean by restraints. I am not saying to set restraints something. Just saying to change the type of myView to SKView.

And remember, am not using storyboards at all.

I cannot find any code to initialize your GameViewController. Where and how are you initializing it? Have you really removed Main.storyboard from your project?

And not using storyboards has nothing to do with changing the type of myView.

(complete code for GameViewController is at the bottom)

Sorry, but I do not understand what you mean by restraints. I am not saying to set restraints something. Just saying to change the type of myView to SKView

I meant setting the safeAreaLayout constraints, so that every screen will fall within the proper edges of the screen (iPhoneX). SKView appears to have no process to do that.


Have you really removed Main.storyboard from your project?

No, I couldn't. So it's still there and Main storyboard filename is set to Main within info.plist.
If you're about to tell me that setting safeArea in Main.storyboard will take care of every screen...you're going hear me cry no matter where you are.

And not using storyboards has nothing to do with changing the type of myView.

Am only using myView as a UIView to set safeAreaLayout. If I can set SafeAreaLayout for every screen somewhere else, I wouldn't use myView at all.

p.s. Would love to know how you're changing some text color here in the forum, makes reading these replies easier

Code Block
import UIKit
import SpriteKit
class GameViewController: UIViewController {
public let myView : UIView = {
let myView = UIView()
return myView
}()
private func addConstraints(){
var constraints = [NSLayoutConstraint]()
//add
constraints.append(myView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor))
constraints.append(myView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor))
constraints.append(myView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor))
constraints.append(myView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor))
//activate
NSLayoutConstraint.activate(constraints)
}
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
view.addSubview(myView)
var scene : GameScene!
DispatchQueue.main.async { scene = GameScene(size: CGSize(width: screenDims.widthPts - self.view.safeAreaInsets.left - self.view.safeAreaInsets.right, height: screenDims.heightPts-self.view.safeAreaInsets.top-self.view.safeAreaInsets.bottom))
myGlobalVars.widthPoints = screenDims.widthPts - self.view.safeAreaInsets.left - self.view.safeAreaInsets.right
myGlobalVars.heightPoints = screenDims.heightPts - self.view.safeAreaInsets.top - self.view.safeAreaInsets.bottom
#if DEBUG
print("SIZE \(screenDims.heightPts)")
print("SIZE \(self.view.safeAreaInsets.top)")
print("SIZE \(self.view.safeAreaInsets.bottom)")
#endif
scene.anchorPoint = CGPoint(x: 0.0, y: 0.0)
scene.backgroundColor = .clear
scene.scaleMode = .aspectFit
view.isHidden = false
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.showsPhysics = true
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
#if DEBUG
print ("GVC viewWillLayoutSubviews")
#endif
let constraints = [
view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
view.centerYAnchor.constraint(equalTo: view.centerYAnchor),
view.widthAnchor.constraint(equalToConstant: 100),
view.heightAnchor.constraint(equalTo: view.widthAnchor)]
NSLayoutConstraint.activate(constraints)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
getScreenDimensions (screen: &screenDims)
if myGlobalVars.safeSceneRect == .zero {
addConstraints()
createConstraints(view: myView)
myGlobalVars.sceneRect = view.frame
}
if #available(iOS 11.0, *) {
myGlobalVars.topSafeArea = view.safeAreaInsets.top
myGlobalVars.bottomSafeArea = view.safeAreaInsets.bottom
} else {
myGlobalVars.topSafeArea = topLayoutGuide.length
myGlobalVars.bottomSafeArea = bottomLayoutGuide.length
}
}
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .portraitUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
#if DEBUG
print ("GVC prefersStatusBarHidden")
#endif
return false
}
}

Accepted Answer

SKView appears to have no process to do that.

Have you ever tried? There's no difficulties to set constraints between the safeArea edges of the main view and SKView.
(code at the bottom)

 it's still there and Main storyboard filename is set to Main within info.plist. 

Then you ARE using storyboard and instantiating GameViewController through storyboard.
I just wanted to clarify how you instantiate GameViewController.
(By the way, why do you insist on not using storyboard? It seems to be the harder way for you.)
Fortunately, if you add right constraints for myView properly, they work as expected whether you set them with code or in storyboard.

Am only using myView as a UIView to set safeAreaLayout.

You can set safeAreaLayout even if myView is aSKView.

Code Block
import UIKit
import SpriteKit
class GameViewController: UIViewController {
//### Change the type of `myView` to `SKView`.
public let myView : SKView = {
let myView = SKView()
//### Needed to make added constraints to work properly
myView.translatesAutoresizingMaskIntoConstraints = false
return myView
}()
private func addConstraints(){
var constraints = [NSLayoutConstraint]()
//add
constraints.append(myView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor))
constraints.append(myView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor))
constraints.append(myView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor))
constraints.append(myView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor))
//activate
NSLayoutConstraint.activate(constraints)
}
override func viewDidLoad() {
super.viewDidLoad()
//### Change the type of `view` to `UIView` (in Main.storyboard)
//if let view = self.view as! SKView? {
view.addSubview(myView)
//### Add constraints in `viewDidLoad()`
addConstraints()
//### You have no need to delay these settings
//var scene : GameScene!
//DispatchQueue.main.async {
let scene = GameScene(size: myView.frame.size)
// let scene = GameScene(size: CGSize(width: screenDims.widthPts - self.view.safeAreaInsets.left - self.view.safeAreaInsets.right, height: screenDims.heightPts-self.view.safeAreaInsets.top-self.view.safeAreaInsets.bottom))
//### Do not get screen metrics in `viewDidLoad()`
// myGlobalVars.widthPoints = screenDims.widthPts - self.view.safeAreaInsets.left - self.view.safeAreaInsets.right
// myGlobalVars.heightPoints = screenDims.heightPts - self.view.safeAreaInsets.top - self.view.safeAreaInsets.bottom
#if DEBUG
//print("SIZE \(screenDims.heightPts)")
print("SIZE \(self.view.safeAreaInsets.top)")
print("SIZE \(self.view.safeAreaInsets.bottom)")
#endif
scene.anchorPoint = CGPoint(x: 0.0, y: 0.0)
scene.backgroundColor = .brown//.clear //### for debugging
scene.scaleMode = .aspectFit
//### Use `myView` instead of `view` to show the GameScene.
myView.isHidden = false
myView.presentScene(scene)
//}
myView.ignoresSiblingOrder = true
myView.showsFPS = true
myView.showsNodeCount = true
myView.showsPhysics = true
//}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
#if DEBUG
print ("GVC viewWillLayoutSubviews")
#endif
//### Remove these ↓
// let constraints = [
// view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// view.centerYAnchor.constraint(equalTo: view.centerYAnchor),
// view.widthAnchor.constraint(equalToConstant: 100),
// view.heightAnchor.constraint(equalTo: view.widthAnchor)]
// NSLayoutConstraint.activate(constraints)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
getScreenDimensions(screen: &screenDims)
if myGlobalVars.safeSceneRect == .zero {
//### Do not add constraints in `viewDidLayoutSubviews()`
// addConstraints()
// createConstraints(view: myView)
myGlobalVars.sceneRect = view.frame
}
if #available(iOS 11.0, *) {
myGlobalVars.topSafeArea = view.safeAreaInsets.top
myGlobalVars.bottomSafeArea = view.safeAreaInsets.bottom
} else {
myGlobalVars.topSafeArea = topLayoutGuide.length
myGlobalVars.bottomSafeArea = bottomLayoutGuide.length
}
//### Change only `size` of the `scene` if you need
myView.scene?.size = myView.frame.size
}
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .portraitUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
#if DEBUG
print ("GVC prefersStatusBarHidden")
#endif
return false
}
}

(Some lines kept there, to clarify which lines should be removed. Please read comments with ###.)




OOper .... there aren't enough letters in the alphabet to thank you, not only for going through all the trouble to edit my code, but for showing me that I had to change the class of Main.storyboard. I pulled out my source code from a few days (where I got the error below). Once I made that one change to the storyboard, it all worked.

Before when I tried creating myView as an SKView I always got this error

Cannot convert return expression of type 'UIView' to return type 'SKView'

Side note: When I changed the class in storyboard to UIView, it was not available in the dropdown list. I had to type it in manually.

On some other boards and/or posts I was told SKView couldn't apply constraints.

Don't worry am going to check your answer soon, I just need to clarify two things you changed:

calling addContraints() in viewDidLoad

I can posts a bunch of links that say that bounds are not guaranteed to be set in viewDidLoad and to wait till viewDidLayoutSubviews

Also, I noticed you removed my calling GameScene from a thread. That was used because GameScene routines were running ahead of schedule. So someone told me to use DispatchQueue.main.async. Can I go back to that?

As for why I left Main.storyboard, I'll start another post for that


I had to type it in manually.

Oh, yes. In my case, I needed to remove the Custom Class setting, then Xcode put light-gray UIView in it.

that bounds are not guaranteed to be set in  viewDidLoad and to wait till viewDidLayoutSubviews

That is true. And that does not mean you need to add constraints in viewDidLayoutSubviews.
When you add constraints referring safeArea and safeArea changes after that, iOS will re-layout subviews using the updated safeArea.

That was used because GameScene routines were running ahead of schedule. 

You may need to fix some parts of GameScene to utilize my code. But using DispatchQueue.main.async is not recommended here.
It just delays the execution of the closure to the next cycle of the main queue, you cannot predict exactly when.
  • Add another method to GameScene and move some processing into it which needs to run after some event is finished.

  • And call the method in your GameViewController from some method which surely runs after the event.

Or
  • Call myView.presentScene(scene) from the method which surely runs after the event.

(In this case, you need to keep the scene as a new property of GameViewController, and use it instead of myView.scene.
myView.scene may be nil until you call myView.presentScene(scene).)



Add another method to GameScene and move some processing into it which needs to run after some event is finished.
And call the method in your GameViewController from some method which surely runs after the event.

I was using a 1 second sleep timer loop in GameScene until GameViweController's last line of code was setting a flag to cancel the loop. but I thought that was too kludgy.

In this case, you need to keep the scene as a new property of GameViewController, and use it instead of myView.scene.

Now that you mention it, I'm not find any code to link GameScene to GameViewcontroller. Only posts I found said "don't run any other code in GVC, after launching GameScene. I hope that's right.

Going to mark this question answered by end of night, so I know you've read this.

My other question is going into its own thread

posts I found said "don't run any other code in GVC, after launching GameScene.

Sorry, I did not know that. Can you show the link to the post? Or can you explain why?
That might be a reasonable statement as some processing of GameScene may be executed independently from the main thread.
To control things well, we should know why.
First block is GVC
Second is GC

You'll notice that I commented out your let scene= and put back in my DispatchQueue. That's the only way it works.

Right now, the only thing that GC is doing is pulling up a blue background.png (when I remove DispatchQueue and use your let scene = the blue background never shows up


GVC
Code Block
import UIKit
import SpriteKit
class GameViewController: UIViewController {
public let myView : SKView = {
let myView = SKView()
myView.translatesAutoresizingMaskIntoConstraints = false
return myView
}()
private func addConstraints(){
var constraints = [NSLayoutConstraint]()
//add
constraints.append(myView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor))
constraints.append(myView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor))
constraints.append(myView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor))
constraints.append(myView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor))
//activate
NSLayoutConstraint.activate(constraints)
}
override func viewDidLoad() {
super.viewDidLoad()
#if DEBUG
print ("GVC viewDidLoad")
#endif
// if let view = self.view as! SKView?
view.addSubview(myView)
addConstraints()
var scene : GameScene!
// var scene = GameScene(size: myView.frame.size)
// let scene = GameScene(size: myView.frame.size)
DispatchQueue.main.async { [self] in scene = GameScene(size: myView.frame.size )
scene.anchorPoint = CGPoint(x: 0.0, y: 0.0)
scene.backgroundColor = .clear
scene.scaleMode = .aspectFit
myView.isHidden = false
myView.ignoresSiblingOrder = true
myView.showsFPS = true
myView.showsNodeCount = true
myView.showsPhysics = true
myView.presentScene(scene)
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
#if DEBUG
print ("GVC viewDidLayoutSubviews")
#endif
getScreenDimensions (screen: &screenDims)
if myGlobalVars.safeSceneRect == .zero {
myGlobalVars.sceneRect = view.frame
myGlobalVars.sceneRect = myView.frame
}
if #available(iOS 11.0, *) {
myGlobalVars.topSafeArea = view.safeAreaInsets.top
myGlobalVars.bottomSafeArea = view.safeAreaInsets.bottom
} else {
myGlobalVars.topSafeArea = topLayoutGuide.length
myGlobalVars.bottomSafeArea = bottomLayoutGuide.length
}
}
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .portraitUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
return false
}
}


GC
Code Block
import UIKit
import SpriteKit
var myBorder = SKSpriteNode()
class GameScene: SKScene, SKPhysicsContactDelegate {
override func sceneDidLoad(){
super.sceneDidLoad()
self.isUserInteractionEnabled = true
}
override func didMove(to view: SKView){
super.didMove(to: view)
myGlobalVars.backGround = SKSpriteNode(imageNamed: "background")
myGlobalVars.backGround.zPosition = theZ.background
self.isHidden = false
self.addChild(myGlobalVars.backGround)
myGlobalVars.safeSceneRect = view.frame
myGlobalVars.backGround.zPosition = theZ.background
myGlobalVars.gameScene = self
}
}

That's the only way it works.

I do not think so. But some not-yet-shown things prevents me to find how to fix. For example what is theZ?

I do not think so. But some not-yet-shown things prevents me to find how to fix. For example what is theZ?

Setting the zPosition to GameScene's background image. which in this case is -1

Setting the zPosition to GameScene's background image. which in this case is -1

Can you show the code of the definition of theZ? Please include all the parts modifying it.
I have a question about using self.view.safeAreaInsets in GameViewController
 
 
Q