在CAShapeLayer笔划中检测触摸

时间:2015-01-22 19:12:29

标签: ios graphics core-animation

我创建了一个播放单个音频的简单音频播放器。视图显示CAShapeLayer循环进度,并使用CATextLayer显示当前时间。下图显示了视图:

enter image description here

到目前为止一切正常,我可以玩,暂停,CAShapeLayer显示进度。现在,我想这样做,当我触摸CAShapeLayer路径的笔划(轨道)部分时,我想要找到那个时候的玩家。我尝试了几种方法,但我无法检测到中风的所有部分的触摸。似乎我所做的计算并不合适。如果有任何身体可以帮助我,我将非常高兴。

这是我的完整代码,

@interface ViewController ()

@property (nonatomic, weak) CAShapeLayer *progressLayer;
@property (nonatomic, weak) CAShapeLayer *trackLayer;
@property (nonatomic, weak) CATextLayer *textLayer;

@property (nonatomic, strong) AVAudioPlayer *audioPlayer;

@property (nonatomic, weak) NSTimer *audioPlayerTimer;

@end


@implementation ViewController


- (void)viewDidLoad
{
    [super viewDidLoad];

    [self prepareLayers];
    [self prepareAudioPlayer];
    [self prepareGestureRecognizers];
}

- (void)prepareLayers
{
    CGFloat lineWidth = 40;
    CGRect shapeRect =  CGRectMake(0,
                                   0,
                                   CGRectGetWidth(self.view.bounds),
                                   CGRectGetWidth(self.view.bounds));

    CGRect actualRect = CGRectInset(shapeRect, lineWidth / 2.0, lineWidth / 2.0);
    CGPoint center = CGPointMake(CGRectGetMidX(actualRect), CGRectGetMidY(actualRect));
    CGFloat radius = CGRectGetWidth(actualRect) / 2.0;

    UIBezierPath *track = [UIBezierPath bezierPathWithArcCenter:center
                                                         radius:radius
                                                     startAngle:0 endAngle:2 * M_PI
                                                      clockwise:true];

    UIBezierPath *progressLayerPath = [UIBezierPath bezierPathWithArcCenter:center
                                                                     radius:radius
                                                                 startAngle:-M_PI_2
                                                                   endAngle:2 * M_PI - M_PI_2
                                                                  clockwise:true];
    progressLayerPath.lineWidth = lineWidth;


    CAShapeLayer *trackLayer = [CAShapeLayer layer];
    trackLayer.contentsScale = [UIScreen mainScreen].scale;
    trackLayer.shouldRasterize = YES;
    trackLayer.rasterizationScale = [UIScreen mainScreen].scale;
    trackLayer.bounds = actualRect;
    trackLayer.strokeColor = [UIColor lightGrayColor].CGColor;
    trackLayer.fillColor = [UIColor whiteColor].CGColor;
    trackLayer.position = self.view.center;
    trackLayer.lineWidth = lineWidth;
    trackLayer.path = track.CGPath;
    self.trackLayer = trackLayer;

    CAShapeLayer *progressLayer = [CAShapeLayer layer];
    progressLayer.contentsScale = [UIScreen mainScreen].scale;
    progressLayer.shouldRasterize = YES;
    progressLayer.rasterizationScale = [UIScreen mainScreen].scale;
    progressLayer.masksToBounds = NO;
    progressLayer.strokeEnd = 0;
    progressLayer.bounds = actualRect;
    progressLayer.fillColor = nil;
    progressLayer.path = progressLayerPath.CGPath;
    progressLayer.position = self.view.center;
    progressLayer.lineWidth = lineWidth;
    progressLayer.lineJoin = kCALineCapRound;
    progressLayer.lineCap = kCALineCapRound;
    progressLayer.strokeColor = [UIColor redColor].CGColor;

    CATextLayer *textLayer = [CATextLayer layer];
    textLayer.contentsScale = [UIScreen mainScreen].scale;
    textLayer.shouldRasterize = YES;
    textLayer.rasterizationScale = [UIScreen mainScreen].scale;
    textLayer.font = (__bridge CTFontRef)[UIFont systemFontOfSize:30.0];
    textLayer.position = self.view.center;
    textLayer.bounds = CGRectMake(0, 0, 300, 100);

    [self.view.layer addSublayer:trackLayer];
    [self.view.layer addSublayer:progressLayer];
    [self.view.layer addSublayer:textLayer];

    self.trackLayer = trackLayer;
    self.progressLayer = progressLayer;
    self.textLayer = textLayer;

    [self displayText:@"Play"];
}

