showing progress on imageview on customcell on uitableview - ios

I followed the codes here https://stackoverflow.com/a/7212943/711837 to get me started to show an indicator my app trying to download images from a particular website. The scenario is:
i have a tableview with many custom cells, the custom cells has 2 labels and a imageview.
i have a NSURL to download the contents that will fill up the labels and then a separate class that will download the images to be filled into the UIImageView. The code for the spinner and the downloading of images are:
resultArray = [[NSArray alloc] initWithArray:response];
[self downloadImageFromInternet:#"http://static.colourlovers.com/images/shapes/0/7/7090/50x50.png"];
//spinner for the download
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.frame = CGRectMake(0, 0, 24, 24);
custcell.accessoryView = spinner;
[spinner startAnimating];
///[spinner release];
[self.thetableView reloadData];
and then i call the [spinner stopAnimating] at the finish downloading method of the class but somehow, the spinners just don't animate, or appear for the matter! am i missing something? or is there somewhere i can refer to? my aim is to show the UIIndicatorView at the place of the UIImageView then after loading, the imageview takes over the same position and this is on every cell.
UPDATED added the methods
-(void) downloadImageFromInternet:(NSString*)urlToImage{
// Create a instance of InternetImage
asynchImage = [[DownloadThumb alloc] initWithUrl:urlToImage];
// Start downloading the image with self as delegate receiver
[asynchImage downloadImage:self];
}
-(void) internetImageReady:(DownloadThumb*)downloadedImage{
// The image has been downloaded. Put the image into the UIImageView
[arrayImg addObject:downloadedImage.Image];
[spinner stopAnimating];
[self.thetableView reloadData];
}
-(void)downloadImage:(id)delegate{
m_Delegate = delegate;
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:ImageUrl]
cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:60.0];
imageConnection = [[NSURLConnection alloc] initWithRequest:imageRequest delegate:self];
if(imageConnection)
{
workInProgress = YES;
m_ImageRequestData = [[NSMutableData data] retain];
}
}

You should really initiate the spinner when you create the cell.
Then you should check in cellForRowAtIndexPath if it should be visible or not.
Finally, when you need to display it, you can use [tableView reloadData] to display or remove it.

Related

Multiple NSURLSessions Causing UITableView Problems

