美文网首页
iOS | iWatch开发 没有转圈的数据请求 是没有灵魂的

iOS | iWatch开发 没有转圈的数据请求 是没有灵魂的

作者: BinaryBang | 来源:发表于2020-03-07 18:11 被阅读0次

    表弟最近在开发一个独立的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点.

    如果这篇文章帮到了你,请点赞,关注,鼓励一下哈~~~

    参考资料

    1.WKInterfaceImage-官方文档译文

    相关文章

      网友评论

          本文标题:iOS | iWatch开发 没有转圈的数据请求 是没有灵魂的

          本文链接:https://www.haomeiwen.com/subject/rbccdhtx.html