2

I'm new to IOS / Objective C and am trying to figure out the best way to create a collection, iterate over it, and time events.

I have a series of lines of a song and I want an individual line of a song to appear on the screen as the music is playing at the right point in the song. So I've started by doing the following: I put the individual lines into a Dictionary and the millisecond value of when the line should appear.

   NSDictionary *bualadhBos = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithInt:2875], @"Muid uilig ag bualadh bos, ",
                             [NSNumber numberWithInt:3407], @"Muid uilig ag tógáil cos, ",
                             [NSNumber numberWithInt:3889], @"Muid ag déanamh fead ghlaice, ",
                             [NSNumber numberWithInt:4401], @"Muid uilig ag geaibíneacht. ",
                             [NSNumber numberWithInt:4900], @"Buail do ghlúine 1, 2, 3, ",
                             [NSNumber numberWithInt:5383], @"Buail do bholg mór buí, ",
                             [NSNumber numberWithInt:5910], @"Léim suas, ansin suigh síos, ",
                             [NSNumber numberWithInt:6435], @"Seasaigh suas go hard arís. ",
                             [NSNumber numberWithInt:6942], @"Sín amach do dhá lamh, ",
                             [NSNumber numberWithInt:7430], @"Anois lig ort go bhfuil tú ' snámh. ",
                             [NSNumber numberWithInt:7934], @"Amharc ar dheis, ansin ar chlé, ",
                             [NSNumber numberWithInt:8436], @"Tóg do shúile go dtí an spéir. ",
                             [NSNumber numberWithInt:8940], @"Tiontaigh thart is thart arís, ",
                             [NSNumber numberWithInt:9436], @"Cuir síos do dhá lámh le do thaobh, ",
                             [NSNumber numberWithInt:9942], @"Lámha suas is lúb do ghlúin, ",
                             [NSNumber numberWithInt:10456], @"Suigí síos anois go ciúin. ", nil
                             ];

then I wanted to iterate over the Dictionary, create a Timer that would call a method that is responsible for changing the text in the textLayer

for (id key in bualadhBos ) {
    NSTimer *timer;
    timer = [[NSTimer scheduledTimerWithTimeInterval:bualadhBos[key] target:self selector:@selector(changeText) userInfo:nil repeats:NO]];

}

-(void)changeText {
    // change the text of the textLayer
    textLayer.string = @"Some New Text";
}

But as I started to debug this and inspect how it might work, I noticed in the debugger that the order which the items appear in the Dictionary have all been shuffled around. I'm also concerned (I don't know enough about this) that I'm creating multiple timers and that there might be a more efficient approach to solving this problem.

Any direction would be greatly appreciated.

Linda Keating
  • 2,215
  • 7
  • 31
  • 63
  • See http://stackoverflow.com/questions/376090/nsdictionary-with-ordered-keys – Paul Cezanne Sep 23 '14 at 13:50
  • A dictionary is unordered. Put you items in an array (you'll probably need to define a "container" object), sort by start time, then just iterate through the array. – Hot Licks Sep 23 '14 at 15:19

2 Answers2

2

I noticed in the debugger that the order which the items appear in the Dictionary have all been shuffled around.

Dictionaries are not ordered collections. Don't rely on the order of the elements being the same as the order that you added the elements. Don't rely on the order of the elements at all, in any respect.

If you want to access the elements of a dictionary in a certain order, create an array containing the keys in the order you prefer. Then iterate over the array and use each key to access the corresponding value in the array. In your case, you could get the array of keys, sort it into ascending order, and use that. Or, you could create an array of dictionaries with keys like "time" and "lyric", one for each line.

All that said, given your current code it's hard to see why you care need to access the elements in a particular order. If you're creating all the timers at once, as you're currently doing, things should work fine until the number of timers becomes a problem. I'm not sure where that point is, but I'm sure it's much greater than 20.

I'm creating multiple timers and that there might be a more efficient approach to solving this problem

Sure. You're creating one timer for each line all at once, so that many timers run concurrently. You could instead just create the first timer and have it's action create another timer when it fires, and so on for each line. To avoid timer drift, use the system time to calculate the appropriate delay to the time when the next line should be displayed.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • 1
    Thanks - You've helped me think a lot clearer about the problem. And yes that's a good point about the order not mattering if the timers are looking after when it happens anyway, but something I didn't mention, that I will want to do is to be able to pause and restart. That'd be a nightmare if there was more than one timer. So I think you're suggestion of using an array is the right way to go. Thanks for the help. – Linda Keating Sep 23 '14 at 14:13
2

Dictionaries are sorted by definition in the way that is most suitable for hash algorithm so you should never rely on their order.

In your case it would be better to build a binary tree and have a single NSTimer that fires once a second, performs binary tree search and returns the closest string for provided time offset.

If you use AVFoundation or AVPlayer for playback. Then to synchronize subtitles with media playback, you could use something like addPeriodicTimeObserverForInterval to fire timer once a second and perform search in your binary tree and update UI.

In pseudo code:

[player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:NULL usingBlock:^(CMTime time) {
    // get playback time
    NSTimeInterval seconds = CMTimeGetSeconds(time);

    // search b-tree
    NSString* subtitle = MyBtreeFindSubtitleForTimeInterval(seconds);

    // update UI
    myTextLabel.text = subtitle;
}];
pronebird
  • 12,068
  • 5
  • 54
  • 82