Objective-C中的WatchKit与Apple Watch开发
2023-10-091.9k 阅读
一、WatchKit 基础
(一)WatchKit 简介
WatchKit 是苹果公司为 Apple Watch 开发提供的一套框架。它允许开发者利用 iOS 应用已有的资源和代码基础,快速开发出与 iPhone 应用紧密结合的 Apple Watch 应用。WatchKit 使得开发者能够创建复杂且功能丰富的手表应用界面,并且通过与 iPhone 进行通信来实现各种功能。
在 Objective - C 开发环境中,WatchKit 提供了一系列的类和接口,方便开发者进行界面布局、交互处理以及数据传递等操作。它的设计理念是充分利用 Apple Watch 有限的屏幕空间和硬件资源,为用户提供简洁而高效的交互体验。
(二)WatchKit 架构
- WatchKit 扩展
- WatchKit 扩展是运行在 Apple Watch 上的代码部分。它负责管理 Watch 应用的用户界面、处理用户交互以及与 iPhone 上的主应用进行通信。在 Objective - C 项目中,WatchKit 扩展是一个独立的 target。例如,当创建一个新的 WatchKit 应用项目时,Xcode 会自动生成一个 WatchKit 扩展 target。
- 它包含了 Interface Controller 类,这些类负责管理 Watch 界面的不同屏幕。例如,以下是一个简单的 Interface Controller 类的声明:
#import <WatchKit/WatchKit.h>
#import <Foundation/Foundation.h>
@interface InterfaceController : WKInterfaceController
@end
- 主应用
- 主应用运行在 iPhone 上,它可以为 WatchKit 扩展提供数据、执行复杂的计算任务等。主应用和 WatchKit 扩展之间通过 Watch Connectivity 框架进行通信。例如,如果 Watch 应用需要获取用户的位置信息,由于 Apple Watch 本身获取位置信息的能力有限,此时可以通过主应用在 iPhone 上获取位置信息,然后传递给 WatchKit 扩展。
- 在主应用中,可以通过以下方式向 WatchKit 扩展发送消息:
#import <WatchConnectivity/WatchConnectivity.h>
@interface ViewController () <WCSessionDelegate>
@property (nonatomic, strong) WCSession *session;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
if ([WCSession isSupported]) {
self.session = [WCSession defaultSession];
self.session.delegate = self;
[self.session activateSession];
}
}
- (void)sendMessageToWatch {
NSDictionary *message = @{@"key": @"value"};
[self.session sendMessage:message replyHandler:^(NSDictionary *replyMessage) {
// 处理回复消息
} errorHandler:^(NSError *error) {
// 处理错误
}];
}
@end
- WatchKit 框架组件
- WKInterfaceObject:这是 WatchKit 中所有界面元素的基类。像
WKInterfaceLabel
、WKInterfaceButton
等都是它的子类。通过这些子类,开发者可以在 Watch 界面上添加文本标签、按钮等元素。例如,添加一个文本标签:
- WKInterfaceObject:这是 WatchKit 中所有界面元素的基类。像
@property (nonatomic, weak) IBOutlet WKInterfaceLabel *myLabel;
- WKInterfaceController:管理 Watch 应用的屏幕界面。它负责加载界面、处理界面上的交互事件等。例如,当用户点击按钮时,可以在
WKInterfaceController
的方法中进行响应:
- (IBAction)buttonTapped {
// 按钮点击处理逻辑
}
二、Apple Watch 界面开发
(一)界面布局
- 尺寸和分辨率
- Apple Watch 有不同的尺寸,主要分为 38mm 和 42mm(初代),以及后续的 40mm 和 44mm 等。在设计界面布局时,需要考虑这些不同尺寸的适配。WatchKit 提供了自动布局机制,类似于 iOS 开发中的 Auto Layout,但针对 Apple Watch 的屏幕特点进行了优化。
- 例如,在设计一个包含按钮和标签的界面时,可以通过设置界面元素的相对位置和大小来实现适配。假设要将一个按钮放在屏幕底部,并且距离底部有一定的间距,可以在 Interface Builder 中设置按钮的
Bottom Space to Container
约束: - 在 Objective - C 代码中,也可以通过代码来动态调整界面元素的位置和大小。例如,根据屏幕尺寸来调整标签的字体大小:
CGSize screenSize = [WKInterfaceDevice currentDevice].screenBounds.size;
if (screenSize.height == 340) {
[self.myLabel setFont:[UIFont systemFontOfSize:14]];
} else {
[self.myLabel setFont:[UIFont systemFontOfSize:16]];
}
- 布局容器
- WKInterfaceGroup:它是一个容器类,可以包含多个界面元素,方便对一组元素进行统一的布局管理。例如,可以将一组按钮放在一个
WKInterfaceGroup
中,然后对这个组进行居中对齐等操作。在 Interface Builder 中,可以直接拖入一个WKInterfaceGroup
,然后将按钮等元素拖入其中。 - WKInterfaceTable:用于在 Watch 界面上显示列表。如果应用需要展示一系列的数据,如联系人列表、待办事项列表等,就可以使用
WKInterfaceTable
。首先在 Interface Builder 中拖入一个WKInterfaceTable
,然后创建一个自定义的 Table Row Controller 类。 - 以下是创建一个简单的
WKInterfaceTable
的代码示例:
- WKInterfaceGroup:它是一个容器类,可以包含多个界面元素,方便对一组元素进行统一的布局管理。例如,可以将一组按钮放在一个
@property (nonatomic, weak) IBOutlet WKInterfaceTable *myTable;
// 在 interface controller 的初始化方法中设置表格行数
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[self.myTable setNumberOfRows:5 withRowType:@"TableRowController"];
}
// 配置表格行的数据
- (void)table:(WKInterfaceTable *)table willDisplayCell:(id)cell forRowAtIndex:(NSInteger)row {
TableRowController *rowController = (TableRowController *)cell;
[rowController.label setText:[NSString stringWithFormat:@"Row %ld", (long)row]];
}
(二)界面元素
- 标签(WKInterfaceLabel)
- 标签用于在 Watch 界面上显示文本信息。可以设置文本的内容、字体、颜色等属性。例如,创建一个标签并设置其文本和字体:
@property (nonatomic, weak) IBOutlet WKInterfaceLabel *welcomeLabel;
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[self.welcomeLabel setText:@"Welcome to my Watch App"];
[self.welcomeLabel setFont:[UIFont systemFontOfSize:16 weight:UIFontWeightMedium]];
}
- 按钮(WKInterfaceButton)
- 按钮是用户交互的重要元素。可以为按钮添加点击事件处理方法。例如,创建一个按钮并处理其点击事件:
@property (nonatomic, weak) IBOutlet WKInterfaceButton *actionButton;
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[self.actionButton setTitle:@"Click me"];
}
- (IBAction)actionButtonTapped {
// 处理按钮点击逻辑,比如显示一个提示信息
[self presentAlertControllerWithTitle:@"Button Clicked" message:@"You clicked the button" preferredStyle:WKAlertControllerStyleAlert actions:@[[WKAlertAction actionWithTitle:@"OK" style:WKAlertActionStyleDefault handler:nil]]];
}
- 图像(WKInterfaceImage)
- 图像元素用于在 Watch 界面上显示图片。可以从应用的资源中加载图片,也可以通过网络下载图片并显示。例如,从资源中加载一张图片:
@property (nonatomic, weak) IBOutlet WKInterfaceImage *myImage;
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[self.myImage setImageNamed:@"myImageName"];
}
如果要从网络加载图片,可以使用 NSURLSession
等网络请求框架,然后将下载的图片数据设置给 WKInterfaceImage
。以下是一个简单的示例:
NSURL *imageURL = [NSURL URLWithString:@"http://example.com/image.jpg"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:imageURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[self.myImage setImage:image];
});
}
}];
[task resume];
三、用户交互处理
(一)手势识别
- 点击手势
- 在 WatchKit 中,按钮本身就默认处理了点击手势。但对于其他界面元素,如
WKInterfaceGroup
或WKInterfaceImage
等,如果需要处理点击手势,可以通过添加WKInterfaceObject
的addGestureRecognizer:
方法来实现。例如,为一个WKInterfaceGroup
添加点击手势:
- 在 WatchKit 中,按钮本身就默认处理了点击手势。但对于其他界面元素,如
@property (nonatomic, weak) IBOutlet WKInterfaceGroup *myGroup;
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
WKTapGestureRecognizer *tapGesture = [[WKTapGestureRecognizer alloc] initWithTarget:self action:@selector(groupTapped)];
[self.myGroup addGestureRecognizer:tapGesture];
}
- (void)groupTapped {
// 处理组的点击逻辑
[self presentAlertControllerWithTitle:@"Group Tapped" message:@"You tapped the group" preferredStyle:WKAlertControllerStyleAlert actions:@[[WKAlertAction actionWithTitle:@"OK" style:WKAlertActionStyleDefault handler:nil]]];
}
- 长按手势
- 可以为界面元素添加长按手势识别。例如,为一个标签添加长按手势:
@property (nonatomic, weak) IBOutlet WKInterfaceLabel *myLabel;
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
WKLongPressGestureRecognizer *longPressGesture = [[WKLongPressGestureRecognizer alloc] initWithTarget:self action:@selector(labelLongPressed)];
[self.myLabel addGestureRecognizer:longPressGesture];
}
- (void)labelLongPressed {
// 处理标签长按逻辑,比如显示更多操作选项
[self presentMenuWithItems:@[[WKMenuItem menuItemWithTitle:@"Option 1" action:@selector(option1Action)],[WKMenuItem menuItemWithTitle:@"Option 2" action:@selector(option2Action)]]];
}
- (void)option1Action {
// 处理选项1的逻辑
}
- (void)option2Action {
// 处理选项2的逻辑
}
(二)菜单交互
- 创建菜单
- 可以为界面元素创建上下文菜单,提供更多的操作选项。通过
presentMenuWithItems:
方法来显示菜单。例如,为一个按钮创建菜单:
- 可以为界面元素创建上下文菜单,提供更多的操作选项。通过
@property (nonatomic, weak) IBOutlet WKInterfaceButton *menuButton;
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[self.menuButton setTitle:@"Show Menu"];
}
- (IBAction)menuButtonTapped {
[self presentMenuWithItems:@[[WKMenuItem menuItemWithTitle:@"Action 1" action:@selector(action1)],[WKMenuItem menuItemWithTitle:@"Action 2" action:@selector(action2)]]];
}
- (void)action1 {
// 处理动作1的逻辑
[self presentAlertControllerWithTitle:@"Action 1" message:@"You selected Action 1" preferredStyle:WKAlertControllerStyleAlert actions:@[[WKAlertAction actionWithTitle:@"OK" style:WKAlertActionStyleDefault handler:nil]]];
}
- (void)action2 {
// 处理动作2的逻辑
[self presentAlertControllerWithTitle:@"Action 2" message:@"You selected Action 2" preferredStyle:WKAlertControllerStyleAlert actions:@[[WKAlertAction actionWithTitle:@"OK" style:WKAlertActionStyleDefault handler:nil]]];
}
- 菜单样式和定制
- 可以设置菜单的样式,如菜单项的标题颜色、背景颜色等。虽然 WatchKit 提供的定制选项相对有限,但可以通过一些技巧来实现特定的效果。例如,可以通过修改全局的外观设置来影响菜单的样式:
[[WKInterfaceController currentPage] setBackgroundColor:[UIColor lightGrayColor]];
这会改变整个界面包括菜单所在背景的颜色。
四、数据处理与存储
(一)与主应用的数据交互
- Watch Connectivity 框架
- 如前文所述,主应用和 WatchKit 扩展通过 Watch Connectivity 框架进行通信。除了发送简单的消息,还可以传输文件、共享数据等。例如,主应用向 WatchKit 扩展传输一个文件:
- 在主应用中:
NSURL *fileURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"example" ofType:@"txt"]];
[self.session transferFile:fileURL metadata:nil];
- 在 WatchKit 扩展中接收文件:
- (void)session:(WCSession *)session didReceiveFile:(WCSessionFile *)file {
NSData *fileData = [NSData dataWithContentsOfURL:file.fileURL];
// 处理接收到的文件数据
}
- 共享数据存储
- 可以使用
NSUserDefaults
来在主应用和 WatchKit 扩展之间共享简单的数据。例如,在主应用中保存一个用户设置:
- 可以使用
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:@"value" forKey:@"key"];
[userDefaults synchronize];
- 在 WatchKit 扩展中读取这个设置:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *value = [userDefaults objectForKey:@"key"];
(二)Watch 本地数据存储
- 文件存储
- Apple Watch 提供了本地文件存储的功能。可以使用
NSFileManager
来进行文件的创建、读取、写入等操作。例如,创建一个新文件并写入数据:
- Apple Watch 提供了本地文件存储的功能。可以使用
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSURL *fileURL = [documentsURL URLByAppendingPathComponent:@"example.txt"];
NSString *content = @"This is some sample content";
NSError *error;
[content writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
// 处理错误
}
- Core Data 存储
- 虽然 Apple Watch 的资源有限,但对于一些较为复杂的数据管理需求,也可以使用 Core Data。首先需要在 WatchKit 扩展的 target 中添加 Core Data 框架。然后创建数据模型,定义实体、属性等。
- 以下是一个简单的 Core Data 示例,创建一个新的实体并保存数据:
// 获取 Core Data 堆栈
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// 创建新实体
NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:context];
NSManagedObject *newObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
// 设置实体属性
[newObject setValue:@"Some Value" forKey:@"myAttribute"];
// 保存上下文
NSError *saveError;
if (![context save:&saveError]) {
// 处理保存错误
}
五、高级功能开发
(一)复杂界面和动画
- 自定义界面过渡
- WatchKit 提供了一些默认的界面过渡效果,如
pushControllerWithName:context:
方法实现的页面推送过渡。但对于更复杂的自定义过渡效果,可以通过组合多个界面元素的显示和隐藏以及动画来实现。例如,创建一个类似淡入淡出的过渡效果: - 假设界面上有两个
WKInterfaceGroup
,一个是当前显示的currentGroup
,另一个是即将显示的nextGroup
。
- WatchKit 提供了一些默认的界面过渡效果,如
[nextGroup setAlpha:0.0];
[nextGroup setHidden:NO];
[UIView animateWithDuration:0.3 animations:^{
[currentGroup setAlpha:0.0];
[nextGroup setAlpha:1.0];
} completion:^(BOOL finished) {
[currentGroup setHidden:YES];
}];
- 动画效果
- 可以为界面元素添加动画效果,如淡入淡出、旋转、缩放等。例如,为一个按钮添加旋转动画:
@property (nonatomic, weak) IBOutlet WKInterfaceButton *animatedButton;
- (void)startButtonAnimation {
[self.animatedButton startAnimatingWithImagesInRange:NSMakeRange(0, 1) duration:2 repeatCount:0];
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(M_PI);
[self.animatedButton setTransform:rotationTransform];
}
(二)传感器和健康数据
- 运动传感器
- Apple Watch 配备了运动传感器,如加速度计和陀螺仪。可以使用 Core Motion 框架来获取这些传感器的数据。例如,获取加速度计数据:
#import <CoreMotion/CoreMotion.h>
@property (nonatomic, strong) CMMotionManager *motionManager;
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
self.motionManager = [[CMMotionManager alloc] init];
if (self.motionManager.isAccelerometerAvailable) {
self.motionManager.accelerometerUpdateInterval = 0.1;
[self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
if (!error) {
CMAcceleration acceleration = accelerometerData.acceleration;
// 处理加速度数据
}
}];
}
}
- 健康数据
- 要访问健康数据,需要使用 HealthKit 框架。首先在项目中添加 HealthKit 框架,然后请求用户授权。例如,读取用户的步数数据:
#import <HealthKit/HealthKit.h>
@property (nonatomic, strong) HKHealthStore *healthStore;
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
self.healthStore = [[HKHealthStore alloc] init];
HKObjectType *stepCountType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
NSArray *healthKitTypesToRead = @[stepCountType];
[self.healthStore requestAuthorizationToShareTypes:nil readTypes:[NSSet setWithArray:healthKitTypesToRead] completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
// 授权成功,读取步数数据
HKQuantityType *stepCountType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow:-24 * 60 * 60];
HKQuery *query = [[HKStatisticsQuery alloc] initWithQuantityType:stepCountType quantitySamplePredicate:[HKQuery predicateForSamplesWithStartDate:startDate endDate:[NSDate date] options:HKQueryOptionNone] statisticsOptions:HKStatisticsOptionCumulativeSum completionHandler:^(HKStatisticsQuery * _Nonnull query, HKStatistics * _Nullable result, NSError * _Nullable error) {
if (!error && result) {
HKQuantity *quantity = [result sumQuantity];
double stepCount = [quantity doubleValueForUnit:[HKUnit countUnit]];
// 处理步数数据
}
}];
[self.healthStore executeQuery:query];
} else {
// 处理授权失败
}
}];
}