iOS添加APP桌面快捷菜单项功能

请看例子,比如全人类都在用的《微信》App,在手机桌面长按(另外比较高大上的叫做3D Touch)应用图标,会弹出一个快捷菜单窗口,点击菜单直接打开应用并进入对应功能页面。

《微信》桌面快捷菜单功能

先不说这个操作,有没有直接打开应用,使用特定功能来得更加“快捷”,如何实现上述功能,才是本篇的关键内容。
配置上述功能的快捷菜单项有两种方式,一种是静态配置方式,另一种是代码设置即动态配置的方式,但功能上都要用到UIApplicationShortcutItem这个类及相关的API。

静态配置

快捷菜单选择项以KV的方式通过info.plist植入,即在info.plist中添加UIApplicationShortcutItems配置项。

TARGETS配置Info
直接编辑Info文件

完成后的结果是这样的:
Property List
Source Code

Source Code格式配置内容进行说明,其中UIApplicationShortcutItems桌面快捷菜单项功能的启用key,添加了UIApplicationShortcutItems才会有桌面快捷菜单项功能;“UIApplicationShortcutItemTypeUIApplicationShortcutItemTitle”为必填项且对应的value值不能为空(字符串),否则该配置菜单不予显示,UIApplicationShortcutItemType作为快捷菜单项的标识,用于点击后识别并处理对应的业务功能,UIApplicationShortcutItemTitle作为菜单项的标题进行对外显示,UIApplicationShortcutItemSubtitle则为副标题进行对外显示;“UIApplicationShortcutItemIconFileUIApplicationShortcutItemIconType”均用于配置菜单项的图标,不同之处在于,前者可自定义图标,后者则需配置系统自身所提供支持的图标类型UIApplicationShortcutIconType,且UIApplicationShortcutItemIconFile优先级要高于UIApplicationShortcutItemIconType配置即两者共存的情况下,以UIApplicationShortcutItemIconFile配置图标来对外显示,另外无论是自定义还是系统提供的图标都不能按照图标的实际颜色显示,都会重新渲染成系统给定的颜色再对外显示;UIApplicationShortcutItemUserInfo则可作为传参来使用,但大部分情况下UIApplicationShortcutItemType在某种意义上可替代之。

动态配置

1.使用UIApplicationShortcutItem类通过构造方法来创建快捷菜单项。

    //使用系统图标(通过名称获取系统图标)
    UIApplicationShortcutIcon *shortcutIcon = [UIApplicationShortcutIcon iconWithSystemImageName:@"folder"];
    //使用系统图标(通过枚举类型获取系统图标)
    //UIApplicationShortcutIcon *shortcutIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeMessage];
    //使用自定义图标
    //UIApplicationShortcutIcon *shortcutIcon = [UIApplicationShortcutIcon iconWithTemplateImageName:@"redtoolbox"];
    UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc] initWithType:@"ToolBox" localizedTitle:@"工具箱" localizedSubtitle:@"工具" icon:shortcutIcon userInfo:@{@"key1":@"特定参数1",@"key2":@"特定参数1"}];
    application.shortcutItems = @[item];
    return YES;
}

2.使用UIMutableApplicationShortcutItem类来修改快捷菜单项的相关属性。

- (void)applicationWillEnterForeground:(UIApplication *)application {
    NSArray *shortcutItems = [application shortcutItems];
    UIApplicationShortcutItem *item = [shortcutItems firstObject];
    UIMutableApplicationShortcutItem *mutItem = [item mutableCopy];
    mutItem.type = @"ToolBox2";
    mutItem.icon = [UIApplicationShortcutIcon iconWithTemplateImageName:@"redtoolbox"];
    mutItem.localizedTitle = @"工具箱2";
    mutItem.localizedSubtitle = nil;
    application.shortcutItems = @[mutItem];
}

