Search code examples
iphoneobjective-cios6uislider

Custom UISlider like the Music app


I've built a custom slider to show the progress of a music track playing and to allow scrubbing within the track.

Both function fine, but there is a slight lag (and jumpy movement) once dragging has stopped and the slider is repositioned - The Apple Music app slider is seamless.

_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;
   }
}

The custom Slider

@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

Solution

  • After alot of logging - I found that the UISlider component had a slight lag updating itself - between 1/10 and 3/10s of a second - which caused the jump.

    I also changed the timer interval to 0.5 seconds and I also helped the update with adding a savedValue variable inside ScrubberSlider and a BOOL hasFinishedMoving, so the beginTrackingWithTouch endTrackingWithTouch methods inside ScrubberSlider looks like this:

    -(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;
    }
    

    and changed the updatePosition to include these vars and validations:

    -(void) updatePosition
    {
       if (!_scrubberSlider.tracking) // this is a UISlider BOOL
       {
           if (_scrubberSlider.hasFinishedMoving)
           {
               _scrubberSlider.value = _scrubberSlider.savedValue;
               _scrubberSlider.hasFinishedMoving = NO;
           }
           else
           {
               CGFloat percent = _theMusicPlayer.currentPlaybackTime / [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue];
    
               _scrubberSlider.value = percent;
           }
       }
    }