forked from trent_larson/crowd-funder-for-time-pwa
feat(ios): implement native share target for images
Implement iOS Share Extension to enable native image sharing from Photos and other apps directly into TimeSafari. Users can now share images from the iOS share sheet, which will open in SharedPhotoView for use as gifts or profile pictures. iOS Native Implementation: - Add TimeSafariShareExtension target with ShareViewController - Configure App Groups for data sharing between extension and main app - Implement ShareViewController to process shared images and convert to base64 - Store shared image data in App Group UserDefaults - Add ShareImageBridge utility to read shared data from App Group - Update AppDelegate to handle shared-photo deep link and bridge data to JS JavaScript Integration: - Add checkAndStoreNativeSharedImage() in main.capacitor.ts to read shared images from native layer via temporary file bridge - Convert base64 data to data URL format for compatibility with base64ToBlob - Integrate with existing SharedPhotoView component - Add "shared-photo" to deep link validation schema Build System: - Integrate Xcode 26 / CocoaPods compatibility workaround into build-ios.sh - Add run_pod_install_with_workaround() for explicit pod install - Add run_cap_sync_with_workaround() for Capacitor sync (which runs pod install internally) - Automatically detect project format version 70 and apply workaround - Remove standalone pod-install-workaround.sh script Code Cleanup: - Remove verbose debug logs from ShareViewController, AppDelegate, and main.capacitor.ts - Retain essential logger calls for production debugging Documentation: - Add ios-share-extension-setup.md with manual Xcode setup instructions - Add ios-share-extension-git-commit-guide.md for version control best practices - Add ios-share-implementation-status.md tracking implementation progress - Add native-share-target-implementation.md with overall architecture - Add xcode-26-cocoapods-workaround.md documenting the compatibility issue The implementation uses a temporary file bridge (AppDelegate writes to Documents directory, JS reads via Capacitor Filesystem plugin) as a workaround for Capacitor plugin auto-discovery issues. This can be improved in the future by properly registering ShareImagePlugin in Capacitor's plugin registry.
This commit is contained in:
10
ios/App/App/App.entitlements
Normal file
10
ios/App/App/App.entitlements
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.timesafari</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -39,6 +39,33 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||
// Check if this is a shared-photo deep link and store image data in a way JS can access
|
||||
if url.scheme == "timesafari" && url.host == "shared-photo" {
|
||||
// Try to get shared image from App Group and store it in a temp file that JS can read
|
||||
// This is a workaround until the plugin is properly registered
|
||||
if let sharedData = getSharedImageData() {
|
||||
// Write to a temp file in the app's Documents directory that JavaScript can read via Filesystem plugin
|
||||
let fileManager = FileManager.default
|
||||
if let documentsDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
|
||||
let tempFileURL = documentsDir.appendingPathComponent("timesafari_shared_photo.json")
|
||||
|
||||
// Create JSON data
|
||||
let jsonData: [String: String] = [
|
||||
"base64": sharedData["base64"] ?? "",
|
||||
"fileName": sharedData["fileName"] ?? ""
|
||||
]
|
||||
|
||||
if let json = try? JSONSerialization.data(withJSONObject: jsonData, options: []) {
|
||||
do {
|
||||
try json.write(to: tempFileURL)
|
||||
} catch {
|
||||
// Error writing temp file - will be handled by JS layer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called when the app was launched with a url. Feel free to add additional processing here,
|
||||
// but if you want the App API to support tracking app url opens, make sure to keep this call
|
||||
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
|
||||
@@ -50,5 +77,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
// tracking app url opens, make sure to keep this call
|
||||
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for shared image from Share Extension
|
||||
* Reads from App Group UserDefaults and returns shared image data if available
|
||||
*
|
||||
* @returns Dictionary with "base64" and "fileName" keys, or nil if no shared image
|
||||
*/
|
||||
func getSharedImageData() -> [String: String]? {
|
||||
let appGroupIdentifier = "group.app.timesafari"
|
||||
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let base64 = userDefaults.string(forKey: "sharedPhotoBase64"),
|
||||
let fileName = userDefaults.string(forKey: "sharedPhotoFileName") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear the shared data after reading
|
||||
userDefaults.removeObject(forKey: "sharedPhotoBase64")
|
||||
userDefaults.removeObject(forKey: "sharedPhotoFileName")
|
||||
userDefaults.synchronize()
|
||||
|
||||
return ["base64": base64, "fileName": fileName]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
48
ios/App/App/ShareImageBridge.swift
Normal file
48
ios/App/App/ShareImageBridge.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Share Image Bridge
|
||||
*
|
||||
* Provides a bridge between JavaScript and native iOS code to access
|
||||
* shared images stored in App Group UserDefaults by the Share Extension.
|
||||
*
|
||||
* This bridge allows the JavaScript layer to read shared image data
|
||||
* that was stored by the Share Extension.
|
||||
*
|
||||
* Note: This class doesn't need Capacitor - it's a simple Swift utility
|
||||
* that reads from App Group UserDefaults. The JavaScript bridge will be
|
||||
* implemented separately.
|
||||
*/
|
||||
@objc(ShareImageBridge)
|
||||
public class ShareImageBridge: NSObject {
|
||||
|
||||
private static let appGroupIdentifier = "group.app.timesafari"
|
||||
private static let sharedPhotoBase64Key = "sharedPhotoBase64"
|
||||
private static let sharedPhotoFileNameKey = "sharedPhotoFileName"
|
||||
|
||||
/**
|
||||
* Get shared image data from App Group UserDefaults
|
||||
* Called from JavaScript via Capacitor bridge
|
||||
*
|
||||
* @returns Dictionary with "base64" and "fileName" keys, or nil if no shared image
|
||||
*/
|
||||
@objc public static func getSharedImageData() -> [String: String]? {
|
||||
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
|
||||
print("ShareImageBridge: Failed to access App Group UserDefaults")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let base64 = userDefaults.string(forKey: sharedPhotoBase64Key),
|
||||
let fileName = userDefaults.string(forKey: sharedPhotoFileNameKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear the shared data after reading
|
||||
userDefaults.removeObject(forKey: sharedPhotoBase64Key)
|
||||
userDefaults.removeObject(forKey: sharedPhotoFileNameKey)
|
||||
userDefaults.synchronize()
|
||||
|
||||
return ["base64": base64, "fileName": fileName]
|
||||
}
|
||||
}
|
||||
|
||||
28
ios/App/App/ShareImagePlugin.swift
Normal file
28
ios/App/App/ShareImagePlugin.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
import Foundation
|
||||
import Capacitor
|
||||
|
||||
/**
|
||||
* Share Image Plugin
|
||||
*
|
||||
* Capacitor plugin that exposes ShareImageBridge functionality to JavaScript.
|
||||
* Allows JavaScript to retrieve shared images from App Group UserDefaults.
|
||||
*/
|
||||
@objc(ShareImagePlugin)
|
||||
public class ShareImagePlugin: CAPPlugin {
|
||||
|
||||
@objc func getSharedImageData(_ call: CAPPluginCall) {
|
||||
guard let sharedData = ShareImageBridge.getSharedImageData() else {
|
||||
call.resolve(["success": false, "data": NSNull()])
|
||||
return
|
||||
}
|
||||
|
||||
call.resolve([
|
||||
"success": true,
|
||||
"data": [
|
||||
"base64": sharedData["base64"] ?? "",
|
||||
"fileName": sharedData["fileName"] ?? ""
|
||||
]
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user