PAYATHON 2026

NativeScript iOS App Delegate Methods 未触发

支付阿杰

结论

这些方法没有触发,通常不是 Mobile Pay SDK 本身的问题,而是 App Delegate 的注册方式或注册时机不对:

  1. 执行 @ObjCClass 后,Objective-C 类已经完成注册。此时再向 prototype 动态添加方法,不一定能把对应的 Objective-C selector 暴露给 iOS。
  2. App Delegate 必须在 NativeScript 启动、Angular bootstrap 和原生 delegate 实例创建之前注册。
  3. 现代 iOS 通常会调用 application:openURL:options:,在 NativeScript 中对应 applicationOpenURLOptions。applicationHandleOpenURL 是较早的回调。
  4. 应用还需要正确配置 Mobile Pay 返回时使用的 URL Scheme,否则 iOS 不会把回调 URL 交给应用。

应创建一个明确实现 UIApplicationDelegate 的类,在类体中直接声明相关方法,并在入口文件启动 Angular 之前把该类赋给 application.ios.delegate。

为什么动态添加的方法收不到回调

下面的写法有一个问题:

@ObjCClass(UIApplicationDelegate)
class UIApplicationDelegateImpl extends UIResponder implements UIApplicationDelegate {
}

appDelegate.prototype.applicationHandleOpenURL = (...) => {
    // ...
};

@ObjCClass 会把 JavaScript/TypeScript 类注册到 Objective-C runtime。注册发生时,类中还没有 applicationHandleOpenURL、applicationOpenURLOptions 等方法。

随后修改 JavaScript prototype,虽然能在 JavaScript 中看到这些函数,但 Objective-C runtime 未必会认为这个类实现了相应的 selector。因此,当 iOS 检查 delegate 是否响应 application:openURL:options: 时,可能会得到否定结果,也就不会调用这些方法。

如果这段代码在 Angular service、component 或插件初始化期间执行,App Delegate 的原生实例还可能已经创建。此时再替换或扩展 delegate,往往已经太晚。

推荐实现

把自定义 App Delegate 放在独立文件中,并将所有需要暴露给 iOS 的方法直接定义在类体内。

例如,创建 app-delegate.ts:

import { ios } from "tns-core-modules/application";

declare const MobilePayManager: any;

@ObjCClass(UIApplicationDelegate)
export class AppDelegate extends UIResponder implements UIApplicationDelegate {
    public applicationDidFinishLaunchingWithOptions(
        application: UIApplication,
        launchOptions: NSDictionary<any, any>
    ): boolean {
        return true;
    }

    // iOS 9 及更高版本通常调用此方法
    public applicationOpenURLOptions(
        application: UIApplication,
        url: NSURL,
        options: NSDictionary<string, any>
    ): boolean {
        return this.handleMobilePayURL(url);
    }

    // 兼容旧版 iOS 回调
    public applicationOpenURLSourceApplicationAnnotation(
        application: UIApplication,
        url: NSURL,
        sourceApplication: string,
        annotation: any
    ): boolean {
        return this.handleMobilePayURL(url);
    }

    // 更早期的 iOS 回调
    public applicationHandleOpenURL(
        application: UIApplication,
        url: NSURL
    ): boolean {
        return this.handleMobilePayURL(url);
    }

    private handleMobilePayURL(url: NSURL): boolean {
        if (!url) {
            return false;
        }

        MobilePayManager
            .sharedInstance()
            .handleMobilePayCallbacksWithUrlSuccessErrorCancel(
                url,
                paymentSuccess,
                paymentFailure,
                paymentCancel
            );

        return true;
    }
}

function paymentSuccess(result: any): void {
    console.log("Mobile Pay payment succeeded", result);
}

function paymentFailure(error: any): void {
    console.log("Mobile Pay payment failed", error);
}

function paymentCancel(result: any): void {
    console.log("Mobile Pay payment cancelled", result);
}

然后在应用入口文件中完成注册。对于这个项目使用的旧版 NativeScript Angular,通常要在 main.ts 调用 bootstrapModule 之前注册:

import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { ios } from "tns-core-modules/application";

import { AppModule } from "./app.module";
import { AppDelegate } from "./app-delegate";

if (ios) {
    ios.delegate = AppDelegate;
}

platformNativeScriptDynamic().bootstrapModule(AppModule);

