表弟最近在开发一个独立的iWatch手表应用,需要应用独立地调用接口去请求数据,而不是和iPhone通信来交换数据.
调接口请求数据本身没什么问题,NSURLSessionDataTask类不仅适用于iPhone,iWatch也能使用.所以AFNetworking等常规请求库,是可以直接使用的.
但是,有一个问题比较尴尬.
平时我们在网络请求时,都会转个圈,提示用户,我正在请求数据.
虽然很多用户神烦这个转圈,但是转圈真的很重要:
- 防止用户重复操作. 有的关键的操作,比如提交订单时,防止用户重复提交.
- 告诉用户系统没有死机. 有的时候,请求数据比较长,通过转圈告诉用户,系统本统依然健在,请耐心等待.
- 提升吸引力.有的应用把转圈的功能也做得很友好,甚至通过转圈就能提升用户的好感.
iPhone上还有专门的UIActivityIndicatorView来处理这个事情.
但是WatckKit中,没有哇...
表弟有点想让请求中按钮变灰的方式来对应.
作为一个优雅的程序员,不允许这样的事情发生.
所以,这一次我们来让菊花(转圈)在Watch应用中绽放!
1 Demo说明
本文的目标,是创建一个实现以下的基本功能的应用:
点击按钮->请求接口->转圈->收到数据->转圈消失,解析数据,显示Table.
基本的流程如下图:
最终效果:
这个gif目前只能播放一遍,需要刷新以下才能观看,原因还在调查中.
2 WatchApp如何显示动态图片
WatchKit中,播放图片的组件是WKInterfaceImage,详细信息可以参考这篇官方文档译文:WKInterfaceImage-官方文档译文
gif文件实际上是一系列图片的组合,不能和jpg或者png那样,直接播放,需要进行额外的处理.这里介绍两种方式播放gif文件:
2.1 把gif资源放在WatchKitApp资源目录
- 2.1.1 获取gif的每一帧资源
-
2.1.2 在WatchKitApp的资源目录中,新建loading文件夹,然后文件名按照loading0,loading1...这样的方式命名:
- 2.1.3 使用以下代码,显示动态图像
```
[interfaceImage setImageNamed:@"loading"];
[interfaceImage startAnimating];
```
2.2 程序自动提取gif的每一帧
-
2.2.1 将图片放到WatchKit Extension bundle中,确保你的图片的Target Membership情况为:
- 2.2.2 用以下代码,提取gif的每一帧图片,并返回一个动画图片序列的UIImage对象
/*
name:要播放的gif图片名;
duration:在多少时间内放完;
*/
- (UIImage *)gifImageNamed:(NSString *)name duration:(NSInteger)duration {
//获取资源文件的路径
NSString * path = [[NSBundle mainBundle] pathForResource:name ofType:nil];
if(!path){
NSLog(@"文件不存在!");
return nil;
}
//加载NSData图片数据
NSData * data = [NSData dataWithContentsOfFile:path];
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t count = CGImageSourceGetCount(source);
if (count <= 1) {
return [[UIImage alloc] initWithData:data];
}
NSMutableArray *images = [NSMutableArray array];
for (size_t i = 0; i < count; i++) {
//在栈中创建一个图片数据结构
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!imageRef) {
continue;
}
//根据图片数据结构,创建一个UIImage对象
[images addObject:[UIImage imageWithCGImage:imageRef]];
//释放已经生成了UIImage对象的图片数据结构
CGImageRelease(imageRef);
}
UIImage *animatedImage = [UIImage animatedImageWithImages:images duration:duration];
CFRelease(source);
return animatedImage;
}
- 2.2.3 使用以下代码显示该动画图片:
[self.ifImage setImage:[self gifImageNamed:@"wait.gif" duration:2]];
[self.ifImage startAnimating];
3 WKInterfaceTable的使用
WatchKit中的表视图是WKInterfaceTable类,相比于UITableView,使用上简化了很多.
3.1 Cell的配置
WKInterfaceTable将负责Cell显示的类,叫做TableRowController,我们需要自定义一个类来承担TableRowController这样的角色.
TableRowController被关联了一个Group,用来对cell上面要呈现的空间进行布局.我们可以将这些控件关联到TableRowController对应的类上,进行控制.
-
3.1.1 在故事板中,拖入一个WKInterfaceTable类.
-
3.1.2 新建一个类TableCell,继承NSObject.需要导入WatchKit:
#import <WatchKit/WatchKit.h>
-
3.1.3 在故事板中的界面上选中Table里的TableRowController,选中id检测器,为其指定类为我们新建的TableCell:
-
3.1.4 在属性检测器中,指定id为"TableCell",在之后的程序中会用到.
-
3.1.5 选中TableRowController下面的Group,并添加一个WKInterfaceLabel.
-
3.1.6 选中WKInterfaceLabel,并关联到TableCell.h文件的输出口中:
@interface TableCell : NSObject
@property (weak, nonatomic) IBOutlet WKInterfaceLabel *ifLabel;
@end
这样,cell就配置好了.
3.2 Table的配置
cell配置完毕之后,就是配置Table了.
- 3.2.1 配置Table的Cell数量
UITableView是通过代理方法获取Cell的数量的,WKInterfaceTable,则是直接设置需要的Cell数量:
//假如要显示4个Cell
[table setNumberOfRows:4 withRowType:@"TableCell"];
注意,这里要指定RowController的Type,需要和3.1.4中设置的保持一致.
- 3.2.2 设置每个Cell的标题
UITableView也是通过代理方法设置cell的显示内容.
WKInterfaceTable,是直接遍历每个RowController进行控制:
//arr是标题的字符串数组.
for(int i = 0; i < table.numberOfRows;i++) {
TableCell * cell = [table rowControllerAtIndex:i];
NSString * title = arr[I];
[cell.ifLabel setText:title];
}
- 3.2.3 设置点击事件
WKInterfaceTable的点击事件,会直接传给InterfaceController的该方法,而不需要设置代理.
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndex:(NSInteger)rowIndex {
NSString * title = self.arr[rowIndex];
NSLog(@"Click %@",title);
}
这样,table也设置好了.
4 WKInterfaceGroup的使用
在显示转圈的时候还有一个问题.
我们在转圈的时候,转圈的图片和Table的位置其实是在一个位置的,在没有数据时,显示转圈图片,隐藏Table;在有数据时,显示Table,隐藏转圈图片.
但是在WatchKit的InterfaceController中,一般只能竖直向下排列,不能两个控件占用同一个位置.只有InterfaceGroup提供了内容元素的布局功能;
InterfaceGroup的内容包含3中布局方式,Horizontal,Vertical,Overlap.
- Horizontal,则里面的内容都是水平方向排列
- Vertical,内容都是竖直方向排列
- Overlap,内容都是互相覆盖的.
所以Overlap符合我们的要求.
但是,我们希望在Table的下方有个返回的按钮,这就不能让Table和返回按钮一起在这个Overlap的Group里,否则他们会互相重叠.
所以,最终的方案是:
- 让Table和返回按钮在一个Vertical布局的Group里;
- 让VerticalGroup,转圈的图片,请求的按钮在一个Overlap布局的Group里
最后还有一点需要注意,在数据返回之后,Table的高度发生了变化需要让Overlap的Group重新调整一下高度,否则不能滚动:
[self.ifGroupRoot sizeToFitHeight];
5 真机请求时的一个大坑
在模拟器调式的时候,很顺利,得到了预期的效果.
但是在真机调式的时候,总是提示"The Internet connection appears to be offline".
这个问题让我头疼了好久.
最终发现可能的原因:
- i 我的WatchApp是通过和iPhone配对安装上去的,所以iWatch和iPhone保持着配对关系.
- ii iWatch配对的iPhone在线时,iWatch并不会自己去请求数据,而是通过iPhone去请求数据
- iii 但是我这个应用是独立的iWatch应用,并没有和iPhone有数据交互,所以提示网络不同.
最终解决的办法是: 让配对的手机关机,飞行模式,或者接触配对.
6 总结
iWatch开发目前相关资料不是那么充足,一路上遇到了很多问题.希望记录下这些问题作为总结,并帮助有需要的人.
项目源代码放到百度网盘上了:
链接:https://pan.baidu.com/s/1jq2lTw3zU-nTsgN2EHQjrw
密码:fdih
运行环境:Xcode 11.3.1
模拟器应该可以直接运行,跑真机的话要注意本文的第5点.
如果这篇文章帮到了你,请点赞,关注,鼓励一下哈~~~
网友评论