SKScene尺寸偏移求助:添加背景宽高比后场景右移
SKScene添加背景.fill后向右偏移的问题
当给背景图片设置.aspectRatio(contentMode: .fill)后,SKScene会向右偏移几个像素(已关闭场景透明度确认尺寸问题),想知道这是SwiftUI的bug还是代码问题。
相关代码
MenuScreenView.swift
import SwiftUI import SpriteKit struct MenuScreenView: View { @Bindable var player: Player private let screenWidth = UIScreen.main.bounds.width private let screenHeight = UIScreen.main.bounds.height var scene: SKScene { let scene = MenuScene() scene.scaleMode = .resizeFill scene.size = CGSize(width: screenWidth, height: screenHeight) return scene } var body: some View { ZStack { Image("background2") .resizable() .aspectRatio(contentMode: .fill) .ignoresSafeArea() SpriteView(scene: scene) .frame(width: screenWidth, height: screenHeight, alignment: .center) .edgesIgnoringSafeArea(.all) } } } #Preview { MenuScreenView(player: Player(name: "Name", score: 100, ship: .frigate, ammo: [.missile: 100])) }
MenuScene.swift
import SpriteKit class MenuScene: SKScene { override init() { super.init(size: UIScreen.main.bounds.size) self.backgroundColor = .clear } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func didMove(to view: SKView) { super.didMove(to: view) setupUI() } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let location = touch.location(in: self) let nodesAtPoint = nodes(at: location) for node in nodesAtPoint { if let nodeName = node.name { print("\(nodeName) button tapped!") } } } private func setupUI() { if isSavedGameExist() { createMenuButtonWith(continueTitle, at: CGPoint(x: frame.midX, y: frame.midY + 60)) } createMenuButtonWith(newGameTitle, at: CGPoint(x: frame.midX, y: frame.midY + 20)) createMenuButtonWith(scoreTitle, at: CGPoint(x: frame.midX, y: frame.midY - 20)) createMenuButtonWith(settingsTitle, at: CGPoint(x: frame.midX, y: frame.midY - 60)) } private func createMenuButtonWith(_ text: String, at position: CGPoint) { let button = MenuButton(buttonText: text) button.position = position addChild(button) } private func isSavedGameExist() -> Bool { // TODO: Add logic of check for existing game return true } // MARK: - Localization helpers private var newGameTitle: String { NSLocalizedString(Localizable.menuNewGameButtonTitle, comment: "") } private var settingsTitle: String { NSLocalizedString(Localizable.menuSettingsButtonTitle, comment: "") } private var scoreTitle: String { NSLocalizedString(Localizable.menuScoreButtonTitle, comment: "") } private var continueTitle: String { NSLocalizedString(Localizable.menuContinueButtonTitle, comment: "") } }
效果对比
- 偏移问题截图:

- 无.fill时正常效果:

问题原因及解决办法
原因
.aspectRatio(contentMode: .fill)会让图片按比例填充父容器,超出部分裁剪,这会导致ZStack内布局计算出现微小偏差;加上直接用UIScreen.main.bounds固定SpriteView尺寸,和SwiftUI的自适应布局系统不兼容,最终出现偏移。
修复方案
改用GeometryReader获取实际布局尺寸,让SpriteView和背景图片的尺寸完全匹配,调整后的代码如下:
修改后的MenuScreenView.swift
import SwiftUI import SpriteKit struct MenuScreenView: View { @Bindable var player: Player var body: some View { GeometryReader { geometry in ZStack { Image("background2") .resizable() .aspectRatio(contentMode: .fill) .frame(width: geometry.size.width, height: geometry.size.height) .ignoresSafeArea() SpriteView(scene: MenuScene(size: geometry.size)) .ignoresSafeArea() } } } } #Preview { MenuScreenView(player: Player(name: "Name", score: 100, ship: .frigate, ammo: [.missile: 100])) }
修改后的MenuScene.swift
import SpriteKit class MenuScene: SKScene { init(size: CGSize) { super.init(size: size) self.backgroundColor = .clear } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func didMove(to view: SKView) { super.didMove(to: view) setupUI() } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let location = touch.location(in: self) let nodesAtPoint = nodes(at: location) for node in nodesAtPoint { if let nodeName = node.name { print("\(nodeName) button tapped!") } } } private func setupUI() { if isSavedGameExist() { createMenuButtonWith(continueTitle, at: CGPoint(x: frame.midX, y: frame.midY + 60)) } createMenuButtonWith(newGameTitle, at: CGPoint(x: frame.midX, y: frame.midY + 20)) createMenuButtonWith(scoreTitle, at: CGPoint(x: frame.midX, y: frame.midY - 20)) createMenuButtonWith(settingsTitle, at: CGPoint(x: frame.midX, y: frame.midY - 60)) } private func createMenuButtonWith(_ text: String, at position: CGPoint) { let button = MenuButton(buttonText: text) button.position = position addChild(button) } private func isSavedGameExist() -> Bool { // TODO: Add logic of check for existing game return true } // MARK: - Localization helpers private var newGameTitle: String { NSLocalizedString(Localizable.menuNewGameButtonTitle, comment: "") } private var settingsTitle: String { NSLocalizedString(Localizable.menuSettingsButtonTitle, comment: "") } private var scoreTitle: String { NSLocalizedString(Localizable.menuScoreButtonTitle, comment: "") } private var continueTitle: String { NSLocalizedString(Localizable.menuContinueButtonTitle, comment: "") } }
修改说明
- 用
GeometryReader获取父容器的实际可用尺寸,替代UIScreen.main.bounds,避免和SwiftUI布局系统冲突。 - 背景图片明确设置frame为GeometryReader的尺寸,确保填充范围准确。
- SpriteView不再设置固定frame,直接跟随ZStack尺寸;初始化MenuScene时传入正确尺寸,保证SKScene坐标系统和SwiftUI布局完全匹配。
内容的提问来源于stack exchange,提问作者Anton_Kuznetsov
相关产品推荐
相关产品推荐

