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.
Related
I've looked through a ton of other StackOverflow posts about this error and all of them provide very reasonable solutions the problem. In other words, they generally pinpoint something in the code that isn't being auto-retained, but should be and then it subsequently causes a crash.
The problem I'm having is that the line of code that Crashlytics is telling me doesn't seem to have anything that could possibly be dealloc'd.. at least that I know of. Hopefully, you'll be able to see something I'm not seeing.
I'm not able to replicate the crash myself, but Crashlytics tells me I've had 146 of these crashes across 28 different users in the last 3 months.
My MainMenuDrawerViewController is a UITableViewController that sits in my left-side drawer (using MMDrawerController).
The crash happens in -tableView:didSelectRowAtIndexPath: on the following line:
[self updateCenterWithViewControllerIdentifiedBy:#"ReportsViewController"];
The only two objects on that line are self and a string literal, so I don't understand what could possibly be dealloc'd and causing the EXC_BAD_ACCESS.
Here is the overall method (with irrelevant code cut out):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row) {
// removed other case statements
case DrawerRowReports: {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self performSegueWithIdentifier:#"ShowReportList" sender:self];
} else {
[self updateCenterWithViewControllerIdentifiedBy:#"ReportsViewController"];
}
break;
}
// removed other case statements
default:
break;
}
}
The -updateCenterWithViewControllerIdentifiedBy: function instantiates a View Controller from the storyboard using the given identifier, then instantiates an MMNavigationController with the first view controller as the root, then updates the mm_drawerController to put that MMNavigationController into the center position.
I'll include that method as well below, BUT the Crashlytics report doesn't say the bad access happens inside the method, it says it happens at the line above.
- (nullable id) updateCenterWithViewControllerIdentifiedBy:(nullable NSString*)storyboardIdentifier {
return [self updateCenterWithViewControllerIdentifiedBy:storyboardIdentifier withCloseAnimation:YES];
}
- (nullable id) updateCenterWithViewControllerIdentifiedBy:(nullable NSString*)storyboardIdentifier withCloseAnimation:(BOOL)withCloseAnimation {
return [self updatePosition:DrawerCenter withViewControllerIdentifiedBy:storyboardIdentifier withValueDictionary:nil withCloseAnimation:withCloseAnimation];
}
- (nullable id) updatePosition:(DrawerPosition)position withViewControllerIdentifiedBy:(nullable NSString*)storyboardIdentifier withValueDictionary:(nullable NSDictionary*)configDictionary withCloseAnimation:(BOOL)withCloseAnimation {
//BaseViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:storyboardIdentifier];
UIViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:storyboardIdentifier];
if (configDictionary != nil) {
for (NSString *fieldname in [configDictionary allKeys]) {
[viewController setValue:[configDictionary objectForKey:fieldname] forKey:fieldname];
}
}
UINavigationController * nav = [[MMNavigationController alloc] initWithRootViewController:viewController];
if (position == DrawerCenter) {
[self.mm_drawerController setCenterViewController:nav
withCloseAnimation:withCloseAnimation
completion:nil];
} else if (position == DrawerRight) {
[self.mm_drawerController setRightDrawerViewController:nav];
} else if (position == DrawerLeft) {
[self.mm_drawerController setLeftDrawerViewController:nav];
} else {
NSLog(#"unknown drawer position: %ld", (long)position);
}
return viewController;
}
In my master view I have 4 static table rows. 2 of these rows drill down into a detail view within the master view and the other 2 replace the contents of the detail view. I control what happens with the didSelectRowAtIndexPath() method by calling showViewController() and showDetailViewController() functions as appropriate:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
Master2TVC *m2tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"master-2"];
[self showViewController:m2tvc sender:self];
} else if (indexPath.row == 1) {
Master3TVC *m3tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"master-3"];
[self showViewController:m3tvc sender:self];
} else if (indexPath.row == 2) {
Detail2VC *d2vc = [self.storyboard instantiateViewControllerWithIdentifier:#"detail-2"];
[self showDetailViewController:d2vc sender:self];
} else if (indexPath.row == 3) {
Detail3VC *d3vc = [self.storyboard instantiateViewControllerWithIdentifier:#"detail-3"];
[self showDetailViewController:d3vc sender:self];
}
}
The template for the Master-Detail template creates a reference from the master view to the detail view:
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
If I understand things correctly, this reference exists so that the master side can send messages to the detail side. In my case the class of my detail view will change (Detail3VC, Detail2VC, etc) so I've decided to remove this line and do the messaging another way; however, now when I load any of my new detail views and change the rotation of the iPad the App sometimes crashes with the error EXC_BAD_ACCESS.
From what I understand EXC_BAD_ACCESS usually means there is an object hanging around somewhere that shouldn't be. I have been unable to find anything in the Apple documentation that talks about having to change anything else when using showDetailViewController() call. In fact, I thought that the reason for using showDetailViewController() is so that the splitViewController manages all the details and you don't have to in your own classes.
Anyone can see the error here?
I have confirmed the crash you encountered. It always, not sometimes, happens when changing iPad's rotation.
Anyway, it seems that we need to implement -targetDisplayModeForActionInSplitViewController: of UISplitViewControllerDelegate and return any value except UISplitViewControllerDisplayModeAutomatic.
- (UISplitViewControllerDisplayMode)targetDisplayModeForActionInSplitViewController:(UISplitViewController *)svc {
return UISplitViewControllerDisplayModePrimaryOverlay;
}
I have two UIViewControllers, one is a UIPickerViewController, the Other a UITableViewController. Ideally the Picker should get a request from the user to add x amount of some item to the tableView. The Picker gets user inputs and assigns them to variables val1, val2, val3, where val1 is the number of items (number of rows) and val2 is the name or label for the item.
PickerViewController.m
- (IBAction)add:(id)sender
{
TableViewController *tvc = [[TableViewController alloc] init];
[tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:tvc animated:YES completion:nil];
}
TableViewController.m
-(void)setValues:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
self.val1 = newVal1;
self.val2 = newVal2;
self.val3 = newVal3;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"UITableViewCell"];
// This is just a header which holds my "Add" button
UIView *header = self.headerView;
[self.tableView setTableHeaderView:header];
[self addNew:self.val1 :self.val2 :self.val3];
}
- (void)addNew:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
if(!self.numberOfRows){
NSLog(#"Initially no of rows = %d", self.numberOfRows);
self.numberOfRows = [self.val1 intValue];
NSLog(#"Then no of rows = %d", self.numberOfRows);
}
else
{
self.numberOfRows = self.numberOfRows + [newVal1 intValue];
NSLog(#"New no rows = %d", self.numberOfRows);
}
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.numberOfRows inSection:0];
// Only run when called again .. not initially
if(self.run != 0){
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationBottom];
self.run ++;
[self.tableView endUpdates];
}
}
// "ADD" button which should go back to the picker and get new items to add to the table
- (IBAction)testAdd:(id)sender
{
PickerViewController *pvc = [[PickerViewController alloc] init];
[self presentViewController:pvc animated:YES completion:nil];
}
Now, I realize every time I call the next view controller I am creating a new instance of it, but I don't know how else to do it, I figure this is the main problem. As of right now, I expect when I leave the tableview for the picker view and return the console should log "New no of rows = x" but that doesn't happen.
I know val3 isn't used and my addNew: may not be the best, but I just need it to handle the basic logging mentioned above and I should be able to take it from there.
Been stuck on this for days
Create a property for TableViewController, and only create it the first time you present it,
- (IBAction)add:(id)sender {
if (! self.tvc) {
self.tvc = [[TableViewController alloc] init];
}
[self.tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:self.tvc animated:YES completion:nil];
}
It's not entirely clear from you question, whether it's this presentation or the one you have in the table view class that you're talking about. It also looks like you're doing something wrong in terms of presentation -- you're presenting the picker view from the table view controller, and also presenting the table view controller from the picker. That's not correct, you should present which ever controller you want to appear second, and that controller should use dismissViewControllerAnimated to go back, not present another controller.
In testAdd you don't need to create a new instance and present it. If you want to go back to the presentingViewController, just use dismissViewControllerAnimated .
And you will go one controller up in the stack.
Alright,
I have read some Q&A's on this, but I need to be able to relate it to my code and I am not being successful, I hope someone is able help me:-)
I have a UITableViewController that is populated with a NSMUtableArray.
I would like to link to each entry and then have that URL display in a DetailViewController which has UIWebView in it.
Now I had the idea of passing a string along from one VC to the next, but that does not seem to work.
This is the code I have in the First TVC that is relevant:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int currentindex = [indexPath row];
switch (currentindex) {
case 0:
_urlAddress = #"http://localhost:8888/thePalmsMenu/breakfast_menu.php";
break;
case 1:
_urlAddress = #"http://localhost:8888/thePalmsMenu/lunch_menu.php";
break;
default:
break;
}
webVC *sView = [[webVC alloc] init];
sView.urlAddress = _urlAddress;
[self.navigationController pushViewController:sView animated:YES];
}
And then I have this in the Second View Controller which has the UIWebView in it.
-(void)viewDidLoad
{
_vc.urlAddress = _urlAddress;
NSURL *url = [NSURL URLWithString:_urlAddress];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_webView loadRequest:requestObj];
}
Now also at the moment the code show I am doing a push navigation, but that is not working - if I embed the first view then I get an error saying I can't push that view twice, so they must be related.
this is the error message for that:
2012-06-08 22:23:28.965 menu_test[68491:f803] nested push animation can result in corrupted navigation bar
2012-06-08 22:23:29.447 menu_test[68491:f803] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
2012-06-08 22:23:29.449 menu_test[68491:f803] Unbalanced calls to begin/end appearance transitions for .
Now even though these seem like 2 questions, they are sure to be related.
Also I have tried playing with NSUserDefaults, but that is not working as smooth, because the link needs to be set before I can use it, rather than directly passing it on.
Any help would be great:-)
What is happen is that you are really pushing the view controller twice, first time is from the storyboard push sequence, and the second time you do it from code,
You have 2 solutions
I will write down the easiest of them (if you need the second too, please ask)
you will need to pass the data from the prepareForSegue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"TheIdentifierOfYourDetailView"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
int currentindex = [indexPath row];
switch (currentindex) {
case 0:
_urlAddress = #"http://localhost:8888/thePalmsMenu/breakfast_menu.php";
break;
case 1:
_urlAddress = #"http://localhost:8888/thePalmsMenu/lunch_menu.php";
break;
default:
break;
}
[[segue destinationViewController] setUrlAddress:_urlAddress];
}
}
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.