阻止总是返回 nil

Block always returning nil

我正在尝试为 WatchKit 应用录制 UIView 动画。首先,我在没有记录 return 零帧的块的情况下实现了该功能。这是由于 [recorder stop] 在动画完成之前被调用(我认为)。所以,我添加了一个完成块。现在,它从来没有 self.completionYES。我希望完成块在动画完成时通知我。我在这里错过了什么?

ViewController.m

-(void)runAnimation{

ALBatteryView *batteryView = [[ALBatteryView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)];
  [self.batteryIcon addSubview:batteryView];
  recorder.view = _batteryIcon;
  [recorder start];
  [batteryView setBatteryLevelWithAnimation:YES forValue:[UIDevice currentDevice].batteryLevelInPercentage inPercent:YES];
  CGFloat batteryPer = [UBattery batteryLevel];
  batteryPercentage.text = [NSString stringWithFormat:@"%.0f%%", batteryPer];
  battery = [NSString stringWithFormat:@"%.0f%%", batteryPer];
  [batteryView batteryAnaminationWithCompletion:^(BOOL finished){
    if (finished){
      [recorder stop];
    }
  }];
}

AlBatteryView.h

@interface ALBatteryView : UIView {
UIView *batteryFill;
}
@property (nonatomic, strong) void(^completion)(BOOL finished);

- (void)setBatteryLevelWithAnimation:(BOOL)isAnimated forValue:(CGFloat)batteryLevel inPercent:(BOOL)inPercent;
- (void)reload;
- (void)batteryAnaminationWithCompletion:(void (^)(BOOL finished))completion;
@end

ALBatteryView.m

- (void)setBatteryLevelWithAnimation:(BOOL)isAnimated forValue:(CGFloat)batteryLevel inPercent:(BOOL)inPercent {

  // Declare the newWidth and save the correct battery level
  // based on inPercent value
  CGFloat newWidth;
  CGFloat newBatteryLevel = (inPercent) ? batteryLevel : batteryLevel * 100;
  // Set the new width
  newWidth = kOnePercent * newBatteryLevel;
  // If animated proceed with the animation
  // else assign the value without animates

  if (isAnimated)
      [UIView animateWithDuration:2.0 animations:^{
          /* This direct assignment is possible
           * using the UIView+ALQuickFrame category
           * http://github.com/andrealufino/ALQuickFrame */
          batteryFill.width = newWidth;
          // Set the color based on battery level
          batteryFill.backgroundColor = [self setColorBasedOnBatteryLevel:newBatteryLevel];
          if (self.completion) {
              self.completion(YES);
          }
      }];

  else
      batteryFill.width = newWidth;

}
-(void)batteryAnaminationWithCompletion:(void (^)(BOOL finished))completion{
  self.completion = completion;
}

您应该在开始动画之前设置完成块(更改代码顺序)。

那么,你的区块应该被声明为副本。

@property (nonatomic, copy) void(^completion)(BOOL finished);

然后,完全删除(错误命名的)batteryAnaminationWithCompletion 方法,只使用 属性:

batteryView.completion = ...

您需要在调用动画 (setBatteryLevelWithAnimation) 之前设置块 属性。否则,当您在设置之前尝试访问它时,它将是 nil

你也应该直接设置你的块属性,这样会更清楚,因为那是你的-batteryAnaminationWithCompletion方法所做的(顺便说一下,它应该拼写为"Animation") 来自:

  [batteryView batteryAnaminationWithCompletion:^(BOOL finished){
    if (finished){
      [recorder stop];
    }
  }];

收件人:

  [batteryView setCompletion:^(BOOL finished){
    if (finished){
      [recorder stop];
    }
  }];

如果我没看错的话,

当你调用-(void)runAnimation 它在这里调用动画块,动画块将引用目前为 nil 的完成

  [batteryView setBatteryLevelWithAnimation:YES forValue:[UIDevice currentDevice].batteryLevelInPercentage inPercent:YES];

如果您认为 animationWithDuration 调用块晚于 2 秒.. 这是错误的.. self.completion 目前为零并且动画的持续时间为 2 秒但 self.completion 不可设置动画并且它提前调用..如果你想等待这个动画完成,你需要某种锁。

[UIView animateWithDuration:2.0 animations:^{
        /* This direct assignment is possible
         * using the UIView+ALQuickFrame category
         * http://github.com/andrealufino/ALQuickFrame */
        batteryFill.width = newWidth;
        // Set the color based on battery level
        batteryFill.backgroundColor = [self setColorBasedOnBatteryLevel:newBatteryLevel];
        if (self.completion) {
                    self.completion(YES);
        }
    }];

只需在块内部的 animateWithDuration 和 NSLog 之前打印出 NSLog 以查看何时引用 (self.completion)..

如果只有 objective 是动画,则界面过于复杂。根本不需要 属性 块。考虑以下...

// ALBatteryView.h
@interface ALBatteryView : UIView

// up to you, but I'd clean up the animating interface by making a property of this class determine
// how it handles levels in all cases as either absolute or %
@property(assign, nonatomic) BOOL inPercent;

// use naming style like the sdk...
- (void)setBatteryLevel:(CGFloat)batteryLevel animated:(BOOL)animated completion:(void (^)(BOOL))completion;

@end

实施中...

// ALBatteryView.m
@interface ALBatteryView ()

// hide the internal view property here.  make it weak, and assign it after [self subView:...
@property(weak,nonatomic) UIView *batteryFill;

@end

仅此一种动画方法,在UIView动画中使用一些技巧,您可以完成所需的一切...

- (void)setBatteryLevel:(CGFloat)batteryLevel animated:(BOOL)animated completion:(void (^)(BOOL))completion {

    // one trick is that UIView animation with zero duration just
    // changes the animatable properties and calls its completion block
    NSTimeInterval duration = (animated)? 2.0 : 0.0;

    CGFloat newBatteryLevel = (self.inPercent)? batteryLevel : batteryLevel * 100;
    CGFloat newWidth = kOnePercent * newBatteryLevel;
    // don't name that color-returning method "set" color.  The "set" part happens here...
    UIColor *newColor = [self colorBasedOnBatteryLevel:newBatteryLevel];

    // now, the animation in just one shot, passing the block parameter
    // which has the same signature as the UIView animation block param
    [UIView animateWithDuration:duration animations:^{
        self.batteryFill.width = newWidth;
        self.batteryFill.backgroundColor = newColor;
    } completion:completion]; 
}