SpriteKit - 安全区域布局 Iphone X

Posted

技术标签:

【中文标题】SpriteKit - 安全区域布局 Iphone X【英文标题】:SpriteKit - safe area layout Iphone X 【发布时间】:2018-01-19 03:52:57 【问题描述】:

所以,目前我有一个我用 sprite kit 制作的游戏,并使用这种方式使所有内容都适合屏幕尺寸:

buyButton = SKSpriteNode(texture: SKTexture(imageNamed: "BuyButton"), color: .clear, size: CGSize(width: frame.maxX / 2.9, height: frame.maxY / 10))
buyButton.position = CGPoint(x:-frame.maxX + frame.midX*2, y: -frame.maxY + frame.midY*1.655)
addChild(buyButton)

您可以看到它使用框架来计算宽度和高度以及场景中节点的位置,这适用于我从 6s 到 8 Plus 使用的所有屏幕尺寸。 但是当谈到 iPhone X 时,它就出现了问题。它似乎拉伸了一切,因为与 iPhone 8 Plus 相比,屏幕尺寸更大且形状奇怪,如下图所示

我一直在寻找这个问题的解决方案,但没有一个真正有帮助,甚至使情况变得更糟,我无法理解如何在我的 sprite kit 项目中以编程方式使用安全区域布局,就像这个解决方案 here

我的问题是

如何让所有内容都适合 iPhone X 的屏幕,以使其适合而不切断右上角的乐谱标签和内容?

编辑 2:

itemDescriptionLabel = UILabel(frame: CGRect(x: frame.maxX - 150, y: frame.maxY - 130 , width: frame.maxX / 0.7, height: frame.maxY / 3.5))
itemDescriptionLabel.font = UIFont(name: "SFCompactRounded-Light", size: 17)
itemDescriptionLabel.text = "This purchase stops all the popup ads that happen after a certain amount of time playing the game from showing up."
itemDescriptionLabel.numberOfLines = 4
itemDescriptionLabel.adjustsFontSizeToFitWidth = true
self.scene?.view?.addSubview(itemDescriptionLabel)

编辑 3

编辑 4

let safeAreaInsets = yourSpriteKitView.safeAreaInsets;
buyButton.position = CGPoint(x:safeAreaInsets.left - frame.maxX + frame.midX*2, y: safeAreaInsets.top - frame.maxY + frame.midY*1.655)

编辑 5

screenWidth = self.view!.bounds.width
screenHeight = self.view!.bounds.height

coinScoreLabel = Score(num: 0,color: UIColor(red:1.0, green: 1.0, blue: 0.0, alpha: 1), size: 50, useFont: "SFCompactRounded-Heavy" )  // UIColor(red:1.00, green:0.81, blue:0.07, alpha:1.0)
coinScoreLabel.name = "coinScoreLabel"
coinScoreLabel.horizontalAlignmentMode = .right
//coinScoreLabel.verticalAlignmentMode = .top
coinScoreLabel.position = CGPoint(x: screenWidth / 2.02, y: inset.top - screenHeight / 2.02)
coinScoreLabel.zPosition = 750
addChild(coinScoreLabel)

我在我拥有的另一个 SpriteNode 上尝试了它,例如下面的这个,它适用于我不知道为什么 黄色标签 会这样做。

 playButton = SKSpriteNode(texture: SKTexture(imageNamed: "GameOverMenuPlayButton"), color: .clear, size: CGSize(width: 120, height: 65))
 playButton.position = CGPoint(x: inset.right - frame.midX, y: inset.bottom + frame.minY + playButton.size.height / 1.7)
 playButton.zPosition = 1000
 deathMenuNode.addChild(playButton) 

这样出来的很完美:

【问题讨论】:

我认为您应该遵循有关安全区域的官方指南,而不是根据您的实际设备尝试加、乘、减或除值。假设未来几个月推出一款新设备,例如 iPhone 11,具有新的安全区尺寸:您是否应该重新计算所有对象的位置? 【参考方案1】:

有趣的问题。当我们尝试将我们的view“转换”为SKView 来创建我们的游戏场景时,问题就出现了。本质上,我们可以截取那个时刻(使用viewWillLayoutSubviews 而不是viewDidLoad)并根据safeAreaLayoutGuide 布局框架属性转换视图框架。

一些代码作为例子:

游戏视图控制器:

class GameViewController: UIViewController 
    override func viewWillLayoutSubviews() 
        super.viewWillLayoutSubviews()
        if #available(ios 11.0, *), let view = self.view 
           view.frame = self.view.safeAreaLayoutGuide.layoutFrame
          
        guard let view = self.view as! SKView? else  return 
        view.ignoresSiblingOrder = true
        view.showsFPS = true
        view.showsNodeCount = true
        view.showsPhysics = true
        view.showsDrawCount = true
        let scene = GameScene(size:view.bounds.size)
        scene.scaleMode = .aspectFill
        view.presentScene(scene)
    
    override func viewDidLoad() 
        super.viewDidLoad()
        print("---")
        print("∙ \(type(of: self))")
        print("---")
    