- (void)prepareAudioPlayer
{
    NSError *error;
    self.audioPlayer = [[AVAudioPlayer alloc]
                        initWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"Song" withExtension:@"mp3"]
                        error:&error];
    self.audioPlayer.volume = 0.2;

    if (!self.audioPlayer) {
        NSLog(@"Error occurred, could not create audio player");
        return;
    }

    [self.audioPlayer prepareToPlay];
}

- (void)prepareGestureRecognizers
{
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                          action:@selector(playerViewTapped:)];
    [self.view addGestureRecognizer:tap];
}

- (void)playerViewTapped:(UITapGestureRecognizer *)tap
{
    CGPoint tappedPoint = [tap locationInView:self.view];

    if ([self.view.layer hitTest:tappedPoint] == self.progressLayer) {
        CGPoint locationInProgressLayer = [self.view.layer convertPoint:tappedPoint toLayer:self.progressLayer];

        NSLog(@"Progress view tapped %@",  NSStringFromCGPoint(locationInProgressLayer));
        // this is called sometimes but not always when I tap the stroke 


    } else if ([self.view.layer hitTest:tappedPoint] == self.textLayer) {
        if ([self.audioPlayer isPlaying]) {
            [self.audioPlayerTimer invalidate];
            [self displayText:@"Play"];
            [self.audioPlayer pause];
        } else {
            [self.audioPlayer play];
            self.audioPlayerTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                                     target:self
                                                                   selector:@selector(increaseProgress:)
                                                                   userInfo:nil
                                                                    repeats:YES];
        }
    }
}

- (void)increaseProgress:(NSTimer *)timer
{
    NSTimeInterval currentTime = self.audioPlayer.currentTime;
    NSTimeInterval totalDuration = self.audioPlayer.duration;
    float progress = currentTime / totalDuration;
    self.progressLayer.strokeEnd = progress;

    int minute = ((int)currentTime) / 60;
    int second = (int)currentTime % 60;

    NSString *progressString = [NSString stringWithFormat:@"%02d : %02d ", minute,second];
    [self displayText:progressString];
}


- (void)displayText:(NSString *)text
{
    UIColor *redColor = [UIColor redColor];
    UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:70];

    NSDictionary *attribtues = @{
                                 NSForegroundColorAttributeName: redColor,
                                 NSFontAttributeName: font,
                                 };

    NSAttributedString *progressAttrString = [[NSAttributedString alloc] initWithString:text
                                                                             attributes:attribtues];
    self.textLayer.alignmentMode = kCAAlignmentCenter;
    self.textLayer.string = progressAttrString;
}

- (void)viewWillTransitionToSize:(CGSize)size
       withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    void(^animationBlock)(id<UIViewControllerTransitionCoordinatorContext>) =

    ^(id<UIViewControllerTransitionCoordinatorContext> context) {

        CGRect rect = (CGRect){.origin = CGPointZero, .size = size};
        CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
        self.progressLayer.position = center;
        self.textLayer.position = center;
        self.trackLayer.position = center;

    };
    [coordinator animateAlongsideTransitionInView:self.view
                                        animation:animationBlock completion:nil];

}

@end

1 个答案:

答案 0 :(得分:0)

据我所知,CALAyer hitTest方法执行非常原始的边界检查。如果该点位于图层边界内,则返回YES,否则返回NO。

CAShapeLayer没有尝试判断该点是否与形状图层的路径相交。

UIBezierPath确实有一个hitTest方法,但我非常确定能够检测到封闭路径内部的触摸,并且无法使用您正在使用的粗线弧线。

如果你想使用路径进行测试,我认为你将不得不推出自己的hitTest逻辑来构建一个UIBezierPath,它是一个定义你正在测试的形状的闭合路径(你的粗弧线)。对于正在进行中的动画执行此操作可能会太慢。

您将面临的另一个问题:您正在询问具有飞行动画的图层。即使您只是简单地为不起作用的图层的中心属性设置动画。相反,你必须询问图层的presentationLayer,它是一个近似于飞行中动画设置的图层。

我认为你最好的解决方案是获取弧的起始位置和结束位置,将开始到结束值转换为起始和结束角度,使用trig将触摸位置转换为角度和半径,然后查看是否触摸位置在开始到结束触摸的范围内并且在指示线的内半径和外半径内。这只需要一些基本的触发。

编辑:

提示:要用于将x和y位置转换为角度的trig函数是反正切。反正切取X和Y值并返回一个角度。但是,要正确使用反正切,您需要实现一堆逻辑来确定您的点所在的圆的象限。在C中,您想要的数学库函数是atan2()atan2()函数会为您处理所有事情。您将转换您的点,因此0,0位于中心,atan2()将为您提供沿圆圈的角度。要计算距圆心的距离,请使用距离公式a² + b² = c²