MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的3D Touch压力感应技术

2021-06-277.1k 阅读

3D Touch 技术概述

3D Touch 的原理及应用场景

3D Touch 是苹果公司在 iPhone 6s 及后续机型上推出的一项交互技术,它能够检测用户按压屏幕的力度,并根据不同的压力值触发不同的响应。这一技术为用户带来了全新的交互体验,使得操作更加直观和高效。例如,在主屏幕上用力按压应用图标,可以弹出快捷操作菜单,快速访问常用功能;在浏览照片时,通过不同压力按压屏幕,可实现快速预览或查看详细信息。

从技术原理上来说,3D Touch 利用了屏幕下方的压力传感器。当用户触摸屏幕时,传感器会感知到压力的大小,并将这一信息传递给系统。系统根据预设的压力阈值,判断用户的操作意图,从而触发相应的功能。这种基于压力感应的交互方式,拓展了传统触摸屏幕交互的维度,为开发者提供了更多创新的空间。

与传统触摸交互的区别

传统的触摸交互主要基于单点或多点触摸,通过检测触摸点的位置、数量以及触摸时间等信息来识别操作,如点击、滑动、缩放等。而 3D Touch 在此基础上增加了压力维度的感知。这意味着开发者可以利用压力信息创建更多独特的交互。例如,在绘图应用中,传统触摸只能根据触摸轨迹绘制线条,而结合 3D Touch 技术,线条的粗细可以根据按压压力的大小实时变化,使绘图更加逼真和自然。又如在游戏中,玩家可以通过不同的按压力度控制角色的动作强度,如轻轻按压实现缓慢移动,用力按压则快速冲刺,大大丰富了游戏的操作方式和趣味性。

Objective - C 中实现 3D Touch 压力感应的基础

系统版本及设备支持

在 Objective - C 项目中使用 3D Touch 压力感应技术,首先要确保目标设备支持 3D Touch 功能。目前,iPhone 6s、iPhone 6s Plus 及后续机型均支持该技术。同时,开发应用所面向的系统版本也有要求,通常建议在 iOS 9.0 及以上版本进行开发,因为从这个版本开始,苹果正式引入了对 3D Touch 的系统级支持和 API。

引入相关框架

在 Objective - C 项目中,需要引入 UIKit 框架,因为 3D Touch 的相关 API 都包含在这个框架中。在 Xcode 项目的 ViewController.m 文件中,通过 #import <UIKit/UIKit.h> 语句引入 UIKit 框架。该框架不仅提供了基本的用户界面元素和交互功能,还为 3D Touch 压力感应技术提供了关键的类和方法。

实现 3D Touch 快捷操作(Peek and Pop)

主屏幕快捷操作(Home Screen Quick Actions)

  1. 静态快捷操作
    • Info.plist 文件中配置静态快捷操作。打开 Info.plist 文件,添加一个名为 UIApplicationShortcutItems 的数组。在数组中,每个元素代表一个快捷操作,是一个字典类型。例如,添加一个名为“新建文档”的快捷操作:
<key>UIApplicationShortcutItems</key>
<array>
    <dict>
        <key>UIApplicationShortcutItemTitle</key>
        <string>新建文档</string>
        <key>UIApplicationShortcutItemSubtitle</key>
        <string>快速创建新文档</string>
        <key>UIApplicationShortcutItemIconType</key>
        <string>UIApplicationShortcutIconTypeCompose</string>
        <key>UIApplicationShortcutItemType</key>
        <string>com.example.createDocument</string>
    </dict>