I'm running into a bit of a strange problem here. One of my NSURLSessions is in charge of getting information for restaurant information that I have stored (restaurant name, restaurant's logo URL, etc), and then the second NSURLSession is in charge of using the restaurant's logo URL to retrieve the specific image and set it for each UITableView's cell.
The problem, however, is that my UITableView does not load anything at all sometimes so the cells are empty, but at other times when I add an extra [_tableView reload] in the NSURLSessions' completion block in the fetchPosts method, it'll work, but then the cells will stop displaying anything again if I re-run it. Something is definitely wrong. Have a look at my code below:
#import "MainViewController.h"
#import "SWRevealViewController.h"
#import "RestaurantNameViewCell.h"
#import "RestaurantList.h"
#interface MainViewController ()
#end
#implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//List of restaurants needed to load home page
_restaurantInformationArray = [[NSMutableArray alloc] init];
self.tableView.dataSource = self;
self.tableView.delegate = self;
//setup for sidebar
SWRevealViewController *revealViewController = self.revealViewController;
if ( revealViewController )
{
[self.sidebarButton setTarget: self.revealViewController];
[self.sidebarButton setAction: #selector( revealToggle: )];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
//Get list of restaurants and their image URLs
[self fetchPosts];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_restaurantInformationArray count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RestaurantNameViewCell *cell = (RestaurantNameViewCell *)[_tableView dequeueReusableCellWithIdentifier:#"restaurantName" forIndexPath:indexPath];
RestaurantList *currentRestaurant = [_restaurantInformationArray objectAtIndex:indexPath.row];
cell.restaurantName.text = currentRestaurant.name;
cell.imageAddress = currentRestaurant.imageURL;
cell.restaurantClicked = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapDetected:)];
cell.restaurantClicked.numberOfTapsRequired = 1;
cell.restaurantLogo.userInteractionEnabled = YES;
[cell.restaurantLogo addGestureRecognizer:cell.restaurantClicked];
cell.restaurantLogo.tag = indexPath.row;
//Add restaurant logo image:
NSString *URL = [NSString stringWithFormat:#"http://private.com/images/%#.png",cell.imageAddress];
NSURL *url = [NSURL URLWithString:URL];
NSURLSessionDownloadTask *downloadLogo = [[NSURLSession sharedSession]downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
cell.restaurantLogo.image = downloadedImage;
}];
[downloadLogo resume];
return cell;
}
-(void)fetchPosts {
NSString *address = #"http://localhost/xampp/restaurants.php";
NSURL *url = [NSURL URLWithString:address];
NSURLSessionDataTask *downloadRestaurants = [[NSURLSession sharedSession]dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *someError;
NSArray *restaurantInfo = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&someError];
for(NSDictionary *dict in restaurantInfo) {
RestaurantList *newRestaurant = [[RestaurantList alloc]init];
newRestaurant.name = [dict valueForKey:#"name"];
newRestaurant.imageURL = [dict valueForKey:#"image"];
[_restaurantInformationArray addObject:newRestaurant];
//Refresh table view to make sure the cells have info AFTER the above stuff is done
[_tableView reloadData];
}
}];
[downloadRestaurants resume];
}
#end
It's probably a very stupid mistake that I'm making, but I'm not certain how I should correct this. I'm new to iOS development, so would greatly appreciate some guidance :)
Besides assuming that your network requests aren't erroring (you should at least log if there are network errors), there are threading issues.
Your NSURLSession callback probably runs on a background thread. This makes it unsafe to call UIKit (aka - [_tableView reloadData]). UIKit isn't thread safe. This means invoking any of UIKit's APIs from another thread creates non-deterministic behavior. You'll want to run that piece of code on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
[_tableView reloadData];
});
Likewise for fetching the images. It's slightly more complicated because of table view cell reuse which could cause the wrong image to display when scrolling. This is because the same cell instance is used for multiple values in your array as the user scrolls. When any of those callbacks trigger, it'll replace whatever image happens to be in that cell. The generally steps to reproduce this is as follows:
TableView requests 5 cells
MainViewController requests 5 images (one for each cell)
User scrolls down one cell
The first cell gets reused as the 6th cell.
MainViewController requests another image for the 6th cell.
The 6th image is retrieved, the callback is triggered, image of the first cell is set to image #6.
The 1st image is retrieved, the callback is triggered, image of the first cell is set to image #1 (incorrect).
You'll need to make sure the cell is displaying the correct cell before attempting to assign the image to it. If you rather not implement that logic for image fetching in cells, you could use SDWebImage instead. Using SDWebImage's [UIImageView sd_setImageWithURL:] is thread safe (it will set the image on the main thread).
Side notes:
You only need to reload data once all your changes are in _restaurantInformationArray, and not every time in the for loop.

How to load UITableview part by part