相比静态配置的方式,动态配置的方式更加灵活,既可以动态快捷菜单项,也提供了多种菜单项图标获取方式,而且在动态配置过程中type不再是必填项,只有localizedTitle为必填项,在app内任何时机都可以创建UIApplicationShortcutItem快捷菜单项,同样也可以在任何时机通过UIMutableApplicationShortcutItem修改快捷菜单项。

#import "ToolBoxMainVC.h"
@implementation ToolBoxMainVC
//创建快捷菜单项
- (IBAction)createShortcutItem:(id)sender {
    UIApplication *application = [UIApplication sharedApplication];
    UIApplicationShortcutIcon *shortcutIcon = [UIApplicationShortcutIcon iconWithSystemImageName:@"folder"];
    UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc] initWithType:@"ToolBox" localizedTitle:@"工具箱" localizedSubtitle:@"工具" icon:shortcutIcon userInfo:@{@"key1":@"特定参数1",@"key2":@"特定参数1"}];
    application.shortcutItems = @[item];
}
//修改快捷菜单项
- (IBAction)changeShortcutItem:(id)sender {
    UIApplication *application = [UIApplication sharedApplication];
    NSArray *shortcutItems = [application shortcutItems];
    UIApplicationShortcutItem *item = [shortcutItems firstObject];
    UIMutableApplicationShortcutItem *mutItem = [item mutableCopy];
    mutItem.type = @"ToolBox2";
    mutItem.icon = [UIApplicationShortcutIcon iconWithTemplateImageName:@"redtoolbox"];
    mutItem.localizedTitle = @"工具箱2";
    mutItem.localizedSubtitle = nil;
    application.shortcutItems = @[mutItem];
}
//清除快捷菜单项
- (IBAction)clearShortcutItem:(id)sender {
    UIApplication *application = [UIApplication sharedApplication];
    application.shortcutItems = nil;
}

@end
增、删、改

处理快捷菜单项点击事件

桌面快捷菜单项点击事件的处理时机有两种,分别为APP冷启动和热启动,冷启动即APP进程关闭的情况下打开APP,热启动即APP已开启但处于后台模式再次进入APP。

  • 冷启动

AppDelegate代理事件didFinishLaunchingWithOptions中捕获点击事件并处理相关业务。通过捕获launchOptions中的 UIApplicationLaunchOptionsShortcutItemKey相关信息来确定是否有快捷菜单项点击事件,然后根据相关信息实现对应的业务功能。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"didFinishLaunchingWithOptions:%@",launchOptions);
    UIApplicationShortcutItem *shortcutItem = launchOptions[UIApplicationLaunchOptionsShortcutItemKey];
    if (shortcutItem) {
        //根据快捷菜单项的类型、传参信息编写对应的业务代码
        NSString *itemType = shortcutItem.type;//类型
        NSDictionary *userInfo = shortcutItem.userInfo;//传参信息
        //这里做弹窗演示
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:[shortcutItem description] preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *action = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
        [alert addAction:action];
        [[self.window rootViewController] presentViewController:alert animated:YES completion:nil];
    }
    return YES;
}

如果是场景应用,则需在指定的场景代理事件- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions;中捕获点击事件并处理相关业务。通过捕获connectionOptionsshortcutItem信息来确定是否有快捷菜单项点击事件,然后根据相关信息实现对应的业务功能。

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    UIApplicationShortcutItem *shortcutItem = connectionOptions.shortcutItem;
    if (shortcutItem) {
        //根据快捷菜单项的类型、传参信息编写对应的业务代码
        NSString *itemType = shortcutItem.type;//类型
        NSDictionary *userInfo = shortcutItem.userInfo;//传参信息
        //这里做弹窗演示
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:[shortcutItem description] preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *action = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
        [alert addAction:action];
        [[self.window rootViewController] presentViewController:alert animated:YES completion:nil];
    }
}

在场景代理事件- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions;中处理快捷菜单项点击事件时日志打印区可能会抛出“whose view is not in the window hierarchy”这样的异常信息,这是由于超前进行UI交互的问题,比如上述代码快中的rootViewController,在没有添加到window上就超前进行present操作,此时就会抛出whose view is not in the window hierarchy的异常日志信息。


