自定义UISlider,如音乐应用程序

时间:2013-02-28 07:40:28

标签: iphone objective-c ios6 uislider

我已经制作了一个自定义滑块来显示音乐曲目播放的进度并允许在曲目中进行清理。

两种功能都很好,但是一旦拖动停止并且滑块重新定位,就会出现轻微滞后(和跳动) - Apple Music应用程序滑块是无缝的。

_scrubberSlider = [[ScrubberSlider alloc] initWithFrame:CGRectMake(0, 0, 300, 30)];
_scrubberSlider.continuous = YES;
_scrubberSlider.maximumValue = 1.0f;
_scrubberSlider.minimumValue = 0.0f;
 [_scrubberSlider addTarget:self action:@selector(handleSliderMove:) forControlEvents:UIControlEventValueChanged];

- (void) handleSliderMove:(UISlider*)sender
{
    CGFloat currentPlayback = sender.value * [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue];
    _theMusicPlayer.currentPlaybackTime = currentPlayback;
}

-(void) handleTrackTime
{
    if (!trackTimer)
    {
    NSNumber *playBackTime = [NSNumber numberWithFloat: _musicPlayer.currentPlaybackTime];
    trackTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(timeIntervalFinished:) userInfo:@{@"playbackTime" : playBackTime} repeats:YES];
    }
}

-(void) timeIntervalFinished:(NSTimer*)sender
{
     [self playbackTimeUpdated:_musicPlayer.currentPlaybackTime];
}

-(void) playbackTimeUpdated:(CGFloat)playbackTime 
{
    // Update time label
    [self updatePosition];
}

-(void) updatePosition
{
   if (!_scrubberSlider.isScrubbing)
   {
   CGFloat percent = _theMusicPlayer.currentPlaybackTime / [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue];
   _scrubberSlider.value = percent;
   }
}

自定义滑块

@interface ScrubberSlider : UISlider

@property(nonatomic) BOOL isScrubbing;

@end

@implementation ScrubberSlider

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
    }
    return self;
}

-(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    [super beginTrackingWithTouch:touch withEvent:event];

    _isScrubbing = YES;

    return YES;
}

- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    [super endTrackingWithTouch:touch withEvent:event];

    _isScrubbing = NO;
}

@end

4 个答案:

答案 0 :(得分:3)

也许你会以错误的方式解决这个问题。而不是试图完全同步到轨道的时间(这导致一些非常讨厌的竞争条件,并且正是为什么你的滑块在“抛出”而不是拖动,保持,然后释放时不能正确更新),尝试使用你有的计时器作为“勾号”。你可以降低火焰速度,比如每半秒减少一次,然后使用与现在基本相同的逻辑,但不是试图在-updatePosition中获得精确的小数值,而是“勾选”滑块向前。

-(void) updatePosition
{
   if (!_scrubberSlider.isScrubbing)
   {
       _scrubberSlider.value += .5f;
   }
}

这并不完美,但是再一次,它比使用NSTimer将自己与音乐曲目的时间同步更加高效和无缝(对于精确的动作来说,这可能是令人厌恶的不准确)。这还要求滑块的最大值等于轨道的最大值,因此开始播放的示例方法包括:

- (IBAction)startPlayback:(id)sender {
    //... Handle however you wish to play the track
    _scrubberSlider.maximumValue = [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue];
    [self handleTrackTime];
}

答案 1 :(得分:2)

经过大量的记录后 - 我发现UISlider组件有一个轻微的滞后更新 - 在1/10到3/10秒之间 - 导致跳跃。

我还将计时器间隔更改为0.5秒,我还通过在savedValue和BOOL ScrubberSlider中添加hasFinishedMoving变量来帮助更新,因此beginTrackingWithTouch { ScrubberSlider中的{1}}方法如下所示:

endTrackingWithTouch

并更改了-(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { [super beginTrackingWithTouch:touch withEvent:event]; _hasFinishedMoving = NO; return YES; } - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { [super endTrackingWithTouch:touch withEvent:event]; _savedValue = self.value; _hasFinishedMoving = YES; } 以包含这些变量和验证:

updatePosition

答案 2 :(得分:2)

AVFoundation framework有一个名为addPeriodicTimeObserverForInterval:queue:usingBlock:的方法,与AVPlayer配合得很好。有AVPlayer demo非常光滑的擦洗。我无法为AVAudioPlayer找到类似的方法,但也许AVPlayer demo会给你一些如何实现平滑擦洗的想法。

部分问题是找到合适的计时器间隔,我确信AVPlayer demo可以帮助您。如果你必须使用计时器,最好使用CADisplayLink(而不是NSTimer),因为在处理更新屏幕上某些内容时更准确。

看起来你正在使用MPMusicPlayerController。如果您正在制作自己的控件,那么使用AVAudioPlayerAVPlayer可能会更好。

答案 3 :(得分:1)

要在滑块拖动时连续更新计时器(没有跳跃运动),我们应该最小化scheduledTimerWithTimeInterval的值。所以,如果您的计时器值是这样的: -

timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateElapsedTime) userInfo:nil repeats:YES];

通过最小化scheduledTimerWithTimeInterval值

来尝试一些小修改
timer=[NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(updateElapsedTime) userInfo:nil repeats:YES];



-(void)updateElapsedTime {

     //do your stuffs here
}