前言
3D Touch是一种立体触控技术,被苹果称为新一代多点触控技术,是在Apple Watch上采用的Force Touch,屏幕可感应不同的感压力度触控。3D Touch,苹果iPhone 6s的新功能,系统版本需要iOS 9.0+ ,有Peek Pop 两种新手种势。
2015年9月10日,苹果在新品发布会上宣布了3D-Touch功能。 2016年6月13日,苹果开发者大会WWDC在旧金山召开,会议宣布可以在待机画面用3D Touch操作通知。
现在,许多安卓手机也用上了3D Touch。
– 摘自百度百科
3D Touch四大功能模块
- Home Screen Quick Actions (主屏幕快捷入口)
- peek And pop (预览和弹出 展示框)
- Force Properties (按压力度属性)
- Web View peek and pop API (HTML链接预览功能)
设备支持判断
所有遵守UITraitEnvironment(特征环境)协议的类,都会有UITraitCollection(特征集合类)的属性,都有forceTouchCapability(力的触觉能力)属性,
而UIResponder就遵守了UITraitEnvironment协议,所以所有继承于UIResponder的类都会使用3DTouch.包括UIView也可以使用forceTouchCapability是枚举型
代码示例
示例代码* OC
1
2
3
4
5
6// UIForceTouchCapability:
typedef NS_ENUM(NSInteger, UIForceTouchCapability) {
UIForceTouchCapabilityUnknown =0, //3D Touch检测失败
UIForceTouchCapabilityUnavailable =1, //3D Touch不可用
UIForceTouchCapabilityAvailable =2 //3D Touch可用
};
1 | // 判断是否支持: |
- swift
1 | // UIForceTouchCapability: |
1 | // 判断是否支持: |
Home Screen Quick Actions开发
简介
通过主屏幕的应用Icon,我们可以用3D Touch呼出一个菜单,进行快速定位应用功能模块相关功能的开发,。如微信、京东的App,本文也将重点介绍这项开发技术。
效果如下:
3D Touch快捷入口标签有两种添加方式:
一种是静态添加,在工程中的info.plist文件中添加相关项目。通过这种方式添加的标签,在app第一次运行前,就可以唤出这些标签。
另一种是动态添加,通过代码给App添加快速入口。这种方式添加的标签,第一次运行App前是看不到这些标签的,必须先运行一次App,以后就可以唤出这些标签了。
静态添加快捷入口标签
静态添加入口标签不需要写代码,只需要在info.plist文件中添加相关功能设置即可。不过因为没有语句提示,所以我们只能一个字母一个字母的去添加,这一点不是很友好。
示例
说明
- 首先是UIApplicationShortcutItems,他是一个数组类型,数组中的每一个元素表示一个入口标签。
- 每个Item是UIApplicationShortcutItems数组中的一个元素,字典类型。在这个字典中配置各个标签的相关属性。
如图如示
效果如图
注:UIApplicationShortcutItemIconFile:当填写上图片图标的我名字就可以显示出图标。
动态添加快捷入口标签
需要用到的类
- 如图
代码示例
OC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// UIApplicationShortcutIconType是系统的图片类型,29种类型图片
UIApplicationShortcutIcon *shareShortcutIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeShare];
UIApplicationShortcutItem *shareShortcutItem = [[UIApplicationShortcutItem alloc] initWithType:@"" localizedTitle:@"分享" localizedSubtitle:@"来点我啊" icon:shareShortcutIcon userInfo:nil];
//可以用iconWithTemplateImageName:来自定义图片 shortcut_QR:图片名
UIApplicationShortcutIcon *payShortcutIcon = [UIApplicationShortcutIcon iconWithTemplateImageName:@"shortcut_imageName"];
UIApplicationShortcutItem *payShortcutItem = [[UIApplicationShortcutItem alloc] initWithType:@"" localizedTitle:@"消息" localizedSubtitle:@"不怕不怕" icon:payShortcutIcon userInfo:nil];
//将实例化的shortcutItem将入到UIApplication的shortcutItems中就完成了。
[UIApplication sharedApplication].shortcutItems = @[shareShortcutItem, payShortcutItem];
Swift
1
2
3
4
5
6
7
8
9//分享
let icon1 = UIApplicationShortcutIcon(type: .share)
let shareItem = UIApplicationShortcutItem(type: "", localizedTitle: "分享", localizedSubtitle: "来点我", icon: icon1, userInfo: nil)
//支付 shortcut_imageName:图片名称
let icon2 = UIApplicationShortcutIcon(templateImageName: "shortcut_imageName")
let playItem = UIApplicationShortcutItem(type: "", localizedTitle: "支付", localizedSubtitle: "不怕不怕", icon: icon2, userInfo: nil)
UIApplication.shared.shortcutItems = [shareItem,playItem]UIApplicationShortcutIconType补充说明
系统一共提供了29种类型样式:
typedef NS_ENUM(NSInteger, UIApplicationShortcutIconType) {
UIApplicationShortcutIconTypeCompose,
UIApplicationShortcutIconTypePlay,
UIApplicationShortcutIconTypePause,
UIApplicationShortcutIconTypeAdd,
UIApplicationShortcutIconTypeLocation,
UIApplicationShortcutIconTypeSearch,
UIApplicationShortcutIconTypeShare,UIApplicationShortcutIconTypeProhibit, //从这开始,iOS 9.1+才支持
UIApplicationShortcutIconTypeContact,
UIApplicationShortcutIconTypeHome,
UIApplicationShortcutIconTypeMarkLocation,
UIApplicationShortcutIconTypeFavorite,
UIApplicationShortcutIconTypeLove,
UIApplicationShortcutIconTypeCloud,
UIApplicationShortcutIconTypeInvitation,
UIApplicationShortcutIconTypeConfirmation,
UIApplicationShortcutIconTypeMail,
UIApplicationShortcutIconTypeMessage,
UIApplicationShortcutIconTypeDate,
UIApplicationShortcutIconTypeTime,
UIApplicationShortcutIconTypeCapturePhoto,
UIApplicationShortcutIconTypeCaptureVideo,
UIApplicationShortcutIconTypeTask,
UIApplicationShortcutIconTypeTaskCompleted,
UIApplicationShortcutIconTypeAlarm,
UIApplicationShortcutIconTypeBookmark,
UIApplicationShortcutIconTypeShuffle,
UIApplicationShortcutIconTypeAudio,
UIApplicationShortcutIconTypeUpdate
}
快捷启动后的处理(检测App启动方式)
代理接受
通过快捷入口进入或者启动app,会实现appDelegate的代理方法:
1 | - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandle |
我们可以判断shortcutItem.localizedTitle属性的值来判断是从哪一个标签进入的App。
示例代码
OC
1
2
3
4
5
6
7
8
9
10
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
//如果是从"分享"这个标签进来的,我们把页面背景颜色修改为黄色,如果是从@"消息"这个标签进来的,我们讲页面背景颜色修改为红色。都不是的话,就直接是白色。
if ([shortcutItem.localizedTitle isEqualToString:@"分享"]) {
self.window.rootViewController.view.backgroundColor = [UIColor yellowColor];
} else if ([shortcutItem.localizedTitle isEqualToString:@"消息"]) {
self.window.rootViewController.view.backgroundColor = [UIColor redColor];
}
}
Swift
1
2
3
4
5
6
7
8
9func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
if shortcutItem.localizedTitle == "来啊"{
window?.rootViewController?.view.backgroundColor= #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)
}else if shortcutItem.localizedTitle == "后门" {
window?.rootViewController?.view.backgroundColor= #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1)
}
}
补充说明
UIApplicationLaunchOptionsShortcutItemKey
在- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(nullable NSDictionary )launchOptions的方法中 。launchOptions 中有个值为UIApplicationLaunchOptionsShortcutItemKey,这个也是获取选择的shortcutItem的方法。
注意
- 快捷入口 无论是启动app,还是将后台运行的app转为前台都会执行代理方法。
反之,如果快捷入口 是将后台运行的app转为前台都会执行代理方法。是不会走didFinishLaunchingWithOptions:的方法。
总结:
我们处理Home Screen Quick Actions功能时最好统一在代理回调的方法里处理。如果在遇到特殊功能,需要判明该功能是启动了整个app时,我们就可以在didFinishLaunchingWithOptions:的时候来判断加以处理!
peek And pop
简介
效果演示:
进 “邮件” 收件箱列表:
按住环信发过来的邮件,力度慢慢增加,会pop出详情效果框:
再使劲达到它的力度,就会等同于你选择了该项,进入详情页面了。
功能的基本思路:
- 控制器注册预览协议(UIViewControllerPreviewingDelegate)
- 遵守协议并实现其必须实现的方法
示例代码
OC
注册
1
2// self:为视图控制器, soureView: 为实现3DTouch的视图,如果是单独的一个view的话,则效果只对该view有作用, 那到时的gesture和location都是相对于这个soureView的。
[self registerForPreviewingWithDelegate:self sourceView:self.view];
2. 遵守代理
1
@interface ViewController ()<UIViewControllerPreviewingDelegate>
实现方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58//MARK: — UIViewControllerPreviewingDelegate
//MARK: -- 返回要显示的控制器
- (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
//初始化要显示的控制器
UIViewController *childVc = [[UIViewController alloc]init];
//1.获取手势,我在view上加了一个tableView,这里我是想要获取我们重按的点是点在哪个cell上。
UIGestureRecognizer *gesture = previewingContext.previewingGestureRecognizerForFailureRelationship;
//2.获取手势所在的tableView中的点位
CGPoint point = [gesture locationInView:self.tableView];
//3. 根据这个点获取是第几行, ROWHight是单个cell的高度
NSInteger index = floor(point.y/ROWHight);
//4.得到第行,而且得到cell
self.indexPath = [NSIndexPath indexPathForRow:index inSection:0];
self.cell =[self.tableView cellForRowAtIndexPath:self.indexPath];
// 加个白色背景,这个背影是高清显示的。
UIView *bgView =[[UIView alloc] initWithFrame:CGRectMake(20, 10, kScreenWidth - 40,kScreenHeight - 20 - 64 * 2)];
bgView.backgroundColor = [UIColor whiteColor];
bgView.layer.cornerRadius = 10;
bgView.clipsToBounds = YES;
[childVc.view addSubview:bgView];
//展示的标题
UILabel *lable1 = [[UILabel alloc] initWithFrame:CGRectMake(0,bgView.bounds.size.height/2-40 , bgView.bounds.size.width, 40)];
lable1.textAlignment = NSTextAlignmentCenter;
lable1.textColor=[UIColor redColor];
lable1.text = self.cell.textLabel.text;
[bgView addSubview:lable1];
// 展示的详情
UILabel *lable2 = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(lable1.frame), bgView.bounds.size.width, 40)];
lable2.textAlignment = NSTextAlignmentCenter;
lable2.textColor = [UIColor lightGrayColor];
NSString *text = self.cell.detailTextLabel.text;
lable2.text= text;
[bgView addSubview:lable2];
//提示lable
UILabel *detail = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(lable2.frame), bgView.bounds.size.width, 40)];
detail.textAlignment = NSTextAlignmentCenter;
detail.textColor=[UIColor yellowColor];
detail.text = @"再按重一点就选择该项了";
[bgView addSubview: detail];
//最后返回的展示的控制器
return childVc;
}
//MARK: -- 再次重按的时候表示进行选择了,然后消失后会下走的方法
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController*)viewControllerToCommit{
//让它走选择了某项的代理方法。
[self tableView:self.tableView didSelectRowAtIndexPath:self.indexPath];
}
补充说明
- location是相对于注册source来获取的。
UIViewControllerPreviewing 主要可用的属性,是相对于注册source的属性:
1
2
3@property (nonatomic, readonly) UIGestureRecognizer *previewingGestureRecognizerForFailureRelationship //手势
@property (nonatomic, readonly) UIView *sourceView //source当初注册传入的视图
@property (nonatomic) CGRect sourceRect //source的frame
注册的时候直接用SourceView为Cell。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28//MARK:-- 返回cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellID forIndexPath:indexPath];
if(cell) {
cell.textLabel.text = self.dataSouce[indexPath.row][@"title"];
cell.detailTextLabel.text = self.dataSouce[indexPath.row][@"detail"];
[self registerForPreviewingWithDelegate:self sourceView:cell];
}
return cell;
}
// 2.在代理中获取点击cell就简单多了
- (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
UIViewController *childVc = [[UIViewController alloc]init];
// 方法一:
self.cell = (UITableViewCell *) previewingContext.sourceView;//这个可以获取注册的sourceView
CGRect frame = previewingContext.sourceRect;//这个可以获取注册的sourceView.frame及大小
// 方法二:
// UIGestureRecognizer *gesture = previewingContext.previewingGestureRecognizerForFailureRelationship;// //2.获取手势
// UIGestureRecognizer *gesture = previewingContext.previewingGestureRecognizerForFailureRelationship;// //2.获取手势所在的tableView中的点位
// self.cell = (UITableViewCell *)gesture.view;//这个可以获取点击所得到的cell;
return childVc;
}
Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106class NexViewController: UIViewController{
let CellId = "CellId"
var currentCell : UITableViewCell?
@IBOutlet weak var tableView: UITableView!
var dataSource : [[String : Any]] = [];
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
}
//遵守协议
extension NexViewController : UITableViewDataSource,UITableViewDelegate,UIViewControllerPreviewingDelegate
{
//TODO: -- 加载数据
func getData() {
for i in 0..<20{
let dic = ["title" : "测试标题 + \(i+1)", "detail" : "详情资料是这样的 + \(i+1)"];
dataSource.append(dic)
}
tableView.reloadData()
}
//MARK: -- UITableViewDataSource
//TODO:-- 每个section有多少条
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return dataSource.count
}
//TODO:-- 返回Cell
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: CellId, for: indexPath)
cell.textLabel?.text = (dataSource[indexPath.row] as! Dictionary)["title"]
cell.detailTextLabel?.text = (dataSource[indexPath.row] as! Dictionary)["detail”]
// 注册3DTouch
registerForPreviewing(with: self, sourceView: cell)
return cell
}
//TODO: -- UITableViewDelegate public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "detail", sender: dataSource[indexPath.row])
}
//重写segue跳转方法
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == “detail"{
if let viewcontroller = segue.destination as? DetailViewController{
viewcontroller.info = sender as! [String : Any]?
}
}
}
//MARK: -- UIViewControllerPreviewingDelegate
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController?{
let childVC = UIViewController()
currentCell = previewingContext.sourceView as? UITableViewCell
//TODO: -- 加白色背景
let bgView = UIView(frame: .init(x: 20, y: 10, width: (view.window?.bounds.size.width)!-40 , height: (view.window?.bounds.size.height)! - 20-64*2))
bgView.backgroundColor= #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
bgView.layer.cornerRadius = 10.0
bgView.clipsToBounds = true
childVC.view.addSubview(bgView)
// 标题
let lable1 = UILabel(frame: .init(x: 0, y: bgView.bounds.size.height/2 - 40, width: bgView.bounds.size.width, height: 40))
lable1.textAlignment = .center
lable1.textColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
lable1.text = currentCell?.textLabel?.text
bgView.addSubview(lable1)
//详情
let lable2 = UILabel(frame: .init(x: 0, y: lable1.frame.maxY, width: bgView.bounds.size.width, height: 40))
lable2.textAlignment = .center
lable2.textColor = #colorLiteral(red: 0.501960814, green: 0.501960814, blue: 0.501960814, alpha: 1)
lable2.text = currentCell?.detailTextLabel?.text
bgView.addSubview(lable2)
//提示
let detail = UILabel(frame: .init(x: 0, y: lable2.frame.maxY, width: bgView.bounds.size.width, height: 40))
detail.textAlignment = .center
detail.textColor= #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1)
detail.text = "再按重一点就选择该项了"
bgView.addSubview(detail)
return childVC
}
//再按重些会选择该项,消失展示框后需要走的代码
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController){
let indexPath = tableView.indexPath(for: currentCell!)
if indexPath != nil{
tableView(tableView, didSelectRowAt: indexPath!)
}
}
}
Force Properties 力量属性
简介
iOS9为我们提供了一个新的交互参数:力度。我们可以检测某一交互的力度值,来做相应的交互处理。例如,我们可以通过力度来控制快进的快慢,音量增加的快慢等。
UITouch新增了两个属性:
- open var force: CGFloat{ get } //force : 手指按下的力度
open var maximumPossibleForce: CGFloat { get } //maximumPossibleForce : 最大可能的力度
在touch中就可以获取这两个值。所以我们就可以触摸按压的时候实现用按压力度来实现酷炫的效果
示例代码
OC
1
2
3
4
5
6
7
8
9
10
11// 根据按压的力度来改变当前视图的颜色
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event{
if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
UITouch *touch = [[touches allObjects] firstObject];
NSLog(@"\n force:%f,maximumPossibleForce:%ff",touch.force,touch.maximumPossibleForce);
CGFloat dito = touch.force/touch.maximumPossibleForce;
self.view.backgroundColor = [UIColor colorWithRed:dito green:dito blue:1/dito alpha:1];
}
}
Swift
1
2
3
4
5
6
7
8
9
10//TODO:--重写touchesMoved的方法,根据按压的力度来改变当前视图的颜色
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if traitCollection.forceTouchCapability == .available{
let touch = touches.first
print("\n force: \(touch!.force),maximumPossibleForce: \(touch?.maximumPossibleForce)")
let dito = (touch?.force)! / (touch?.maximumPossibleForce)!
view.backgroundColor = UIColor(red: 0.3+5*dito, green: 0.3+5*dito, blue: dito, alpha:1)
}
}
Web View peek and pop API HTML链接预览功能
简介
原生的safari浏览器中的超链接可以支持3D touch,出现超链接的预览,使用方法和前文中提到的方法类似。ios 9 中增加了一个新的浏览器控制器SFSafariViewController。它可以在你的程序中直接嵌入 Safari浏览器,而且支持3DTouch效果。
示例代码
OC
1
2
3
4
5
6
7
8// 1.首先导入SafariServices.h
#import <SafariServices/SafariServices.h>
// 2.设置连接:
SFSafariViewController *safariView = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:@"https://www.baidu.com"] entersReaderIfAvailable:YES];
// 3.推出控制器:
[self.navigationController pushViewController:safariView animated:YES];
Swift
1
2
3
4
5
6
7// 1. 导入框架
import SafariServices
// 2. 使用
let url = URL(string: "https://www.baidu.com")
let safariView = SFSafariViewController(url: url! , entersReaderIfAvailable: true)
navigationController?.pushViewController(safariView, animated: true)
结尾
3D Touch确实挺炫酷的,特别是Home Screen Quick Actions(主屏幕快捷入口)和
peek And pop(预览和弹出功能),增加了用户体验。
这里我只是简单地介绍了它的使用,如有疑问请Q我: 342112780