Run again#2
Open
PullFrom wants to merge 33 commits into
Open
Conversation
- Remove retain/release/autorelease calls - Replace NSAutoreleasePool with @autoreleasepool in main.m - Convert @Property (retain) to @Property (strong) - Remove dealloc bodies that only contained release calls CocosDenshion files excluded via -fno-objc-arc compiler flag.
- Remove retain/release/autorelease calls across CardEngine and Classes - Replace NSAutoreleasePool with @autoreleasepool in main.m - Convert @Property (retain) to (strong) and delegate properties to (weak) - Preserve dealloc methods that perform observer/timer cleanup - CocosDenshion remains under MRC via -fno-objc-arc compiler flag
- Add LSAudioEngine class providing the same minimal API surface (sharedEngine, preloadEffect:, playEffect:) used by the app - Use AVAudioPlayer with AVAudioSessionCategoryAmbient so sounds mix with other audio and respect the silent switch - Rewrite LabSolitaireViewController.m to use LSAudioEngine - Remove all CocosDenshion source file references from the project
The placeholder listed missing third-party files that have now been replaced by LSAudioEngine.
- Replace require/require_quiet macros with __Require/__Require_Quiet across 7 files (73 conversions); add #include <AssertMacros.h> where needed - Add __bridge casts in animation context handling (CEStackView.m, LabSolitaireViewController.m) - Annotate weak delegate ivars with __weak to match property attributes - Refactor 4 methods in LocalPlayer.m to use early return instead of goto-past-block, which is illegal under ARC - Replace deprecated applicationFrame and statusBarOrientation in CETableView.m Project now builds cleanly on Xcode 26 / iOS 17.
- Add LabSolitaireSceneDelegate adopting UIWindowSceneDelegate - Make LabSolitaireAppDelegate inherit from UIResponder; remove window/viewController properties (now scene-owned) - Move window setup, root view controller, splash, and state save/restore from AppDelegate to SceneDelegate - Replace NSMainNibFile in Info.plist with UIApplicationSceneManifest declaring LabSolitaireSceneDelegate as the scene delegate - Use NSStringFromClass in main.m for type-safe delegate reference - Remove unused MainWindow.xib and placeholder LabSolitaire.storyboard from project (files retained on disk for now) App now launches into the splash screen and accepts touch events.
- Guard info: against re-entry when info view is already open; delegate to _addInfoSubview: for tab switch instead of full re-open - Fix _addInfoSubview: animation context to track the actual subview, so _currentInfoView reflects the visible tab and close dismisses the right view
The card-move and card-flip animations in CEStackView passed an NSMutableDictionary holding all animation parameters through beginAnimations:context: as a void pointer, retrieving it in the animationStopped:finished:context: callback. Both casts used plain __bridge, which under ARC does not transfer ownership. The dictionary was a local object — once the originating method returned, ARC released it, and by the time the animation completed the void* context pointed to recycled memory (manifesting as a phantom UIImageConfiguration receiving objectForKey:). This crashed on every double-tap auto-move and on auto-putaway. - Line 2700: __bridge -> __bridge_retained when passing to context - Line 2734: __bridge -> __bridge_transfer when retrieving from context The pair is balanced: __bridge_retained adds a +1 retain that survives the animation; __bridge_transfer hands ownership back to ARC, which releases the dictionary normally when the local goes out of scope. Audited the remaining __bridge casts in LabSolitaireViewController.m; they pass strong IBOutlets (_aboutView, _settingsView, _rulesView, _gameOverView) which are guaranteed alive for the animation lifetime, so plain __bridge is correct there. The game now plays end-to-end: drag-and-drop, double-tap auto-move, auto-putaway, and multi-card moves all work.
- Replace deprecated willRotateToInterfaceOrientation: with modern viewWillTransitionToSize:withTransitionCoordinator: - Use self.view.bounds instead of [UIScreen mainScreen].bounds for orientation-aware layout - Reset _darkView.userInteractionEnabled immediately in closeInfo: to prevent touch blocking when rotation interrupts the animation - Apply correct layout on initial display via viewDidLayoutSubviews with one-shot guard (_initialLayoutApplied), replacing deprecated self.interfaceOrientation
The view controller is now the single source of truth for orientation. CETableView previously detected its orientation via window scene iteration in drawRect:, which was unreliable on first draw — especially in the Simulator, where bounds and scene state may not be finalized before the initial render, causing the background to appear upside-down and grayscale until the first rotation. - Add BOOL landscape property to CETableView, set by the view controller in adjustLayoutForOrientation: - Custom setter triggers setNeedsDisplay only on value change to avoid redundant redraws during card animations - Set contentMode = UIViewContentModeRedraw so bounds changes also trigger a fresh drawRect:
UIAlertView is fully unavailable in scene-based apps since iOS 13 and crashed at runtime when tapping "New" or long-pressing "Undo". - New Game confirmation: cancel + destructive "New Game" actions - Undo All confirmation: cancel + destructive "Undo All" actions, both handlers reset _undoAllAlertOpen - Remove obsolete alertView:clickedButtonAtIndex: delegate method and the kResetTableAlertTag / kUndoAllAlertTag constants
App Store launch buttons in the About panel (Lab Solitaire, Parlour Solitaire, Glider Classic) used the iOS 10 deprecated openURL: API. Switched to the modern variant — same URLs, no behavioral change. Note: Soft Dorothy's products may no longer be available in the App Store. The links and the About panel content should be reviewed separately as a product decision.
Replaces the deprecated UIKit animation pattern (begin/commitAnimations with delegate callback) with the modern block-based API in LabSolitaireViewController.m. Five animation blocks migrated: - info: (about panel slide-in) - _addInfoSubview: (tab crossfade) - closeInfo: (panel slide-out, including auto-putaway and game-won reset logic in completion handler) - openGameOverView: (both branches: crossfade and slide-in with Babip win sound in completion handler) The animationStopped:finished:context: dispatcher and its #pragma mark are no longer needed and have been removed. Eliminates 14 deprecation warnings from this file. CEStackView.m still has 11 such warnings to be addressed in a separate step.
_aboutView was a 768×1024 transparent wrapper around a single 708×708 inner container, nested inside an unused UIViewController wrapper (id="iVc-L5-Qkp"). Both wrapper layers were vestigial from earlier iOS architectures. Two related cleanups in en.lproj/LabSolitaireViewController.xib: 1. Dissolved the inner 708×708 container, promoting all subviews to be direct children of _aboutView. Resized _aboutView from 768×1024 to 708×708 to match the other tab panels. 2. Removed the orphan UIViewController wrapper (iVc-L5-Qkp), along with its iOS 7-era topLayoutGuide and bottomLayoutGuide children. _aboutView is now a standalone top-level <view>, matching _rulesView, _settingsView, and _gameOverView. The viewController wrapper was also actively harmful: UIKit resized its view to fill the screen on load, overriding the XIB- defined 708×708 frame with the device's screen size (768×1024 on classic iPad). Removing the wrapper allows _aboutView to keep its proper size at runtime. This addresses the "orphan view controller iVc-L5-Qkp" item on the migration to-do list. Only en.lproj is changed. fr.lproj and ja.lproj retain their legacy structure for the time being.
After the XIB restructure, _aboutView's XIB-default origin is (0, 0), which made the cold-launch slide-in animate diagonally from top-left to bottom-center. Other tabs avoided this because _addInfoSubview: pre-positions them via _positionSubviewBottomAndCentered: before the crossfade. Added explicit pre-animation positioning in info: and the "info not yet open" branch of openGameOverView:, placing the view horizontally centered and vertically just below the screen. The animation now slides up cleanly from the bottom.
Previously, switching between About/Rules/Settings/GameOver tabs caused a visible mid-frame darkening: each tab's PaperTablet.png background faded through alpha 0.5, letting the dark _darkView (alpha 0.75 black) bleed through both layers. The notepad appeared to flicker dark and back during every crossfade. Replaced per-tab notepad backgrounds with a single shared _paperBackgroundView held directly on _darkView. Each tab's internal PaperTablet image is hidden when the tab is added and unhidden when it's removed, leaving only the content (labels, buttons) to crossfade. The notepad now stays visually stable during tab transitions and slides up/down together with the active tab on open/close. Now that all four tab panels share an identical 708×708 size (after the previous XIB cleanup), the shared background and any active tab travel identical distances in slide animations, so no asymmetric positioning workarounds are needed. - New ivar UIImageView *_paperBackgroundView, lazy-created and reused across opens - Tag-based identification (kPaperTabletBackgroundTag = 42) of XIB-internal backgrounds; en.lproj XIB updated accordingly - Helper _ensurePaperTabletTagged: provides a runtime fallback for fr.lproj and ja.lproj XIBs that haven't been edited - All slide and crossfade paths updated to handle the shared background symmetrically (info:, _addInfoSubview:, closeInfo:, openGameOverView: both branches, viewWillTransitionToSize:)
Replaces the legacy iTunesArtwork.png and per-size Icon-*.png files with a single 1024×1024 source image in an Asset Catalog, from which Xcode generates all required icon sizes at build time. The 1024 source was upscaled in Affinity from the original 512×512 iTunesArtwork.png, in sRGB without alpha. - New Assets.xcassets with AppIcon.appiconset (single-size 1024 marketing icon) - Build setting ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon - Removed legacy CFBundleIconFiles, CFBundleIcons, and UIPrerenderedIcon keys from Info.plist - Removed Icon-72.png and Icon-144.png from Copy Bundle Resources (files retained in repo for reference) - iTunesArtwork-1024.png kept at repo root as source reference - LargeSolitaireIcon@2x.png remains in the bundle — used in the About panel UI, not as the app icon
Wraps three previously hard-coded strings in LabSolitaireViewController.m with NSLocalizedString: - "Free" (cell label) - "Any" (tableau label) - "You" (leaderboard player name fallback) Adds the corresponding entries to en.lproj/Localizable.strings. fr.lproj/Localizable.strings and ja.lproj/Localizable.strings are intentionally not updated. Without entries, NSLocalizedString falls back to the English key text — preferable to invented translations. These three strings can be translated later by someone with language expertise.
Replaces three per-language .strings files (en, fr, ja) with a single String Catalog (.xcstrings) at the project root. The String Catalog supports modern Xcode tooling: visual editor, "missing translation" highlighting, and JSON-based diffs. All 13 keys from the previous .strings files are preserved with their en/fr/ja translations. The three keys "Free", "Any", "You" added in the previous commit have only en values; fr and ja are intentionally left blank to be translated later. Note: per-language XIBs (LabSolitaireViewController.xib in fr.lproj and ja.lproj) are not yet migrated and remain in use. That cleanup is the next step.
Adds 36 new keys to Localizable.xcstrings, covering all
user-visible strings from the three localized
LabSolitaireViewController.xib files (en, fr, ja). The
catalog now has 49 keys total: 13 from the previous code-
side migration plus 36 from the XIBs.
Coverage by panel:
- Tab headers (3)
- About panel (12)
- Rules panel (5, including the long rules body and
diagram labels)
- Settings panel (14)
- Game-over panel (3, with "Games Played"/"Games Won"/
"Percentage Won" deduplicated against Settings)
The fr XIB, despite using the legacy archive 7.10 format
with base64-encoded multiline content, was fully extracted
without loss.
Some keys have intentionally missing translations:
- Stale fr version/copyright entries ("v. 1.0.8",
"©2011-2012") were not imported; en values are canonical
- "Touch here" review prompt (added to en after fr/ja
XIBs were last updated in 2011-2012) has only en
- "Also available from Soft Dorothy:" was never translated
to ja in the original
The XIBs are not modified in this commit — they continue
to drive the UI exactly as before. The next commit will
switch the en XIB to identifier-based runtime localization
and remove the fr/ja XIBs.
Remove these two keys entirely from the catalog: - "v. 1.1.1" - "©2011-2015 Soft Dorothy LLC"
…translations ## Runtime localization The English XIB now drives all language variants. Each translatable element has an accessibilityIdentifier matching its NSLocalizedString key. _localizeViewTree: walks the four panel views (_aboutView, _rulesView, _settingsView, _gameOverView) in viewDidLoad and replaces UILabel.text and UIButton titles via NSLocalizedString lookup. ## Per-language XIBs removed fr.lproj/LabSolitaireViewController.xib (legacy 7.10 archive format, broken on iOS 17+) and ja.lproj/LabSolitaireViewController.xib are no longer needed and have been deleted. All translations now live in Localizable.xcstrings. ## Locale registration cleanup - CFBundleDevelopmentReg
Several text elements in the en XIB (which serves as the master layout for all languages) were tight against their English text and truncated French and German translations with ellipses. Affected: tab headers (About / Rules / Settings). Adjusted widths and, where needed, x-positions to keep the layout balanced. No code or translation changes — purely XIB geometry.
Replaces two static labels ("v. 1.1.1" and "©2011-2015 Soft
Dorothy LLC") with a single label that displays:
V. <version> ©2011–<build year> Soft Dorothy LLC
Version is read at runtime from CFBundleShortVersionString
(falls back to "?" if missing). Build year is taken from the
__DATE__ preprocessor macro. The combined text is intentionally
not localized — "V." and "©" are universal enough.
XIB: removed the version label, widened the remaining label
to fit the combined text, and tagged it (tag=600) for runtime
lookup.
Code: 4 lines in viewDidLoad after the localization calls.
Two related issues with the auto-putaway controls: 1. On first open of the Settings tab, the Smart/All selection UI showed incorrect state — both buttons appeared blue and the red selection indicator was at its XIB default position. The state only updated after the user tapped a button. Fixed by calling updateSettingsInterface in settingsInfo: before the subview is added. 2. The default auto-putaway mode for fresh installs was kAutoPutawayModeAll. Changed to kAutoPutawayModeSmart to better match user expectations. Only affects fresh installs; existing users keep their stored preference.
…ew.m Eliminates the remaining 11 animation deprecation warnings. Both animation blocks in this file are converted: Block 1 — returnDraggedCardsWithAnimation: A single animation that snaps dragged cards back to their origin when dropped on an invalid target. The draggedCardsReturned:finished:context: callback is inlined into the completion block. The first card determination is moved before the animation so the delegate beginAnimatingCardMove: call (issued after the animateWithDuration: call) still has access to it. Block 2 — animateWithDictionary: A generic animation wrapper used by handleAnimation:'s multi-stage state machine for card moves, flips, and deals. The animationStopped:finished:context: callback is inlined into the completion block. The __bridge_retained / __bridge_transfer ARC workaround for the context parameter is no longer needed — the completion block captures the dictionary directly. The UIViewAnimationCurve from the dictionary is converted to UIViewAnimationOptions via the standard "<< 16" bit-shift. The handleAnimation: state machine itself is unchanged. The _animationRefCount lifecycle and all delegate call ordering are preserved. This completes Etappe B of the deprecated-warning cleanup. GameKit warnings (~22) remain and depend on the Game Center keep-or-remove decision.
Owner
Replaces the deprecated GameKit APIs and the custom multi-row
leaderboard display with a system Game Center button. Personal
stats stay visible in the Settings tab as a single compact line.
## Score submission
LocalPlayer.postLeaderboardScore: now uses the modern
[GKLeaderboard submitScore:context:player:leaderboardIDs:
completionHandler:] API instead of the deprecated GKScore class.
Submission still happens at the same points: noteAtleastOneCardMoved
(games_played) and checkGameState (games_won).
Authentication uses GKLocalPlayer.gamePlayerID and .displayName,
replacing the deprecated .playerID and .alias.
Note: existing local NSUserDefaults stats are keyed by
gamePlayerID rather than the legacy playerID. Pre-release this
is fine; for a future release we'd want a one-time migration.
## Custom leaderboard display removed
The 4-step async chain (loadScores → loadPlayedScores →
loadAliases → updateUI) and its 250-line
updateGlobalScoresInterface method are gone. Together with:
- selectLeaderboardScope: action and the friends/all toggle
- 9 IBOutlets for the multi-row layout
- The mergeLocalPlayerScoreWithLeaderboardScores: helpers
- Several LocalPlayer methods (retrieveLeaderboardScores:...,
retrieveAliasesForPlayerIDs:, etc.) and their delegate
callbacks
- ~370 lines from LocalPlayer.m, ~525 lines from
LabSolitaireViewController.m, ~50 lines from headers, ~185
lines from the XIB
## New: Game Center button + Personal Score line
Settings tab now shows:
- "Games Won: <won> of <played> (<percent>%)" — a single
localized line. Em-dash for undefined percent (0 of 0).
Uses lround for percentage so 99.5%+ rounds to 100%.
- A "Game Center" button that presents
GKGameCenterViewController in leaderboards mode when the
user is authenticated. Stays silent when not authenticated
(UX polish to follow in a separate commit).
The personal score line uses a positional format string
("%1$d of %2$d (%3$@%%)") so translators can re-order
arguments.
## Authentication flow
When GameKit needs to present its sign-in UI, the
authenticateHandler now passes the viewController to a new
LocalPlayerDelegate method,
localPlayer:needsToPresentAuthenticationViewController:,
which the view controller implements to present it modally.
This replaces the previous code path that ignored the
viewController parameter entirely.
## Bug fixes included
- Retain cycle in LocalPlayer.authenticateLocalPlayer: the
authenticateHandler block captured both localPlayer and
self strongly. Both are now captured weakly with a strong
re-capture inside the block.
- Same __weak/__strong pattern applied to the submitScore
completion block as defensive code hygiene.
- App freeze when tapping the Game Center button without
authentication: openGameCenter: now checks
_localPlayer.usingGameCenter && _localPlayer.authenticated
before presenting GKGameCenterViewController.
## Localization
New key "Games Won: %1$d of %2$d (%3$@%%)" with translations
in en, de, fr, ja added to Localizable.xcstrings. The "Game
Center" button label is also localized in all four languages.
## Note: Game Center entitlement required
After this change, GameKit integration depends on the
"Game Center" entitlement being added to the project's
Signing & Capabilities. Without it, the console will show:
ERROR: The Game Center entitlement is required to use GameKit.
The code is correct and entitlement-ready, but the project
configuration step is separate — it requires App Store
Connect leaderboard setup plus a Capabilities update in
Xcode. Until that's done, the Game Center button stays
disabled at runtime, which is the correct behavior given
the current entitlement state.
## Net change
-1091 lines, +168 lines = -923 lines.
The XIB had useAutolayout="YES" set at the document level plus fixedFrame="YES" and translatesAutoresizingMaskIntoConstraints="NO" on individual views — all leftovers from a previous Xcode version's partial Auto Layout enablement, never followed through with actual constraints. The combination caused 42 "Views without any layout constraints may clip their content or overlap other views" warnings. Removed all three. The XIB now consistently uses the springs-and-struts (autoresizing mask) layout that the code actually relies on for positioning.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@softdorothy Hi John,
With the help of Claude Opus I got this app running again on my current iPad. Do you have an interest in making it available in the App Store again?
BTW, I still know nothing about Objective C, but apparently I can describe things precise enough and obviously the LLM has digested all the history of programming for iOS.