游戏场景:

class GameScene: SKScene 
    override func didMove(to view: SKView) 
        print("---")
        print("∙ \(type(of: self))")
        print("---")
        let labLeftTop = SKLabelNode.init(text: "LEFTTOP")
        labLeftTop.horizontalAlignmentMode = .left
        labLeftTop.verticalAlignmentMode = .top
        let labRightTop = SKLabelNode.init(text: "RIGHTTOP")
        labRightTop.horizontalAlignmentMode = .right
        labRightTop.verticalAlignmentMode = .top
        let labLeftBottom = SKLabelNode.init(text: "LEFTBTM")
        labLeftBottom.horizontalAlignmentMode = .left
        labLeftBottom.verticalAlignmentMode = .bottom
        let labRightBottom = SKLabelNode.init(text: "RIGHTBTM")
        labRightBottom.horizontalAlignmentMode = .right
        labRightBottom.verticalAlignmentMode = .bottom
        self.addChild(labLeftTop)
        self.addChild(labRightTop)
        self.addChild(labLeftBottom)
        self.addChild(labRightBottom)
        labLeftTop.position = CGPoint(x:0,y:self.frame.height)
        labRightTop.position = CGPoint(x:self.frame.width,y:self.frame.height)
        labLeftBottom.position = CGPoint(x:0,y:0)
        labRightBottom.position = CGPoint(x:self.frame.width,y:0)
    

其他方法:

另一种仅获取安全区域布局框架和当前视图框架之间的差异而不通过viewWillLayoutSubviews 来启动我们的场景的方法是使用协议。

P.S.:在下面,我报告了一张参考图片,您可以在其中看到不同的尺寸:

主屏幕高度(我们的视图高度) 安全区 状态栏高度(在顶部,iPhone X 为 44 像素)

游戏视图控制器:

protocol LayoutSubviewDelegate: class 
    func safeAreaUpdated()

class GameViewController: UIViewController 
    weak var layoutSubviewDelegate:LayoutSubviewDelegate?
    override func viewWillLayoutSubviews() 
        super.viewWillLayoutSubviews()
        if let _ = self.view 
           layoutSubviewDelegate?.safeAreaUpdated()
        
    
    override func viewDidLoad() 
        super.viewDidLoad()
        print("---")
        print("∙ \(type(of: self))")
        print("---")
        guard let view = self.view as! SKView? else  return 
        view.ignoresSiblingOrder = true
        view.showsFPS = true
        view.showsNodeCount = true
        view.showsPhysics = true
        view.showsDrawCount = true
        let scene = GameScene(size:view.bounds.size)
        scene.scaleMode = .aspectFill
        view.presentScene(scene)
    

游戏场景:

class GameScene: SKScene,LayoutSubviewDelegate 
    var labLeftTop:SKLabelNode!
    var labRightTop:SKLabelNode!
    var labLeftBottom:SKLabelNode!
    var labRightBottom:SKLabelNode!
    func safeAreaUpdated() 
        if let view = self.view 
            let top = view.bounds.height-(view.bounds.height-view.safeAreaLayoutGuide.layoutFrame.height)+UIApplication.shared.statusBarFrame.height
            let bottom = view.bounds.height - view.safeAreaLayoutGuide.layoutFrame.height-UIApplication.shared.statusBarFrame.height
            refreshPositions(top: top, bottom: bottom)
        
    
    override func didMove(to view: SKView) 
        print("---")
        print("∙ \(type(of: self))")
        print("---")
        if let view = self.view, let controller = view.next, controller is GameViewController 
            (controller as! GameViewController).layoutSubviewDelegate = self
        
        labLeftTop = SKLabelNode.init(text: "LEFTTOP")
        labLeftTop.horizontalAlignmentMode = .left
        labLeftTop.verticalAlignmentMode = .top
        labRightTop = SKLabelNode.init(text: "RIGHTTOP")
        labRightTop.horizontalAlignmentMode = .right
        labRightTop.verticalAlignmentMode = .top
        labLeftBottom = SKLabelNode.init(text: "LEFTBTM")
        labLeftBottom.horizontalAlignmentMode = .left
        labLeftBottom.verticalAlignmentMode = .bottom
        labRightBottom = SKLabelNode.init(text: "RIGHTBTM")
        labRightBottom.horizontalAlignmentMode = .right
        labRightBottom.verticalAlignmentMode = .bottom
        self.addChild(labLeftTop)
        self.addChild(labRightTop)
        self.addChild(labLeftBottom)
        self.addChild(labRightBottom)
        labLeftTop.position = CGPoint(x:0,y:self.frame.height)
        labRightTop.position = CGPoint(x:self.frame.width,y:self.frame.height)
        labLeftBottom.position = CGPoint(x:0,y:0)
        labRightBottom.position = CGPoint(x:self.frame.width,y:0)
    
    func refreshPositions(top:CGFloat,bottom:CGFloat)
        labLeftTop.position = CGPoint(x:0,y:top)
        labRightTop.position = CGPoint(x:self.frame.width,y:top)
        labLeftBottom.position = CGPoint(x:0,y:bottom)
        labRightBottom.position = CGPoint(x:self.frame.width,y:bottom)
    

