Skip to content

Migrate Swift Radio to SwiftUI for v4.0.0 - #219

Merged
fethica merged 11 commits into
devfrom
release/4.0.0-swiftui
Oct 5, 2026
Merged

fethica merged 11 commits into
devfrom
release/4.0.0-swiftui

Conversation

@fethica

@fethica fethica commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

Migrates Swift Radio's screens and navigation from UIKit to SwiftUI, including the stations list, popup player, station information and About screens.

Adds SwiftRadioCore to separate reusable playback, station catalog, artwork and system-media logic from presentation. Phone and CarPlay share these services and their state, while the separation makes playback and catalog behavior easier to test independently of the UI.

@fethica
fethica requested a balanced review from Copilot October 4, 2026 20:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The broad UI, playback, and CarPlay migration still has unresolved catalog-state and audio-activation defects alongside pending device and runtime acceptance gates.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity

Open (3)
What changed in this PR

Migrates the application from UIKit coordinators and screens to SwiftUI, backed by a shared SwiftRadioCore package for phone and CarPlay playback, catalog, artwork, and system-media behavior.

Changes:

  • Replaces UIKit navigation and views with SwiftUI screens, popup playback, sharing, and accessibility-aware components.
  • Introduces shared, tested playback/catalog infrastructure with CarPlay integration.
  • Updates dependencies, release metadata, documentation, and CI/archive validation.