执行顺序不能颠倒。下面这行:

ios.delegate = AppDelegate;

必须放在这行之前:

platformNativeScriptDynamic().bootstrapModule(AppModule);

如果已有插件扩展了 App Delegate

直接替换 ios.delegate,可能会覆盖其他插件提供的实现。如果需要兼容现有 delegate,可以继承它,而不是创建一个与其无关的新类:

import { ios } from "tns-core-modules/application";

const ExistingAppDelegate =
    ios.delegate || UIResponder;

@ObjCClass(UIApplicationDelegate)
class AppDelegate extends ExistingAppDelegate
    implements UIApplicationDelegate {

    public applicationOpenURLOptions(
        application: UIApplication,
        url: NSURL,
        options: NSDictionary<string, any>
    ): boolean {
        const handled = MobilePayManager
            .sharedInstance()
            .handleMobilePayCallbacksWithUrlSuccessErrorCancel(
                url,
                paymentSuccess,
                paymentFailure,
                paymentCancel
            );

        return handled === undefined ? true : !!handled;
    }

    public applicationHandleOpenURL(
        application: UIApplication,
        url: NSURL
    ): boolean {
        MobilePayManager
            .sharedInstance()
            .handleMobilePayCallbacksWithUrlSuccessErrorCancel(
                url,
                paymentSuccess,
                paymentFailure,
                paymentCancel
            );

        return true;
    }
}

ios.delegate = AppDelegate;

能否安全继承现有 delegate,取决于 NativeScript 版本和相关插件实现 App Delegate 的方式。如果现有 delegate 还负责处理通知、Firebase 或深链接,自定义实现就不能无条件接收所有 URL。更稳妥的处理方式是先判断 URL 是否属于 Mobile Pay。如果不属于,再交给原有实现。

检查 URL Scheme 配置

即使 App Delegate 配置正确,没有注册 URL Scheme 仍然收不到回调。需要在 App_Resources/iOS/Info.plist 中配置 Mobile Pay 返回应用时使用的 scheme,例如:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>mobilepay-callback</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>your-app-scheme</string>
        </array>
    </dict>
</array>

your-app-scheme 必须与传给 Mobile Pay SDK 的回调 scheme 完全一致。具体的 scheme 名称以及 Mobile Pay SDK 要求的其他 Info.plist 配置,应以当前使用的 SDK 版本接入说明为准。

可以先在处理函数中打印收到的 URL:

private handleMobilePayURL(url: NSURL): boolean {
    console.log("Received callback URL:", url.absoluteString);

    // 调用 Mobile Pay SDK
    return true;
}

方法名称必须准确

NativeScript 会把 Objective-C selector 映射为连续的驼峰式方法名:

application:handleOpenURL:
→ applicationHandleOpenURL

application:openURL:options:
→ applicationOpenURLOptions

application:openURL:sourceApplication:annotation:
→ applicationOpenURLSourceApplicationAnnotation

下面的方法不是 UIApplicationDelegate 的 URL 回调:

openURL(url: NSURL): boolean

它不能代替前面的 delegate 方法,可以删除。

还需要注意的事项

  • App Delegate 注册代码应放在入口模块中,不能等到页面、组件或 Angular service 创建后再执行。
  • 不要先声明一个空的 @ObjCClass,再向其 prototype 添加需要暴露给 Objective-C 的方法。
  • 优先实现 applicationOpenURLOptions,同时保留旧方法,以兼容旧系统或旧 SDK。
  • 如果应用启用了基于 scene 的生命周期,回调也可能进入 Scene Delegate。不过这个项目使用 NativeScript 5.3,应先检查传统 App Delegate 和 URL Scheme 配置。
  • 修改 Info.plist 或原生 delegate 后,需要重新构建并安装应用,以免旧的原生构建产物影响验证。
  • 如果回调函数依赖某个 service 实例,需要显式保存该实例或绑定上下文,不能假定 Objective-C 调用时的 this 仍指向原来的 Angular service。

修正的重点是:在应用启动前注册 delegate,并把 URL 回调方法直接声明在 @ObjCClass 类中。Objective-C runtime 识别对应 selector 后,Mobile Pay 返回应用时,applicationOpenURLOptions 或兼容的 applicationHandleOpenURL 才能收到调用。

备注:内容仅供参考。