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)

как вы можете видеть, он использует рамку для расчета ширины и высоты вместе с положением узла на сцене, это работает со всеми размерами экрана, которые я использовал от 6s до 8 Plus. но когда дело доходит до 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) 

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

введите описание изображения здесь


person Astrum    schedule 19.01.2018    source источник
comment
Я думаю, что вы должны следовать официальным рекомендациям о безопасной зоне, а не пытаться складывать, умножать, вычитать или делить значения в соответствии с вашим фактическим устройством. Предположим, что в ближайшие месяцы выйдет новое устройство, например iPhone 11, с новыми размерами безопасной зоны: нужно ли пересчитывать позиции всех объектов?   -  person Alessandro Ornano    schedule 23.01.2018


Ответы (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)
person Clafou    schedule 19.01.2018
comment
Я попробовал это, но это дает мне ошибку, я не знаю, что вы подразумеваете под частью yourSpriteKitView (что я должен поставить в этом месте?) - person Astrum; 20.01.2018
comment
Я не знаю, где ваш код, но если он находится в контроллере представления, вы можете использовать self.view вместо этого yourSpriteKitView. В принципе, любой представленный UIView должен подойти. - person Clafou; 21.01.2018
comment
Кажется, это работает хорошо, за исключением того, что каждый раз, когда я хочу это сделать, мне нужно определить вещь if # available(iOS 11.0, *) {, тогда я должен иметь эту строку в функции, и, по правде говоря, я на самом деле не хочу сделать это для всех моих узлов спрайтов, которые у меня есть на экране. во всяком случае вокруг этого? - person Astrum; 21.01.2018
comment
iPhone X может работать только с iOS 11 или более поздней версии, поэтому вы можете безопасно использовать нулевые вставки для iOS ‹ 11. Вы даже можете использовать вычисляемую переменную, например: вернуть self.view.safeAreaInsets } вернуть .zero } - person Clafou; 21.01.2018
comment
Хорошо, я использую эту переменную, описанную в моем GameScene, попробую использовать ее и скоро свяжусь с вами. - person Astrum; 21.01.2018
comment
Привет, я попробовал это, и, похоже, он работал для одного из моих узлов спрайта кнопки воспроизведения, но не для моей метки подсчета очков. не могли бы вы взглянуть на РЕДАКТИРОВАТЬ 5, пожалуйста, спасибо. - person Astrum; 22.01.2018
comment
Это сработало только частично и по какой-то причине не работает для моей метки подсчета очков, можно было бы взглянуть на редактирование 5, спасибо, я увеличил ваш ответ. - person Astrum; 22.01.2018
comment
Все это звучит немного запутанно, я бы посоветовал вам попробовать разобраться на бумаге и соответствующим образом написать свой код. Деление высоты экрана на 2,02 — это странно. возможно, вам следует добавить высоту, а затем вычесть половину высоты спрайта плюс какое-то поле или что-то в этом роде. В любом случае удачи. - person Clafou; 22.01.2018
comment
Привет, спасибо за вашу помощь, это работает для нижней части экрана, так как я тестировал его в новом проекте. проблема сейчас в том, что верхняя часть экрана, кажется, не хочет делать то же самое. не могли бы вы помочь мне с этим, пожалуйста, я разместил еще один вопрос об этом здесь: топ">stackoverflow.com/questions/48483786/ - person Astrum; 28.01.2018

Интересный вопрос. Проблема возникает, когда мы пытаемся «преобразовать» наш представление в 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("---")
    }
}

Игровая сцена:

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.: ниже я сообщаю об эталонном изображении, на котором вы можете увидеть разные размеры между:

  • высота основного экрана (наша высота просмотра)
  • безопасный район
  • высота строки состояния (вверху, 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)
    }
}

Игровая сцена:

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

Игровая сцена:

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

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

введите описание изображения здесь

