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 ###.)




Can you show the code of the definition of theZ? Please include all the parts modifying it.

Of course, here's my entire DataObjects file. It's just an enum, never gets changed.

Line 57

Code Block
import UIKit
import SpriteKit
let myVer = ver.free
var myCurrentLevel = 1
var screenDims = ScreenDimensions( widthPix : 0.0,
heightPix : 0.0,
widthPts : 0.0,
heightPts : 0.0)
struct ScreenDimensions{
var widthPix : CGFloat
var heightPix : CGFloat
var widthPts : CGFloat
var heightPts : CGFloat
}
struct GlobalVars{
var backGround : SKSpriteNode
var gemBase : SKSpriteNode
var currentNode : SKSpriteNode
var widthPoints : CGFloat
var heightPoints : CGFloat
var widthPixels : CGFloat
var heightPixels : CGFloat
var sceneRect : CGRect
var safeSceneRect : CGRect
var gameScene : GameScene
var currentGem : SKSpriteNode
var fileName : String
var topSafeArea : CGFloat
var bottomSafeArea : CGFloat
var domHand : DominantHand
}
enum bodyMasks: UInt32 {
case blankMask = 0b0
case slotMask = 0b1
case gemBaseMask = 0b10
case borderMask = 0b100
case guessBarMask = 0b1000
case guessSlotMask = 0b10000
case gemMask = 0b100000
case edgeMask = 0b1000000
}
enum ver
{
case free
case paid
}
struct zPositions
{
let controler : CGFloat = -2
let background : CGFloat = -1
let gemBase : CGFloat = 0
let theBoarder : CGFloat = 0
let guessBar : CGFloat = 0
let guessGemBase : CGFloat = 0
let guessSlot : CGFloat = 1
let gem : CGFloat = 1
}
enum Gems
{
case red
case yellow
case blue
case green
case aqua
case orange
}
enum NodeType : Int
{
case gem = 0,
border,
guessBar,
guessSlot,
edge
}
enum GemStatus : Int
{
case home = 0, moving, guessed
}
let GemColors :[Gems] = [
.red,
.yellow,
.blue,
.green,
.aqua,
.orange]

Of course, here's my entire DataObjects file. It's just an enum, never gets changed.

Thanks for showing your code and clarification. That may increase chance for my codes to work properly.

Still some parts missing, I assumetheZ is defined as a global var as follows and never gets changed.
Code Block
var theZ = zPositions()



So, we have no need to consider the timings of theZ being modified.

My concern is that DispatchQueue.main.async delays the execution of the closure, and it guarantees nothing.
In some iOS versions, it may be called after the layout of subviews, in some other cases, it may be called before the layout.

You should better not rely on such an unpredictable delay of DispatchQueue.main.async.
This sort of fragile implementation is hard to debug, as it works as expected in some specific environments.

You may need to delay GameScene(size: myView.frame.size) and myView.presentScene(scene) until some metrics of myView is fixed, based on some reliable basis.

Please try something like this:
Code Block
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
#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)
//### Better avoid `DispatchQueue.main.async`
//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)
//}
}


Code Block
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
}
//### Instantiate and show `GameScene` if not yet
if myView.scene == nil {
let scene = GameScene(size: myView.frame.size )
scene.anchorPoint = CGPoint(x: 0.0, y: 0.0)
scene.backgroundColor = .clear
scene.scaleMode = .aspectFit
myView.presentScene(scene)
}
}


The moment I learned the lifecycle function orientations I should have realized to move it. SMH! Not to try and defend myself, but built my game based on a lesson I took from a video. I guess it was a simpler app, so it worked fine.

Now I have to. find the original answer to your question so I can mark it read.

Thanks for everything.


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