如何避开上述异常问题,这里推荐两种方案:
1.采用延时操作的方法

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    UIApplicationShortcutItem *shortcutItem = connectionOptions.shortcutItem;
    if (!shortcutItem) {//添加延时
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //根据快捷菜单项的类型、传参信息编写对应的业务代码
            NSString *itemType = shortcutItem.type;//类型
            NSDictionary *userInfo = shortcutItem.userInfo;//传参信息
            //这里做弹窗演示
            UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:[shortcutItem description] preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *action = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
            [alert addAction:action];
            [[self.window rootViewController] presentViewController:alert animated:YES completion:nil];
        });
    }
}

2.先标记快捷菜单项再择机处理点击事件

@interface SceneDelegate ()
@property (nonatomic, strong, nullable) UIApplicationShortcutItem *shortcutItem;/**< 快捷菜单项*/
@end
@implementation SceneDelegate
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    //1.先标记快捷菜单项(缓存)
    self.shortcutItem = connectionOptions.shortcutItem;
}
- (void)sceneDidBecomeActive:(UIScene *)scene {
    //2.择机处理点击事件
    if (self.shortcutItem) {
        //根据快捷菜单项的类型、传参信息编写对应的业务代码
        NSString *itemType = self.shortcutItem.type;//类型
        NSDictionary *userInfo = self.shortcutItem.userInfo;//传参信息
        //这里做弹窗演示
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:[self.shortcutItem description] preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *action = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
        [alert addAction:action];
        [[self.window rootViewController] presentViewController:alert animated:YES completion:nil];
    }
    //3.清除快捷菜单项标记(缓存)
    self.shortcutItem = nil;
}
@end
  • 热启动

AppDelegate代理事件- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler中捕获快捷菜单项并处理相关业务。

- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
    //根据快捷菜单项的类型、传参信息编写对应的业务代码
    NSString *itemType = shortcutItem.type;//类型
    NSDictionary *userInfo = shortcutItem.userInfo;//传参信息
    //这里做弹窗演示
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:[shortcutItem description] preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
    [alert addAction:action];
    [[self.window rootViewController] presentViewController:alert animated:YES completion:nil];
    completionHandler(YES);
}

同样如果是场景应用,则需在指定的场景代理事件- (void)windowScene:(UIWindowScene *)windowScene performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler中捕获快捷菜单项并处理相关业务。

- (void)windowScene:(UIWindowScene *)windowScene performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
    //根据快捷菜单项的类型、传参信息编写对应的业务代码
    NSString *itemType = shortcutItem.type;//类型
    NSDictionary *userInfo = shortcutItem.userInfo;//传参信息
    //这里做弹窗演示
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:[shortcutItem description] preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
    [alert addAction:action];
    [self.window.rootViewController presentViewController:alert animated:YES completion:nil];
    completionHandler(YES);
}

最后提一句,在使用快捷菜单项冷启动APP时,无论是场景应用和非场景应用,APP启动时并不会执行performActionForShortcutItem代理方法,所以不必担心业务功能重复执行的问题。

至此APP桌面快捷菜单项功能已基本完成,最后看效果。

APP桌面快捷菜单项功能

这里插一条冷知识

1.不知道有没有小伙伴注意到文章开头的第一张图片,图片中的弹窗菜单项移除App编辑主屏幕中间多出了分享App这一项,但自己正在开发中的项目有的有而有的又没有分享App这一项,而且系统自带App的快捷菜单项好像也都没有分享App这一项,有经验的小伙伴肯定要举手了,确实只要是已上架的App都会有分享App这一项,包括正在开发准备版本迭代的App项目,至于为什么系统自带的App却没有,最后发现在App Store上只要是Apple主体下开发的App,都没有分享App这一快捷功能。


2.自定义添加的快捷菜单项,目前无法自定义排序显示,但可以根据系统排序大致规则,调整快捷菜单项的type以达到想要的排序效果。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容