Skip to content

Run again#2

Open
PullFrom wants to merge 33 commits into
softdorothy:mainfrom
PullFrom:run-again
Open

Run again#2
PullFrom wants to merge 33 commits into
softdorothy:mainfrom
PullFrom:run-again

Conversation

@PullFrom
Copy link
Copy Markdown

@PullFrom PullFrom commented May 4, 2026

@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.

PullFrom added 30 commits April 22, 2026 21:59
- 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.
@softdorothy
Copy link
Copy Markdown
Owner

softdorothy commented May 4, 2026 via email

PullFrom added 2 commits May 4, 2026 23:21
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.
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.

2 participants