I have an UITableView its loading number ofsongs from the back end (actually those are song's urls). Those are getting in JSON format. Now my problem is when number of songs are increases the table getting stuck. It hard to scroll. So how I can avoid this?
I'm loading views top of a main view. In main view viewDidLoad
arrSongList=[[NSMutableArray alloc]init];
arrSongList=[ws GetSongs];`
Then there is a menu to select song option. When I click on that menu cell it loads the songs view. for loading song view
-(void)getAllSongsAndReloadTable :(id)sender//1
{
[viewDisplayArea addSubview:viewSongs];
[self flipView : viewDisplayArea: viewDisplayArea : UIViewAnimationTransitionFlipFromLeft];
[progressAlert dismissWithClickedButtonIndex:0 animated:YES];
}
ViewSongs view has the song table. It loads in this way
This is for numberOfRowsInSection
else if (tableView.tag==4)
{
return [arrSongList count];
}
This is for cellForRowAtIndexPath
else if (tableView.tag==4)//All Songs
{
cell4 =nil;
if (cell4 == nil) {
NSArray *nib =[[NSBundle mainBundle]loadNibNamed:#"SongCell" owner:self options:nil];
cell4=[nib objectAtIndex:0];
}
[cell4 setSelectionStyle:UITableViewCellSelectionStyleNone];
cell4.lblSong.text=[[arrSongList objectAtIndex:indexPath.row] valueForKey:#"SONGTITLE"];
cell4.lblArtist.text=[[arrSongList objectAtIndex:indexPath.row] valueForKey:#"ARTISTNAME"];
NSString *imgUrl=[[arrSongList objectAtIndex:indexPath.row]valueForKey:#"SONGIMG"];
NSData *data=[NSData dataWithContentsOfURL:[NSURL URLWithString: imgUrl]];
UIImage *img = [[UIImage alloc] initWithData:data];
cell4.imgSong.image=img;
NSString *imgUrlLarge=[[arrSongList objectAtIndex:indexPath.row]valueForKey:#"SONGIMGLARGE"];
NSData *dataLarge=[NSData dataWithContentsOfURL:[NSURL URLWithString: imgUrl]];
UIImage *imgLarge = [[UIImage alloc] initWithData:dataLarge];
cell4.imgSongLarge=[[UIImageView alloc]init];
cell4.imgSongLarge.image=imgLarge;
[cell4.btnTick addTarget:self action:#selector(tickButtonTapped:) forControlEvents:UIControlEventTouchUpInside] ;
[cell4.btnAddtoMyList addTarget:self action:#selector(topLeft:) forControlEvents:UIControlEventTouchUpInside];
return cell4;
}
You will be having a array of songs urls. And you would have given number of rows in table as arrayOfUrlSongs.count. In stead of that make
Make a global count
int count = 10;
if(arrayOfUrlSongs.count < count)
return arrayOfUrlSongs.count;
else
{
return count;
}
Keep a reload button in footer view of table. On click of that increment count by 10 and reload table.
I hope this helps you.
You should also consider loading the cells in batches. I'd recommend checking out the Sensible TableView framework. The framework will automatically fetch all the songs from your web service and divide them in batches if you wish. Should save you a ton of work.

Activity Indicator in a list

I am creating a real estate app. I have a screen which displays a listing of all entries with a thumbnail and a little text on the side. These I have loaded from the server when the app launched. Each entry can have up to 5 photos, which I do not pre-load for obvious reasons. My issue is this… when the user selects an entry, the app downloads the larger photos from the server. Depending on circumstances this can take a few seconds. Right now the app just hangs for those few seconds. I don't know of any practical way to use an activity indicator in a list. A header space just seems like wasted space to use only to display"Loading…". Anyone have any ideas on what I can do to let the user know that loading is in progress?
Clarification: Once an entry is selected from the list, I load up another Table View Controller which has the photos in its list of selections. I currently load the photos in the ViewDidLoad using
NSData *myPhoto = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
You can:
Use UIActivityIndicatorView to show a spinning activity indicator in the precise spot where the image will eventually be loaded.
In a separate queue download the image. While the below code uses GCD, it's actually much better to use NSOperationQueue because on a slow network, using GCD can consume all of the available worker threads, detrimentally affecting performance on the app. A NSOperationQueue with a reasonable maxConcurrentOperationCount (such as 4 or 5) is much better.
When the download is complete, dispatch the updating of the UI back to the main queue (e.g. turn off the activity indicator and set the image).
This is sample code from a gallery app that shows how you might do it. This is probably more complicated than you need and might be hard to repurpose via cut-and-paste, but the loadImage method shows the basic elements of the solution.
#interface MyImage : NSObject
#property (nonatomic, strong) NSString *urlString;
#property (nonatomic, strong) UIImageView *imageView;
#property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
#property (nonatomic, strong) UIView *view;
#property BOOL loading;
#property BOOL loaded;
#end
#implementation MyImage
// I find that I generally can get away with loading images in main queue using Documents
// cache, too, but if your images are not optimized (e.g. are large), or if you're supporting
// older, slower devices, you might not want to use the Documents cache in the main queue if
// you want a smooth UI. If this is the case, change kUseDocumentsCacheInMainQueue to NO and
// then use the Documents cache only in the background thread.
#define kUseDocumentsCacheInMainQueue NO
- (id)init
{
self = [super init];
if (self)
{
_view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, IMAGE_WIDTH, IMAGE_HEIGHT)];
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, IMAGE_WIDTH, IMAGE_HEIGHT)];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.clipsToBounds = YES;
[_view addSubview:_imageView];
_loading = NO;
_loaded = NO;
}
return self;
}
- (void)loadImage:(dispatch_queue_t)queue
{
if (self.loading)
return;
self.loading = YES;
ThumbnailCache *cache = [ThumbnailCache sharedManager];
if (self.imageView.image == nil)
{
// I've implemented a caching system that stores images in my Documents folder
// as well as, for optimal performance, a NSCache subclass. Whether you go through
// this extra work is up to you
UIImage *imageFromCache = [cache objectForKey:self.urlString useDocumentsCache:kUseDocumentsCacheInMainQueue];
if (imageFromCache)
{
if (self.activityIndicator)
{
[self.activityIndicator stopAnimating];
self.activityIndicator = nil;
}
self.imageView.image = imageFromCache;
self.loading = NO;
self.loaded = YES;
return;
}
// assuming we haven't found it in my cache, then let's see if we need to fire
// up the spinning UIActivityIndicatorView
if (self.activityIndicator == nil)
{
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview:self.activityIndicator];
}
[self.activityIndicator startAnimating];
// now, in the background queue, let's retrieve the image
dispatch_async(queue, ^{
if (self.loading)
{
UIImage *image = nil;
// only requery cache for Documents cache if we didn't do so in the main
// queue for small images, doing it in the main queue is fine, but apps
// with larger images, you might do this in this background queue.
if (!kUseDocumentsCacheInMainQueue)
image = [cache objectForKey:self.urlString useDocumentsCache:YES];
// if we haven't gotten the image yet, retrieve it from the remote server
if (!image)
{
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:self.urlString]];
if (data)
{
image = [UIImage imageWithData:data];
// personally, I cache my image to optimize future access ... you might just store in the Documents folder, or whatever
[cache setObject:image forKey:self.urlString data:data];
}
}
// now update the UI in the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (self.loading)
{
[self.activityIndicator stopAnimating];
self.activityIndicator = nil;
self.imageView.image = image;
self.loading = NO;
self.loaded = YES;
}
});
}
});
}
}
// In my gallery view controller, I make sure to unload images that have scrolled off
// the screen. And because I've cached the images, I can re-retrieve them fairly quickly.
// This sort of logic is critical if you're dealing with *lots* of images and you want
// to be responsible with your memory.
- (void)unloadImage
{
// remove from imageview, but not cache
self.imageView.image = nil;
self.loaded = NO;
self.loading = NO;
}
#end
By the way, if the image you're downloading is in a UIImageView in a UITableViewCell the final update back to the table might want to do something about checking to see if the cell is still on screen (to make sure it wasn't dequeued because the UITableViewCell scrolled off the screen). In that case, the final UI update after successful download of the image might do something like:
dispatch_async(dispatch_get_main_queue(), ^{
// if the cell is visible, then set the image
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell)
{
cell.imageView.image = image;
}
});
Note, this is using the UITableView method cellForRowAtIndexPath, which should not be confused with the UITableViewController method tableView:cellForRowAtIndexPath.
For one of my projects i used this custom class for UIImageView:
https://github.com/nicklockwood/AsyncImageView
Small tutorial is located here: http://www.markj.net/iphone-asynchronous-table-image/
With just few lines of code i managed to implement asynchronous loading of images, caching etc. Just give it a look.

iPhone: How can I have my refresh UIBarButtonItem change to UIActivityIndicator while task is running?

I have a refresh button that executes a function to initiate a request and receive a response. While it's parsing the response I'm changing my last updated text to keep the user informed on what is going on. I'm hoping though to have the refresh button become the uiactivityindicator while the "procedure" is running. How can I accomplish this? Here is a screenshot of my storyboard to help you get and idea of the setup. Let me know if I can provide anything else. Thanks!
Image below shows how I created the bottom bar through the simulated metrics drop down list to the right.
EDIT:
Here is the altered code I used from the marked answer.
// Create UIActivityIndicator UIBarButtonItem
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[activityView startAnimating];
UIBarButtonItem *loadingView = [[UIBarButtonItem alloc] initWithCustomView:activityView];
// Toolbar with LoadingView and LastUpdatedTitle with Flex Spacing
[self.navigationController.toolbar setItems:(NSArray *)[NSArray arrayWithObjects: loadingView, self.flexSpaceOne, self.lastUpdated, self.flexSpaceTwo, nil]];
When I wanted to change it back I simply "reset" my items using the same setItems:arrayWithObjets: method. Except that time I would change out the loadingview with self.refreshButton.
Define some ivars to access your toolbar and its items:
NSMutableArray *toolbarItems;
IBOutlet UIToolbar *toolbar;
Then, in your method where you kick off the task,
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 25, 25)];
UIBarButtonItem *loadingView = [[UIBarButtonItem alloc] initWithCustomView:activityView];
[toolbarItems replaceObjectAtIndex:0 withObject:loadingView];
toolbar.items = toolbarItems;
Then do the reverse when it is done to add your refresh button again.
This is a good time to use blocks and start another thread.
- (IBAction)refresh:(id)sender
{
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[spinner startAnimating];
self.navigationItem.rightBarButtonItem = [[BarButtonItem alloc] initWithCustomView:spinner];
dispatch_queue_t request = dispatch_queue_create("request data", NULL);
dispatch_async(request, ^{
// call the method to request data
dispatch_async(dispatch_get_main_queue(), ^{
self.navigationItem.rightBarButtonItem = sender;
// populate the table with the data
});
});
dispatch_release(request);
}
You'll notice (of course) that I haven't put the spinner where you want it. That's because I'm not confident about how to access the tool bar items -- but hopefully this will put you on the right track.

Asynchronously loading sound resources in viewDidLoad crashes

All,
I am attempting to load a set of sounds asynchronously when I load a UIViewController. At about the same time, I am (occasionally) also placing a UIView on the top of my ViewController's hierarchy to present a help overlay. When I do this, the app crashes with a bad exec. If the view is not added, the app does not crash. My ViewController looks something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
__soundHelper = [[SoundHelper alloc] initWithSounds];
// Other stuff
}
- (void)viewDidAppear:(BOOL)animated
{
// ****** Set up the Help Screen
self.coachMarkView = [[FHSCoachMarkView alloc] initWithImageName:#"help_GradingVC"
coveringView:self.view
withOpacity:0.9
dismissOnTap:YES
withDelegate:self];
[self.coachMarkView showCoachMarkView];
[super viewDidAppear:animated];
}
The main asynchronous loading method of SoundHelper (called from 'initWithSounds') looks like this:
// Helper method that loads sounds as needed
- (void)loadSounds {
// Run this loading code in a separate thread
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *loadSoundsOp = [NSBlockOperation blockOperationWithBlock:^{
// Find all sound files (*.caf) in resource bundles
__soundCache = [[NSMutableDictionary alloc]initWithCapacity:0];
NSString * sndFileName;
NSArray *soundFiles = [[NSBundle mainBundle] pathsForResourcesOfType:STR_SOUND_EXT inDirectory:nil];
// Loop through all of the sounds found
for (NSString * soundFileNamePath in soundFiles) {
// Add the sound file to the dictionary
sndFileName = [[soundFileNamePath lastPathComponent] lowercaseString];
[__soundCache setObject:[self soundPath:soundFileNamePath] forKey:sndFileName];
}
// From: https://stackoverflow.com/questions/7334647/nsoperationqueue-and-uitableview-release-is-crashing-my-app
[self performSelectorOnMainThread:#selector(description) withObject:nil waitUntilDone:NO];
}];
[operationQueue addOperation:loadSoundsOp];
}
The crash seems to occur when the block exits. The init of FHSCoachMarkView looks like this:
- (FHSCoachMarkView *)initWithImageName:(NSString *) imageName
coveringView:(UIView *) view
withOpacity:(CGFloat) opacity
dismissOnTap:(BOOL) dismissOnTap
withDelegate:(id<FHSCoachMarkViewDelegate>) delegateID
{
// Reset Viewed Coach Marks if User Setting is set to show them
[self resetSettings];
__coveringView = view;
self = [super initWithFrame:__coveringView.frame];
if (self) {
// Record the string for later reference
__coachMarkName = [NSString stringWithString:imageName];
self.delegate = delegateID;
UIImage * image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
// ****** Configure the View Hierarchy
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imgView];
[__coveringView.superview insertSubview:self aboveSubview:__coveringView];
// ****** Configure the View Hierarchy with the proper opacity
__coachMarkViewOpacity = opacity;
self.hidden = YES;
self.opaque = NO;
self.alpha = __coachMarkViewOpacity;
imgView.hidden = NO;
imgView.opaque = NO;
imgView.alpha = __coachMarkViewOpacity;
// ****** Configure whether the coachMark can be dismissed when it's body is tapped
__dismissOnTap = dismissOnTap;
// If it is dismissable, set up a gesture recognizer
if (__dismissOnTap) {
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(coachMarkWasTapped:)];
[self addGestureRecognizer:tapGesture];
}
}
return self;
}
I have tried invoking the asynchronous block using both NSBlockOperation and dispatch_async and both have had the same results. Additionally, I've removed the aysnch call altogether and loaded the sounds on the main thread. That works fine. I also tried the solution suggested by #Jason in: NSOperationQueue and UITableView release is crashing my app but the same thing happened there too.
Is this actually an issue with the view being added in FHSCoachMarkView, or is it possibly related to the fact that both access mainBundle? I'm a bit new to asynch coding in iOS, so I'm at a bit of a loss. Any help would be appreciated!
Thanks,
Scott
I figured this out: I had set up a listener on the SoundHelper object (NSUserDefaultsDidChangeNotification) that listened for when NSUserDefaults were changed, and loaded the sounds if the user defaults indicated so. The FHSCoachMarkView was also making changes to NSUserDefaults. In the SoundHelper, I was not properly checking which defaults were being changed, so the asynch sound loading method was being called each time a change was made. So multiple threads were attempting to modify the __soundCache instance variable. it didn't seem to like that.
Question: Is this the correct way to answer your own question? Or should I have just added a comment to the question it self?
Thanks.

Resources