Search code examples
iosuitableviewuislider

Continuously update UISlider within UITableViewCell


I'm looking to use a UISlider as an "Audio VU Meter" type indicator, where an audio track's level continuously updates the value of the UISlider. Each track in my application is currently situated as a UITableViewCell, with the VU Meter being one subview within the cell's content view. As many as 12 tracks are displayed at a time.

The updates to the VU meter are coming via a TCP/IP port, so once I receive the new VU level and set the fader value (mySlider.value = newValue). However, the only way I can get the slider to update immediately is by calling performSelectorOnMainThread:@selector(reloadData) withObject:myTableView waitUntilDone:NO. Seems like I shouldn't have to reload the entire table's data.

Code:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TrackTableCell *cell = (TrackTableCell *)[tableView dequeueReusableCellWithIdentifier:@"TrackTableCell"];

    if(cell == nil)
    {
        [[NSBundle mainBundle] loadNibNamed:@"TrackTableCell" owner:self options:nil];
        cell = [self trackTableCell];

        /**************************/
        /********* FADER  *********/
        /**************************/
        VolumeSlider *fader = [[VolumeSlider alloc] initWithFrame:CGRectMake(0, 0, 265, 30)];
        [fader setRotatedThumbImage:[UIImage imageNamed:@"FaderCap.png"]];
        [fader setRotatedMinTrackImage:[UIImage imageNamed:@"VolumeSlider.png"]];
        [fader setRotatedMaxTrackImage:[UIImage imageNamed:@"VolumeSlider.png"]];

        [fader addTarget:self action:@selector(volumeFaderSliderAction:) forControlEvents:UIControlEventValueChanged];

        fader.value = 0;

        // rotate the slider
        fader.transform = CGAffineTransformMakeRotation(-M_PI_2);
        [cell.contentView addSubview:fader];

        fader.bounds = CGRectMake(0, 0, 265, 30);
        fader.center = CGPointMake(70, 515);

        // property of the cell
        cell.volumeSlider = fader;
    }


    // set the slider to 0
    cell.volumeSlider.value = [[self.tracks objectAtIndex:indexPath.row] volume];

    return cell;
}

The following method is called via a notification that the data model ([[tracks objectAtIndex:trackNumber] volume]) has been updated.

- (void) updateVolumeFader:(NSNotification *)note
{
    NSDictionary *extraInfo = [note userInfo];
    int trackNumber = [[extraInfo objectForKey:@"trackNumber"] intValue];
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:trackNumber inSection:0];

    TrackTableCell *cell = (TrackTableCell *)[self.tracksTableView cellForRowAtIndexPath:indexPath];

    cell.volumeSlider.value = [[tracks objectAtIndex:trackNumber] volume]; 
}

In reviewing the code, my guess is it's in the way I'm obtaining the cell pointer. I'm guessing calling cellForRowAtIndexPath is not the best way. Further, I am just now realizing that calling cellForRowAtIndexPath in itself should update the fader to the new value, in the tracks array.

As has been suggested perhaps it's best to keep a cached array of all of my tracks' sliders (or all of my cells, for that matter). While this is certainly not the most efficient code, I still don't understand why it doesn't work?


Solution

  • If your notifications are not coming through on the main thread, your code to update the slider won't work. The code in your notification method is otherwise fine; simply place it inside a block and execute on the main thread, or register on the main thread (operation queue) when you register for the notification.