Reordering a NSFetchedResultsController

I’m working with a NSFetchedResultsController backed UITableView.  Up until now I’ve just been ordering the items by the time they were added.

I’m moving on to Delete and Edit.  Editing the content itself isn’t needed, once an item is added to this list, it can’t be changed. Deleting is straightforward in

(void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath
*)newIndexPath {

With reordering, I have a conundrum.  I have an approach for the actual re-order that will have the View and the data playing nice. But, now I need to persist the order.  I don’t have a property in Core Data for this (yet).  I’m trying to figure out what to set this property to. The brute force approach is to use the indexPath of the cell.  I have concerns with this.  Adding an item automatically puts it at the top of the list. This would give it an index of 0 (I only have 1 section).  But, then I would have to update the order property of every other item in the list. The same goes for moving: each item and every item in between (I can luckily skip the outer bounds) will have to be updated. I don’t like this. There has to be a better way, I just need to figure it out.

Some kind of indexPath+hash? indexPath+integer?

Eventually I’ll reach a list size where I run into performance problems with the brute force approach. The cop out would be to limit the size of the list. I don’t want to do that either.  I don’t know if I’ll go to the extreme of Brent Simmons’ data set of 30,000 items from Daring Fireball’s archive, but I want to shoot for that.

I’ll update this post with my progress

Update 2014-05-31 01:02 PM

This is a great starting point for dealing with reordering. I was on the right track with only dealing with the differences between items that get moved.  Dragging an item way down on a list may(?) not happen much, it’s annoying to do. I think I’m going with that assumption for now.  I still don’t feel great about the number of updates for an insert…

Batch fetch/update/save is my starting point

Update 2014-06-06 04:04 PM

It looks like iOS 8 might have better bulk update support. Downloading this session now

@inessential @brentsimmons You might like the Core Data video. new NSBatchUpdateRequest api with a “mark all as read” example as the demo

>

-- solsberg (@solsberg) June 6, 2014

AVSpeechSynthesizer's queue doesn't work

Sort of. It acts as a queue, but subsequent items have problems. Taken from the documentation of - (void)speakUtterance:(AVSpeechUtterance *)_utterance_

Calling this method adds the utterance to a queue; utterances are spoken in the order in which they are added to the queue. If the synthesizer is not currently speaking, the utterance is spoken immediately.

This is true. You can queue up as many AVSpeechUtterance objects as you want, and they will be spoken, in order.  The problem is if you try to act on the Synthesizer after the first Utterance has been spoken. Specifically, I'm having an issue with pausing. But, stopping has an issue as well. If I try to pause during the first utterance using pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate, I get the expected behavior. The speech stops and I can start it again with continueSpeaking. (Even on the first Utterance, AVSpeechBoundaryWord, is giving me issues so I'm putting that aside for now). However, if I try to pause on any of the subsequent Utterances, nothing happens.  The pausedSpeaking call is ignored. (The synthesizer isn't nil, I checked). So, why am I queuing up a lot of text? I plan on having AVSpeechSynthesizer speak a lot of text. This text is stored in CoreData.  Let's say my main object is called Read. Instead of storing all of the text for that object in Read.text I've split out the text into slices, stored in a separate Text entity. A Read object can have many of these.  This gives me control over how much text I load into memory at once.  My original plan was to load each slice into an Utterance.  When the speaking was done for that Utternace, queue up the next one (it should start immediatly or close to it). Fragments of this below:

    self.speechSynthesizer = [AVSpeechSynthesizer new];
    self.speechSynthesizer.delegate = self;

- (BOOL)addWordsToQueue:(NSString *)words
{
    if ( !words ) {
        return NO;
    }

    AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:words];
    utterance.rate = AVSpeechUtteranceMinimumSpeechRate;
    [self.speechSynthesizer speakUtterance:utterance];

    return YES;
}

//used in view
- (void)play
{
    [self.speechSynthesizer continueSpeaking];
}

- (void)pause
{
    [self.speechSynthesizer pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
}

- (BOOL)isPaused
{
    return self.speechSynthesizer.isPaused;
}

#pragma mark - AVSpeechSynthesizerDelegate

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance
{
    NSString *words = [read getAndIncrementCurrentWordsAsString];

    if ( words != nil ) {
        [self addWordsToQueue:words];
    }
}

Again, the first Utterance works fine, the rest don't. I've also experimented with queueing multiple slices at once, as well as checking if the synthesizer I get in didFinishSpeechUtterance is the correct instance.

I'm not the first person to run into this issue. There are also a number of radars. I've added one as well.

There is a workaround, but it's dirty and shouldn't be needed. In didFinishSpeechUtterance I recreate my AVSpeechSynthesizer so I'm always dealing with the first item in the queue. I really don't like it.  This is still broken in 7.1 beta 5. Hopefully it gets fixed. I'd like to implement this properly.

- (void) resetSynth
{
    self.speechSynthesizer = [AVSpeechSynthesizer new];
    self.speechSynthesizer.delegate = self;
}

#pragma mark - AVSpeechSynthesizerDelegate

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance
{
    [self resetSynth];
    NSString *words = [read getAndIncrementCurrentWordsAsString];

    if ( words != nil ) {
        [self addWordsToQueue:words];
    }
}

This unfortunately still happens in the 7.1 beta