AppleWatch 开发的一些知识总结

最近忙了一阵子Apple Watch 的开发。虽然Apple Watch 4马上就要发布了,但详细的中文开发资料还不是很多。我这里就不按照步骤走了,总结一下非常规的知识点以及个人理解。


  • 静态布局

    所有静态UI控件都在 Storyboard 上完成,彻底抛弃frame的概念。WatchKit 布局方式很像CSS,包括绝对布局和相对布局,支持横向排列和纵向排列,UI之间无法重叠。但可以使用 Group 处理。 比如我们去拖一个 左图片 右文字 的 Button,该如何操作呢?

    1、拖一个button, 设置其 Content 为 Group 类型(默认为 Text)。

    2、在该 Group 上再拖一个 Group2 ,用来容纳图片和文字,只要让该 Group2 居中,那么图片和文字就可以居中啦。

    3、设置 Group2 的 Layout 为 Horizontal ,在 Group2 上拖进来一个 WKInterfaceImage 和一个 WKInterfaceLabel 即可。

    具体层级关系如下图:
    AppleWatch 开发的一些知识总结_第1张图片


  • 动态绘制

    WatchKit 上的 UI 控件不多,和 UIKit 有些类似。但由于不支持frame的概念,所以很难处理根据网络数据的不同,frame 不同的问题。比如这种曲线图。
    AppleWatch 开发的一些知识总结_第2张图片

    WatchKit 上没有提供 CALayer 相关的 类。但可以采用 UIBezierPath 和 CGContextRef 相结合的方法去动态绘制。绘制完成后,将画布合成为 UIImage, 然后就可以放到 WKInterfaceImage 上显示了。

    需要注意的是,画布的大小要设置为屏幕尺寸的二倍(或者直接乘以 screenScale ),不然绘制出来的很模糊。

    这里只摘要一小段代码示例(根据不同体重绘制不同位置的白点)

    - (void)drawGraph{
       CGFloat graphWidth = CGRectGetWidth([WKInterfaceDevice currentDevice].screenBounds);
       CGFloat graphHeight = CGRectGetWidth([WKInterfaceDevice currentDevice].screenBounds);
    
       float scaleFactor = [WKInterfaceDevice currentDevice].screenScale;
       CGSize size = CGSizeMake(graphWidth*scaleFactor, graphHeight*scaleFactor);
    
       NSArray *positionsArray = [self transPointToValue:self.dataArray];
    
       UIGraphicsBeginImageContext(size);
    
       CGContextRef context = UIGraphicsGetCurrentContext();
    
       //绘制体重的小白点
       [self drawWhiteDotPath:positionsArray];
    
       /*
        这里你可以绘制任何东西
       */
    
       UIImage *image = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];
    
       UIGraphicsEndImageContext();
    
       [self.weightCurveImage setImage:image];
    }
    - (void)drawWhiteDotPath:(NSArray *)pointArray{
       for (NSValue *pointValue in pointArray) {
           UIBezierPath *dotPath = [UIBezierPath bezierPath];
           [[UIColor whiteColor] setStroke];
           [[UIColor whiteColor] setFill];
           [dotPath addArcWithCenter:pointValue.CGPointValue radius:5 startAngle:0 endAngle:2*M_PI clockwise:YES];
           dotPath.lineWidth = 1;
           [dotPath fill];
           [dotPath stroke];
       }
    }

  • 数据交互(未激活虚拟SIM卡的情况,激活之后是否一样还待测试)

    首先知道一点,在 watchOS 2.0之前,只能通过蓝牙进行数据传输。Watch上的网络请求也是交给 iPhone 去发起,然后把结果传递回来。而之后,Apple Watch 支持连接WIFI,利用 NSURLSession 可以自主发起网络请求。

    1、与手机处于同一 WiFi 下,手机会把该 WiFi 共享给 Watch。

    2、iPhone 和 Watch 正常连接,如果 iPhone 断开网络,Watch 也无法使用自己的WiFi。

    3、iPhone 和 Watch 断开连接(iPhone关闭蓝牙),等半分钟左右,Watch 会使用自主的WiFi。

    WCSession

    正常连接的时候可以通过 WCSession 进行网络通信。 Watch 通过 WCSession 给 iPhone上的app 发送消息,会静默启动app。但反过来却不会。

    sendMessage 为异步操作,所以涉及到更新UI的情况,注意要在主线程中进行。

    [session sendMessage:@{@"xxx":@"xxx"} replyHandler:^(NSDictionary<NSString *,id> * _Nonnull replyMessage) {
    NSDictionary *messageDict = (NSDictionary *)replyMessage;
    dispatch_async(dispatch_get_main_queue(), ^{
        //更新UI
    });
    } errorHandler:^(NSError * _Nonnull error) {
    
    }];

    官方文档说 session 的激活再要尽量早的地方,一般在 app 的 didFinishLaunchingWithOptions进行激活,可以为此专门建一个类,作为 session 的 delegate ,这样可以避免在 AppDelegate 写太多代码,保持简洁。而 Watch 方面 Session 的激活,可以在 InterfaceController 类的awakeWithContext方法里进行。

    另外,为了保持Session的稳定性,要在断开连接的时候做一下激活Session的处理。

    iPhone方面:

    - (void)sessionDidDeactivate:(WCSession *)session __IOS_AVAILABLE(9.3) __WATCHOS_UNAVAILABLE{
      //重新启动 Session
      if ([WCSession isSupported]) {
          WCSession *session = [WCSession defaultSession];
          session.delegate = self;
          [session activateSession];
      }
    }

    Apple Watch 方面:

    - (void)sessionReachabilityDidChange:(WCSession *)session{
    //重新启动 Session
      if (!session.reachable) {
          WCSession *session = [WCSession defaultSession];
          session.delegate = self;
          [session activateSession];
      }
    }

    NSURLSession

    如上文提到的,当 Watch 与iPhone 断开连接,可以自主发起网络请求:

      NSString *urlString = [your.host stringByAppendingString:urlString];
    
      NSMutableDictionary *headers = [NSMutableDictionary dictionary];
      [headers setValue:@"application/json; charset=utf-8" forKey:@"Content-Type"];
      [headers setValue:@"application/json" forKey:@"Accept"];
    
      NSURL *url = [NSURL URLWithString:urlString];
      NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
      [request setHTTPMethod:your.methordType?:@"GET"];
      //设置 header
      for (NSString *keyString in headers.allKeys) {
          [request addValue:headers[keyString] forHTTPHeaderField:keyString];
      }
    
      //设置请求参数
      NSMutableDictionary *tempDcit = [[NSMutableDictionary alloc] initWithDictionary:parameters];
      [tempDcit setValue:@"xxxx" forKey:@"xxxx"];
    
      [request setHTTPBody:[[tempDcit dictionaryToJsonString] dataUsingEncoding:NSUTF8StringEncoding]];
    
      NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
      if (@available(watchOS 4.0, *)) {
          configuration.waitsForConnectivity = YES;
      }
      NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
    
      NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                                  completionHandler:
                                        ^(NSData *data, NSURLResponse *response, NSError *error) {
                                            //请求结果处理
                                        }];
      [dataTask resume];

    既然 Apple Watch 也有了网络请求,那么在开发过程中如何选择,是利用 NSURLSession自主请求还是利用 WCSession 把请求任务给手机,让手机传递请求结果呢?个人觉得最好的方案是两个都要兼容。建议对请求做一层封装过滤,如果 session.reachable 为YES,把请求交给 iPhone,否则,就由 Watch 发起请求。


  • Some Tips

    WKCrownDelegate

    通过准守 WKCrownDelegate 协议,我们可以监听表冠的旋转,实现自己的需求,比如调整数字。

    参考资料:传送门

    - (void)willActivate {
     [super willActivate];
     self.crownSequencer.delegate = self;
     [self.crownSequencer focus];
    }
    
    #pragma mark - WKCrownDelegate
    
    CGFloat crownAccumulator = 0.0;
    - (void)crownDidRotate:(nullable WKCrownSequencer *)crownSequencer rotationalDelta:(double)rotationalDelta API_AVAILABLE(watchos(3.0)){
      crownAccumulator += rotationalDelta;
      if (crownAccumulator > 0.1) {
         //让你的数字+0.1
          crownAccumulator = 0.0;
      } else if (crownAccumulator < -0.1) {
          //让你的数字-0.1
          crownAccumulator = 0.0;
      }
    }

    PS:通过改变参考值 0.1 来调整表冠的灵敏度。

​ 打包:

​ 对于iOS,bitcode是可选的;对于watchOS,bitcode是必须的;因此,Archive 的时候要把 WatchExtension 的 bitcode 设置为 YES; 版本号和构建号要和 app 保持一致。

=======完结

你可能感兴趣的:(iOS)