fix(ios): enable Capacitor plugin discovery via CAPBridgedPlugin conformance
Capacitor iOS was not discovering DailyNotificationPlugin because it did not conform to the CAPBridgedPlugin protocol required for runtime discovery. Changes: - Add @objc extension to DailyNotificationPlugin implementing CAPBridgedPlugin with identifier, jsName, and pluginMethods properties - Force-load plugin framework in AppDelegate before Capacitor initializes - Remove duplicate BGTaskScheduler registration from AppDelegate (plugin handles it) - Update podspec to use dynamic framework (static_framework = false) - Add diagnostic logging to verify plugin discovery Result: Plugin is now discovered by Capacitor and all methods are accessible from JavaScript. Verified working with checkPermissionStatus() method. Files modified: - ios/Plugin/DailyNotificationPlugin.swift: Added CAPBridgedPlugin extension - test-apps/ios-test-app/ios/App/App/AppDelegate.swift: Force-load + diagnostics - ios/DailyNotificationPlugin.podspec: Dynamic framework setting - doc/directives/0003-iOS-Android-Parity-Directive.md: Documented solution
This commit is contained in:
@@ -1,32 +1,108 @@
|
||||
import UIKit
|
||||
import Capacitor
|
||||
import BackgroundTasks
|
||||
import DailyNotificationPlugin
|
||||
import ObjectiveC
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
// Background task identifiers
|
||||
private let fetchTaskIdentifier = "com.timesafari.dailynotification.fetch"
|
||||
private let notifyTaskIdentifier = "com.timesafari.dailynotification.notify"
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Register background tasks
|
||||
if #available(iOS 13.0, *) {
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: fetchTaskIdentifier, using: nil) { task in
|
||||
// Background fetch task handler
|
||||
// Plugin will handle this
|
||||
task.setTaskCompleted(success: true)
|
||||
}
|
||||
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: notifyTaskIdentifier, using: nil) { task in
|
||||
// Background notify task handler
|
||||
// Plugin will handle this
|
||||
task.setTaskCompleted(success: true)
|
||||
NSLog("DNP-DEBUG: AppDelegate.application(_:didFinishLaunchingWithOptions:) called")
|
||||
|
||||
// DIAGNOSTIC: Test plugin class accessibility via Objective-C runtime
|
||||
NSLog("DNP-DEBUG: Testing plugin class accessibility...")
|
||||
|
||||
// Test 1: NSClassFromString (Objective-C runtime lookup)
|
||||
if let pluginClass = NSClassFromString("DailyNotificationPlugin") {
|
||||
NSLog("DNP-DEBUG: ✅ NSClassFromString found plugin class: %@", String(describing: pluginClass))
|
||||
} else {
|
||||
NSLog("DNP-DEBUG: ❌ NSClassFromString could NOT find plugin class 'DailyNotificationPlugin'")
|
||||
}
|
||||
|
||||
// Test 2: Swift type access
|
||||
let pluginType = DailyNotificationPlugin.self
|
||||
NSLog("DNP-DEBUG: Plugin class type: %@", String(describing: pluginType))
|
||||
NSLog("DNP-DEBUG: Plugin class name: %@", String(describing: type(of: pluginType)))
|
||||
|
||||
// Test 3: Verify CAPPlugin inheritance
|
||||
// Note: 'is' test is always true because DailyNotificationPlugin extends CAPPlugin
|
||||
NSLog("DNP-DEBUG: ✅ Plugin is a CAPPlugin subclass (DailyNotificationPlugin extends CAPPlugin)")
|
||||
|
||||
// Test 4: Check if class conforms to NSObjectProtocol (required for Objective-C runtime)
|
||||
// Note: 'is' test is always true because CAPPlugin extends NSObject which conforms to NSObjectProtocol
|
||||
NSLog("DNP-DEBUG: ✅ Plugin conforms to NSObjectProtocol (CAPPlugin extends NSObject)")
|
||||
|
||||
// Test 5: Try to get class name via Objective-C runtime
|
||||
let className = NSStringFromClass(pluginType)
|
||||
NSLog("DNP-DEBUG: Plugin class name (NSStringFromClass): %@", className)
|
||||
|
||||
// Test 6: Check if framework is loaded
|
||||
if let framework = Bundle(identifier: "org.cocoapods.DailyNotificationPlugin") {
|
||||
NSLog("DNP-DEBUG: ✅ Plugin framework bundle found: %@", framework.bundlePath)
|
||||
} else {
|
||||
NSLog("DNP-DEBUG: ⚠️ Plugin framework bundle not found via identifier")
|
||||
// Try alternative: check if class is in main bundle
|
||||
if Bundle.main.path(forResource: "DailyNotificationPlugin", ofType: "framework") != nil {
|
||||
NSLog("DNP-DEBUG: ✅ Plugin framework found in main bundle")
|
||||
} else {
|
||||
NSLog("DNP-DEBUG: ⚠️ Plugin framework not found in main bundle")
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Force-load the plugin framework before Capacitor initializes
|
||||
// objc_getClassList may not include classes from frameworks that haven't been loaded yet
|
||||
// Even though NSClassFromString can find the class, Capacitor's discovery uses objc_getClassList
|
||||
// which only includes loaded classes. We need to ensure the framework is loaded.
|
||||
NSLog("DNP-DEBUG: Force-loading DailyNotificationPlugin framework...")
|
||||
_ = DailyNotificationPlugin.self // Force class load
|
||||
NSLog("DNP-DEBUG: DailyNotificationPlugin class reference created - framework should be loaded")
|
||||
|
||||
// Verify class is now in objc_getClassList
|
||||
let classCount = objc_getClassList(nil, 0)
|
||||
let classes = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(classCount))
|
||||
defer { classes.deallocate() }
|
||||
let releasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classes)
|
||||
let numClasses = objc_getClassList(releasingClasses, Int32(classCount))
|
||||
|
||||
var foundInClassList = false
|
||||
for i in 0..<Int(numClasses) {
|
||||
if let aClass = classes[i] {
|
||||
let className = NSStringFromClass(aClass)
|
||||
if className == "DailyNotificationPlugin" {
|
||||
foundInClassList = true
|
||||
NSLog("DNP-DEBUG: ✅ DailyNotificationPlugin found in objc_getClassList")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundInClassList {
|
||||
NSLog("DNP-DEBUG: ❌ DailyNotificationPlugin NOT found in objc_getClassList (this is the problem!)")
|
||||
} else {
|
||||
// Test if class conforms to CAPBridgedPlugin (required for Capacitor discovery)
|
||||
if let aClass = NSClassFromString("DailyNotificationPlugin") {
|
||||
// Try using CAPBridgedPlugin.self (the actual protocol, not string lookup)
|
||||
// This is what Capacitor uses: class_conformsToProtocol(aClass, CAPBridgedPlugin.self)
|
||||
let conformsToBridgedPlugin = class_conformsToProtocol(aClass, CAPBridgedPlugin.self)
|
||||
NSLog("DNP-DEBUG: Class conforms to CAPBridgedPlugin (using .self): %@", conformsToBridgedPlugin ? "YES" : "NO")
|
||||
|
||||
// Test if class can be cast to CapacitorPlugin.Type
|
||||
if let pluginType = aClass as? CAPPlugin.Type {
|
||||
// Try casting to CapacitorPlugin (which is CAPPlugin & CAPBridgedPlugin)
|
||||
if let capacitorPluginType = pluginType as? (CAPPlugin & CAPBridgedPlugin).Type {
|
||||
NSLog("DNP-DEBUG: ✅ Can cast to (CAPPlugin & CAPBridgedPlugin).Type")
|
||||
} else {
|
||||
NSLog("DNP-DEBUG: ❌ Cannot cast to (CAPPlugin & CAPBridgedPlugin).Type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Background task registration is handled by DailyNotificationPlugin.load()
|
||||
// Do NOT register here to avoid duplicate registration crash
|
||||
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user