64、探索iOS陀螺仪、加速度计与相机功能

探索iOS陀螺仪、加速度计与相机功能

1. 陀螺仪与加速度计的应用:滚动的球

在iOS开发中,我们可以利用陀螺仪和加速度计实现有趣的交互效果,比如控制一个球在屏幕上滚动。

1.1 viewDidLoad方法

viewDidLoad 方法与本章其他部分的操作类似,但有一个重要区别:这里使用了更高的更新频率,每秒更新60次。在告诉运动管理器执行加速度计更新报告的代码块中,我们将加速度对象传递给视图,然后调用 update 方法,该方法会根据加速度和自上次更新以来经过的时间更新视图中球的位置。由于该代码块可以在任何线程上执行,而UIKit对象(包括 UIView )的方法只能在主线程上安全使用,因此我们再次强制 update 方法在主线程上调用。

1.2 编写球视图
  • BallView.h文件
    • 导入 Core Motion 头文件。
    • 添加控制器将用于传递加速度值的属性和用于更新球位置的方法。
#import <UIKit/UIKit.h>
#import <CoreMotion/CoreMotion.h>

@interface BallView : UIView

@property (assign, nonatomic) CMAcceleration acceleration;

- (void)update;

@end
  • BallView.m文件
    • 在文件顶部添加类扩展,定义一些属性用于跟踪球的状态。
#import "BallView.h"

@interface BallView ()

@property (strong, nonatomic) UIImage *image;
@property (assign, nonatomic) CGPoint currentPoint;
@property (assign, nonatomic) CGPoint previousPoint;
@property (assign, nonatomic) CGFloat ballXVelocity;
@property (assign, nonatomic) CGFloat ballYVelocity;

@end

这些属性的作用如下:
| 属性 | 作用 |
| ---- | ---- |
| UIImage *image | 指向要在屏幕上移动的精灵图像 |
| CGPoint currentPoint | 保存球的当前位置 |
| CGPoint previousPoint | 保存上次绘制精灵的位置 |
| CGFloat ballXVelocity | 跟踪球在X轴上的当前速度 |
| CGFloat ballYVelocity | 跟踪球在Y轴上的当前速度 |

  • 初始化方法
    @implementation 部分的开头添加以下方法:
@implementation BallView

- (void)commonInit {
    self.image = [UIImage imageNamed:@"ball"];
    self.currentPoint = CGPointMake((self.bounds.size.width / 2.0f) +
                                    (self.image.size.width / 2.0f),
                                    (self.bounds.size.height / 2.0f) +
                                    (self.image.size.height / 2.0f));
}