File Description
SwiftRadio.xcodeproj/​project.pbxproj Updates targets, packages, versions, and Swift 6 settings.
SwiftRadio.xcodeproj/​project.xcworkspace/​xcshareddata/​swiftpm/​Package.resolved Locks updated playback and popup dependencies.
SwiftRadio/​Views/​LogoShareView.xib Removes legacy share-card XIB.
SwiftRadio/​Views/​LogoShareView.swift Removes legacy UIKit share view.
SwiftRadio/​Views/​GradientBackgroundView.swift Removes UIKit gradient view.
SwiftRadio/​Views/​AlbumArtworkView.swift Removes UIKit artwork component.
SwiftRadio/​ViewControllers/​LoaderController.swift Removes UIKit loading controller.
SwiftRadio/​ViewControllers/​LibrariesViewController.swift Removes UIKit libraries screen.
SwiftRadio/​ViewControllers/​FeaturesViewController.swift Removes UIKit features screen.
SwiftRadio/​ViewControllers/​BottomSheetViewController.swift Removes UIKit options sheet.
SwiftRadio/​ViewControllers/​BaseController.swift Removes UIKit base controller.
SwiftRadio/​Services/​AudioSetupService.swift Removes legacy playback setup.
SwiftRadio/​Networking/​NetworkService.swift Removes legacy networking layer.
SwiftRadio/​Model/​RadioStation.swift Moves station model into core.
SwiftRadio/​Helpers/​UIViewController+Email.swift Removes UIKit mail helper.
SwiftRadio/​Helpers/​UITableViewCell+reuseIdentifier.swift Removes table-cell helper.
SwiftRadio/​Helpers/​UIImageView+Cache.swift Removes image-view networking helper.
SwiftRadio/​Helpers/​UIImage+DropShadow.swift Removes legacy shadow helper.
SwiftRadio/​Helpers/​UIImage+Cache.swift Removes image networking helper.
SwiftRadio/​Helpers/​ShareActivity.swift Replaces legacy sharing implementation.
SwiftRadio/​Helpers/​NSLayoutConstraint+with.swift Removes Auto Layout helper.
SwiftRadio/​Helpers/​Handoffable.swift Replaces UIKit Handoff support.
SwiftRadio/​Helpers/​BottomSheetHandler.swift Replaces coordinator option routing.
SwiftRadio/​Coordinators/​MainCoordinator.swift Removes main UIKit coordinator.
SwiftRadio/​Coordinators/​Coordinator.swift Removes coordinator protocols.
SwiftRadio/​Coordinators/​AboutCoordinator.swift Removes About coordinator.
SwiftRadio/​Cells/​NothingFoundCell.xib Removes legacy empty-list cell.
SwiftRadio/​App/​SceneDelegate.swift Removes UIKit phone scene delegate.
SwiftRadio/​App/​SwiftRadioApp.swift Adds SwiftUI application entry point.
SwiftRadio/​App/​AppDelegate.swift Initializes shared composition and CarPlay routing.
SwiftRadio/​App/​AppEnvironment.swift Composes shared playback and catalog services.
SwiftRadio/​App/​RootView.swift Owns bootstrap, navigation, and popup presentation.
SwiftRadio/​App/​ConfiguredStationsLoader.swift Resolves configured station source.
SwiftRadio/​App/​ArtworkLoaderKey.swift Injects shared artwork loading into SwiftUI.
SwiftRadio/​App/​UITestRadioPlayer.swift Adds deterministic UI-test playback engine.
SwiftRadio/​UI/​ActivityView.swift Wraps the system share sheet.
SwiftRadio/​UI/​AirPlayButton.swift Wraps the AirPlay route picker.
SwiftRadio/​UI/​ArtworkView.swift Loads station and track artwork.
SwiftRadio/​UI/​CrossfadeImage.swift Provides native image transitions.
SwiftRadio/​UI/​EqualizerBars.swift Provides playback indication.
SwiftRadio/​UI/​GradientBackground.swift Recreates the native gradient.
SwiftRadio/​UI/​LogoShareView.swift Renders the SwiftUI share card.
SwiftRadio/​UI/​MailComposeView.swift Wraps the mail composer.
SwiftRadio/​UI/​MarqueeText.swift Wraps scrolling now-playing titles.
SwiftRadio/​UI/​PlaybackActivityIndicator.swift Wraps playback/loading animations.
SwiftRadio/​UI/​PlaybackArtworkImage.swift Handles artwork playback transitions.
SwiftRadio/​UI/​SafariView.swift Wraps the in-app browser.
SwiftRadio/​Features/​Loading/​LoadingScreen.swift Adds SwiftUI loading and retry UI.
SwiftRadio/​Features/​Stations/​StationsScreen.swift Adds searchable station catalog.
SwiftRadio/​Features/​Stations/​StationRow.swift Adds adaptive station cards.
SwiftRadio/​Features/​Stations/​NowPlayingIndicatorButton.swift Adds toolbar playback indicator.
SwiftRadio/​Features/​Stations/​EmptyStationsView.swift Adds empty-catalog presentation.
SwiftRadio/​Features/​StationInfo/​StationInfoScreen.swift Adds station detail screen.
SwiftRadio/​Features/​NowPlaying/​NowPlayingView.swift Adds popup player content and presentation.
SwiftRadio/​Features/​NowPlaying/​PlaybackControls.swift Adds transport and seek controls.
SwiftRadio/​Features/​NowPlaying/​NowPlayingOptionsSheet.swift Adds player actions sheet.
SwiftRadio/​Features/​About/​AboutScreen.swift Adds SwiftUI About navigation.
SwiftRadio/​Features/​About/​LibrariesScreen.swift Adds dependency listing.
SwiftRadio/​Features/​About/​GitHubClient.swift Fetches repository and contributor metadata.
SwiftRadio/​Features/​About/​FeaturesScreen.swift Adds configured feature list.
SwiftRadio/​Features/​About/​ContributorsScreen.swift Adds contributor grid.
SwiftRadio/​Features/​About/​ConfiguredRowID.swift Generates stable configurable row IDs.
SwiftRadio/​CarPlay/​CarPlaySceneDelegate.swift Bridges shared state into CarPlay templates.
SwiftRadio/​CarPlay/​CarPlayObservation.swift Tracks CarPlay-relevant state changes.
SwiftRadio/​Config/​InfoSection.swift Makes configuration models sendable.
SwiftRadio/​Config/​Content.swift Adds playback and CarPlay localized content.
SwiftRadio/​Config/​Config.swift Adds audio policy and updates dependencies.
SwiftRadio/​Localizable.xcstrings Adds new UI and accessibility strings.
SwiftRadio/​Data/​stations.json Refreshes the sample station catalog.
SwiftRadio/​SwiftRadio.entitlements Removes legacy playable-content entitlement.
SwiftRadio/​Info.plist Removes UIKit phone scene configuration.
SwiftRadio/​Info-CarPlay.plist Updates CarPlay scene metadata.
SwiftRadioUITests/​SwiftRadioUITests.swift Expands deterministic UI regression coverage.
Packages/​SwiftRadioCore/​Package.swift Defines the iOS 17 Swift 6 core package.
Packages/​SwiftRadioCore/​Package.resolved Locks FRadioPlayer 0.4.0.
Packages/​SwiftRadioCore/​.gitignore Ignores package build artifacts.
Packages/​SwiftRadioCore/​README.md Documents composition and playback invariants.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Models/​RadioStation.swift Defines shared station data and helpers.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Models/​RadioStation+ArtworkSource.swift Classifies artwork sources.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Models/​StationsResponse.swift Defines station JSON envelope.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Stations/​StationsStore.swift Owns loading, filtering, and selection.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Stations/​StationsStore+LoadState.swift Defines loading and activation states.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Stations/​StationsLoader.swift Defines catalog loader boundary.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Stations/​RemoteStationsLoader.swift Loads remote station catalogs.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Stations/​BundleStationsLoader.swift Loads bundled station catalogs.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Stations/​CatalogListContent.swift Pages catalogs for CarPlay limits.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Images/​ArtworkLoader.swift Adds cached, coalesced artwork loading.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​PlayerService.swift Coordinates playback, seeking, and activation.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​PlayerService+State.swift Defines transport state.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​PlayerService+Readiness.swift Defines media readiness.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​RadioPlaying.swift Defines the playback engine boundary.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​RadioPlayerEvents.swift Defines engine event delivery.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​RadioPlayerState.swift Defines engine readiness events.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​RemoteCommand.swift Enumerates supported remote commands.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​RemoteCommandHandling.swift Defines testable remote-command registration.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​RemoteCommandsController.swift Coordinates system transport commands.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​SystemRemoteCommandHandler.swift Adapts MediaPlayer commands.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​NowPlayingInfoPublisher.swift Publishes lock-screen metadata.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​MainActorDelivery.swift Safely delivers engine callbacks.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Playback/​AudioSessionConfigurator.swift Configures deferred audio-session ownership.
Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Handoff/​HandoffActivity.swift Generates track-search Handoff URLs.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​StationsStoreTests.swift Tests catalog and selection behavior.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​StationsRefreshTests.swift Tests overlapping and cancelled refreshes.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​StationsLoaderTests.swift Tests remote loading failures and caching.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​StationActivationTests.swift Tests phone and CarPlay activation rules.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​PlayerServiceTests.swift Tests transport and seek coordination.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​PlayerArtworkTests.swift Tests artwork identity protection.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​PlaybackInterruptionTests.swift Tests interruption and stale-event handling.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​RemoteCommandsControllerTests.swift Tests remote command behavior.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​RealEngineSeekTests.swift Tests seeking against FRadioPlayer.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​RadioStationTests.swift Tests station decoding and URLs.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​NowPlayingInfoPublisherTests.swift Tests system metadata replacement.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​HandoffSearchURLTests.swift Tests Handoff URL generation.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​CatalogListContentTests.swift Tests CarPlay pagination.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​AudioSessionConfiguratorTests.swift Tests audio category options.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​ArtworkLoaderTests.swift Tests artwork loading and caching.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​ArtworkLoaderCoalescingTests.swift Tests shared artwork requests.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fakes/​ActivationGate.swift Controls audio activation in tests.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fakes/​ControlledStationsLoader.swift Controls overlapping catalog requests.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fakes/​FakeRadioPlayer.swift Provides deterministic core playback.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fakes/​FakeRemoteCommandHandler.swift Fakes remote command registration.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fakes/​FakeStationsLoader.swift Supplies deterministic catalogs.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fakes/​ObservationCounter.swift Tracks observation invalidations.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fakes/​StubURLProtocol.swift Stubs network responses.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fixtures/​stations.json Supplies station test fixtures.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fixtures/​station.png Supplies artwork fixture.
Packages/​SwiftRadioCore/​Tests/​SwiftRadioCoreTests/​Fixtures/​placeholder.png Supplies fallback artwork fixture.
Documentation/​SwiftUIParity.md Documents migration parity and validation.
Documentation/​Releases/​4.0.0.md Adds v4.0.0 release notes.
.gitignore Ignores generated build artifacts.
.github/​workflows/​tests.yml Runs core and UI tests.
.github/​workflows/​archive.yml Validates unsigned archives and IPAs.
.github/​workflows/​ios.yml Builds the phone target across Xcode versions.
.github/​workflows/​carplay.yml Builds the CarPlay target.
.github/​workflows/​release.yml Publishes tagged releases with reviewed notes.
.github/​scripts/​select-simulator.py Selects an iOS 27 simulator.
.github/​scripts/​package-unsigned-app.py Packages and validates unsigned IPAs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Packages/SwiftRadioCore/Sources/SwiftRadioCore/Playback/PlayerService.swift Outdated
Comment thread Packages/SwiftRadioCore/Sources/SwiftRadioCore/Stations/StationsStore.swift Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The release workflow no longer embeds tag-derived version values, causing subsequent tagged releases to fail validation.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (3)

