Skip to content

Commit e534bd1

Browse files
Dead code sweep: delete the storyboard-era leftovers (UI modernization PR 1)
Deletes 13 files with zero callers: the pre-PermissionManager permission system (CameraAccess), an orphaned IBOutlet controller whose xib is long gone (RolePickerOptionController), stubs (PopoverController, TorchTest, empty UIOrientationHelpers), unused UIKit extensions (CGImage, UIView, UIButton, UIViewController), the old ObjC frame path (UIImage+ImageProcessing), and two empty test bridging headers. Trims dead members from OrientationUtils and UIImage+gif. UIImage+ImageProcessing.h was silently providing UIKit to every Swift file in the target via the bridging header; 10 files now import UIKit explicitly. Adds Docs/UI_MODERNIZATION.md with the full inventory and PR sequence, and fixes stale storyboard claims in CLAUDE.md. 348/348 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 334838f commit e534bd1

27 files changed

Lines changed: 143 additions & 557 deletions

CLAUDE.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,14 @@ When adding new remote commands, you **must** implement `NSCoding` (`encode`/`in
7171

7272
### UI Architecture
7373

74-
The app is migrating from UIKit (Storyboards) to SwiftUI:
75-
- **DeviceScannerViewController** — UIKit + Storyboard, entry point
76-
- **RolePickerController** — UIKit, role selection after connection
77-
- **CameraViewController** — UIKit, manages `AVCaptureSession` for the camera device
78-
- **MonitorViewController** — UIKit host that embeds `MonitorView` (SwiftUI) via `UIHostingController`
79-
- **MonitorView / MonitorViewModel** — SwiftUI view and ObservableObject view model for the monitor UI
80-
- **CameraViewModel** — ObservableObject for camera-side progress overlays
74+
The app is migrating from UIKit to SwiftUI (no storyboards or xibs remain; the window is built programmatically in `SceneDelegate`). See `Docs/UI_MODERNIZATION.md` for the migration plan. Most screens are SwiftUI views hosted by thin UIKit shells via `UIHostingController`:
75+
- **WelcomeViewController** — entry point (root of the nav controller), hosts `WelcomeView`
76+
- **RolePickerController** — role selection, hosts `RolePickerView`; also declares `RemoteCamSystem.shared`
77+
- **DeviceScannerViewController** — peer discovery, hosts `DeviceScannerView`; owns the actor lifecycle
78+
- **MonitorViewController** — hosts `MonitorView`; also defines `MonitorActor`
79+
- **CameraViewController** — pure UIKit, manages `AVCaptureSession` for the camera device (no SwiftUI view yet)
80+
- **WatchRemoteCameraController** — Watch-remote mode, embeds `CameraViewController` as a child
81+
- View models (`WelcomeViewModel`, `DeviceScannerViewModel`, `MonitorViewModel`, `CameraViewModel`) are ObservableObjects bridging actors to SwiftUI
8182

8283
The `^{ }` prefix operator (defined in Theater) dispatches a closure to the main thread — you'll see it used extensively for UI updates from actor message handlers.
8384

Docs/UI_MODERNIZATION.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# UI Modernization Plan
2+
3+
Companion to [MODERNIZATION.md](MODERNIZATION.md) (Theater/actor removal). That doc is about
4+
the concurrency layer; this one is about the UI layer: killing the last UIKit shells,
5+
Objective-C files, and dead code left over from the storyboard era.
6+
7+
Each PR below is a small, independently shippable slice. The **worklog** at the bottom
8+
collects raw material (numbers, before/after, war stories) for a blog post about
9+
modernizing this app with Claude (Fable).
10+
11+
## Where the app actually is (inventory, 2026-07-05)
12+
13+
Better than expected:
14+
15+
- **Zero storyboards, zero xibs.** Launch screen is the `UILaunchScreen` plist dict.
16+
- **UIScene lifecycle fully adopted**; `AppDelegate` is minimal, window built in `SceneDelegate`.
17+
- **Deprecated-API greps come back clean**: no `keyWindow`, `statusBarOrientation`,
18+
`UIAlertView`, `AVCaptureStillImageOutput`, `openURL(_:)`.
19+
- **Four of five screens are already SwiftUI**, hosted by thin UIKit shells:
20+
`WelcomeView`, `RolePickerView`, `DeviceScannerView`, `MonitorView`.
21+
22+
What's left:
23+
24+
| Surface | Size | Blocker |
25+
|---|---|---|
26+
| Dead code (old permission system, orphaned IB controller, stubs, unused extensions) | ~10 files | none — delete |
27+
| Objective-C: `CPSoundManager`, `RCTimer` (+ app bridging header) | 3 files | none — trivial ports |
28+
| UIKit shells: `WelcomeViewController`, `RolePickerController` | ~320 lines | thin coordinators |
29+
| `DeviceScannerViewController` | 367 lines | owns actor lifecycle; is `ViewCtrlActor<Self>` target |
30+
| `MonitorViewController` + glue | ~820 lines | `MonitorActor: ViewCtrlActor<MonitorViewController>` calls the concrete VC |
31+
| `CameraViewController` | 1729 lines | AVFoundation capture graph; no SwiftUI view exists |
32+
| `WatchRemoteCameraController` | 322 lines | embeds CameraViewController as child |
33+
34+
The structural blocker for the hard tier is Theater's `ViewCtrlActor<ConcreteVC>` generic
35+
binding — actors are type-welded to UIKit view controllers. That work overlaps with
36+
MODERNIZATION.md Phases 4–5.
37+
38+
## PR sequence
39+
40+
### PR 1: Dead code sweep — **this PR**
41+
Pure deletion, no behavior change.
42+
43+
Files deleted (all verified zero callers):
44+
- `CameraAccess.swift` — the pre-`PermissionManager` permission system, entirely superseded
45+
- `RolePickerOptionController.swift` — IBOutlet controller whose xib died long ago
46+
- `PopoverController.swift` — SwiftUI "Hello, World!" stub
47+
- `TorchTest.swift` — diagnostic scaffolding
48+
- `UIOrientationHelpers.swift` — literally empty (header comment + import)
49+
- `CGImage.swift` — unused `rotated(by:)`
50+
- `UIViewController.swift` — unused child-VC helpers (call sites use `addChild` directly)
51+
- `UIButton.swift``styleButton`, only caller was `RolePickerOptionController`
52+
- `UIView.swift``roundCorners`/`styleEmbeddedView`, only caller was `styleButton`
53+
- `UIImage+ImageProcessing.h/.m` — old ObjC frame path, replaced by `JPEGFrameEncoder`/`HEICFrameEncoder`
54+
- `RemoteShutterTests-Bridging-Header.h`, `RemoteShutterUITests-Bridging-Header.h` — empty
55+
56+
Members trimmed from live files:
57+
- `OrientationUtils`: `transformToUIKit`, `transformToUIImage`, `transformOrientationToImage`
58+
- `UIImage+gif`: `gifImageWithURL`
59+
60+
Plus: drop `UIImage+ImageProcessing.h` from the app bridging header, clear the test
61+
targets' `SWIFT_OBJC_BRIDGING_HEADER` settings, fix stale storyboard claims in CLAUDE.md.
62+
63+
### PR 2: Retire the Objective-C
64+
Port `CPSoundManager` (countdown beeps, `AVAudioPlayer`) and `RCTimer`
65+
(recursive-`dispatch_after` countdown) to small Swift types; delete
66+
`RemoteCam-Bridging-Header.h`. Zero ObjC left in the app target.
67+
68+
### PR 3: Collapse the easy shells
69+
Delete `iAdViewController` (empty base class), fold `WelcomeViewController` down,
70+
slim `RolePickerController`; move the `RemoteCamSystem.shared` declaration out of it.
71+
72+
### PR 4: Decouple actors from concrete VCs
73+
Re-target `ViewCtrlActor` bindings at protocols/view models instead of concrete VC types
74+
(coordinates with MODERNIZATION.md Phases 4–5). Unblocks DeviceScanner and Monitor shells.
75+
76+
### PR 5+: Camera surface
77+
SwiftUI chrome around a `UIViewRepresentable` preview layer for `CameraViewController`;
78+
`WatchRemoteCameraController` follows.
79+
80+
## Worklog (blog raw material)
81+
82+
### PR 1 — dead code sweep
83+
- Inventory ran as two parallel read-only agents: one walked every view controller,
84+
the other chased ObjC/bridging headers/deprecated APIs. ~30 min wall clock.
85+
- Expected storyboards to hunt; found none. The docs said "DeviceScannerViewController —
86+
UIKit + Storyboard, entry point" — the code said otherwise. Docs lie, greps don't.
87+
- Best find: an entire permission system (`CameraAccess.swift`) still compiling,
88+
fully replaced, zero callers — each method politely apologizing in comments that
89+
the new system had taken over.
90+
- Dead-code cascade: deleting one orphaned IB controller made `UIButton.swift` dead,
91+
which made `UIView.swift` dead. Delete one leaf, the branch comes with it.
92+
- Two "dead" files (`PopoverController.swift`, `TorchTest.swift`) turned out not to be
93+
in the pbxproj at all — orphaned on disk, never even compiling.
94+
- Plot twist: removing `UIImage+ImageProcessing.h` from the bridging header broke the
95+
build in 10 files that never imported UIKit. Its `#import <UIKit/UIKit.h>` had been
96+
silently granting UIKit to every Swift file in the target for years. The dead code
97+
was load-bearing — not for what it did, but for what it imported. Fix: explicit
98+
`import UIKit` in the 10 files that were freeloading.
99+
- Net: 27 files changed, +136/−557 lines; 13 files deleted; 348/348 tests green.

RemoteCam/AlertPresenting.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Foundation
9+
import UIKit
910

1011
/// Handle returned by `AlertPresenting.showAlert` for later update/dismiss.
1112
protocol AlertHandle: AnyObject {

RemoteCam/CGImage.swift

Lines changed: 0 additions & 49 deletions
This file was deleted.

RemoteCam/CameraAccess.swift

Lines changed: 0 additions & 108 deletions
This file was deleted.

RemoteCam/OrientationUtils.swift

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import Foundation
1010
import AVFoundation
11+
import UIKit
1112

1213
public class OrientationUtils {
1314

@@ -27,54 +28,4 @@ public class OrientationUtils {
2728
}
2829
}
2930

30-
class public func transformToUIKit(o: AVCaptureVideoOrientation) -> UIInterfaceOrientation {
31-
switch o {
32-
33-
case .landscapeLeft:
34-
return .landscapeLeft
35-
36-
case .landscapeRight:
37-
return .landscapeRight
38-
39-
case .portraitUpsideDown:
40-
return .portraitUpsideDown
41-
42-
default:
43-
return .portrait
44-
}
45-
}
46-
47-
class public func transformToUIImage(o: AVCaptureVideoOrientation) -> UIImage.Orientation {
48-
switch o {
49-
50-
case .landscapeLeft:
51-
return .left
52-
53-
case .landscapeRight:
54-
return .right
55-
56-
case .portraitUpsideDown:
57-
return .down
58-
59-
default:
60-
return .up
61-
}
62-
}
63-
64-
class public func transformOrientationToImage(o: UIInterfaceOrientation) -> UIImage.Orientation {
65-
switch o {
66-
67-
case .landscapeLeft:
68-
return .left
69-
70-
case .landscapeRight:
71-
return .right
72-
73-
case .portraitUpsideDown:
74-
return .down
75-
76-
default:
77-
return .up
78-
}
79-
}
8031
}

RemoteCam/Photos.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import Foundation
10+
import UIKit
1011

1112
func goToPhotos() {
1213
#if targetEnvironment(macCatalyst)

RemoteCam/PopoverController.swift

Lines changed: 0 additions & 21 deletions
This file was deleted.

RemoteCam/RemoteCam-Bridging-Header.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@
22
// Use this file to import your target's public headers that you would like to expose to Swift.
33
//
44

5-
#import "UIImage+ImageProcessing.h"
65
#import "RCTimer.h"
76
#import "CPSoundManager.h"

RemoteCam/RemoteCamScanning.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import Foundation
1010
import MultipeerConnectivity
11+
import UIKit
1112

1213
extension RemoteCamSession {
1314

0 commit comments

Comments
 (0)