- (id)initWithCoder:(NSCoder *)coder  {
    self = [super initWithCoder:coder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}

initWithCoder: initWithFrame: 方法都会调用 commonInit 方法,这样我们的视图类既可以从代码中创建,也可以从nib文件中创建。

  • 绘制方法
    取消注释 drawRect: 方法并实现它:
- (void)drawRect:(CGRect)rect
{
    // Drawing code
    [self.image drawAtPoint:self.currentPoint];
}
  • 其他方法
    在类的末尾添加以下方法:
#pragma mark -

- (void)setCurrentPoint:(CGPoint)newPoint {
    self.previousPoint = self.currentPoint;
    _currentPoint = newPoint;

    if (self.currentPoint.x < 0) {
        _currentPoint.x = 0;
        self.ballXVelocity = 0;
    }
    if (self.currentPoint.y < 0){
        _currentPoint.y = 0;
        self.ballYVelocity = 0;
    }
    if (self.currentPoint.x > self.bounds.size.width - self.image.size.width) {
        _currentPoint.x = self.bounds.size.width - self.image.size.width;
        self.ballXVelocity = 0;
    }
    if (self.currentPoint.y >
        self.bounds.size.height - self.image.size.height) {
        _currentPoint.y = self.bounds.size.height - self.image.size.height;
        self.ballYVelocity = 0;
    }

    CGRect currentRect =
    CGRectMake(self.currentPoint.x, self.currentPoint.y,
               self.currentPoint.x + self.image.size.width,
               self.currentPoint.y + self.image.size.height);
    CGRect previousRect =
    CGRectMake(self.previousPoint.x, self.previousPoint.y,
               self.previousPoint.x + self.image.size.width,
               self.currentPoint.y + self.image.size.width);
    [self setNeedsDisplayInRect:CGRectUnion(currentRect, previousRect)];
}

- (void)update {
    static NSDate *lastUpdateTime = nil;

    if (lastUpdateTime != nil) {
        NSTimeInterval secondsSinceLastDraw =
            [[NSDate date] timeIntervalSinceDate:lastUpdateTime];

        self.ballYVelocity = self.ballYVelocity -
                             (self.acceleration.y * secondsSinceLastDraw);
        self.ballXVelocity = self.ballXVelocity +
                             (self.acceleration.x * secondsSinceLastDraw);

        CGFloat xAccel = secondsSinceLastDraw * self.ballXVelocity * 500;
        CGFloat yAccel = secondsSinceLastDraw * self.ballYVelocity * 500;

        self.currentPoint = CGPointMake(self.currentPoint.x + xAccel,
                                        self.currentPoint.y + yAccel);
    }
    // Update last time with current time
    lastUpdateTime = [[NSDate alloc] init];
}

@end
1.3 计算球的运动
  • drawRect:方法 :非常简单,只是在 currentPoint 存储的位置绘制在 commonInit 中加载的图像。
  • setCurrentPoint:方法
    • 首先将旧的 currentPoint 值存储在 previousPoint 中,并将新值赋给 currentPoint
    • 进行边界检查,如果球的x或y位置小于0或大于屏幕的宽度或高度(考虑图像的宽度和高度),则停止该方向的加速度。
    • 计算两个 CGRect ,一个包含新图像将绘制的区域,另一个包含上次绘制的区域,使用这两个矩形确保在绘制新球的同时擦除旧球。
    • 创建一个新的矩形,它是上述两个矩形的并集,并将其传递给 setNeedsDisplayInRect: 方法,以指示需要重绘视图的部分。
  • update方法
    • 声明一个静态 NSDate 变量 lastUpdateTime ,用于跟踪自上次调用 update 方法以来经过的时间。
    • 第一次调用该方法时, lastUpdateTime nil ,不执行任何操作。
    • 其他时候,计算自上次调用该方法以来经过的时间。
    • 计算两个方向的新速度,通过将当前加速度加到当前速度上,并乘以 secondsSinceLastDraw ,使加速度在时间上保持一致。
    • 根据速度计算自上次调用该方法以来像素的实际变化,将速度和经过的时间的乘积乘以500,以创建看起来自然的运动。
    • 创建一个新的点,将当前位置加上计算出的加速度,并将其赋给 currentPoint
    • 最后,用当前时间更新 lastUpdateTime

提示 :如果想让球更自然地从墙壁反弹,而不是仅仅停止,可以将 setCurrentPoint: 方法中 self.ballXVelocity = 0; 改为 self.ballXVelocity = - (self.ballXVelocity / 2.0); ,将 self.ballYVelocity = 0; 改为 self.ballYVelocity = - (self.ballYVelocity / 2.0); 。这样,球的速度将减半并反向。

在构建应用程序之前,使用前面提到的技术添加 Core Motion 框架。添加完成后,即可构建并运行应用程序。如果一切顺利,应用程序将启动,你可以通过倾斜手机来控制球的移动。当球到达屏幕边缘时,它应该停止。将手机向相反方向倾斜,球应该开始向相反方向滚动。

以下是球运动的流程图:

graph TD
    A[开始] --> B{lastUpdateTime是否为nil}
    B -- 是 --> C[不执行操作]
    B -- 否 --> D[计算经过时间secondsSinceLastDraw]
    D --> E[计算新速度ballXVelocity和ballYVelocity]
    E --> F[计算像素变化xDelta和yDelta]
    F --> G[更新currentPoint]
    G --> H[更新lastUpdateTime]
    C --> I[结束]
    H --> I
2. 相机与照片库的使用

在iOS设备中,iPhone、iPad和iPod touch都内置了相机,并且有一个名为“照片”的应用程序来管理拍摄的照片和视频。开发者的程序可以使用内置相机拍照,也可以让用户从设备上已存储的媒体中选择和查看。

2.1 使用图像选择器和UIImagePickerController

由于iOS应用程序是沙盒化的,应用通常无法访问其沙盒之外的照片或其他数据。不过,相机和媒体库可以通过图像选择器提供给应用使用。

图像选择器是一种可以从指定源选择图像的机制。最初,它仅用于选择图像,现在也可用于拍摄视频。通常,图像选择器会使用图像和/或视频列表作为源,也可以指定使用相机作为源。

图像选择器界面由 UIImagePickerController 类实现。创建该类的实例,指定委托,指定图像源以及是否让用户选择图像或视频,然后展示它。图像选择器会控制设备,让用户从现有媒体库中选择图片或视频,或者使用相机拍摄新的图片或视频。用户做出选择后,可以进行一些基本编辑,如缩放或裁剪图像,或修剪视频片段。这些行为都由 UIImagePickerController 实现,开发者无需做太多复杂的工作。

如果用户不点击“取消”,用户拍摄或选择的图像或视频将传递给委托。无论用户是选择媒体文件还是取消操作,委托都负责关闭 UIImagePickerController ,以便用户返回应用程序。

2.2 使用图像选择器控制器

创建 UIImagePickerController 非常简单,但并非所有iOS设备都有相机,如旧款iPod touch和第一代iPad。因此,在创建 UIImagePickerController 实例之前,需要检查应用当前运行的设备是否支持所需的图像源。可以使用 UIImagePickerController 的类方法 isSourceTypeAvailable: 进行检查,示例如下:

if ([UIImagePickerController isSourceTypeAvailable:
     UIImagePickerControllerSourceTypeCamera]) {
    // 设备支持使用相机作为图像源
}

可以指定的图像源类型有:
| 源类型 | 说明 |
| ---- | ---- |
| UIImagePickerControllerSourceTypeCamera | 使用内置相机拍摄图片或视频 |
| UIImagePickerControllerSourceTypePhotoLibrary | 让用户从现有媒体库中选择图像或视频 |
| UIImagePickerControllerSourceTypeSavedPhotosAlbum | 让用户从现有照片库中选择图像,且选择范围限于相机胶卷 |

确认设备支持所需的图像源后,启动图像选择器的步骤如下:
1. 创建 UIImagePickerController 实例。
2. 指定委托。
3. 设置图像源。
4. (可选)设置使用的相机设备(如果设备有多个相机)。
5. 展示图像选择器。

示例代码如下:

UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
picker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
[self presentViewController:picker animated:YES completion:nil];

提示 :在有多个相机的设备上,可以通过设置 cameraDevice 属性为 UIImagePickerControllerCameraDeviceFront UIImagePickerControllerCameraDeviceRear 来选择使用的相机。使用 isCameraDeviceAvailable: 方法可以检查前置或后置相机是否可用。

2.3 实现图像选择器控制器委托

要知道用户何时使用完图像选择器,需要实现 UIImagePickerControllerDelegate 协议。该协议定义了两个方法: imagePickerController:didFinishPickingMediaWithInfo: imagePickerControllerDidCancel:

  • imagePickerController:didFinishPickingMediaWithInfo:方法 :当用户成功拍摄照片或视频,或从媒体库中选择了项目时调用。第一个参数是之前创建的 UIImagePickerController 的指针,第二个参数是一个 NSDictionary 实例,包含所选照片或所选视频的URL,以及可选的编辑信息(如果在图像选择器控制器中启用了编辑功能,且用户实际进行了编辑)。该字典中,原始未编辑的图像存储在键 UIImagePickerControllerOriginalImage 下。示例代码如下:
- (void)imagePickerController:(UIImagePickerController *)picker
            didFinishPickingMediaWithInfo:(NSDictionary *)info {
    UIImage *selectedImage = info[UIImagePickerControllerEditedImage];
    UIImage *originalImage = info[UIImagePickerControllerOriginalImage];

    // do something with selectedImage and originalImage

    [picker dismissViewControllerAnimated:YES completion:nil];
}

编辑信息字典中,还可以通过键 UIImagePickerControllerCropRect 下存储的 NSValue 对象获取编辑时选择的图像部分,将其转换为 CGRect 的示例代码如下:

NSValue *cropValue = info[UIImagePickerControllerCropRect];
CGRect cropRect = [cropValue CGRectValue];

注意 :如果返回给委托的图像来自相机,该图像不会自动存储在照片库中,应用程序有责任在必要时保存图像。

  • imagePickerControllerDidCancel:方法 :如果用户决定取消操作,未拍摄或选择任何媒体时调用。图像选择器调用此委托方法只是通知用户已完成选择器操作且未选择任何内容。

虽然 UIImagePickerControllerDelegate 协议中的两个方法都标记为可选,但实际上并非如此,因为像图像选择器这样的模态视图必须被通知关闭。

以下是使用图像选择器的流程图:

graph TD
    A[开始] --> B{设备是否支持图像源}
    B -- 是 --> C[创建UIImagePickerController实例]
    B -- 否 --> D[结束]
    C --> E[设置委托]
    E --> F[设置图像源]
    F --> G[(可选)设置相机设备]
    G --> H[展示图像选择器]
    H --> I{用户操作}
    I -- 完成选择 --> J[调用imagePickerController:didFinishPickingMediaWithInfo:方法]
    I -- 取消 --> K[调用imagePickerControllerDidCancel:方法]
    J --> L[处理所选媒体并关闭图像选择器]
    K --> L
    L --> D

通过上述内容,我们了解了如何在iOS开发中利用陀螺仪和加速度计控制球的滚动,以及如何使用图像选择器让用户拍摄照片或选择媒体。这些功能为开发者提供了丰富的交互和媒体处理能力,可以创建出更加有趣和实用的应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值