请看例子,比如全人类都在用的《微信》App,在手机桌面长按(另外比较高大上的叫做3D Touch
)应用图标,会弹出一个快捷菜单窗口,点击菜单直接打开应用并进入对应功能页面。
先不说这个操作,有没有直接打开应用,使用特定功能来得更加“快捷”,如何实现上述功能,才是本篇的关键内容。
配置上述功能的快捷菜单项有两种方式,一种是静态配置方式,另一种是代码设置即动态配置的方式,但功能上都要用到
UIApplicationShortcutItem
这个类及相关的API。
静态配置
快捷菜单选择项以KV
的方式通过info.plist
植入,即在info.plist
中添加UIApplicationShortcutItems
配置项。
完成后的结果是这样的:
以
Source Code
格式配置内容进行说明,其中UIApplicationShortcutItems
为桌面快捷菜单项
功能的启用key
,添加了UIApplicationShortcutItems
才会有桌面快捷菜单项
功能;“UIApplicationShortcutItemType
、UIApplicationShortcutItemTitle
”为必填项且对应的value
值不能为空(字符串),否则该配置菜单不予显示,UIApplicationShortcutItemType
作为快捷菜单项的标识,用于点击后识别并处理对应的业务功能,UIApplicationShortcutItemTitle
作为菜单项的标题进行对外显示,UIApplicationShortcutItemSubtitle
则为副标题进行对外显示;“UIApplicationShortcutItemIconFile
、UIApplicationShortcutItemIconType
”均用于配置菜单项的图标,不同之处在于,前者可自定义图标,后者则需配置系统自身所提供支持的图标类型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;
中捕获点击事件并处理相关业务。通过捕获connectionOptions
的shortcutItem
信息来确定是否有快捷菜单项点击事件,然后根据相关信息实现对应的业务功能。
- (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桌面快捷菜单项功能已基本完成,最后看效果。
这里插一条冷知识
1.不知道有没有小伙伴注意到文章开头的第一张图片,图片中的弹窗菜单项移除App
和编辑主屏幕
中间多出了分享App
这一项,但自己正在开发中的项目有的有而有的又没有分享App
这一项,而且系统自带App的快捷菜单项好像也都没有分享App
这一项,有经验的小伙伴肯定要举手了,确实只要是已上架的App都会有分享App
这一项,包括正在开发准备版本迭代的App项目,至于为什么系统自带的App却没有,最后发现在App Store上只要是Apple
主体下开发的App,都没有分享App
这一快捷功能。
2.自定义添加的快捷菜单项,目前无法自定义排序显示,但可以根据系统排序大致规则,调整快捷菜单项的
type
以达到想要的排序效果。