Skip to content

Rebuild the app on iOS 26, SwiftUI and Swift 6 - #32

Open
bystritskiy wants to merge 1 commit into
developfrom
modernize/ios26
Open

Rebuild the app on iOS 26, SwiftUI and Swift 6#32
bystritskiy wants to merge 1 commit into
developfrom
modernize/ios26

Conversation

@bystritskiy

Copy link
Copy Markdown
Member

Why

The app on develop did not play. The 2019 SwiftUI rewrite left every screen as a // TODO: stub, the real game logic sat commented out in DEPRECATED/GameViewController.swift, and the project still targeted iOS 15 with Swift 5 behind a UIKit/storyboard launch path. This rebuilds it on a current stack and, in the process, makes it a working game again.

The game rules were recovered from the code that actually shipped to the App Store, which lives on master (HardcoreTap/HardcoreTap/ViewController.swift).

What changed

Project

  • project.pbxproj regenerated with PBXFileSystemSynchronizedRootGroup (Xcode 16+) β€” sources join the target by living in the folder, no per-file entries to maintain
  • iOS 15 β†’ iOS 26, Swift 5.0 β†’ Swift 6 language mode
  • Info.plist and LaunchScreen.storyboard removed; both are generated from INFOPLIST_KEY_* build settings
  • Added a unit test target; deleted the HardcoreTap for Watch scheme, which pointed at a target that has never existed in this project file

Code β€” 17 files replacing the previous tree

  • AppDelegate + SceneDelegate + storyboard β†’ @main App + TabView
  • Game rules ported into GameEngine, on @Observable and ContinuousClock. Every method takes the current instant as a parameter rather than reading the clock, which is what lets the tests play a full round β€” hits, misses, tolerance decay, restart cool-down β€” in 11 ms
  • The two hand-animated CAGradientLayers become a MeshGradient that pulses on the beat; the tolerance window is now visible as an arc on the beat ring, so the difficulty curve is legible at a glance
  • Firebase, AdMob and StoreKit leftovers removed; SwiftEntryKit replaced with native toasts β€” the app now has zero dependencies
  • Game Center moved from the long-deprecated GKScore.report to async GKLeaderboard

Tooling

  • Localizable.strings β†’ String Catalog, 58 keys with Russian translations
  • 16 Swift Testing cases covering the engine and record persistence
  • .swiftlint.yml: whitelist_rules β†’ only_rules; the old key is a hard error on current SwiftLint, so linting was failing outright
  • Fastfile no longer calls cocoapods β€” CocoaPods was removed in 2022 and the test lane has been broken since

Verification

Built, tested (16/16 passing) and run on an iPhone 17 simulator. The full loop works end to end: start β†’ tick β†’ missed-beat detection β†’ game-over screen. The nickname loads from the legacy userNAME key, and the Russian locale renders correctly.

Two defects found and fixed while verifying: the s unit was not localized, and Game Center flashed a blank sheet over the app at launch.

For the reviewer

  • Backwards compatibility: UserDefaults keys userNAME, highscore_normal and bgSound are kept verbatim, so an existing install keeps its nickname and record.
  • Game Center sign-in is deliberately not auto-presented. GKLocalPlayer's handler fires during launch and may hand back a view controller; presenting it immediately throws a modal over the game before the player has asked for anything, and when Game Center is misconfigured that modal is blank. The leaderboard offers a button instead.
  • The leaderboards do not exist in App Store Connect. The IDs in GameMode.leaderboardID are new. Until they are configured, submissions no-op and the leaderboard falls back to local records.
  • Hardcore mode uses a Β±0.01 s window. The original set tolerance to 0.0, which was only reachable because time was quantised to hundredths by a Timer; against a continuous clock it is unhittable. 0.01 s matches the "accurate to hundredths" the game describes.
  • The 1024 app icon is composited from the existing trophy artwork over the brand gradient β€” no 1024 source existed in the repo. Worth replacing if the vector original is around.
  • Snapfile deleted: it referenced a UI test target that has not been on this branch since 2019. Restore it alongside real UI tests if automated screenshots are wanted.

Unrelated, but worth knowing: master's Info.plist contains a live-looking Fabric API key in git history. Fabric is long dead, but this repository is public.

πŸ€– Generated with Claude Code

The working tree had drifted into a state where the app did not actually
play: the 2019 SwiftUI rewrite left every screen as a `// TODO:` stub, the
real game logic sat commented out in DEPRECATED/GameViewController.swift,
and the project still targeted iOS 15 with Swift 5 and a UIKit/storyboard
launch path.

Project
- Regenerate project.pbxproj using PBXFileSystemSynchronizedRootGroup, so
  sources join the target by living in the folder
- iOS 15 -> iOS 26, Swift 5.0 -> Swift 6 language mode
- Drop Info.plist and LaunchScreen.storyboard in favour of INFOPLIST_KEY_*
- Add a unit test target; delete the watch scheme, whose target never existed

Code
- Replace AppDelegate/SceneDelegate/storyboard with an @main App and TabView
- Port the game rules from the shipped 1.1 code on master into GameEngine,
  built on @observable and ContinuousClock. Every method takes the current
  instant as a parameter, which is what lets the tests play a full round
  without waiting on wall-clock time
- Redraw the two hand-animated CAGradientLayers as a MeshGradient that
  pulses on the beat, and surface the tolerance window as a ring arc
- Remove the Firebase, AdMob and StoreKit leftovers, and replace
  SwiftEntryKit with native toasts: the app now has no dependencies
- Move Game Center onto the async GKLeaderboard API, and let sign-in be
  something the player asks for rather than a modal thrown up at launch

Tooling
- Convert Localizable.strings to a String Catalog (en source, ru translated)
- Add 16 Swift Testing cases covering the engine and record persistence
- Fix .swiftlint.yml, whose whitelist_rules key current SwiftLint rejects
- Drop the cocoapods hook from the Fastfile; CocoaPods left in 2022, and the
  test lane has been broken ever since

UserDefaults keys userNAME, highscore_normal and bgSound are kept verbatim
so an existing install keeps its nickname and record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant