# iOS Share Extension Improvements

**Date:** 2025-11-24  
**Purpose:** Explore alternatives to improve user experience by eliminating interstitial UI and simplifying app launch mechanism

## Current Implementation Issues

1. **Interstitial UI**: Users see `SLComposeServiceViewController` with a "Post" button before the app opens
2. **Deep Link Dependency**: App relies on deep link (`timesafari://shared-photo`) to detect shared images, even though data is already in App Group

## Improvement 1: Skip Interstitial UI

### Current Approach
- Uses `SLComposeServiceViewController` which shows a UI with "Post" button
- User must tap "Post" to proceed

### Alternative: Custom UIViewController (Headless Processing)

Replace `SLComposeServiceViewController` with a custom `UIViewController` that:
- Processes the image immediately in `viewDidLoad`
- Shows no UI (or minimal loading indicator)
- Opens the app automatically

**Implementation:**

```swift
import UIKit
import UniformTypeIdentifiers

class ShareViewController: UIViewController {
    
    private let appGroupIdentifier = "group.app.timesafari.share"
    private let sharedPhotoBase64Key = "sharedPhotoBase64"
    private let sharedPhotoFileNameKey = "sharedPhotoFileName"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Process image immediately without showing UI
        processAndOpenApp()
    }
    
    private func processAndOpenApp() {
        guard let extensionContext = extensionContext,
              let inputItems = extensionContext.inputItems as? [NSExtensionItem] else {
            extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
            return
        }
        
        processSharedImage(from: inputItems) { [weak self] success in
            guard let self = self else {
                self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
                return
            }
            
            if success {
                self.openMainApp()
            }
            
            // Complete immediately - no UI shown
            self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
        }
    }
    
    private func processSharedImage(from items: [NSExtensionItem], completion: @escaping (Bool) -> Void) {
        // ... (same implementation as current)
    }
    
    private func openMainApp() {
        guard let url = URL(string: "timesafari://shared-photo") else {
            return
        }
        
        var responder: UIResponder? = self
        while responder != nil {
            if let application = responder as? UIApplication {
                application.open(url, options: [:], completionHandler: nil)
                return
            }
            responder = responder?.next
        }
        
        extensionContext?.open(url, completionHandler: nil)
    }
}
```

**Info.plist Changes:**
- Already configured correctly with `NSExtensionPrincipalClass`
- No storyboard needed (already removed)

**Benefits:**
- ✅ No interstitial UI - app opens immediately
- ✅ Faster user experience
- ✅ More seamless integration

**Considerations:**
- ⚠️ User has less control (can't cancel easily)
- ⚠️ No visual feedback during processing (could add minimal loading indicator)
- ⚠️ Apple guidelines: Extensions should provide value even if they don't open the app

## Improvement 2: Direct App Launch Without Deep Link

### Current Approach
- Share Extension stores data in App Group UserDefaults
- Share Extension opens app via deep link (`timesafari://shared-photo`)
- App receives deep link → checks App Group → processes image

### Alternative: App Lifecycle Detection

Instead of using deep links, the app can check for shared data when it becomes active:

**Option A: Check on App Activation**

```swift
// In AppDelegate.swift
func applicationDidBecomeActive(_ application: UIApplication) {
    // Check for shared image from Share Extension
    if let sharedData = getSharedImageData() {
        // Store in temp file for JS to read
        writeSharedImageToTempFile(sharedData)
        
        // Navigate to shared-photo route directly
        // This would need to be handled in JS layer
    }
}
```

**Option B: Use Notification (More Reliable)**

```swift
// In ShareViewController.swift (after storing data)
private func openMainApp() {
    // Store a flag that image is ready
    guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
        return
    }
    userDefaults.set(true, forKey: "sharedPhotoReady")
    userDefaults.synchronize()
    
    // Open app (can use any URL scheme or even just launch the app)
    guard let url = URL(string: "timesafari://") else {
        return
    }
    
    var responder: UIResponder? = self
    while responder != nil {
        if let application = responder as? UIApplication {
            application.open(url, options: [:], completionHandler: nil)
            return
        }
        responder = responder?.next
    }
}

// In AppDelegate.swift
func applicationDidBecomeActive(_ application: UIApplication) {
    let appGroupIdentifier = "group.app.timesafari.share"
    guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
        return
    }
    
    // Check if shared photo is ready
    if userDefaults.bool(forKey: "sharedPhotoReady") {
        userDefaults.removeObject(forKey: "sharedPhotoReady")
        userDefaults.synchronize()
        
        // Process shared image
        if let sharedData = getSharedImageData() {
            writeSharedImageToTempFile(sharedData)
            
            // Trigger JS to check for shared image
            // This could be done via Capacitor App plugin or custom event
        }
    }
}
```

**Option C: Check on App Launch (Most Direct)**

```swift
// In AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Check for shared image immediately on launch
    checkForSharedImageOnLaunch()
    
    return true
}

func applicationDidBecomeActive(_ application: UIApplication) {
    // Also check when app becomes active (in case it was already running)
    checkForSharedImageOnLaunch()
}

private func checkForSharedImageOnLaunch() {
    if let sharedData = getSharedImageData() {
        writeSharedImageToTempFile(sharedData)
        
        // Post a notification or use Capacitor to notify JS
        NotificationCenter.default.post(name: NSNotification.Name("SharedPhotoReady"), object: nil)
    }
}
```

**JavaScript Integration:**

```typescript
// In main.capacitor.ts
import { App } from '@capacitor/app';

// Listen for app becoming active
App.addListener('appStateChange', async ({ isActive }) => {
  if (isActive) {
    // Check for shared image when app becomes active
    await checkAndStoreNativeSharedImage();
  }
});

// Also check on initial load
if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'ios') {
  checkAndStoreNativeSharedImage().then(result => {
    if (result.success) {
      // Navigate to shared-photo route
      router.push('/shared-photo' + (result.fileName ? `?fileName=${result.fileName}` : ''));
    }
  });
}
```

**Benefits:**
- ✅ No deep link routing needed
- ✅ More direct data flow
- ✅ App can detect shared content even if it was already running
- ✅ Simpler URL scheme handling

**Considerations:**
- ⚠️ Need to ensure app checks on both launch and activation
- ⚠️ May need to handle race conditions (app launching vs. share extension writing)
- ⚠️ Still need some way to open the app (minimal URL scheme still required)

## Recommended Approach

**Best of Both Worlds:**

1. **Use Custom UIViewController** (Improvement 1) - Eliminates interstitial UI
2. **Use App Lifecycle Detection** (Improvement 2, Option C) - Direct data flow

**Combined Implementation:**

```swift
// ShareViewController.swift - Custom UIViewController
class ShareViewController: UIViewController {
    // Process immediately in viewDidLoad
    // Store data in App Group
    // Open app with minimal URL (just "timesafari://")
}

// AppDelegate.swift
func applicationDidBecomeActive(_ application: UIApplication) {
    // Check for shared image
    // If found, write to temp file and let JS handle navigation
}
```

**JavaScript:**
```typescript
// Check on app activation
App.addListener('appStateChange', async ({ isActive }) => {
  if (isActive) {
    const result = await checkAndStoreNativeSharedImage();
    if (result.success) {
      router.push('/shared-photo' + (result.fileName ? `?fileName=${result.fileName}` : ''));
    }
  }
});
```

This approach:
- ✅ No interstitial UI
- ✅ No deep link routing complexity
- ✅ Direct data flow via App Group
- ✅ Works whether app is running or launching fresh