person Alessandro Ornano    schedule 19.01.2018
comment
Привет, спасибо за ответ, у этого есть проблемы, во-первых, когда я нажимаю кнопку рекламы в правом верхнем углу экрана, которая вызывает меню, содержащее UIlabel, который создает подвид, он не показывает остальную часть меню и вместо этого уменьшает размер экрана, как показано в редактировании 2: я также предоставил код UILabel. - person Astrum; 20.01.2018
comment
Эта проблема также сохраняется через другие экраны меню, к которым я пытаюсь получить доступ в игре, которые не работают должным образом или даже не показывают их. - person Astrum; 20.01.2018
comment
Ваша проблема может возникнуть, если ваш рекламный SDK не обновлен или не соответствует безопасной зоне. Вы уверены, что он совместим с iOS 11 и iPhoneX? - person Alessandro Ornano; 20.01.2018
comment
Да, кнопка рекламы на самом деле не показывает рекламу, она просто показывает меню, где люди могут купить часть игры без рекламы. Также мои рекламные SDK обновлены до последней версии на вчерашний день. - person Astrum; 20.01.2018
comment
Не могли бы вы сказать мне, что это за SDK? У вас есть веб-ссылка, чтобы добраться до него? - person Alessandro Ornano; 20.01.2018
comment
Проблема не в SDK и даже не в рекламе, я думаю, что проблема заключается в том, что UILabel создает подвид. например, когда вы в приведенном выше решении переопределяете функцию viewWillLayoutSubviews, это то, что вызывает конфликт. см. РЕДАКТИРОВАТЬ 2 в исходном сообщении - person Astrum; 20.01.2018
comment
Метод viewWillLayoutSubviews вызывается после viewDidLoad, вы также можете протестировать его, используя точки останова на обоих методах. Таким образом, если viewDidLoad уже запущен, все подпредставления могут присутствовать и отображаться на экране. Я думаю, вы просто перехватываете это/эти виды и меняете их/их положение в соответствии с безопасной зоной. Я могу помочь вам, если вы покажете больше кода. - person Alessandro Ornano; 20.01.2018
comment
В любом случае, я не могу иметь черные полосы на экране и просто настраивать свои узлы, такие как метки для безопасной зоны? без переопределения функции в контроллере представления (не могли бы вы взглянуть на решение Clafou для этого и сказать мне, что вы думаете.) Потому что я действительно не хочу черных полос на экране iPhone, если я могу помочь. - person Astrum; 20.01.2018
comment
Следовали ли вы также инструкциям о черных полосах, показанным, например, в этом ответе? Взгляните также на эту ссылку gitHub со всеми последними образами запуска iOS. Сообщите мне ваши результаты. - person Alessandro Ornano; 20.01.2018
comment
Привет, спасибо за вашу помощь с этим, я взял точный код для другого решения и скопировал его прямо в новый проект Swift, который полностью обновлен и готов для ios 11, и он не вышел, как на вашем скриншоте, но пришел как в редактировании 3 в моем исходном посте. (я думаю, что это как-то связано с частью навигации) Также возможно выполнить редактирование 4 и просто получить вставки безопасной области представления spritekit вместо того, чтобы писать весь этот код в представлении контроллер и игровая сцена, если да, то что бы я поставил в качестве части SpritekitView? (как видно в редактировании 4) - person Astrum; 21.01.2018
comment
Привет, Astrum, здесь вы можете найти страницу gitHub с моими источниками. Что касается вашего редактирования 4, вы всегда должны использовать протоколы, потому что вы запустили свою сцену из viewDidLoad, поэтому сцена была построена на этапе, когда мы еще не знаем правильного размера безопасной области. С моим протоколом вы можете сделать, внутри safeAreaUpdated делаем также: if let view = self.view { let safeAreaInsets = view.safeAreaInsets } - person Alessandro Ornano; 21.01.2018
comment
Я добавил также третий способ сделать это: через подклассы. Выберите более удобный для вас метод, возможно, этот последний позволит вам оставить нетронутым ваш код, просто добавив другой код ниже ваших классов... - person Alessandro Ornano; 21.01.2018