Add macOS Catalyst support for RollkofferSimulator
- Enable Mac Catalyst in Xcode project (SUPPORTS_MACCATALYST=YES) - Set macOS deployment target to 13.0 (Ventura+) - Add keyboard support for all scenes (Escape, Space, Enter) - Add macOS menu bar with game commands (Cmd+P pause, Cmd+R restart) - Configure window size restrictions for macOS - Update Info.plist with macOS minimum version
This commit is contained in:
@@ -14,6 +14,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
func application(_ application: UIApplication,
|
func application(_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
// Configure for macOS
|
||||||
|
configureMacOS()
|
||||||
|
#endif
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,10 +37,60 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||||
// Resume game if needed
|
// Resume game if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
// MARK: - macOS Configuration
|
||||||
|
private func configureMacOS() {
|
||||||
|
// Set minimum window size for macOS
|
||||||
|
UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.forEach { windowScene in
|
||||||
|
windowScene.sizeRestrictions?.minimumSize = CGSize(width: 400, height: 600)
|
||||||
|
windowScene.sizeRestrictions?.maximumSize = CGSize(width: 600, height: 900)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func buildMenu(with builder: UIMenuBuilder) {
|
||||||
|
super.buildMenu(with: builder)
|
||||||
|
|
||||||
|
// Remove unnecessary menus for a game
|
||||||
|
builder.remove(menu: .format)
|
||||||
|
builder.remove(menu: .edit)
|
||||||
|
|
||||||
|
// Add Game menu
|
||||||
|
let pauseCommand = UIKeyCommand(
|
||||||
|
title: "Pause",
|
||||||
|
action: #selector(handlePauseCommand),
|
||||||
|
input: "p",
|
||||||
|
modifierFlags: .command
|
||||||
|
)
|
||||||
|
|
||||||
|
let restartCommand = UIKeyCommand(
|
||||||
|
title: "Neustart",
|
||||||
|
action: #selector(handleRestartCommand),
|
||||||
|
input: "r",
|
||||||
|
modifierFlags: .command
|
||||||
|
)
|
||||||
|
|
||||||
|
let gameMenu = UIMenu(
|
||||||
|
title: "Spiel",
|
||||||
|
children: [pauseCommand, restartCommand]
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.insertSibling(gameMenu, afterMenu: .file)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handlePauseCommand() {
|
||||||
|
NotificationCenter.default.post(name: .pauseGame, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleRestartCommand() {
|
||||||
|
NotificationCenter.default.post(name: .restartGame, object: nil)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Notification Names
|
// MARK: - Notification Names
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
static let pauseGame = Notification.Name("pauseGame")
|
static let pauseGame = Notification.Name("pauseGame")
|
||||||
static let resumeGame = Notification.Name("resumeGame")
|
static let resumeGame = Notification.Name("resumeGame")
|
||||||
|
static let restartGame = Notification.Name("restartGame")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ class GameViewController: UIViewController {
|
|||||||
|
|
||||||
// Setup notification observers
|
// Setup notification observers
|
||||||
setupNotificationObservers()
|
setupNotificationObservers()
|
||||||
|
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
setupMacCatalyst()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupNotificationObservers() {
|
private func setupNotificationObservers() {
|
||||||
@@ -45,6 +49,13 @@ class GameViewController: UIViewController {
|
|||||||
name: .pauseGame,
|
name: .pauseGame,
|
||||||
object: nil
|
object: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(handleRestartNotification),
|
||||||
|
name: .restartGame,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handlePauseNotification() {
|
@objc private func handlePauseNotification() {
|
||||||
@@ -57,12 +68,46 @@ class GameViewController: UIViewController {
|
|||||||
// This is just a notification that the app is going to background
|
// This is just a notification that the app is going to background
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func handleRestartNotification() {
|
||||||
|
guard let skView = self.view as? SKView else { return }
|
||||||
|
|
||||||
|
let menuScene = MenuScene(size: skView.bounds.size)
|
||||||
|
menuScene.scaleMode = .aspectFill
|
||||||
|
|
||||||
|
let transition = SKTransition.fade(withDuration: 0.5)
|
||||||
|
skView.presentScene(menuScene, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
private func setupMacCatalyst() {
|
||||||
|
// Configure window appearance for macOS
|
||||||
|
if let windowScene = view.window?.windowScene {
|
||||||
|
windowScene.title = "Rollkoffer Simulator"
|
||||||
|
|
||||||
|
// Set window style
|
||||||
|
if let titlebar = windowScene.titlebar {
|
||||||
|
titlebar.titleVisibility = .visible
|
||||||
|
titlebar.toolbarStyle = .unified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable keyboard input
|
||||||
|
override var canBecomeFirstResponder: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
return .all
|
||||||
|
#else
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
return .portrait
|
return .portrait
|
||||||
} else {
|
} else {
|
||||||
return .all
|
return .all
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
override var prefersStatusBarHidden: Bool {
|
override var prefersStatusBarHidden: Bool {
|
||||||
|
|||||||
@@ -50,5 +50,9 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>13.0</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Copyright 2024 Ingo K. All rights reserved.</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -381,6 +381,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = Info.plist;
|
INFOPLIST_FILE = Info.plist;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
@@ -393,9 +394,12 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.ingok.RollkofferSimulator;
|
PRODUCT_BUNDLE_IDENTIFIER = com.ingok.RollkofferSimulator;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTS_MACCATALYST = YES;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
@@ -409,6 +413,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = Info.plist;
|
INFOPLIST_FILE = Info.plist;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
@@ -421,9 +426,12 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.ingok.RollkofferSimulator;
|
PRODUCT_BUNDLE_IDENTIFIER = com.ingok.RollkofferSimulator;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTS_MACCATALYST = YES;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
|||||||
@@ -223,6 +223,27 @@ class GameOverScene: SKScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Keyboard Handling (macOS)
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
override var canBecomeFirstResponder: Bool { true }
|
||||||
|
|
||||||
|
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
||||||
|
guard let key = presses.first?.key else {
|
||||||
|
super.pressesBegan(presses, with: event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key.keyCode {
|
||||||
|
case .keyboardSpacebar, .keyboardReturnOrEnter:
|
||||||
|
retryGame()
|
||||||
|
case .keyboardEscape:
|
||||||
|
returnToMenu()
|
||||||
|
default:
|
||||||
|
super.pressesBegan(presses, with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private func retryGame() {
|
private func retryGame() {
|
||||||
let pressDown = SKAction.scale(to: 0.9, duration: 0.1)
|
let pressDown = SKAction.scale(to: 0.9, duration: 0.1)
|
||||||
let pressUp = SKAction.scale(to: 1.0, duration: 0.1)
|
let pressUp = SKAction.scale(to: 1.0, duration: 0.1)
|
||||||
|
|||||||
@@ -318,6 +318,29 @@ class GameScene: SKScene {
|
|||||||
isDragging = false
|
isDragging = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Keyboard Handling (macOS)
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
override var canBecomeFirstResponder: Bool { true }
|
||||||
|
|
||||||
|
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
||||||
|
guard let key = presses.first?.key else {
|
||||||
|
super.pressesBegan(presses, with: event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key.keyCode {
|
||||||
|
case .keyboardEscape:
|
||||||
|
togglePause()
|
||||||
|
case .keyboardSpacebar:
|
||||||
|
if gameState.currentState == .paused {
|
||||||
|
resumeGame()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
super.pressesBegan(presses, with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// MARK: - Pause Handling
|
// MARK: - Pause Handling
|
||||||
private func togglePause() {
|
private func togglePause() {
|
||||||
if gameState.currentState == .playing {
|
if gameState.currentState == .playing {
|
||||||
|
|||||||
@@ -245,6 +245,25 @@ class MenuScene: SKScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Keyboard Handling (macOS)
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
override var canBecomeFirstResponder: Bool { true }
|
||||||
|
|
||||||
|
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
||||||
|
guard let key = presses.first?.key else {
|
||||||
|
super.pressesBegan(presses, with: event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key.keyCode {
|
||||||
|
case .keyboardSpacebar, .keyboardReturnOrEnter:
|
||||||
|
startGame()
|
||||||
|
default:
|
||||||
|
super.pressesBegan(presses, with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private func startGame() {
|
private func startGame() {
|
||||||
// Button press effect
|
// Button press effect
|
||||||
let pressDown = SKAction.scale(to: 0.9, duration: 0.1)
|
let pressDown = SKAction.scale(to: 0.9, duration: 0.1)
|
||||||
|
|||||||
@@ -280,6 +280,27 @@ class VictoryScene: SKScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Keyboard Handling (macOS)
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
override var canBecomeFirstResponder: Bool { true }
|
||||||
|
|
||||||
|
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
||||||
|
guard let key = presses.first?.key else {
|
||||||
|
super.pressesBegan(presses, with: event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key.keyCode {
|
||||||
|
case .keyboardSpacebar, .keyboardReturnOrEnter:
|
||||||
|
playAgain()
|
||||||
|
case .keyboardEscape:
|
||||||
|
returnToMenu()
|
||||||
|
default:
|
||||||
|
super.pressesBegan(presses, with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private func playAgain() {
|
private func playAgain() {
|
||||||
let pressDown = SKAction.scale(to: 0.9, duration: 0.1)
|
let pressDown = SKAction.scale(to: 0.9, duration: 0.1)
|
||||||
let pressUp = SKAction.scale(to: 1.0, duration: 0.1)
|
let pressUp = SKAction.scale(to: 1.0, duration: 0.1)
|
||||||
|
|||||||
Reference in New Issue
Block a user