I have a simple application which is made up of a UITabBar where each Tab is a UITableViewController. For the purpose of this question, I will only focus on the UITableViewController called Videos (1st Tab) and another UITableViewController called Languages (2nd Tab).
The Videos Tab is made up of one section of a list of Videos. The Languages tab contains two sections, where section 0 is Leaflets and section 1 = the same corresponding Videos as the Videos tab.
So for example, if the Videos tab has:
Video 10010
Video 20010
Video 30010
Video 40010
Video 50010
Then the Languages tab section 1 will also have:
Video 10010
Video 20010
Video 30010
Video 40010
Video 50010
I have some code which puts a star on any cell that has been selected and the title of this cell is getting added to Core Data (to be displayed in the 3rd tab called Favourites - but this isn't important for this question).
I want to ensure consistency within the app, so if I place a star in Video 20010 in the Videos tab, I want to make sure that the Video 20010 in the Languages also has a star.
That part works. However, the issue is that the star gets placed in the Leaflets section (section 0) as well as the Videos section (section 1) of the Languages tab.
Here is part of the code of the cellForRow in the Languages tab.
// This code is important because I might set the Leaflets section to be a favourite from within the Languages tab (not related to the Videos tab, etc).
if(indexPath.section==0)
{
customCell.customCellLabel.text = [NSString stringWithFormat:#"%#",[self.availableLeaflets objectAtIndex:indexPath.row]];
NSString *key = [NSString stringWithFormat:#"%#_%ld_%ld", self.selectedLanguage, (long)indexPath.section, (long)indexPath.row];
if (self.favoritesDict[key]) {
// show the favorite image
customCell.customCellImage.hidden = NO;
} else {
// hide the favorite image
customCell.customCellImage.hidden = YES;
}
}
else
{
customCell.customCellLabel.frame = CGRectMake(8, 20, 100, 40);
customCell.customCellLabel.text = [NSString stringWithFormat:#"%#",[self.availableVideos objectAtIndex:indexPath.row]];
NSString *key = [NSString stringWithFormat:#"%#_%ld_%ld", self.selectedLanguage, (long)indexPath.section, (long)indexPath.row];
if (self.favoritesDict[key]) {
// show the favorite image
customCell.customCellImage.hidden = NO;
} else {
// hide the favorite image
customCell.customCellImage.hidden = YES;
}
}
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Favourites"];
request.predicate = [NSPredicate predicateWithFormat:#"title != nil"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *favs = [context executeFetchRequest:request error:&error];
if (!favs)
{
NSLog(#"Nothing to see here");
}
else
{
NSLog(#"Number of objects %lu", [favs count]);
for (Favourites *favourite in favs)
{
NSString *string = [NSString stringWithFormat:#"%#", favourite.title];
favourite.title = string;
NSLog(#"The favourite titles are %#", favourite.title);
if ([customCell.customCellLabel.text isEqualToString:favourite.title])
{
customCell.customCellImage.hidden = NO;
}
}
}
Update
When a cell is favourited, this is what's happening:
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
CustomLeafletVideoTableViewCell *selectedCell = (CustomLeafletVideoTableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
NSString *cellTitle = selectedCell.customCellLabel.text;
NSLog(#"The text is %#", cellTitle);
NSManagedObjectContext *context = [self managedObjectContext];
NSString *key = [NSString stringWithFormat:#"%#_%ld_%ld", self.selectedLanguage, (long)indexPath.section, (long)indexPath.row];
if (self.favoritesDict[key] == nil)
{
self.favoritesDict[key] = #(1);
Favourites *favourites = [NSEntityDescription insertNewObjectForEntityForName:#"Favourites" inManagedObjectContext:context];
favourites.title = cellTitle;
}
else
{
[self.favoritesDict removeObjectForKey:key];
NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Favourites" inManagedObjectContext: context];
request.predicate = [NSPredicate predicateWithFormat:#"title == %#", cellTitle];
NSArray *items = [context executeFetchRequest:request error:&error];
if (error == nil && items.count)
{
NSManagedObject *managedObject = items[0];
[context deleteObject:managedObject];
}
}
[[NSUserDefaults standardUserDefaults] setObject:self.favoritesDict forKey:#"favoritesDict"];
NSError *error = nil;
if (![context save:&error])
{
// Error
}
[cell hideUtilityButtonsAnimated:YES];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
I am not interested in putting the image in the Leaflets section of the Languages tab (section 0); I just want to target the Videos section (section 1). With this code above, the correct cell in section 1 is getting the UIImageView being applied, but I want to make sure the first section (Leaflets) is NOT getting the star as well.
Any guidance on this would be really appreciated.
Right after you dequeue the cell say customCell.customCellImage.hidden = YES; Then change:
if ([customCell.customCellLabel.text isEqualToString:favourite.title])
{
customCell.customCellImage.hidden = NO;
}
To:
if ([customCell.customCellLabel.text isEqualToString:favourite.title] && indexPath.section == 1)
{
customCell.customCellImage.hidden = NO;
}
In tableView:cellForRowAtIndexPath:, you have the key part, which is the indexPath. Add the following condition to your if statement.
indexPath.section == 1
Related
I have a method in ViewController.m called getData which is called inside viewDidLoad:
-(void)getData {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"WorkoutHasExercise" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
request.resultType = NSDictionaryResultType;
request.propertiesToFetch = [NSArray arrayWithObjects:#"exerciseName", #"reps", #"sets", nil];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(workoutName = %#)", _workoutName];
[request setPredicate:pred];
NSManagedObject *matches = nil;
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if ([objects count] == 0) {
} else {
[_exercises removeAllObjects];
for (int x = 0; x < [objects count]; x++) {
matches = objects[x];
[_exercises addObject:[matches valueForKey:#"exerciseName"]];
[_totalSets addObject:[matches valueForKey:#"sets"]];
[_totalReps addObject:[matches valueForKey:#"reps"]];
[_currentSets addObject:[NSNumber numberWithInteger:0]];
}
}
[_exercisesTableView reloadData];
}
I also have a custom UITableViewCell with two buttons initiated in cellForRowAtIndexPath:
ActiveWorkoutCell *cell = (ActiveWorkoutCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"ActiveWorkoutCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
cell.increaseButton.tag = indexPath.row;
cell.decreaseButton.tag = indexPath.row;
In ActiveWorkoutCell.m I have 2 IBActions for the buttons:
- (IBAction)decreaseSets:(id)sender {
ActiveWorkoutViewController *vc = [[ActiveWorkoutViewController alloc] init];
[vc decreaseSets:[sender tag]];
}
- (IBAction)increaseSets:(id)sender {
ActiveWorkoutViewController *vc = [[ActiveWorkoutViewController alloc] init];
[vc increaseSets:[sender tag]];
}
The IBActions call these 2 methods back in ViewController.m
-(void)increaseSets:(NSInteger)row {
[self getData];
//There will be code here to increase the value of currentSets[row]
}
-(void)decreaseSets:(NSInteger)row {
[self getData]
//Code to decrease value...
}
PROBLEM:
When getData is called from viewDidLoad, it works fine.
The problem occurs when returning to ViewController.m from the IBAction in ActiveWorkoutCell.m.
When I call [self getData] in increaseSets the fetch request returns an empty array. This is what is confusing me - the code works fine when it is first called but not at all when called the second time after the custom cell Action has been triggered.
Here is my viewDidLoad if it helps:
- (void)viewDidLoad {
[super viewDidLoad];
_exercises = [NSMutableArray array];
_totalSets = [NSMutableArray array];
_currentSets = [NSMutableArray array];
_totalReps = [NSMutableArray array];
_titleLabel.text = _workoutName;
_exercisesTableView.allowsSelection = NO;
[self getData];
}
_workoutName is given a value in prepareForSegue in the previous view controller.
I think I found the issue. You are instantiating the "ActivityWorkOutViewController" when the IBAction methods called and it will be a new instance and then in those methods you are calling [self getData] which pointing to the new instance which has no variables instantiated or viewDidLoad happened, so your mutable arrays are not allocated and hence they are empty.
Just use the old instance of the class to get the data.
I am not sure how you are referencing those classes. I am just in a confusion about that. But, you might check the allocations and calling the right class to get the data
I have a screen that holds a UITableView, in this screen I have an array of NSManagedObjects. It's working just fine, but as I try move to another screen (click on a specific cell, and push a new screen), then return to the same UITableView screen, all the objects got lost.
What does it means? I try to print the array of the NSManagedObjects and it's fine, all the objects there, but as I print the description of each object, I get nil from all the object attributes.
Someone knows whats the cause of it? I don't know why but it worked just fine 12 hours ago, but now it's all messed up and I don't have a clue what have I done.
Thanks in advance!
Save method:
- (void)saveContext {
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
NSError *error = nil;
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
else {
NSLog(#"Context saved!");
}
}
}
This is how I save the objects:
NSDictionary *response = responseObject;
if ([[response valueForKey:#"status"] rangeOfString:#"ok"].location != NSNotFound)
{
NSArray *data = [response objectForKey:#"data"];
if (data.count != 0)
{
if (page.integerValue == 0) {
[[DownloadData sharedData] deleteAllObjectsFromEntityName:#"DbHomeCuisine"];
[[DownloadData sharedData] deleteAllObjectsFromEntityName:#"DbHomeCategory"];
[[DownloadData sharedData] deleteAllObjectsFromEntityName:#"DbHomeDish"];
}
NSMutableArray *homePageObjects = [[NSMutableArray alloc] initWithCapacity:data.count];
for (NSDictionary *object in data)
{
NSNumber *type = [object objectForKey:#"type"];
switch (type.integerValue) {
case 1:
{
NSDictionary *content = [object objectForKey:#"content"];
NSManagedObjectContext *context = [[MainDb sharedDb] managedObjectContext];
DbHomeCuisine *homeCuisine = [NSEntityDescription insertNewObjectForEntityForName:#"DbHomeCuisine" inManagedObjectContext:context];
NSInteger cuisineId = [[content valueForKey:#"cuisine_id"] integerValue];
homeCuisine.cuisine = [self gCuisineWithCuisineId:[NSNumber numberWithInteger:cuisineId]];
NSInteger count = [[content valueForKey:#"count"] integerValue];
homeCuisine.count = [NSNumber numberWithInteger:count];
homeCuisine.type = type;
[homePageObjects addObject:homeCuisine];
}
break;
case 2:
{
NSDictionary *content = [object objectForKey:#"content"];
NSManagedObjectContext *context = [[MainDb sharedDb] managedObjectContext];
DbHomeCategory *homeCategory = [NSEntityDescription insertNewObjectForEntityForName:#"DbHomeCategory" inManagedObjectContext:context];
NSInteger categoryId = [[content valueForKey:#"category_id"] integerValue];
homeCategory.category = [self gCategoryWithCategoryId:[NSNumber numberWithInteger:categoryId]];
NSInteger count = [[content valueForKey:#"count"] integerValue];
homeCategory.count = [NSNumber numberWithInteger:count];
homeCategory.type = type;
[homePageObjects addObject:homeCategory];
}
break;
case 3:
{
NSDictionary *content = [object objectForKey:#"content"];
NSManagedObjectContext *context = [[MainDb sharedDb] managedObjectContext];
DbHomeDish *homeDish = [NSEntityDescription insertNewObjectForEntityForName:#"DbHomeDish" inManagedObjectContext:context];
homeDish.dishId = [self gInt:content forKey:#"dish_id"];
homeDish.headline = [AppUtils checkForEmptyValue:[content valueForKey:#"title"]];
homeDish.text = [AppUtils checkForEmptyValue:[content valueForKey:#"description"]];
homeDish.cuisineId = [self gInt:content forKey:#"cuisine_id"];
homeDish.cuisine = [self gCuisineWithCuisineId:homeDish.cuisineId];
homeDish.creationDate = [AppUtils checkForEmptyValue:[content valueForKey:#"creation_time"]];
homeDish.userId = [self gInt:content forKey:#"user_id"];
homeDish.longitude = [self gDouble:content forKey:#"lng"];
homeDish.latitude = [self gDouble:content forKey:#"lat"];
homeDish.lastPromoteDate = [AppUtils checkForEmptyValue:[content valueForKey:#"last_promote_time"]];
homeDish.price = [self gInt:content forKey:#"price"];
homeDish.currency = [AppUtils checkForEmptyValue:[content valueForKey:#"currency"]];
homeDish.countryName = [AppUtils checkForEmptyValue:[content valueForKey:#"country_name"]];
homeDish.baseCurrency = [self gFloat:content forKey:#"base_currency"];
homeDish.exchangeRate = [self gFloat:content forKey:#"exchange_rate"];
homeDish.countryIsoCode = [AppUtils checkForEmptyValue:[content valueForKey:#"country_iso_code"]];
homeDish.mainPhoto = [AppUtils checkForEmptyValue:[content valueForKey:#"main_photo"]];
homeDish.like = [self gLikeWithDishId:homeDish.dishId];
homeDish.profileImageURL = [AppUtils checkForEmptyValue:[content valueForKey:#"profile_img_url"]];
homeDish.likeCount = [self gInt:content forKey:#"likes"];
homeDish.type = type;
[homePageObjects addObject:homeDish];
}
break;
default:
break;
}
}
// ##log -- Save data to core data and device
//
//
[[MainDb sharedDb] saveContext];
if (success) {
success(operation, homePageObjects);
}
}
}
Seriously, you should consider refactoring using a NSFetchedResultsController. Start from the template provided in Xcode (New Project -> Master/Detail -> check Core Data, the code is in MasterViewController.m).
I strongly discourage loading Core Data objects into an array to be displayed in a table view. Your problem is typical for such a setup, and you will run into memory and performance issues eventually as well.
I'm facing problem with saving UITableViewCell's state and can't figure out how to solve it. Hope somebody can help me.
Explanation:
There is an API on server and I get data from it and then store it inside NSMutableArray. Each object of an array contains property ready which can be 1 or 0. So I've no problems with populating UITableView with this data but not every data object is ready (i.e 0) and I need to get progress of completion at server and after that to show it in each cell is need it. I've UIProgressView in dynamic prototype of UITableViewCell and set progress after getting. There is no problem if such "not ready" object is only one. But if there are many objects I can't show progress and I don't understand why.
So here is my code.
cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"readyCell";
AVMMovieCell *cell = [self.readyTable dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil) {
cell = (AVMMovieCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
AVMFilmsStore *oneItem;
oneItem = [readyArray objectAtIndex:indexPath.row];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if (oneItem.ready==1){
cell.progressLabel.hidden = YES;
cell.progressLine.hidden = YES;
if ([selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
if (![cell.progressLabel.text isEqualToString:#""]&& ![cell.progressLabel.text isEqualToString:#"Success"] && ![cell.progressLabel.text isEqualToString:#"Creating"]){
cell.progressLabel.hidden = NO;
cell.progressLine.hidden = NO;
} else {
cell.progressLabel.hidden = YES;
cell.progressLine.hidden = YES;
}
}
else{
if(!oneItem.isProcessing && !cell.selected){
cell.progressLabel.hidden = YES;
cell.progressLine.hidden = YES;
}
}
} else { //if processing
if (![processingCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]]){
[processingCellsArray addObject:[NSString stringWithFormat:#"%#",rowNsNum]];
if (!cell.isSelected){
[cell setSelected:YES];
}
cell.progressLabel.hidden = NO;
cell.progressLine.hidden = NO;
NSArray * arrayOfThingsIWantToPassAlong =
[NSArray arrayWithObjects: cell, oneItem, indexPath, nil];
if(!isMaking){
[self performSelector:#selector(getProgress:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:0];
} else{
[self performSelector:#selector(getProgress:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:0.5];
}
isMaking = YES;
} else {
if (!cell.isSelected){
[cell setSelected:YES];
}
cell.progressLabel.hidden = NO;
cell.progressLine.hidden = NO;
NSArray * arrayOfThingsIWantToPassAlong =
[NSArray arrayWithObjects: cell, oneItem, indexPath, nil];
if(!isMaking){
[self performSelector:#selector(getProgress:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:0];
} else{
[self performSelector:#selector(getProgress:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:0.3];
}
isMaking = YES;
}
}
return cell;
}
and getProgress method:
-(void)getProgress:(NSArray*)args{
if (progManager == nil && !progStop){
__block AVMFilmsStore * oneItem = args[1];
if(!oneItem.isLocal){
__block AVMMovieCell * cell = args[0];
__block NSIndexPath *indexPath = args[2];
progManager = [AFHTTPRequestOperationManager manager];
__block NSString *token = [defaults objectForKey:#"token"];
__block NSString *header = [NSString stringWithFormat:#"Bearer %#",token];
__block NSDictionary *params = #{#"lang": NSLocalizedString(#"lang",nil),#"project":oneItem.fileId};
__block NSString *oneHundredPercent;
__block NSString *progIsText;
progManager.responseSerializer = [AFJSONResponseSerializer serializer];
[progManager.requestSerializer setValue:header forHTTPHeaderField:#"Authorization"];
if(cell.selected || isMaking) { //if I just check for "cell.selected" is always "NO"
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
[progManager POST:#"http://example.com/api/project/get-progress" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([[responseObject objectForKey:#"result"]isEqualToString:#"success"]){
progCreate = [responseObject objectForKey:#"progress"];
oneHundredPercent = #"100";
if ([progCreate intValue]==[oneHundredPercent intValue]){
if([processingCellsArray containsObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]]){
[processingCellsArray removeObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
[cell setSelected:NO];
}
[readyArray removeAllObjects];
[defaults setObject:#"false" forKey:#"isSomethingInSort"];
isMaking = NO;
[self getReadyMovies:progIsText nameLabel:oneItem.fileName];
} else{
if([progCreate intValue]>=50){
if([progCreate intValue]>=60){
self.navigationController.navigationItem.leftBarButtonItem.enabled = YES;
createMainButton.enabled = YES;
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"gotFiftyNote" object:#"50"];
[cell.progressLine setProgress:[progCreate floatValue]/100 animated:YES];
} else {
[cell.progressLine setProgress:progUploadLimit];
}
progManager = nil;
progManager.responseSerializer = nil;
progManager.requestSerializer = nil;
token = nil;
header = nil;
params = nil;
progIsText = nil;
oneItem = nil;
cell = nil;
indexPath = nil;
isMaking = YES;
progCreate = nil;
oneHundredPercent = nil;
[self getProgress:args];
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
NSLog(#"Error: %#", error.localizedDescription);
}];
}
}
}
}
Any suggestions will be helpful for me. I've a headache for two weeks with this problem.
I see your code but is kind of difficult to follow with those large methods. I wouldn't keep track of the processing cells in an array. Each cell has an object to represent, those object have a bool value of ready and a progress value, right?. So try something like this:
Make sure each of your cells have a progressView as a subview.
Your cell class should have a public method named styleForReady:(bool)isReady andProgress:(nsinteger)progress
Make the service call to see if they are done or not, for each model. Whenever that service call comes back, you just update the model objects in the array, and after they have the new progress values you do [self.tableView reloadData]. This would trigger numberOfRows (which should return arrayOfObjects.count) and cellForRowAtIndexPath:(which should dequeue the cell for that indexPath, grab the model representing that cell, something like arrayOfObjects[indexPath.row], and finally, call the cell to style itself based on that model doing [cell styleForReady:objectModel.ready andProgress:objectModel.progress])
Keep in mind that the controller should just keep track of the model objects, dequeue the cell and tell the cell to style passing the model. Don't put all the logic in the controller.
I have some object that is image that is in relation with object user mani-to-one like that Image <<---> User. Now that i want to do, when the user is login i display a button to each images for add to favourites, when i click this button is run this code:
User * user = [[UserController sharedInstance] currentUser];
Image * image = (Image*)[user.managedObjectContext objectWithID:[self.yacht objectID]];
yacht.whoLiked = user
the problem is not i the same controller but in the Main Controller before, because what i do is load al the image's thumb in a collection view (and in this controller load all the data from the DB) then when i press the thumb i go in another controller that show me the big image and the button for add favourites, when i press it and then come back to the old controller in the viewDidAppear of the old controller i reload every time the data from the db but i can't see any change, if i change section (controller) and i come back to see i see the data update
this is how I call the Db from Main Controller:
- (void)fetchImages
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Image"];
request.predicate = [NSPredicate predicateWithFormat:#"ANY whichCategories.name =[cd] %#", self.category.name];
NSSortDescriptor *sortName = [[NSSortDescriptor alloc] initWithKey:#"headline" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
request.sortDescriptors = [NSArray arrayWithObject:sortName];
NSError * error = nil;
self.images = [self.database.managedObjectContext executeFetchRequest:request error:&error];
[self.collectionView reloadData];
}
- (void)useDocument
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.database.fileURL path]]) {
// CREATE
[self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self fetchImages];
}];
} else if (self.database.documentState == UIDocumentStateClosed) {
// OPEN
[self.database openWithCompletionHandler:^(BOOL success) {
[self fetchImages];
}];
} else if (self.database.documentState == UIDocumentStateNormal) {
// USE
[self fetchImages];
}
}
- (void)setDatabase:(UIManagedDocument *)database
{
if (_database != database) {
_database = database;
[self useDocument];
}
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self useDocument];
//[self.collectionView reloadData];
[UIView beginAnimations:#"" context:nil];
[UIView setAnimationDuration:0.5];
self.collectionView.alpha = 1;
[UIView commitAnimations];
}
Why if i come back and return the code work else is like that I didn't call the server for refresh the array?
Triy with this code:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Favorits" inManagedObjectContext:moc]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title == %#", #"Some Title"];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:request error:&error];
YourObject *object = [results objectAtIndex:0];
object.title = #"Bla bla bla";
//save changes
[moc save:&error];
if (error) {
//Do something
}
this 2 link is helpful:
http://developer.apple.com/library/iOS/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html
If I didn't misunderstand you, the problem is that when you refresh the array, your data is not updated.
If you reload from the DB and you haven't saved your changes, then you will get the values are in the DB. You should do:
NSError *saveError = nil;
[user.managedObjectContext save:&saveError];
if (error) {
//Do whatever
}
You should use this code before call again the function to reload the array from the DB. The place it's up to you, but if you only call to refresh the array from DB when you are loading the viewcontroller, you can save the data when you are leaving the viewController.
I'm using GCD to load my UITableView data on the background thread, however doing so mixes up the data in my custom UITableViewCell. The titleLabel and imageView on the cell are fine, but the textLabel (the subtitle) is wrong on every cell. This doesn't happen when the data is loaded on the main thread, and the data doesn't come from multiple arrays, so I can only guess it's because of my use of GCD, which I am new to.
Firstly, I set up the NSOperationQueue like so...
- (void)setUpTableForAlbums
{
dispatch_async(dispatch_get_global_queue(0, 0), ^
{
[self setUpTableForAlbumsFD];
dispatch_async(dispatch_get_main_queue(), ^
{
[albumTable reloadData];
});
});
}
The setUpTableForAlbumsFD selector is as so...
- (void)setUpTableForAlbumsFD
{
// __block CLProgressIndeterminateView *clP = [[CLProgressIndeterminateView alloc] initWithFrame:CGRectMake(325, tableScrollView.frame.size.height/2, 310, 20)];
// [tableScrollView addSubview:clP];
// [clP startAnimating];
type = #"Albums";
queryAlbums = [MPMediaQuery albumsQuery];
[queryAlbums setGroupingType:MPMediaGroupingAlbum];
mainArrayAlbum = [[NSMutableArray alloc] init];
otherArrayAlbum = [[NSMutableArray alloc] init];
theOtherArrayAlbum = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSArray *fullArray = [queryAlbums collections];
for (MPMediaItemCollection *collection in fullArray)
{
item = [collection representativeItem];
NSString *albumName = [item valueForProperty:MPMediaItemPropertyAlbumTitle];
NSString *albumArtist = [item valueForProperty:MPMediaItemPropertyArtist];
NSString *filePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png", albumName]];
Album *album = [[Album alloc] init];
album.albumTitle = albumName;
album.albumArtwork = [UIImage imageImmediateLoadWithContentsOfFile:filePath];
if (album.albumTitle.length > 4)
{
if ([album.albumTitle hasPrefix:#"The "])
{
album.albumOrderTitle = [album.albumTitle substringFromIndex:4];
}
else
{
album.albumOrderTitle = album.albumTitle;
}
}
else
{
album.albumOrderTitle = album.albumTitle;
}
album.albumArtist = albumArtist;
if (![mainArrayAlbum containsObject:album])
{
[mainArrayAlbum addObject:album];
}
}
}
The Album custom class is just a container for the data.
The cellForRowAtIndex path method is as so...
MasterCellAlbum *albumCell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (!albumCell)
{
albumCell = [[MasterCellAlbum alloc] initWithStyle:nil reuseIdentifier:#"Cell"];
}
alphabet = [self alphabet:#"album" withIndex:YES];
[albumCell setSelectionStyle:UITableViewCellEditingStyleNone];
NSString *alpha = [alphabet objectAtIndex:indexPath.section];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.albumOrderTitle beginswith[c] %#", alpha];
NSArray *predict = [mainArrayAlbum filteredArrayUsingPredicate:predicate];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
Album *album1 = [predict objectAtIndex:indexPath.row];
albumCell.titleLabel.text = album1.albumTitle;
albumCell.textLabel.text = album1.albumArtist;
albumCell.avatarImageView.image = album1.albumArtwork;
longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(albumLittleMenu:)];
[albumCell addGestureRecognizer:longPress];
return albumCell;
Am I using GCD correctly, or is there another way I should be doing it?
Yikes. There are lots of things that are, shall we say, interesting about this code. Let's start with the first method:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [NSInvocationOperation alloc];
operation = [operation initWithTarget:self selector:#selector(setUpTableForAlbumsFD) object:nil];
[operation setCompletionBlock:^
{
[albumTable reloadData];
}];
[operationQueue addOperation:operation];
operation = nil;
What I think you're tying to do is execute the -setUpTableForAlbumsFD method in the background, and then when it's done, reload the tableView.
First, the completionBlock doesn't execute on the main thread (which is where you MUST call -reloadData from). The docs say:
The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context.
The simpler way to do this method would be:
dispatch_async(dispatch_get_global_queue(0,0), ^{
[self setUpTableForAlbumsFD];
dispatch_async(dispatch_get_main_queue(), ^{
[albumTable reloadData];
}
});
Now for the setUpTableForAlbumsFD method...
- (void)setUpTableForAlbumsFD {
type = #"Albums";
queryAlbums = [MPMediaQuery albumsQuery];
[queryAlbums setGroupingType:MPMediaGroupingAlbum];
mainArrayAlbum = [[NSMutableArray alloc] init];
NSArray *fullArray = [queryAlbums collections];
for (MPMediaItemCollection *collection in fullArray) {
item = [collection representativeItem];
NSString *albumName = [item valueForProperty:MPMediaItemPropertyAlbumTitle];
NSString *albumArtist = [item valueForProperty:MPMediaItemPropertyArtist];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
You should do these two lines of finding the NSDocumentDirectory outside of the for loop, for efficiency.
NSString *filePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png", albumName]];
UIImage *artwork = [UIImage imageImmediateLoadWithContentsOfFile:filePath];
I'm assuming this is a UIImage category method?
Album *album = [[Album alloc] init];
album.albumTitle = albumName;
if (album.albumTitle.length > 4) {
if ([[NSString stringWithFormat:#"%c%c%c%c", [album.albumTitle characterAtIndex:0], [album.albumTitle characterAtIndex:1], [album.albumTitle characterAtIndex:2], [album.albumTitle characterAtIndex:3]] isEqual: #"The "]) {
Yikes! Just do: if ([album.albumTitle hasPrefix:#"The "]) {
album.albumOrderTitle = [album.albumTitle substringWithRange:NSMakeRange(4, album.albumTitle.length-4)];
And here do: album.albumOrderTitle = [album.albumTitle substringFromIndex:4];
} else {
album.albumOrderTitle = album.albumTitle;
}
} else {
album.albumOrderTitle = album.albumTitle;
When you see multiple lines that are doing the same thing like this, it's a sign you can pull it out and do it differently. For example, you could always set the album.albumOrderTitle to the albumTitle, and then only do something different if the albumTitle length is more than 4 and it has a prefix of #"The ".
}
album.albumArtist = albumArtist;
album.albumArtwork = artwork;
if (![mainArrayAlbum containsObject:album]) {
[mainArrayAlbum addObject:album];
}
}
}
Your cellForRowAtIndexPath: is similarly convoluted:
MasterCellAlbum *albumCell = [[MasterCellAlbum alloc] init];
You should be using UITableView's cell-reuse mechanism.
alphabet = [self alphabet:#"album" withIndex:YES];
[albumCell setSelectionStyle:UITableViewCellEditingStyleNone];
NSString *alpha = [alphabet objectAtIndex:indexPath.section];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.albumOrderTitle beginswith[c] %#", alpha];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
NSArray *predict = [mainArrayAlbum filteredArrayUsingPredicate:predicate];
Why are you re-filtering the mainArrayAlbum every time you need a cell? It looks like you're always going to be grabbing the same alphabet, which means you're always going to be defining the same predicate, which means you're always going to be ending up with the same predict array.
Album *album1 = [predict objectAtIndex:indexPath.row];
albumCell.titleLabel.text = album1.albumTitle;
albumCell.textLabel.text = album1.albumArtist;
if (album1.albumArtwork) {
albumCell.avatarImageView.image = album1.albumArtwork;
} else {
albumCell.avatarImageView.image = [UIImage imageNamed:#"albumArtInvertedLight1.png"];
}
longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(albumLittleMenu:)];
[albumCell addGestureRecognizer:longPress];
return albumCell;
So, there are some obvious places where your code can use some improvement. Honestly, I think the answer to the problem you're having is because you're trying to reload the tableview on a background thread, which is a Bad Idea™.