</array>
- 在 `AppDelegate.m` 文件中处理快捷操作的点击事件。实现 `application:performActionForShortcutItem:completionHandler:` 方法:
- (BOOL)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL success))completionHandler {
    if ([shortcutItem.type isEqualToString:@"com.example.createDocument"]) {
        // 执行新建文档的逻辑,例如跳转到新建文档界面
        NSLog(@"执行新建文档操作");
        completionHandler(YES);
        return YES;
    }
    completionHandler(NO);
    return NO;
}
  1. 动态快捷操作
    • 在运行时动态创建快捷操作。在需要创建快捷操作的地方,例如在某个视图控制器中,创建 UIApplicationShortcutItem 对象并添加到应用的快捷操作列表中。以下代码在视图加载时创建一个动态快捷操作:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIApplicationShortcutItem *dynamicShortcut = [[UIApplicationShortcutItem alloc] initWithType:@"com.example.dynamicAction" localizedTitle:@"动态操作" localizedSubtitle:@"这是一个动态创建的快捷操作" icon:[UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeShare] userInfo:nil];
    [[UIApplication sharedApplication] setShortcutItems:@[dynamicShortcut]];
}
- 同样在 `AppDelegate.m` 文件的 `application:performActionForShortcutItem:completionHandler:` 方法中处理动态快捷操作的点击事件:
- (BOOL)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL success))completionHandler {
    if ([shortcutItem.type isEqualToString:@"com.example.dynamicAction"]) {
        // 执行动态操作的逻辑
        NSLog(@"执行动态操作");
        completionHandler(YES);
        return YES;
    }
    completionHandler(NO);
    return NO;
}

Peek and Pop 交互

  1. 实现 Peek(预览)
    • 为视图控制器启用 Peek 功能,需要实现 UIViewControllerPreviewingDelegate 协议。在视图控制器的 viewDidLoad 方法中注册预览:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    if ([self.traitCollection respondsToSelector:@selector(forceTouchCapability)] && self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
        [self registerForPreviewingWithDelegate:self sourceView:self.view];
    }
}
- 实现 `UIViewControllerPreviewingDelegate` 协议的 `previewingContext:viewControllerForLocation:` 方法,返回预览视图控制器:
- (UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
    // 找到触摸点对应的视图或数据模型
    UIView *touchedView = [self.view hitTest:location withEvent:nil];
    if (touchedView) {
        // 创建并配置预览视图控制器
        PreviewViewController *previewVC = [[PreviewViewController alloc] initWithNibName:@"PreviewViewController" bundle:nil];
        // 根据触摸点对应的视图或数据模型传递相关数据给预览视图控制器
        previewVC.previewData = @"示例数据";
        previewingContext.sourceRect = touchedView.frame;
        return previewVC;
    }
    return nil;
}
  1. 实现 Pop(详细查看)
    • 实现 UIViewControllerPreviewingDelegate 协议的 previewingContext:commitViewController: 方法,将预览视图控制器过渡到详细视图控制器:
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
    [self showViewController:viewControllerToCommit sender:self];
}

检测压力值

获取压力信息

  1. 在触摸事件中获取压力值
    • 重写视图控制器的触摸事件方法,如 touchesBegan:withEvent:touchesMoved:withEvent:touchesEnded:withEvent:,在这些方法中获取压力值。以下以 touchesBegan:withEvent: 方法为例:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    if (touch.force > 0) {
        CGFloat force = touch.force;
        CGFloat maxForce = touch.maximumPossibleForce;
        CGFloat normalizedForce = force / maxForce;
        NSLog(@"触摸开始,压力值: %f,归一化压力值: %f", force, normalizedForce);
    }
}
  1. 使用压力值进行实时交互
    • 以一个简单的绘图应用为例,在 touchesMoved:withEvent: 方法中,根据压力值实时改变绘制线条的粗细:
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGFloat force = touch.force;
    CGFloat lineWidth = force * 10; // 根据压力值调整线条宽度,这里简单乘以10作为示例
    // 进行绘图操作,设置线条宽度等属性
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, lineWidth);
    // 其他绘图逻辑...
}

