0

From within my GameViewController's viewDidLoad() function I do two things:

1- I create a sub UIVIew, myView, which is added to the main view,

2- I call my function, addConstraints, which is in the GameViewController's code.

When I create GameScene, self.myView.frame.width & self.myView.frame.height contain the correct values, and abide by the safeAreaLayout guides.

I want to make my code clearer, so I've moved the addConstraints function to a separate file. But when I run my app that way self.myView.frame.width & self.myView.frame.height are zero.

So am curious if I am somehow translating the function incorrectly, or maybe this is something I can't move outside of the main code?

The first block is the original code, the 2nd is the function

//located with GameViewControl    
private func addConstraints(){
        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))
        NSLayoutConstraint.activate(constraints)
    }

translated into a function

//called from GameViewControl
    createConstraints(view: myView)
    ....
        
//located outside of GameViewControl
func createConstraints(view: UIView) {
    var constraints = [NSLayoutConstraint]()
                    
    constraints.append(view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor))
    constraints.append(view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor))
    constraints.append(view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor))
    constraints.append(view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor))
                    
    NSLayoutConstraint.activate(constraints)
}

And here is the full file of GameViewControl

import UIKit
import SpriteKit

class GameViewController: UIViewController {
    
    private let myView : UIView = {
        let myView = UIView()
        setViewAttributes(view: myView)
        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 ("viewDidLoad")
        #endif
        
        if let view = self.view as! SKView? {
            getScreenDimensions (screen: &screenDims)
            view.addSubview(myView)
            addConstraints()

            var scene : GameScene!
            DispatchQueue.main.async { scene = GameScene(size: CGSize(width: self.myView.frame.width, height: self.myView.frame.width))
                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 viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        #if DEBUG
            print ("GVC viewDidLayoutSubviews")
        #endif

        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 {
        #if DEBUG
            print ("shouldAutorotate")
        #endif
        return false
    }
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        #if DEBUG
            print ("supportedInterfaceOrientations")
        #endif
        if UIDevice.current.userInterfaceIdiom == .phone {
            return .allButUpsideDown
        } else {
            return .all
        }
    }
    override var prefersStatusBarHidden: Bool {
        #if DEBUG
            print ("prefersStatusBarHidden")
        #endif
        return false
    }
}

The call to function setViewAttributes does pass the view to a function, and I have verified that that function is working.

func setViewAttributes(view: UIView)
{
    view.alpha                = 0.0
    view.frame.size.height    = UIScreen.main.nativeBounds.height
    view.frame.size.width     = UIScreen.main.nativeBounds.width
    view.translatesAutoresizingMaskIntoConstraints = false
    view.backgroundColor      = .clear
}
LuisC329
  • 131
  • 8

2 Answers2

0

You can't just move this kind of function because the view variable inside it refers to GameViewController.view.

If you want to do a refactor like this you can move this method to an extension and call it inside your GameViewController.

public extension UIView {
    func createConstraints() {
        guard let superview = superview else { return }

        view.translatesAutoresizingMaskIntoConstraints = false
        var constraints = [NSLayoutConstraint]()
                        
        constraints.append(leadingAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.leadingAnchor))
        constraints.append(trailingAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.trailingAnchor))
        constraints.append(bottomAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.bottomAnchor))
        constraints.append(topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor))
                        
        NSLayoutConstraint.activate(constraints)
    }
}

And then instead of calling addConstraints() in viewDidLoad() you should call myView.createConstraints(). It should work the same as before.

You can read more about extension from Extensions — The Swift Programming Language.

And also you should read this too Programmatically Creating Constraints

ibrahimyilmaz
  • 2,317
  • 1
  • 24
  • 28
  • Just to make sure, I was, trying, to pass the view into the function. Tried it directly, and as a reference. – LuisC329 Oct 30 '20 at 20:23
  • There is a function in there that already passes the view with no problem. Just updated the OP – LuisC329 Oct 30 '20 at 20:54
  • You better use constraints instead of setting frame. And extensions is a good way for this kind solutions. It's called Auto Layout yet another reading for you :) – ibrahimyilmaz Oct 31 '20 at 18:36
  • Am not using storyboard. Technically I can move from this, by leaving my original code. It's more of a "why can't I put this in a function properly" question. – LuisC329 Oct 31 '20 at 22:03
  • You can. But you're trying to add constraint from view to view. Second one should be superview. view.leadingAnchor.constraint(equalTo: superview.leadingAnchor) – ibrahimyilmaz Nov 01 '20 at 07:30
  • Am answering my own question. But please if you see anything wrong in the answer feel free to comment. – LuisC329 Nov 01 '20 at 18:20
-1

After going through the code, over and over I realized I was calling the safeAreaLayout routine before viewDidLayoutSubviews. Now I can move my call to a function and it works. Now I'd love to know why it was working before, when I was calling it prematurely. But maybe I should be grateful that I found a substantial error.

So now my code looks the same as the code in the OP, except every call regarding screen dimensions, and safeAreaLayout, are done in the viewDidLayoutSubviews function.

And addConstraints is now an external function

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    getScreenDimensions (screen: &screenDims)
    view.addSubview(myView)
    addConstraints()
    myGlobalVars.sceneRect = view.frame
    createConstraints(view: myView)
    
    if #available(iOS 11.0, *) {
        myGlobalVars.topSafeArea    = view.safeAreaInsets.top
        myGlobalVars.bottomSafeArea = view.safeAreaInsets.bottom
    } else {
        myGlobalVars.topSafeArea    = topLayoutGuide.length
        myGlobalVars.bottomSafeArea = bottomLayoutGuide.length
    }
}
LuisC329
  • 131
  • 8