前言
iPhone 6s和iPhone 6s Plus为多点触摸界面带来了强大的3D触摸新维度。这项新技术可以感知用户按下显示屏的深度,让他们比以往任何时候都更能使用你的应用程序和游戏。更多关于3D Touch的介绍可以参见这里。
正文
接下来会介绍一下所有关于3D Touch开发的一些内容。
0.判断3D Touch是否可用
先判断设备是否支持3D Touch,这里主要用到的类是:UITraitCollection。在iOS9之后,可以使用该类判断设备是否支持3D Touch,苹果官方说明如下:
主要是使用了forceTouchCapability属性,该属性的枚举值包括:
//未知
UIForceTouchCapabilityUnknown = 0,
//不可用
UIForceTouchCapabilityUnavailable = 1,
//可用
UIForceTouchCapabilityAvailable = 2
用户在使用APP的时候也有可能在设置中关闭3D Touch,这个时候可以实现traitCollectionDidChange:代理方法去监听是否改变:(在VC中实现UITraitEnvironment协议)
#pragma mark - UITraitEnvironment
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
if (previousTraitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
[self showAlertWithStrig:@"3D Touch已关闭"];
}else if (previousTraitCollection.forceTouchCapability == UIForceTouchCapabilityUnavailable) {
[self showAlertWithStrig:@"3D Touch已打开"];
}
}
这里注意:拿到的traitcollection是previousTraitCollection。
1.Home screen quick action API(主屏幕交互)
该API主要用于添加应用程序图片的快捷方式,以预测并加速用户与应用的互动。
1.1.用例
1.2.代码实例
两种方法实现该特性,直接使用代码开发,或者直接在Info.plist文件配置。
1.2.1.Static quick actions
直接在application:didFinishLaunchingWithOptions:方法中处理:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSMutableArray *shortCutItemArr = [NSMutableArray arrayWithCapacity:4];
UIApplicationShortcutIcon *icon1 = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeSearch];
UIApplicationShortcutItem *shortItem1 = [[UIApplicationShortcutItem alloc] initWithType:@"com.zhanggui.Demo.search" localizedTitle:@"搜索" localizedSubtitle:@"搜索想要的电影" icon:icon1 userInfo:nil];
[shortCutItemArr addObject:shortItem1];
[UIApplication sharedApplication].shortcutItems = shortCutItemArr;
return YES;
}
设置shortcutItems即可。
1.2.2.Dynamic quick actions
直接使用Info.plist文件配置:
<array>
<dict>
<key>UIApplicationShortcutItemIconType</key>
<string>UIApplicationShortcutIconTypeShare</string>
<key>UIApplicationShortcutItemTitle</key>
<string>取票码</string>
<key>UIApplicationShortcutItemType</key>
<string>com.zhanggui.Demo.getTicket</string>
<key>UIApplicationShortcutItemUserInfo</key>
<dict>
<key>key2</key>
<string>value2</string>
</dict>
</dict>
</array>
关于key值的介绍,可以参见Info.plist Keys and Values。
处理点击元素监听:
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
NSLog(@"%@",shortcutItem);
}
在AppDelegate中实现协议application:performActionForShortcutItem:completionHandler:方法即可。
1.3.核心类说明
UIApplicationShortcutItem:3D Touch弹框中每一条操作元素。
UIApplicationShortcutIcon:操作元素的icon。
1.3.1.UIApplicationShortcutItem
#if USE_UIKIT_PUBLIC_HEADERS || !__has_include(<UIKitCore/UIApplicationShortcutItem.h>)
//
// UIApplicationShortcutItem.h
// UIKit
//
// Copyright © 2015-2018 Apple Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKitDefines.h>
NS_ASSUME_NONNULL_BEGIN
@class UIImage;
typedef NS_ENUM(NSInteger, UIApplicationShortcutIconType) {
UIApplicationShortcutIconTypeCompose,
UIApplicationShortcutIconTypePlay,
UIApplicationShortcutIconTypePause,
UIApplicationShortcutIconTypeAdd,
UIApplicationShortcutIconTypeLocation,
UIApplicationShortcutIconTypeSearch,
UIApplicationShortcutIconTypeShare,
UIApplicationShortcutIconTypeProhibit NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeContact NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeHome NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeMarkLocation NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeFavorite NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeLove NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeCloud NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeInvitation NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeConfirmation NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeMail NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeMessage NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeDate NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeTime NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeCapturePhoto NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeCaptureVideo NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeTask NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeTaskCompleted NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeAlarm NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeBookmark NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeShuffle NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeAudio NS_ENUM_AVAILABLE_IOS(9_1),
UIApplicationShortcutIconTypeUpdate NS_ENUM_AVAILABLE_IOS(9_1)
} API_AVAILABLE(ios(9.0)) API_UNAVAILABLE(tvos) API_UNAVAILABLE(macos);
UIKIT_EXTERN API_AVAILABLE(ios(9.0)) API_UNAVAILABLE(tvos) API_UNAVAILABLE(macos)
@interface UIApplicationShortcutIcon : NSObject <NSCopying>
// Create an icon using a system-defined image.
+ (instancetype)iconWithType:(UIApplicationShortcutIconType)type;
// Create an icon from a custom image.
// The provided image named will be loaded from the app's bundle
// and will be masked to conform to the system-defined icon style.
+ (instancetype)iconWithTemplateImageName:(NSString *)templateImageName;
@end
UIKIT_EXTERN API_AVAILABLE(ios(9.0)) API_UNAVAILABLE(tvos) API_UNAVAILABLE(macos)
@interface UIApplicationShortcutItem : NSObject <NSCopying, NSMutableCopying>
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithType:(NSString *)type localizedTitle:(NSString *)localizedTitle localizedSubtitle:(nullable NSString *)localizedSubtitle icon:(nullable UIApplicationShortcutIcon *)icon userInfo:(nullable NSDictionary<NSString *, id <NSSecureCoding>> *)userInfo NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithType:(NSString *)type localizedTitle:(NSString *)localizedTitle;
// An application-specific string that identifies the type of action to perform.
@property (nonatomic, copy, readonly) NSString *type;
// Properties controlling how the item should be displayed on the home screen.
@property (nonatomic, copy, readonly) NSString *localizedTitle;
@property (nullable, nonatomic, copy, readonly) NSString *localizedSubtitle;
@property (nullable, nonatomic, copy, readonly) UIApplicationShortcutIcon *icon;
// Application-specific information needed to perform the action.
// Will throw an exception if the NSDictionary is not plist-encodable.
@property (nullable, nonatomic, copy, readonly) NSDictionary<NSString *, id <NSSecureCoding>> *userInfo;
@end
UIKIT_EXTERN API_AVAILABLE(ios(9.0)) API_UNAVAILABLE(tvos) API_UNAVAILABLE(macos)
@interface UIMutableApplicationShortcutItem : UIApplicationShortcutItem
// An application-specific string that identifies the type of action to perform.
@property (nonatomic, copy) NSString *type;
// Properties controlling how the item should be displayed on the home screen.
@property (nonatomic, copy) NSString *localizedTitle;
@property (nullable, nonatomic, copy) NSString *localizedSubtitle;
@property (nullable, nonatomic, copy) UIApplicationShortcutIcon *icon;
// Application-specific information needed to perform the action.
// Will throw an exception if the NSDictionary is not plist-encodable.
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id <NSSecureCoding>> *userInfo;
@end
NS_ASSUME_NONNULL_END
#else
#import <UIKitCore/UIApplicationShortcutItem.h>
#endif
常用方法:
创建一个UIApplicationShortcutItem:
- (instancetype)initWithType:(NSString *)type localizedTitle:(NSString *)localizedTitle localizedSubtitle:(nullable NSString *)localizedSubtitle icon:(nullable UIApplicationShortcutIcon *)icon userInfo:(nullable NSDictionary<NSString *, id <NSSecureCoding>> *)userInfo NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithType:(NSString *)type localizedTitle:(NSString *)localizedTitle;
其中:
- type代表:必传,app定义的主屏幕快速操作类型,可以用于确定某元素点击。
- localizedTitle:展示的title
- localizedSubtitle:sub title
- icon:该item左侧或者右侧的icon,类型为UIApplicationShortcutIcon
- userInfo:用户自定义的一些信息
1.3.2.UIApplicationShortcutIcon
icon有两种获取方式:一种是使用系统自带的类型,一种是用户自定义(根据图片名)。
1.3.2.1 系统自带类型
// Create an icon using a system-defined image.
+ (instancetype)iconWithType:(UIApplicationShortcutIconType)type;
系统自带类型包括以下几种:
1.3.2.1 用户自定义
// Create an icon from a custom image.
// The provided image named will be loaded from the app's bundle
// and will be masked to conform to the system-defined icon style.
+ (instancetype)iconWithTemplateImageName:(NSString *)templateImageName;
用户自定义的图片格式规定如下:
如果图片找不到,则展示为原点。效果可自己测试一下。
1.4.使用方式
- 直接使用Info.plist文件进行配置
- 直接编写代码
当同时使用两者时,会进行叠加,并且Info.plist配置中的元素会优先于AppDelegate中配置的元素展示。
1.5.个数限制
常规情况下,最多可以自定义4个Home screen quick action。在APP未上线之前,可以看到自定义的4个,多余的将会被忽略。在APP上线之后,将会有一个系统自带的item:分享 “APP名称”。总共不超过5个。
1.6.使用场景
能够进行快捷操作的事件,比如微信的扫一扫,微信支付、我的电影票等。
2.UIKit peek and pop API(预览和跳转)
UIKit的peek和pop API允许开发者在维护用户上下文的同时,在应用中提供对附加内容的轻松访问。使用peek quick actions可以为应用的触摸和按住操作提供按下的替换。
peek:当用户点击特定的view,会提供一个额外的预览视图。
pop:确认查看该内容,并且导航到该内容详情。
在iOS9以及以后的SDK中,为UIViewController提供了注册3D Touch和取消注册3D Touch的新方法,它们是:
// Registers a view controller to participate with 3D Touch preview (peek) and commit (pop).
- (id <UIViewControllerPreviewing>)registerForPreviewingWithDelegate:(id<UIViewControllerPreviewingDelegate>)delegate sourceView:(UIView *)sourceView NS_AVAILABLE_IOS(9_0);
- (void)unregisterForPreviewingWithContext:(id <UIViewControllerPreviewing>)previewing NS_AVAILABLE_IOS(9_0);
在注册方法中,sourceView就是要添加3D Touch的view。调用该注册方法的时候,它做了三件事:
- 注册调用该方法的控制器参与3D Touch的预览(preview)和执行(commit)
- 从接收方的视图层级结构中,将sourceView指定为响应强按压的视图
- 指定委托对象,当用户进行强按压时依次请求peek和pop,该委托对象用于协调操作(vc实现了peek和pop代理方法)
你可以在一个vc中指定多个sourceView,但是不能同一个view指定为sourceView多次。
注册方法返回的上下文对象的生命周期由系统管理。如果你需要指明取消注册该vc,把该context对象传给unregisterForPreviewingWithContext:。如果开发者不取消注册,系统会在改VC释放的时候自动取消注册。
Demo中的注册方法实现如下:
if ([self.imageView respondsToSelector:@selector(traitCollection)]) {
if([self.traitCollection respondsToSelector:@selector(forceTouchCapability)]) {
if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
[self registerForPreviewingWithDelegate:(id)self sourceView:self.imageView];
}
}
}
注册完成之后,需要实现UIViewControllerPreviewingDelegate,该代理主要有两个方法:
- (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
ImageViewController *imageVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"ImageVC"];
[self presentViewController:imageVC animated:YES completion:nil];
}
- (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
ImageViewController *imageVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"ImageVC"];
imageVC.preferredContentSize = CGSizeMake(0.0, 300);
return imageVC;
}
第一个方法用于处理commit操作,也就是确认操作,确认3D Touch执行操作。
第二个方法在用户按下源视图显示peek时调用,实现此方法用以返回预览视图控制器。(这里返回的是ImageViewController)。如果此处返回nil,将会禁用预览。此处还会使用到previewingContext.sourceReact,该属性主要是在sourceView坐标系中,矩形响应用户的3D触摸,当矩形周会内容模糊时,矩形在视觉上保持清晰,具体可参见视频: https://github.com/ScottZg/MarkDownResource/blob/feature/addrubyimagefile/3DTouch/sourcReact.MP4
2.1.快速操作
可以使用该API进行快速操作需求开发,例如微信重压聊天列表中的某个cell,就可以弹出快速操作:不再关注、删除。这些快速操作很简单:在预览的VC中添加下面的代码即可。实例代码如下:
- (NSArray<id<UIPreviewActionItem>> *)previewActionItems {
NSMutableArray *arrItem = [NSMutableArray arrayWithCapacity:2];
UIPreviewAction *cancel = [UIPreviewAction actionWithTitle:@"取消" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
NSLog(@"cancel");
}];
UIPreviewAction *ok = [UIPreviewAction actionWithTitle:@"删除" style:UIPreviewActionStyleDestructive handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
self.imageView.image = nil;
}];
UIPreviewAction *select = [UIPreviewAction actionWithTitle:@"选中" style:UIPreviewActionStyleSelected handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
NSLog(@"selected");
}];
// 组操作
UIPreviewAction *add = [UIPreviewAction actionWithTitle:@"增加" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
NSLog(@"增加");
}];
UIPreviewAction *update = [UIPreviewAction actionWithTitle:@"更新" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
NSLog(@"更新");
}];
UIPreviewActionGroup *group = [UIPreviewActionGroup actionGroupWithTitle:@"更多操作" style:UIPreviewActionStyleDefault actions:@[add,update]];
[arrItem addObject:cancel];
[arrItem addObject:ok];
[arrItem addObject:select];
[arrItem addObject:group];
return arrItem;
}
这里主要实现了previewActionItems方法,用来设置预览的Action元素,这里涉及到的类有:
- UIPreviewAction:预览的Action,当用户在支持3D Touch并且在peek视图下向上滑动,即可看到这些Action。
- UIPreviewActionItem:协议,包含了一个只读的title。
- UIPreviewActionGroup:操作组,可以设置一个操作组进行操作,例如二次确认。
2.1.1.UIPreviewAction
具体的操作元素,包括title、类型以及事件处理。其中style有以下三种类型:
typedef NS_ENUM(NSInteger,UIPreviewActionStyle) {
UIPreviewActionStyleDefault=0, //默认
UIPreviewActionStyleSelected, //选中
UIPreviewActionStyleDestructive, //销毁:红色
} NS_ENUM_AVAILABLE_IOS(9_0);
2.1.2.UIPreviewActionItem
协议,包含了一个属性title。代表操作action的title。
2.1.3.UIPreviewActionGroup
操作组,支持一个元素多个操作,具体可以参见下面的小视频:https://github.com/ScottZg/MarkDownResource/blob/feature/addrubyimagefile/3DTouch/group.MP4
3.Web view peek and pop API
在网页中,对于网页中的链接,peek和pop是默认支持的,可以通过设置allowsLinkPreview进行开启或关闭:
WKWebView:
/*! @abstract A Boolean value indicating whether link preview is allowed for any
links inside this WKWebView.
@discussion The default value is YES on Mac and iOS.
*/
@property (nonatomic) BOOL allowsLinkPreview API_AVAILABLE(macosx(10.11), ios(9.0));
UIWebView:
@property (nonatomic) BOOL allowsLinkPreview NS_AVAILABLE_IOS(9_0); // default is NO
默认情况下使用苹果自带的浏览器打开,这样就会跳出自己的应用。
尝试自己对wkwebview进行注册,然后自己设置peek和pop,发现无效。
可以使用SFSafariViewController替代WebView。
4.UITouch force properties
UITouch类提供了两个新的属性来支持自定义实现3D Touch:
// Force of the touch, where 1.0 represents the force of an average touch
@property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0);
// Maximum possible force with this input mechanism
@property(nonatomic,readonly) CGFloat maximumPossibleForce NS_AVAILABLE_IOS(9_0);
具体使用可以参见Demo。