压力阈值与反馈

  1. 设置压力阈值
    • 开发者可以根据应用的需求设置不同的压力阈值,以触发不同的操作。例如,在一个游戏中,设置轻轻按压(小于 0.3 的归一化压力值)为缓慢移动角色,用力按压(大于 0.7 的归一化压力值)为快速冲刺。在触摸事件处理方法中添加如下逻辑:
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGFloat normalizedForce = touch.force / touch.maximumPossibleForce;
    if (normalizedForce < 0.3) {
        // 缓慢移动角色逻辑
        NSLog(@"缓慢移动角色");
    } else if (normalizedForce > 0.7) {
        // 快速冲刺角色逻辑
        NSLog(@"快速冲刺角色");
    }
}
  1. 提供触觉反馈
    • 使用 UIImpactFeedbackGenerator 类为用户提供触觉反馈,增强交互体验。例如,在用力按压触发某个操作时,提供强烈的触觉反馈:
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGFloat normalizedForce = touch.force / touch.maximumPossibleForce;
    if (normalizedForce > 0.7) {
        // 快速冲刺角色逻辑
        NSLog(@"快速冲刺角色");
        
        UIImpactFeedbackGenerator *impactFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy];
        [impactFeedback impactOccurred];
    }
}

3D Touch 在不同应用场景中的优化

游戏应用中的优化

  1. 精准控制与响应
    • 在游戏中,3D Touch 的压力感应需要提供精准的控制。例如,在赛车游戏中,玩家通过按压屏幕控制赛车的加速力度。为了实现精准控制,需要对压力值进行平滑处理,避免因压力值的微小波动导致赛车速度的剧烈变化。可以使用滑动平均算法来处理压力值。在游戏的更新循环中添加如下代码:
// 定义一个数组来存储最近的压力值
NSMutableArray<NSNumber *> *forceHistory = [NSMutableArray arrayWithCapacity:10];
// 在触摸事件处理中更新压力值历史
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGFloat force = touch.force;
    [forceHistory addObject:@(force)];
    if (forceHistory.count > 10) {
        [forceHistory removeObjectAtIndex:0];
    }
    CGFloat totalForce = 0;
    for (NSNumber *number in forceHistory) {
        totalForce += number.floatValue;
    }
    CGFloat smoothedForce = totalForce / forceHistory.count;
    // 根据平滑后的压力值控制赛车速度
    CGFloat carSpeed = smoothedForce * 100; // 简单示例,根据需求调整
    NSLog(@"平滑后的压力值: %f,赛车速度: %f", smoothedForce, carSpeed);
}
  1. 与游戏场景的融合
    • 3D Touch 交互要与游戏场景深度融合,增强游戏的沉浸感。例如,在冒险游戏中,当玩家靠近隐藏的宝藏时,用力按压屏幕可以触发挖掘动作。在游戏场景检测到玩家靠近宝藏区域时,通过触摸事件处理实现:
// 在游戏场景更新中检测是否靠近宝藏区域
- (void)updateGameScene {
    // 假设 isNearTreasure 为检测是否靠近宝藏的布尔值
    if (isNearTreasure) {
        // 注册触摸事件处理
        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(checkForceForDigging:)];
        [self.view addGestureRecognizer:tapGesture];
    }
}
- (void)checkForceForDigging:(UITapGestureRecognizer *)gesture {
    UITouch *touch = [gesture touchesForView:self.view].anyObject;
    CGFloat normalizedForce = touch.force / touch.maximumPossibleForce;
    if (normalizedForce > 0.5) {
        // 执行挖掘宝藏的逻辑
        NSLog(@"挖掘宝藏");
    }
}