Comment thread .github/workflows/release.yml Outdated

- name: Package unsigned IPA
-onlyUsePackageVersionsFromResolvedFile -jobs 2 \
CODE_SIGNING_ALLOWED=NO

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Website-only catalog refreshes can leave stale station values in phone and CarPlay selection handlers.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Website omission from equality prevents CarPlay refresh

Packages/​SwiftRadioCore/​Sources/​SwiftRadioCore/​Stations/​CatalogListContent.swift:13

The synthesized Equatable conformance compares page stations with RadioStation.==, which intentionally ignores website. CarPlay uses this value to suppress list rebuilding, so a website-only catalog refresh leaves row handlers capturing the old station; selecting one can write the stale website back into the shared store. Make this content comparison include full catalog fields (or explicitly include website in the CarPlay cache key).

Medium severity Ignoring website in equality retains stale row action

SwiftRadio/​Features/​Stations/​StationRow.swift:29

RadioStation.== deliberately ignores website, but this equatable row's action captures the complete station value. After a website-only catalog refresh, SwiftUI considers the row unchanged and retains the old closure; tapping it passes the stale station to activate, which can overwrite the refreshed website and expose the old link. Include website in the row comparison (or resolve the current station by ID inside the action).

@fethica
fethica marked this pull request as ready for review October 5, 2026 01:12
@fethica
fethica merged commit ddecb37 into dev Oct 5, 2026
7 checks passed
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