Been looking for a solution for a long while now, but still no success, perhaps someone here can help me.
I created an application that runs a thread that communicates with a webserver in the background. Occasionally the thread receives information that I'd like to display in a UITableView. Because of the dynamic nature of the content I decided a NSMutableArray should be sufficient to store the information from the webserver.
Whenever I receive information from the webservice my AppDelegate sends a notification. My UITableView registers to receive certain kinds of information which I intend to show in the UITableView.
The initWithStyle method of the UITableViewController contains the following code:
- (void)addContact:(NSNotification *)note {
NSLog(#"%#", [note object]);
NSString *message = (NSString *)[note object];
NSString *name = [[message componentsSeparatedByString:#":"] objectAtIndex:2];
contact = name;
[array addObject:contact];
}
- (void)viewDidLoad {
[super viewDidLoad];
array = [[NSMutableArray alloc] initWithObjects:#"test1", #"test2", nil];
tv.dataSource = self;
tv.delegate = self;
self.tableView = tv;
}
The array in AddContact contains the data displayed in the TableView. All data I manually add to the array in ViewDidLoad: will be shown in the table, but it doesn't happen for the data added in the AddContact: method.
Anyone got an idea what I'm doing wrong?
Is the update happening on the main thread?
You can use this sort of refresh function to make sure it does.
- (void)refresh {
if([NSThread isMainThread])
{
[self.tableView reloadData];
[self.tableView setNeedsLayout];
[self.tableView setNeedsDisplay];
}
else
{
[self performSelectorOnMainThread:#selector(refresh) withObject:nil waitUntilDone:YES];
}
}
Related
I am pretty stuck with this and hoping someone can show me some light about this.
I need help to figure out how to refresh the table list on the main view from the slide-in menu.
IN MAIN VIEW
So basically, I have a main view that has a list of events. I use this function to retrieve data from the database:
[self loadEvents:eventCat sortEvent:nil searchDate:dateSelected];
Inside that function, it has the script that gets data, determines if there is content and then reloads the table:
- (void)loadEvents:(NSString*)searchType sortEvent:(NSString*)sortType searchDate:(NSString *)dateSelect{
NSString *url = [NSString stringWithFormat: #"http://www.url.com/eventList.php];
NSURL *eventURL = [NSURL URLWithString: url];
NSLog(#"%#", eventURL);
NSData *jsonData = [NSData dataWithContentsOfURL: eventURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error: &error];
self.eventList = [NSMutableArray array];
NSArray *eventArray = [dataDictionary objectForKey:#"fitpass"];
//NSString *eventFound = [dataDictionary objectForKey:#"notfound"];
//if nothing is found
if ([dataDictionary objectForKey:#"notfound"]){
tableView.hidden = YES;
noEvent.hidden = NO;
NSLog(#"123");
}else{
noEvent.hidden = YES;
tableView.hidden = NO;
NSLog(#"RELOADDATA");
for(NSDictionary *eventDictionary in eventArray){
Event *event = [Event blogPostWithTitle:[eventDictionary objectForKey:#"name"]];
event.eventTime = [eventDictionary objectForKey:#"event_time"];
[self.eventList addObject:event];
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
}
I tried using the NSNotificationCenter method to refresh the data. So I have this in the viewDidLoad Method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshTableWithNotification:) name:#"RefreshTable" object:nil];
Then this function right after ViewDidLoad Method:
- (void)refreshTableWithNotification:(NSNotification *)notification
{
[self.tableView reloadData];
}
IN THE SLIDE MENU VIEW
In the side menu, I have a table list to populate the different categories, and when use click a category, it should refresh the event list in the main view:
//on direct press, trigger segue
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//get current value and set the eventCategory ID in userDefault
NSDictionary *catPicked = [self.catList objectAtIndex:indexPath.row];
NSString *eventCat =[catPicked objectForKey:#"ID"];
NSLog(#"%#Picked:", eventCat);
[userDefaults setObject:eventCat forKey:kEventCat];
[userDefaults synchronize];
NSLog(#"%#entered", [userDefaults objectForKey:kEventCat]);
//redirect back to eventList;
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
UIViewController *vc = [mainStoryboard instantiateViewControllerWithIdentifier: #"eventListViewController"];
//refresh data
EventLists *destinationController = [[EventLists alloc] init];
[destinationController loadEvents:eventCat sortEvent:nil searchDate:nil];
//send notofication over
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefreshTable" object:nil userInfo:nil];
[destinationController.view setNeedsDisplay];
[[SlideNavigationController sharedInstance] popToRootAndSwitchToViewController:vc
withSlideOutAnimation:self.slideOutAnimationEnabled
andCompletion:nil];
return;
}
What I am stuck is that I can see the data (eventList) is updated, but the view does not refresh...
Any help would be very much appreciated!!!
Rory!!!
Thank you so much for your help!! I finally figured it out with your hint.
So basically I thought it was passing he data because of my log. But it was just merely running the function. No data was passed.
And the solution was actually rather simple. Since the data was not really being passed over, refreshing the table in the notification function will not work. The data needs to be reloaded. In other word, I just needed to change the notification function in the Main View to:
- (void)refreshTableWithNotification:(NSNotification *)notification
{
[self loadEvents:[userDefaults objectForKey:kEventCat] sortEvent:nil searchDate:dateSelected];
}
Then my Side Menu View is now much simpler since all I needed was the Notification call and view switch:
//redirect back to eventList;
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
UIViewController *vc = [mainStoryboard instantiateViewControllerWithIdentifier: #"eventListViewController"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefreshTable" object:nil userInfo:nil];
And yes! I needed to move the notification in the Main view to ViewDidAppear:
- (void)viewDidAppear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshTableWithNotification:) name:#"RefreshTable" object:nil];
}
Thank you so much for your help!!
I have a UITableView that loads thumbnails into cells aynchronously as follows:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:
^{
ThumbnailButtonView *thumbnailButtonView = [tableViewCell.contentView.subviews objectAtIndex:i];
UIImage *image = [self imageAtIndex:startingThumbnailIndex + i];
[self.thumbnailsCache setObject: image forKey:[NSNumber numberWithInt:startingThumbnailIndex + i]];
[[NSOperationQueue mainQueue] addOperationWithBlock:
^{
UITableViewCell *tableViewCell = [self cellForRowAtIndexPath:indexPath];
if (tableViewCell)
{
[activityIndicatorView stopAnimating];
[self setThumbnailButtonView:thumbnailButtonView withImage:image];
}
}];
}];
[self.operationQueue addOperation:operation];
[self.operationQueues setObject:operation forKey:[NSNumber numberWithInt:startingThumbnailIndex + i]];
As per a technique I learned in a WWDC presentation, I store all of my operation queues in a NSCache called operationQueues so that later on I can cancel them if the cell scrolls off the screen (there are 3 thumbnails in a cell):
- (void) tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger startingThumbnailIndex = [indexPath row] * self.thumbnailsPerCell;
for (int i = 0; i < 3; i++)
{
NSNumber *key = [[NSNumber alloc] initWithInt:i + startingThumbnailIndex];
NSOperation *operation = [self.operationQueues objectForKey:key];
if (operation)
{
[operation cancel];
[self.operationQueues removeObjectForKey:key];
}
}
}
However, I notice if I repeatedly launch, load, then close my UITableView, I start recieving memory warnings, and then eventually the app crashes. When I remove this line:
[self.operationQueues setObject:operation forKey:[NSNumber numberWithInt:startingThumbnailIndex + i]];
The memory issues go away. Does anyone have any clue on why storing the operation queues in a cache or an array causes the app to crash?
Note: I learnt about NSCache and NSOperationQueue a couple of days ago so I might be wrong.
I don't think that this is a problem with NSOperationQueue, you are adding images to your thumbnailsCache but when the view scrolls off-screen they are still in memory. I am guessing that when the cells scroll back in, you re-create your images. This is probably clogs your memory.
Also, shouldn't you be caching your images instead of your operations?
EDIT
I did some detailed testing with NSCache by adding images and strings until my app crashed. It doesn't seem to be evicting any items so I wrote my custom cache, which seems to work:
#implementation MemoryManagedCache : NSCache
- (id)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(reduceMemoryFootprint) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (void)reduceMemoryFootprint
{
[self setCountLimit:self.countLimit/2];
}
#end
My First View is a UIViewController i have a UITable View in it and it works just fine.
From the first view i push another view, in that view i select a group name then the view dismiss, what i want is when i select the group name from the second view the first view loads the values of that group name to the table view, this is the code i use and its not working
the second view code
- (IBAction)sendNames:(id)sender {
if (!keyValue) {
NSLog(#"Didnt Select Any Group To Send");
return;
}
ViewController *theInstance = [[ViewController alloc] init];
[theInstance setGroupName:keyValue];
[theInstance ReceivedGroup:nil];
[self dismissViewControllerAnimated:YES completion:nil];
//tried that option and didnt work too, i added the rest of the code in the viewDidLoad
//[[NSNotificationCenter defaultCenter] postNotificationName:#"RG" object:nil];
}
keyValue is NSString that been set on tableView didSelectRowAtIndexPath
the first view
- (IBAction)ReceivedGroup:(id)sender {
NSString *path = [self dataFilePath];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *tempArray = [dict objectForKey:groupName];
nameArray = [[NSMutableArray alloc] init];
[nameArray addObjectsFromArray:tempArray];
[nameTable reloadData];
NSLog(#"Group Name : %#",groupName);
NSLog(#"Array : %#",nameArray);
}
groupName is NSString
In the log i get the groupName and the nameArray printed
but the tableView is empty.
EDIT: I fixed the problem and posted my answer
Your viewcontroller "theInstance" will be deallocated immediately after calling sendNames: so nothing's gonna happen. You should rather pass a pointer of your first viewcontroller to your second viewcontroller (e.g. set it as a property) and perform all operations on this viewcontroller.
subclass UITableViewController
#interface SecondViewController : UITableViewController
#property (nonatomic, strong) ViewController *firstViewController;
#end
if using storyboard segues implement in your first viewcontroller :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
((SecondViewController *) segue.destinationViewController).firstViewController = self;
}
sendNames: should then look like this:
- (IBAction)sendNames:(id)sender {
if (!keyValue) {
NSLog(#"Didnt Select Any Group To Send");
return;
}
ViewController *theInstance = self.firstViewController;
[theInstance setGroupName:keyValue];
[theInstance ReceivedGroup:nil];
[self dismissViewControllerAnimated:YES completion:nil];
//tried that option and didnt work too, i added the rest of the code in the viewDidLoad
//[[NSNotificationCenter defaultCenter] postNotificationName:#"RG" object:nil];
}
I Fixed it by sending the keyValue using NSNotificationCenter
Here is my code now
For The Second View
- (IBAction)sendNames:(id)sender {
if (!keyValue) {
NSLog(#"Didnt Select Any Group To Send");
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"RG" object:keyValue];
[self dismissViewControllerAnimated:YES completion:nil];
}
Then On The First View
- (void)receiveNotification:(NSNotification *)notification {
NSString *Key = [notification object];
nameArray = [[NSMutableArray alloc] init];
[nameArray removeAllObjects];
NSString *path = [self dataFilePath];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *tempArray = [dict objectForKey:Key];
[nameArray addObjectsFromArray:tempArray];
[self.nameTable reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[nameTable numberOfRowsInSection:0] - 1 inSection:0];
[nameTable scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"RG" object:nil];
}
I have a MasterDetail app that I am working on that will be used to show players (as in sports) and detail stats. The list of players is called from a Postgres Database and parsed from JSON using the JSONModel. So far, I am able to get all of the data that I need from the Postgres DB, and display it perfectly in the MasterView. I am using the NSNotificationCenter to pass the data from the Master to the Detail view (I fetch the data using a function in the MasterView). I am able to pass the data accurately to the Detail view, but for some reason, my didSelectRowAtIndexPath is not working right. I obviously did something wrong, but I have no idea what it is. Here are the important parts of the code:
In the MasterViewController.m viewDidAppear:
-(void)viewDidAppear:(BOOL)animated
{
//fetch the feed from the Postgres Database
[JSONHTTPClient getJSONFromURLWithString:#"http://myurl" completion:^(NSDictionary *json, JSONModelError *err) {
NSError* error = nil;
_feed = [[PostgresFeed alloc]initWithDictionary:json error:&error];
//Print the data fethced to NSLog in JSON format
NSLog(#"Players: %#", _feed.players);
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:nil userInfo:json];
//reload the table view after data collected
[self.tableView reloadData];
}];
}
and I collect that info in my DetailViewController.m like so:
- (void)handleNotification:(NSNotification *) notification
{
NSLog(#"%#", notification.userInfo);
NSArray *playerData = [notification.userInfo objectForKey:#"player"];
NSDictionary *firstElement = [playerData objectAtIndex:0];
nameLabel.text = [firstElement objectForKey:#"name"];
}
and then my didSelectRowAtIndexPath in my MasterViewController
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
Player *selectedPlayer = [_players objectAtIndex:indexPath.row];
if (_delegate) {
[_delegate selectedPlayer:slectedPlayer];
}
}
There you have it. If you want me to post more or if you want code from the DetailViewController.m, MasterViewController.h, or PlayerSelectionDelegate.h, just let me know.
As a note, I originally created this based off of the Ray Wenderlich iPad SplitView app tutorial a while back. And yes, I am new to this all.
You need to place the NSNotification in
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
//Here you have the NSDictionary from the json
[JSONHTTPClient getJSONFromURLWithString:#"http://myurl" completion:^(NSDictionary *json, JSONModelError *err) {
NSError* error = nil;
_feed = [[PostgresFeed alloc]initWithDictionary:json error:&error];
//Print the data fethced to NSLog in JSON format
NSLog(#"Players: %#", _feed.players);
[JSONHTTPClient getJSONFromURLWithString:#"http://myurl" completion:^(NSDictionary *json, JSONModelError *err) {
NSError* error = nil;
_feed = [[PostgresFeed alloc]initWithDictionary:json error:&error];
//Print the data fethced to NSLog in JSON format
NSLog(#"Players: %#", _feed.players);
//Assuming that you have the players in the same order as your list
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:nil userInfo:[[json objectForKey:#"players"]objectAtIndex:indexPath.row]];
}];
Player *selectedPlayer = [_players objectAtIndex:indexPath.row];
if (_delegate) {
[_delegate selectedPlayer:slectedPlayer];
}
}
And in your DetailViewController :
- (void)handleNotification:(NSNotification *) notification
{
NSLog(#"%#", notification.userInfo);
nameLabel.text = [notification.userInfo objectForKey:#"name"];
}
The problem:
When I do a total Core Data refresh via a pull-to-refresh control (deleting everything and then adding everything again), my automatic NSFetchedResultsController (CoreDataTableViewController) just deletes all rows and doesn't show the new data.
My setup:
I use a Core Data datastore which I fetch data from via the use of a subclass of CoreDataTableVewController. In this case, I have a list of groups the logged-in user is added to, which is fetched by using the predicate:
[NSPredicate predicateWithFormat:#"users CONTAINS %#", self.currentUser];
I then use a datastore refresh function (the same as on app startup) to refresh the data, via a pull-to-refresh control (SVPullToRefresh).
[self.tableView addPullToRefreshWithActionHandler:^{
DatabaseMerger *merger = [DatabaseMerger sharedInstance];
[merger refreshDatabase:self.context];
self.tableView.pullToRefreshView.lastUpdatedDate = [NSDate date];
}];
In this refresh, all the Core Data Managed Objects get deleted, and then added again with the use of JSON data.
With the use of the CoreDataTableViewController, which observes the context, the rows should be refreshed if the context changes. Currently, the only thing that changes, is that all the rows disappear.
The weird thing is, that when I pop the viewcontroller, and push it again (via the navigationbar's back-button), the rows are correctly displayed with the data. This seems to me that it is a problem with fetching the data realtime.
What I have tried:
Manually refetching the data via the use of [self.fetchedresultscontroller performFetch:&error]
Deleting the fetchedresultscontroller's cache via the use of [NSFetchedResultsController deleteCacheWithName:nil]
Setting the fetchresultscontroller to nil, and then setting the controller up again
Relevant Code:
- (void)setupFetchedResultsController
{
NSFetchRequest *groupRequest = [NSFetchRequest fetchRequestWithEntityName:#"Group"];
groupRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]];
groupRequest.predicate = [NSPredicate predicateWithFormat:#"users CONTAINS %#", self.currentUser];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:groupRequest managedObjectContext:self.context sectionNameKeyPath:nil cacheName:nil];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupFetchedResultsController];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.debug = YES;
self.currentUser = [Settings currentUser:self.context];
[self.tableView addPullToRefreshWithActionHandler:^{
DatabaseMerger *merger = [DatabaseMerger sharedInstance];
[merger refreshDatabase:self.context];
self.tableView.pullToRefreshView.lastUpdatedDate = [NSDate date];
}];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveFinishedNotification:)
name:#"dataMergeFinished"
object:nil];
// Do any additional setup after loading the view.
}
- (void)receiveFinishedNotification:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"dataMergeFinished"]) {
[self.tableView.pullToRefreshView stopAnimating];
[NSFetchedResultsController deleteCacheWithName:nil];
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
if (error) {
DebugLog(#"Error: %#", error);
}
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.fetchedResultsController = nil;
}
I am using iOS 5.1.1 with ARC
Have you implemented the NSFetchedResultsController delegates? If you haven't the FRC wont auto-update.
implement the following:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
NSLog(#"POSTDETAIL: controllerWillChangeContent");
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
NSLog(#"POSTDETAIL: controllerDidChangeContent");
[self.tableView reloadData];
}
That should refresh your table view