绘图应用中的优化

  1. 压力与笔触效果的优化
    • 在绘图应用中,压力与笔触效果的关系至关重要。除了简单的线条粗细变化,还可以根据压力值调整笔触的透明度、颜色深度等。例如,在绘制油画效果时,用力按压可以使颜色更浓郁。在触摸事件处理中实现如下逻辑:
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGFloat force = touch.force;
    CGFloat lineWidth = force * 10;
    CGFloat alpha = force * 0.5; // 压力越大,透明度越高
    UIColor *color = [UIColor colorWithRed:0 green:0 blue:1 alpha:alpha];
    // 进行绘图操作,设置线条宽度、颜色等属性
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, lineWidth);
    CGContextSetStrokeColorWithColor(context, color.CGColor);
    // 其他绘图逻辑...
}
  1. 压力感应的区域优化
    • 为了提升绘图体验,可以对压力感应的区域进行优化。例如,在绘图区域的边缘,压力感应可以有不同的响应,如在边缘轻轻按压实现画布的平移,用力按压实现画布的缩放。在触摸事件处理中添加区域检测逻辑:
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];
    CGFloat force = touch.force;
    CGFloat edgeThreshold = 20; // 边缘阈值
    if (location.x < edgeThreshold || location.x > self.view.bounds.size.width - edgeThreshold || location.y < edgeThreshold || location.y > self.view.bounds.size.height - edgeThreshold) {
        if (force < 0.3) {
            // 平移画布逻辑
            NSLog(@"平移画布");
        } else if (force > 0.7) {
            // 缩放画布逻辑
            NSLog(@"缩放画布");
        }
    } else {
        // 正常绘图逻辑
        //...
    }
}

兼容性与调试

兼容性处理

  1. 低版本系统兼容
    • 为了确保应用在不支持 3D Touch 的设备或低版本系统上正常运行,需要进行兼容性处理。在代码中通过检查系统版本和设备特性来决定是否启用 3D Touch 相关功能。例如,在注册预览功能时:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    if ([self.traitCollection respondsToSelector:@selector(forceTouchCapability)] && self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
        [self registerForPreviewingWithDelegate:self sourceView:self.view];
    } else {
        // 处理不支持 3D Touch 的情况,例如提供替代的交互方式
        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
        [self.view addGestureRecognizer:tapGesture];
    }
}
  1. 不同设备尺寸兼容
    • 不同设备尺寸可能对 3D Touch 交互产生影响。在设计 Peek 和 Pop 交互时,要确保预览视图和详细视图在不同设备上都能正确显示和交互。可以使用 Auto Layout 或 Size Classes 来布局视图,使其适应不同的屏幕尺寸。例如,在预览视图控制器的视图加载方法中:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 使用 Auto Layout 布局预览视图中的控件
    UILabel *previewLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    previewLabel.text = self.previewData;
    [self.view addSubview:previewLabel];
    [previewLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);
    }];
}

调试技巧

  1. 压力值调试
    • 在调试过程中,准确获取和分析压力值是关键。可以通过在触摸事件处理方法中添加日志输出,如前文在 touchesBegan:withEvent: 等方法中输出压力值和归一化压力值。此外,还可以使用 Instruments 工具中的 Core Motion 工具来实时监测压力值的变化。在 Xcode 中,选择“Product” -> “Profile”,然后在 Instruments 中选择 Core Motion,运行应用并进行触摸操作,即可在 Core Motion 工具中查看压力值的实时变化曲线。
  2. 交互调试
    • 对于 Peek and Pop 等交互的调试,可以使用模拟器的 3D Touch 模拟功能。在 Xcode 模拟器中,选择“Hardware” -> “3D Touch”,可以选择不同的压力级别进行模拟操作,方便开发者测试不同压力下的交互效果。同时,在实现 UIViewControllerPreviewingDelegate 协议的方法中添加日志输出,如在 previewingContext:viewControllerForLocation:previewingContext:commitViewController: 方法中输出相关信息,以帮助定位问题。例如:
- (UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
    NSLog(@"请求预览视图,触摸点位置: %@", NSStringFromCGPoint(location));
    //...
    return previewVC;
}
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
    NSLog(@"提交详细视图");
    [self showViewController:viewControllerToCommit sender:self];
}

通过以上对 Objective - C 中 3D Touch 压力感应技术的详细介绍,开发者可以充分利用这一技术为应用带来创新的交互体验,同时通过优化和调试,确保应用在不同设备和系统版本上的稳定运行。无论是游戏、绘图还是其他类型的应用,3D Touch 都为开发者提供了拓展交互方式的有力工具。