SpriteKit - безопасная схема расположения Iphone X

Итак, в настоящее время у меня есть игра, которую я сделал в наборе спрайтов, и я использовал этот способ подгонки всего к размеру экрана:

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)

как вы можете видеть, он использует рамку для вычисления ширины и высоты вместе с положением узла на сцене, это работает со всеми размерами экрана, которые я использовал от 6 до 8 плюс. но когда дело доходит до iPhone X есть проблема с ним. кажется, что все растягивается из-за того, что размер экрана больше и странной формы, как вы можете видеть ниже по сравнению с iPhone 8 Plus

Я искал решения этой проблемы, но ни один из них не помог, а просто не усугубил ситуацию, и я не мог понять, как программно использовать макет безопасной области в моем проекте набора спрайтов, как в этом решении здесь

Мой вопрос

Как мне сделать так, чтобы все помещалось на экране 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) 

Получилось вот так, и это идеально:

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)

Интересный вопрос. Проблема возникает, когда мы пытаемся "преобразовать" нашу точку зрения в SKView создать нашу игровую сцену. По сути, мы могли бы перехватить этот момент (используя viewWillLayoutSubviews вместо viewDidLoad) и преобразуйте фрейм представления в соответствии со свойством фрейма макета safeAreaLayoutGuide.

Некоторый код в качестве примера:

GameViewController:

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("---")
    }
}

GameScene:

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 для запуска нашей сцены, можно использовать протоколы.

PS: внизу я привожу справочное изображение, где вы можете увидеть различные размеры между:

  • высота основного экрана (высота нашего обзора)
  • безопасный район
  • высота строки состояния (сверху 44 пикселя для iPhone X)

GameViewController:

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)
    }
}

GameScene:

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 мы можем установить это как GenericScene как этот пример:

GameViewController:

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()
        }
    }
}

GameScene:

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
        }
    }
}

Рекомендации:

Другие вопросы по тегам