输出:

子类化方法:

另一种在不触及当前类代码的情况下获得正确安全区域尺寸的方法可以使用一些子类,因此对于我们的GameViewController,我们将其设置为GameController,对于GameScene,我们可以将其设置为@ 987654337@这样的例子:

游戏视图控制器:

class GameViewController: GameController 
    override func viewDidLoad() 
        super.viewDidLoad()
        guard let view = self.view as! SKView? else  return 
        view.ignoresSiblingOrder = true
        let scene = GameScene(size:view.bounds.size)
        scene.scaleMode = .aspectFill
        view.presentScene(scene)
    

protocol LayoutSubviewDelegate: class 
    func safeAreaUpdated()

class GameController:UIViewController 
    weak var layoutSubviewDelegate:LayoutSubviewDelegate?
    override func viewWillLayoutSubviews() 
        super.viewWillLayoutSubviews()
        if let _ = self.view 
            layoutSubviewDelegate?.safeAreaUpdated()
        
    

游戏场景:

class GameScene: GenericScene 
    override func safeAreaUpdated() 
        super.safeAreaUpdated()
        if let view = self.view 
            let insets = view.safeAreaInsets
            // launch your code to update positions here
        
    
    override func didMove(to view: SKView) 
        super.didMove(to: view)
        // your stuff
    

class GenericScene: SKScene, LayoutSubviewDelegate 
    func safeAreaUpdated()
    override func didMove(to view: SKView) 
        if let view = self.view, let controller = view.next, controller is GameViewController 
            (controller as! GameViewController).layoutSubviewDelegate = self
        
    

参考资料:

【讨论】:

嗨,谢谢你的回复,这有问题,首先当我点击屏幕右上角的广告按钮时,它会弹出一个菜单,其中包含一个 UIlabel,它不会生成一个子视图显示菜单的其余部分,而是减小屏幕的大小,如编辑 2 所示:我也提供了 UILabel 的代码。 这个问题在我尝试在游戏中访问的其他菜单屏幕上仍然存在,这些屏幕不能正常工作或什至没有显示。 如果您的广告 SDK 不是最新的或不遵守安全区域,则可能会出现您的问题,您确定此 SDK 兼容 iOS 11 和 iPhoneX 吗? 是的,广告按钮实际上并没有显示广告,它只是显示了一个菜单,人们可以在其中购买游戏的无广告部分。我的广告 SDK 也更新到昨天的最新版本 你能告诉我这个SDK是什么吗?您有可以访问它的网络链接吗?【参考方案2】:

您可以通过视图的safeAreaInsets 获得边距。大多数设备上的插图为零,但在 iPhone X 上非零。

例如,将您的行替换为:

let safeAreaInsets = yourSpriteKitView.safeAreaInsets;
buyButton.position = CGPoint(x:safeAreaInsets.left - frame.maxX + frame.midX*2, y: safeAreaInsets.top - frame.maxY + frame.midY*1.655)

【讨论】:

我试过这个但它给了我一个错误,我不知道你的 yourSpriteKitView 部分是什么意思(我在那个地方放了什么?) 我不知道你的代码在哪里,但如果它在视图控制器中,那么你可以使用self.view 代替yourSpriteKitView。基本上任何呈现的 UIView 都应该这样做。 这似乎运作良好,除非每次我想这样做我都必须定义 if #available(iOS 11.0, *) 事情然后我必须在函数中有该行并告诉你说实话,我真的不想为我在屏幕上的所有精灵节点这样做。无论如何? iPhone X 只能运行 iOS 11 或更高版本,因此您可以安全地为 iOS 好的,所以我正在使用它,我的 GameScene 中描述的变量将尝试使用它并尽快回复您

以上是关于SpriteKit - 安全区域布局 Iphone X的主要内容,如果未能解决你的问题,请参考以下文章

如何在 iPhone x 中设置安全区域布局

如何使用自动布局在 iphone x 中处理具有安全区域的键盘? [复制]

使用 iPhone X 的安全区域指南计算 UIView 的高度

安全区域布局指南不适用于 UITableView 的 BackgroundView

我正在使用 SpriteKit 制作游戏,但我的视图只占据了屏幕上看似安全的区域。如何扩展此视图?

自动布局无法正确处理 iPhone X 缺口