Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
54d4561158
commit
fcc266d795
132
Agents.md
132
Agents.md
@ -203,6 +203,138 @@ Button("Save") { state.save() }
|
|||||||
- Avoid UIKit colors in SwiftUI code.
|
- Avoid UIKit colors in SwiftUI code.
|
||||||
|
|
||||||
|
|
||||||
|
## watchOS Development (CRITICAL)
|
||||||
|
|
||||||
|
**Read this entire section before implementing any watch functionality.**
|
||||||
|
|
||||||
|
### Creating a Watch Target
|
||||||
|
|
||||||
|
When adding a watchOS target to an existing iOS app:
|
||||||
|
|
||||||
|
1. **File → New → Target → "Watch App for watchOS"**
|
||||||
|
2. Choose **"Watch App for Existing iOS App"** (NOT standalone)
|
||||||
|
3. Name it appropriately (e.g., `AppNameWatch`)
|
||||||
|
4. Xcode creates a folder like `AppNameWatch Watch App/`
|
||||||
|
|
||||||
|
### CRITICAL: Embedding the Watch App
|
||||||
|
|
||||||
|
⚠️ **THIS IS THE #1 CAUSE OF "WATCH APP NOT INSTALLED" ERRORS** ⚠️
|
||||||
|
|
||||||
|
The watch app MUST be embedded in the iOS app for deployment to real devices:
|
||||||
|
|
||||||
|
1. Select the **iOS target** in Xcode
|
||||||
|
2. Go to **Build Phases** tab
|
||||||
|
3. Verify there's an **"Embed Watch Content"** phase
|
||||||
|
4. **CRITICAL**: Ensure **"Code Sign On Copy"** is CHECKED ✓
|
||||||
|
|
||||||
|
If "Embed Watch Content" doesn't exist:
|
||||||
|
1. Click **"+"** → **"New Copy Files Phase"**
|
||||||
|
2. Rename to **"Embed Watch Content"**
|
||||||
|
3. Set **Destination** to **"Products Directory"**
|
||||||
|
4. Set **Subpath** to `$(CONTENTS_FOLDER_PATH)/Watch`
|
||||||
|
5. Add the watch app (e.g., `AppNameWatch Watch App.app`)
|
||||||
|
6. **CHECK "Code Sign On Copy"** ← This is critical!
|
||||||
|
|
||||||
|
Without proper embedding, the iOS app installs but the watch app does NOT install on the paired Apple Watch.
|
||||||
|
|
||||||
|
### Bundle Identifiers
|
||||||
|
|
||||||
|
Watch app bundle IDs MUST be prefixed with the iOS app's bundle ID:
|
||||||
|
|
||||||
|
```
|
||||||
|
iOS app: com.company.AppName
|
||||||
|
Watch app: com.company.AppName.watchkitapp ← MUST start with iOS bundle ID
|
||||||
|
```
|
||||||
|
|
||||||
|
Also verify `WKCompanionAppBundleIdentifier` in the watch target's build settings matches the iOS app's bundle ID exactly.
|
||||||
|
|
||||||
|
### Data Sync: WatchConnectivity (NOT App Groups)
|
||||||
|
|
||||||
|
**DO NOT use App Groups for iPhone ↔ Watch data sharing.**
|
||||||
|
|
||||||
|
App Groups:
|
||||||
|
- ❌ Do NOT work between iPhone and Apple Watch
|
||||||
|
- ❌ Different container paths on each device
|
||||||
|
- ❌ Will waste hours debugging why data isn't syncing
|
||||||
|
- ✅ Only work between an app and its extensions on the SAME device
|
||||||
|
|
||||||
|
**Use WatchConnectivity framework instead:**
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// iOS side - WatchConnectivityService.swift
|
||||||
|
import WatchConnectivity
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
final class WatchConnectivityService: NSObject, WCSessionDelegate {
|
||||||
|
static let shared = WatchConnectivityService()
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
super.init()
|
||||||
|
if WCSession.isSupported() {
|
||||||
|
WCSession.default.delegate = self
|
||||||
|
WCSession.default.activate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncData(_ data: [String: Any]) {
|
||||||
|
guard WCSession.default.activationState == .activated,
|
||||||
|
WCSession.default.isPaired,
|
||||||
|
WCSession.default.isWatchAppInstalled else { return }
|
||||||
|
|
||||||
|
try? WCSession.default.updateApplicationContext(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### WatchConnectivity Methods
|
||||||
|
|
||||||
|
| Method | Use Case |
|
||||||
|
|--------|----------|
|
||||||
|
| `updateApplicationContext` | Latest state that persists (use this for most syncs) |
|
||||||
|
| `sendMessage` | Immediate delivery when counterpart is reachable |
|
||||||
|
| `transferUserInfo` | Queued delivery, guaranteed but not immediate |
|
||||||
|
|
||||||
|
### watchOS Framework Limitations
|
||||||
|
|
||||||
|
These iOS frameworks are NOT available on watchOS:
|
||||||
|
|
||||||
|
- ❌ `CoreImage` - Generate QR codes on iOS, send image data to watch
|
||||||
|
- ❌ `UIKit` (mostly) - Use SwiftUI
|
||||||
|
- ❌ `AVFoundation` (limited)
|
||||||
|
|
||||||
|
### Simulator Limitations
|
||||||
|
|
||||||
|
WatchConnectivity on simulators is **unreliable**:
|
||||||
|
|
||||||
|
- `isWatchAppInstalled` often returns `false` even when running
|
||||||
|
- `isReachable` may be `false` even with both apps running
|
||||||
|
- `updateApplicationContext` may fail with "counterpart not installed"
|
||||||
|
|
||||||
|
**Workarounds for simulator testing:**
|
||||||
|
1. Add `#if targetEnvironment(simulator)` blocks with sample data
|
||||||
|
2. Test real sync functionality on physical devices only
|
||||||
|
|
||||||
|
### Debugging Watch Sync Issues
|
||||||
|
|
||||||
|
If `isWatchAppInstalled` returns `false`:
|
||||||
|
|
||||||
|
1. ✅ Check "Embed Watch Content" build phase exists
|
||||||
|
2. ✅ Check "Code Sign On Copy" is enabled
|
||||||
|
3. ✅ Verify bundle ID is prefixed correctly
|
||||||
|
4. ✅ Clean build folder (⇧⌘K) and rebuild
|
||||||
|
5. ✅ On iPhone, open Watch app → verify app appears under "Installed"
|
||||||
|
|
||||||
|
### NSObject Requirement
|
||||||
|
|
||||||
|
`WCSessionDelegate` is an Objective-C protocol, so conforming classes must inherit from `NSObject`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
final class WatchConnectivityService: NSObject, WCSessionDelegate {
|
||||||
|
// NSObject is required for WCSessionDelegate conformance
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## SwiftData Instructions
|
## SwiftData Instructions
|
||||||
|
|
||||||
If SwiftData is configured to use CloudKit:
|
If SwiftData is configured to use CloudKit:
|
||||||
|
|||||||
10
README.md
10
README.md
@ -177,6 +177,16 @@ BusinessCardTests/ # Unit tests
|
|||||||
**watchOS Target:**
|
**watchOS Target:**
|
||||||
|
|
||||||
- WatchConnectivity (for receiving cards from iPhone)
|
- WatchConnectivity (for receiving cards from iPhone)
|
||||||
|
- Bundle ID must be prefixed with iOS bundle ID (e.g., `com.mbrucedogs.BusinessCard.watchkitapp`)
|
||||||
|
|
||||||
|
### Watch App Embedding (CRITICAL)
|
||||||
|
|
||||||
|
The watch app must be embedded in the iOS app for deployment. In the iOS target's **Build Phases**:
|
||||||
|
|
||||||
|
1. **"Embed Watch Content"** phase must exist
|
||||||
|
2. **"Code Sign On Copy"** must be CHECKED ✓
|
||||||
|
|
||||||
|
Without this, the iOS app installs but the watch app does NOT install on the paired Apple Watch. See `Agents.md` for full troubleshooting guide.
|
||||||
|
|
||||||
### CloudKit Container
|
### CloudKit Container
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user