Can't update a tableview automatically - ios

I have a view controller with many views and a tableview.
The tableview's cells are customized, so there is another class for setting up the cells.
In each cell there is a button. The image of this button changes depending on the cell's content (this content gets read from a DB).
Basically, when the user presses the button, it changes itself to another image, a new status is written to the DB but the tableview does not update itself automatically.
The method for the button is in the custom cell class, so I've tried to instantiate my view controller (the one with the tableview) and execute a method for updating some labels in the views and the tableview:
ViewControllerWithTable *vc = [[ViewControllerWithTable alloc] init];
[vc updateEverything];
But this doesn't work.
The same "updateEverything" method, called from the same "ViewControllerWithTable" (adding a reload button) works perfectly.
Adding the "[tableView reloadData]" in the viewWillAppear method won't work because all the action is done in the same view.
What am I missing?
EDIT: adding some code to be more clear.
This is the method I use to update the tableview. It's inside the ViewController with the embedded tableview and it works when triggered by a button in one of the views:
- (void) updateEverything {
// lots of DB writing and reading, plus label text changing inside all the views
[tableView reloadData];
}
This is the IBAction for the button press and it's in the custom cell class:
-(void) btnPresaPressed:(UIButton *)sender {
AppDelegate *deleg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
deleg.did = sender.tag;
NSString *s1 = NSLocalizedString(#"ALERT_TITLE", nil);
NSString *s2 = NSLocalizedString(#"ALERT_BODY", nil);
NSString *s3 = NSLocalizedString(#"YES", nil);
NSString *s4 = NSLocalizedString(#"NO", nil);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:s1
message:s2
delegate:self
cancelButtonTitle:s4
otherButtonTitles:s3, nil];
[alertView setTag:1];
[alertView show];
}
This method shows an alert view that calls another method, always in the custom cell class:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
AppDelegate *deleg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
DbOperations *db = [[DbOperations alloc] init];
NSString *alrtTitle = [alertView buttonTitleAtIndex:buttonIndex];
NSString *s3 = NSLocalizedString(#"YES", nil);
NSString *s4 = NSLocalizedString(#"NO", nil);
switch (alertView.tag) {
case 1:
//
break;
case 2:
if ([alrtTitle isEqualToString:s3]) {
// DB writing and reading
ViewControllerWithTable *vc = [[ViewControllerWithTable alloc] init];
[vc updateEverything];
} else if ([alrtTitle isEqualToString:s4]){
//
}
break;
case 3:
if ([alrtTitle isEqualToString:s3]) {
//
}
break;
default:
break;
}
}
In this case, the updateEverything method don't work.

EDIT after you added more code:
In the following lines:
if ([alrtTitle isEqualToString:s3]) {
// DB writing and reading
ViewControllerWithTable *vc = [[ViewControllerWithTable alloc] init];
[vc updateEverything];
you are instantiating a new view controller altogether that has nothing to do with your original view controller that displayed the table view. So, you are sending the update message to the wrong object.
What you need is a mechanism for your cell to know which is the right controller to send the message to.
One easy solution would be using NSNotificationCenter:
the view controller register itself for a certain kind of notification:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(updateEverything:)
name:kCellSentUpdateMessageNotification
object:nil];
your cell sends the notification, instead of calling the message directly:
[[NSNotificationCenter defaultCenter] postNotificationName:kCellSentUpdateMessageNotification object:nil];
OLD Answer:
You should call
[self.tableView reloadData]
from your updateEverything method implementation. This will reload the table data, effectively updating its rows appearance. The updateEverything method shall be called when tapping on the button in a row for this to work, obviously.
If that does not work, please provide more code.

Have you try to put
[[self tableView] reloadData];
I remember I had an issue like that, and this line on top solve my problem.

This is your problem:
The method for the button is in the custom cell class, so I've tried
to instantiate my view controller (the one with the tableview) and
execute a method for updating some labels in the views and the
tableview:
You created an entirely new view controller that has no knowledge of your tableview. The best thing to do is to create a property in your cell subclass and set it to your view controller when you set the cell. Then you can call your updateEverything method on that property.

Related

UIViewImage not loading in Subview within a Class method

I am trying to load a UIImage View into a tabbar application with a global class, the image doesn't load the image the first time the rootviewcontroler is loaded, but it does show the alert. If you click on another tabbar item to load another view and come back to the first view the alert and image both show up correctly.
#import "globalNetworkCheck.h"
#import "CheckNetworkAvaliblity.h"
#import "ViewController.h"
#implementation globalNetworkCheck
static globalNetworkCheck *getNetworkStatus = nil;
static UIView *viewNetworkError = nil;
+(globalNetworkCheck *)getNetworkStatus
{
#synchronized(self)
{ if(getNetworkStatus==nil) {
// getNetworkStatus= [globalNetworkCheck new];
if ([CheckNetworkAvaliblity CheckNetwork])
{
{
[(UIView *)viewNetworkError removeFromSuperview];
viewNetworkError.backgroundColor=[UIColor clearColor];
viewNetworkError.hidden=YES;
[viewNetworkError removeFromSuperview];
[[viewNetworkError subviews]
makeObjectsPerformSelector:#selector(removeFromSuperview)];
NSLog(#"Success, Your network is available");
}
}
else
{
UIImageView *imgNoConnection = [[UIImageView alloc] initWithFrame:CGRectMake(0, 70, 320 , 449)];
viewNetworkError = [[UIView alloc]init];
[imgNoConnection setImage:[UIImage imageNamed:#"Noconnection.png"]];
viewNetworkError.frame = CGRectMake(0, 0, 320 , 449);
[viewNetworkError addSubview: imgNoConnection];
viewNetworkError.hidden = NO;
[[UIApplication sharedApplication].keyWindow.rootViewController.view addSubview:viewNetworkError];
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle: #"Connection Failed"
message: #"You are not connected to the internet. Please check your network settings and try again."
delegate: self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
viewNetworkError.hidden = NO;
NSLog(#"Sorry, Network connection is unavailable");
}}}
return getNetworkStatus;
}
#end
I am calling the class method on
-(void)viewWillAppear:(BOOL)animated {
globalNetworkCheck *obj=[globalNetworkCheck getNetworkStatus];
Remove this line:
[viewNetworkError addSubview:viewNetworkError];
Strange to me, how are you going to add viewNetworkError to viewNetworkError.
Edit
I think you mean
[self addSubview:viewNetworkError];
You are going to need somewhere to add the view. Right now this class method has no association on the view hierarchy. If you want to keep it a class method like this then you'll either need to add your error views to the window or the root view controllers view.
[[UIApplication sharedApplication].keyWindow.rootViewController.view addSubview:viewNetworkError];
or
[[UIApplication sharedApplication].keyWindow addSubview:viewNetworkError];
Without understanding the full view stack it is a bit hard to answer further. Alternatively with more rework you can put this responsibility on the AppDelegate rather than a class method like this. Create a view that your App delegate can add to the hierarchy as needed, create a public getter on your app delegate to check current network status, etc. Here is an example of how to great a HUD type view:
http://blog.mugunthkumar.com/coding/ios-code-tweetbot-like-alertpanels/

Tableview Reload not working for a Parent VC / Child VC scenario

I have a scenario where my tableview reload is not working. Please let me know if I have modeled my design correctly or I need to change it.
I have a core view controller and each tab bar view controller is inherited from this core view controller
Each of the tab bar view controller has a tableview and implements the UITableViewDelegate and UITableViewDataSource protocol.
If the app is launched for the first time, In core view controller's viewDidLoad , I have a method to fetch data from web. In the child view controller's viewDidLoad method I have a method which populates the data source for the tableview.
So the issue is on the first launch, data is fetched and saved successfully in Core data but it does not reload the tableview so its empty.
So Currently as per the below code, on First launch I see the Alert view then the loading view and I see the data getting saved in Core Data. But the first tab bar's table view loads up empty with no data and the reason being, its [reload is not being called]. But on next launch the data is there.
Below is the code
Core View COntroller
- (void)viewDidLoad
{
[super viewDidLoad];
if (!self.myManagedObjectContext) {
//Get Shared Instance of managedObjectContext
self.myManagedObjectContext = [LTDataModel sharedInstance].mainObjectContext;
//Check if managed object context has required data
......
if (<data not found> {
[self fetchDataIntoContext];
}
}
-(void)fetchDataIntoContext {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Initializing..."
message:#"This is your first launch of App"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[message show];
UIView *loadingView = [[UILoadingView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:loadingView];
dispatch_queue_t fetchEventQ = dispatch_queue_create("Fetcher", NULL);
dispatch_async(fetchEventQ, ^{
<add data to core data>
[[LTDataModel sharedInstance] save];
dispatch_async(dispatch_get_main_queue(), ^{
[[self.view.subviews lastObject] removeFromSuperview];
});
});
}
Child View Controller
--(void)setMyArrayOfEvents:(NSMutableArray *)myArrayOfEvents {
if (_arrayOfMyEvents != arrayOfMyEvents) {
_arrayOfMyEvents = arrayOfMyEvents;
[self.eventTableView reloadData];
}
}
-(void) viewDidLoad {
[super viewDidLoad];
//Get Shared Instance of managedObjectContext if it does not exist
if (!self.myManagedObjectContext) {
self.myManagedObjectContext = [LTDataModel sharedInstance].mainObjectContext;
}
//Populate the array which feeds the tableview
[self populateTableViewArrayFromContext];
}
-(void)populateTableViewArrayFromContext
{
<Fetch data dfrom Core Data ......>
self.myArrayOfEvents = [[NSMutableArray alloc] initWithArray:localArray];
}
I solved the issue. The issue was not with Child VC's viewDidLoad not being called. Before the background fetching of data finishes the loading view is removed.
Hence the solution was I added the function that populates the data source arrays in the core (parent) view controller with empty body. The Child VC implements the functionality. And called the method before I remove the loading view. Look at code below that I changed in coew view controller
CORE VIEW CONTROLLER
-(void)fetchDataIntoContext {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Initializing..."
message:#"This is your first launch of App"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[message show];
UIView *loadingView = [[UILoadingView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:loadingView];
dispatch_queue_t fetchEventQ = dispatch_queue_create("Fetcher", NULL);
dispatch_async(fetchEventQ, ^{
<add data to core data>
[[LTDataModel sharedInstance] save];
dispatch_async(dispatch_get_main_queue(), ^{
**[self populateTableViewArrayFromContext];**
[[self.view.subviews lastObject] removeFromSuperview];
});
});
}
-(void)populateTableViewArrayFromContext {
//empty the implemetation is in Child VC
}

Can't reload a collection view - null

I'm developing a simple app which downloads images from Dribbble, but I'm having problem reloading the data for my collection view. I have two views set up with ViewDeck, center is my main view which contains the collection view and another view contains table view with settings and from there I'm trying to call a method in the first view and reload data when item is tapped but it just doesn't work.
I tried to call the same method from the main window using button -> worked like a charm but from the second window it just doesn't update the data.
I tried to debug somehow and seems like my collection is null when the reload is called, no idea why.
SettingsViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tap");
JKViewController *appDelegate = [[JKViewController alloc] init];
appDelegate.dataHasChanged = YES;
[appDelegate refresh];
[self.viewDeckController closeLeftViewAnimated:YES];
}
MainView
- (void)refresh{
NSLog(#"refresh");
if(dataHasChanged)
{
switch (listType) {
case 0:
[self refreshWithList:SPListPopular];
break;
case 1:
[self refreshWithList:SPListEveryone];
break;
case 2:
[self refreshWithList:SPListDebuts];
break;
case 3:
[self refreshWithList:SPListPopular];
break;
default:
[self refreshWithList:nil];
break;
}
dataHasChanged = NO;
NSLog(#"Should refresh");
}
NSLog(#"%d", [self->shots count]);
NSLog(#"Collection view: %#",self.collectionView.description);
NSLog(#"self.list: %#",self.list);
NSLog(#"List type: %d", listType);
}
This doesn't work :/, but when I call it from button in the MainView it works.
- (IBAction)changeList:(id)sender {
[self refreshWithList:SPListDebuts];
}
Does anyone know what could be the issue?
Edit - Solved
Getting the right instance of the centerViewController
JKViewController *mainController = ((UINavigationController*)self.viewDeckController.centerController).visibleViewController.navigationController.viewControllers[0];
The reason that you are not seeing your data being updated is because you are creating a new view controller and telling that to refresh. This new view controller has been initialized but not added to your view hierarchy. What you want to do is message the existing view controller like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tap");
JKViewController *mainViewController = self.viewDeckController.centerViewController;
mainViewController.dataHasChanged = YES;
[mainViewController refresh];
[self.viewDeckController closeLeftViewAnimated:YES];
}
Also, please note that I have changed the variable name in my revision. Naming a UIViewController instance 'appDelegate' is very confusing.

iOS - Prompt When Navigating From View

I have a UINavigationController inside a UITabBarController. The navigationcontroller has a UITableView and a form for editing items. The problem is that if a tab is tapped during editing, the form is just cleared and the user is dumped back to the UITableView.
Is there a way I can add a prompt to confirm navigation away from the edit view?
First, declare a BOOL in your .h to store the editing state. Also declare a temporary variable we will use later for storing the selected row.
BOOL isEditing;
NSUInteger selectedRow;
In your viewDidLoad, initialize the boolean to NO
- (void)viewDidLoad {
// initialization
isEditing = NO;
[super viewDidLoad];
}
You can then conform your view controller to UITextFieldDelegate and UIAlertViewDelegate. The text field delegate allows the controller to receive callbacks when editing ends and begins for the text fields and the alert view delegate allow it to receive callbacks when an alert view is dismissed.
#interface MyController : UIViewController <UITextFieldDelegate, UIAlertViewDelegate>
You then also need to set all the text field's delegates to be assigned to the controller. So in your cellForRowAtIndexPath when you add the text fields, just add this:
textField.delegate = self;
Once you have this, you are all set up to receive callbacks from the text field - so now implement the following two methods like so:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
isEditing = YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
isEditing = NO;
}
Now the key here is to make a separate method for pushing the next view, so just do something like this (like you would normally when the table view row is selected):
- (void)showNextView {
// in this method create the child view controller and push it
// like you would normally when a cell is selected
// to get the selected row, use the `selectedRow` variable
// we declared earlier.
}
You now need to implement the table view callback when the user selects a row - in this method we test if they are editing and show them a prompt if they are. If they aren't, we go to the next view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
selectedRow = [indexPath row];
if (isEditing) {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Continue Editing?"
message:#"Continue Editing or discard edits"
delegate:self
cancelButtonTitle:#"Discard"
otherButtonTitles:#"Continue"];
[alert show];
[alert release];
return;
}
[self showNextView];
}
Finally, we need to implement the alert view delegate callback for when the alert view is dismissed:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [alertView cancelButtonIndex]) return; // stay editing
[self showNextView];
}
Hope that all makes sense and is helpful to you!
Since you are using a UINavigationController, if you are pushing this "form" onto the stack you could set
#property(nonatomic) BOOL hidesBottomBarWhenPushed
That way the tab bar would be hidden until they are done with the form.
I solved this eventually by using a custom UIBarButtonItem which looks like a back arrow.

AlertView to Pause Code

This seems to be a white elephant for the iPhone. I've tried all sorts, even loops and I can't make it work.
I have a view that loads a table. However I've moved into object archiving and for development purposes I want to have an initial AlertView that asks if a user wants to use a saved database or download a fresh copy
(void)showDBAlert {
UIActionSheet *alertDialogOpen;
alertDialogOpen = [[UIActionSheet alloc] initWithTitle:#"DownloadDB?"
delegate:self
cancelButtonTitle:#"Use Saved"
destructiveButtonTitle:#"Download DB"
otherButtonTitles:nil];
[alertDialogOpen showInView:self.view];
}
I'm using an ActionSheet in this instance. And I have implemented the protocol:
#interface ClubTableViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource, UIActionSheetDelegate>
Based on this I run this to check what button was pressed:
(void)actionSheet:(UIActionSheet *) actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *buttonTitle=[actionSheet buttonTitleAtIndex:buttonIndex];
if ( [buttonTitle isEqualToString:#"Download DB"] ) {
[self loadDataFromDB];
}
if ([buttonTitle isEqualToString:#"Use Saved"] ) {
self.rows = [NSKeyedUnarchiver unarchiveObjectWithFile:[self archivePath]];
if (self.rows == nil) {
[self loadDataFromDB];
}
}
}
The problem is my code to build the table executes before the user has made there choice. This causes all sorts of havok. As a result, how can I pause the code until a user has made there choice?
If you are using UITableView, you will not be able to "pause" the code, however this wouldn't be the method of choice anyways. A possible solution would be to load an empty table (return 0 in (UITableView *)tableView:numberOfRowsInSection) until the user has selected something, then reload the tableView's data with [tableView reloadData];
In your tableView:numberOfRowsInSection: just return zero until the user has made a decision.
Add [self.tableView reloadData]; at the end of the actionSheet delegate method. This will trigger all UITableViewDataSource methods again.

Resources