UITableView and background image shift down after dismissing modalview - iphone

I have a view with a tab bar and a table view in it. At a certain point I call a modal view, and when I dismiss it, the original view seems to stretch or shift down, as the background image is lower and the table view is longer to the point that the last cell goes outside of the frame.
I call the modal view from tableView:didSelectRowAtIndexPath:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableDictionary *row = [[[[self.productDetails valueForKey:#"sections"] objectAtIndex:indexPath.section] valueForKey:#"rows"] objectAtIndex:indexPath.row];
NSMutableDictionary *post = [self.productDetails valueForKey:#"post"];
if ([[row valueForKey:#"type"] isEqualToString:#"pick"])
{
self.xPicker = nil;
self.xPicker.options = nil;
self.pickerOptions = nil;
self.xPicker = [[[TablePicker alloc] initWithNibName:#"TablePicker" bundle:[NSBundle mainBundle]] autorelease];
NSMutableDictionary *newPicker = [[[NSMutableDictionary alloc] init] autorelease];
[newPicker setValue:[NSString stringWithFormat:#"%d", indexPath.section] forKey:#"section"];
[newPicker setValue:[NSString stringWithFormat:#"%d", indexPath.row] forKey:#"row"];
[newPicker setValue:[row valueForKey:#"options"] forKey:#"options"];
[newPicker setValue:[row valueForKey:#"value"] forKey:#"value"];
self.xPicker.options = [NSMutableDictionary dictionaryWithDictionary:newPicker];
self.pickerOptions = [NSDictionary dictionaryWithDictionary:newPicker];
self.xPicker.view.frame = self.view.bounds;
[self presentModalViewController:self.xPicker animated:YES];
}
}
Inside the modal view controller, I do some stuff, then fire a notification to let the parent controller know that it's ok to dismiss it. This part doesn't feel right, but I was getting an error occassionally when I had the modal view call:
[self.parentViewController dismissModalViewControllerAnimated:YES];
Anyways, the code block where I dismiss the modal view is as follows:
- (void) reloadCell
{
[self dismissModalViewControllerAnimated:YES];
if (self.xPicker.choice != nil)
{
NSDictionary *choice = self.xPicker.choice;
NSMutableDictionary *row = [[[[self.productDetails valueForKey:#"sections"] objectAtIndex:[[choice valueForKey:#"section"] intValue]] valueForKey:#"rows"] objectAtIndex:[[choice valueForKey:#"row"] intValue]];
[row setObject:[choice valueForKey:#"value"] forKey:#"value"];
[[self.productDetails valueForKey:#"post"] setObject:[choice valueForKey:#"value"] forKey:[row valueForKey:#"key"]];
Request *xReq = [[[Request alloc] init] autorelease];
[NSThread detachNewThreadSelector:#selector(updateOrderFromProductDetails:) toTarget:xReq withObject:[self.productDetails valueForKey:#"post"]];
self.xPicker = nil;
}
}
I don't know if it's related, but I found it interesting that when I present this modal controller while the device is in landscape mode, the view goes wonky.
Does anyone know how to fix this?
Edit: After some searching, it appears that both problems are related and have to do with my current view controller being a subview of another view controller, but not through a navigation controller.
As I understand it, my decision to not use a nav controller is confusing my view stack somehow. I had a feeling that would come back to bite me in the butt.
Unfortunately, I can't seem to find a solution to my problem anywhere.

If you have a nib associated with the views - go into the nib for each view and set the following attributes on the main view in each nib:
status bar: unspecified
top bar: unspecified
bottom bar: unspecified
Then in the size:
x:0,y:0,W:320,H480
Back in the code after you've allocated each view also set the 'wantsFullScreenLayout' property to TRUE on each view.
Edit: explanation (? maybe ? :-))
If you have a status bar specified in the nib then the uikit automatically adjusts your views to cater for the status bar (and you'll see that your view yorigin is 0 and height is 460 in the nib) but if you look at the offset/height in the frame in your viewDidAppear method its been set to 20/460 to cater for the status bar.
For some reason presenting modal view controllers seems to mess with this and adjust for the status bar again, shifting your view down another 20. Setting status bar to unspecified and setting wantsFullScreen layout stops uikit messing with your frame and gives you control (hence setting height to 480 in the nib sizing).
You will also need to move all of your view elements down by 20 to cater for the status bar height in the 480 height view.

Ok, I have one solution to this problem.
Instead of having my current view controller call presentModalViewController, I call it from the appDelegate tabBarController like so:
[[(myAppDelegate *)[[UIApplication sharedApplication] delegate] tabBarController] presentModalViewController:self.myModalViewController animated:YES];
This still feels a bit hacky, but it solves both problems of
1) resizing when the modal view is dismissed
2) the modal view being unable to pop up in landscape mode
I hope this can help someone else out there in this cruel Apple world.

When your main view comes back, try adding:
[myTableView setFrame:CGRectMake(0,0,320,411)];.

Related

iOS: Why isn't this View Controller getting pushed onto the Navigation Controller's stack?

I have a modal view that is a Navigation Controller. When one of the rows in its UITableView gets tapped, the correct View Controller for that row should be initialized and pushed onto the Navigation Controller's stack (so that the screen now shows that View Controller). But it's not working. I've been trying to debug it for a while, and it appears that the Navigation Controller's retain count is 0 at the time pushViewController is called. I assume that means it has been deallocated, and that this is the root of the problem. But I can't figure out why.
In the following code, AddSportDelegate.m presents the modal view that contains the necessary Navigation Controller (_addItemNavController) initialized with the necessary AddItemTableViewController. Tapping on one of the rows of the Table View managed by AddItemViewController calls the showAddItemDataView: method of AddSportDelegate, which in turn should push the correct ViewController onto the _addItemNavController stack. But, as I note in a comment in the code, the retain count of _addItemNavController at that moment is 0.
Note: I realize this code has memory leaks. I deleted some release lines for the sake of brevity. I also haven't included the code for the view controller that is supposed to be getting pushed, since it doesn't have anything at the moment beyond a UILabel identifying that it is the right View Controller.
AddItemDelegate.m
#synthesize addItemNavController = _addItemNavController;
- (void)showAddItemViewController:(UIViewController *)viewController
{
_parentVC = viewController;
[_parentVC retain];
tc = [[AddItemTableViewController alloc] init];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonSystemItemDone target:self action:#selector(cancelAdd)];
tc.navigationItem.leftBarButtonItem = cancelButton;
tc.title = #"Select a Category";
_addItemNavController = [[AddItemNavController alloc] initWithRootViewController:tc];
tc.superViewController = _addItemNavController;
[_parentVC.navigationController presentModalViewController:_addItemNavController animated:YES];
}
- (void)showAddItemDataView:(SportCategory *)category
{
[category retain];
UIViewController *vc;
if (category.name == #"Soccer") {
vc = [[AddSoccerDataViewController alloc] init];
}else{
vc = [[AddBaseballDataViewController alloc] init];
}
//retain count already 0
NSLog(#"retain count: %i", [_addItemNavController retainCount]);
[_addItemNavController.navigationController pushViewController:vc animated:YES];
}
AddItemTableViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
_addItemDelegate = [[AddItemDelegate alloc] init];
SportCategory *soccer = [[SportCategory alloc] initWithCategoryName:#"Soccer"];
SportCategory *baseball = [[SportCategory alloc] initWithCategoryName:#"Baseball"];
_categories = [[NSArray alloc] initWithObjects:soccer,baseball,nil];
[self.tableView reloadData];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SportCategory *selectedCategory = [_categories objectAtIndex:[indexPath row]];
[_addItemDelegate showAddItemDataView:selectedCategory];
}
I am going to take a shot at this.
if (category.name == #"Soccer")
I come from a java background, but I know a little objective - c. I thought you can't compare strings with == which would mean your view controller was never created. Maybe try a isEqualToString method.
That is my only thought, I could be wrong. But Best of Luck.
The '==' operator isn't the good way to compare strings, but anyway your code should fall into the else part.
About your question, _addItemNavController must be nil because your NSLog prints 0 for its retain count.
Is the method -(void)showAddItemViewController:(UIViewController *)viewController called somewhere ?
Your view controller doesn't seem to be initialized.
A bit of sleep helped me find the problem. There were actually two:
1) The final line in AddItemDelegate read:
[_addItemNavController.navigationController pushViewController:vc animated:YES];
However, _addItemNavController IS the navigation controller, so the '.navigationController' part needed to be deleted.
2) I also needed to assign tc.addItemDelegate to self in showAddItemViewController.

Odd problem loading data into a text view field

BRIEF: Hi everyone, painfully working through my first app and couldn't find a solution online for this "loading" issue.
ATTEMPTING TO DO: (a)From a table view (b)click on a cell (c)push into a detail view where a text view field is populated from a property list file.
PROBLEM: Everything works (Nav bar title updates, backbutton title changes correctly) but the text view field comes up blank (default state from IB)... at first. If I click the back button, then back into the detail view, the text view then loads correctly and stays that way. Totally baffled.
CODE:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// check for view controller
if (self.savedScriptDetailVC ==nil) {
SavedScriptDetailVC *savedScriptDetail =[[SavedScriptDetailVC alloc]initWithNibName:#"SavedScriptDetailView" bundle:nil];
self.savedScriptDetailVC = savedScriptDetail;
[savedScriptDetail release];
}
// find row
NSInteger row = [indexPath row];
//get filepaths and set up arrays for pulling project names and text data
NSString *textPath = [self savedScriptsPath]; //using outside function to get path
NSArray *array = [self savedProjectNames]; //using outside function to pull project names
NSArray *textArray =[[NSArray alloc] initWithContentsOfFile:textPath];
textArray = [textArray valueForKey:#"ScriptText"];
//populate text for textview in detail view. Works, but have to drill down, back out before effect takes, why?-->
savedScriptDetailVC.fScriptText.text = [NSString stringWithFormat:#"%#", [textArray objectAtIndex:row]];
savedScriptDetailVC.title =[NSString stringWithFormat:#"%#", [array objectAtIndex:row]]; //change nav title
//change back button text
UIBarButtonItem *backButton =[[UIBarButtonItem alloc]initWithTitle:#"Scripts" style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem =backButton;
[backButton release];
//using delegate to push views
ProjectAppDelegate *delegate =[[UIApplication sharedApplication]delegate];
[delegate.savedScriptsNavC pushViewController:savedScriptDetailVC animated:YES];
}
The actual view (and all its subviews) for a view controller is not loaded until it is needed, i.e. the view property is accessed. So at the point that you first try to set the value of the text field, the text field doesn't exist yet because none of the views in the view controller have been created yet. If you were to check, you would find that savedScriptDetailVC.fScriptText is nil.
A simple fix is to access [savedScriptDetailVC view] before trying to manipulate those subviews.
I agree with Anomie's explanation: the first time you try to set the text of the text field, the text field is nil so nothing happens. I recommend a different solution, however. Give SavedScriptDetailVC one or more properties where you can store whatever information it needs to do its job, and set those properties before you push the controller onto the navigation stack. Let SavedScriptDetailVC be responsible for properly configuring the views that it manages, probably in its -viewWillAppear method.
In general, the only object that should manipulate a view controller's views is that view controller. If you adopt that philosophy you'll completely avoid the kind of problem you're having now.

Need help with table view detail view. I can't seem to write values to the UILabel's text property?

I have a TableView setup as my "root view controller" and a detail view controller called "DetailController" to allow me to display the detail view for what the user selects in the main table view. The navigation is working correctly - when I select a row in my table the screen slides to the left and I'm presented with my detail view, but I can't update the detail view's UILabel text properties. It's like it is just ignoring my UILable set property methods (see code below) as it isn't giving me any errors or warnings.
I've tested that I do have the values I need when I hit the "tableView:didSelectRowAtIndexPath:" method, but for some reason I can't seem to update the detail view's UILabels' text properties. Using NSLog's I can see that I am getting the values and passing them correctly to the UILabels. However the detail view isn't showing the passed values in its UILabels.
The strange thing is that I CAN set the detail view's title property correctly, just not the UILabel properties, which is very confusing!
Here's my code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
if (self.detailView == nil) {
DetailController *detail = [[DetailController alloc] initWithNibName:#"DetailController" bundle:nil];
self.detailView = detail;
[detail release];
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSDictionary *dict = [[NSDictionary alloc] initWithDictionary:[theList getItem:cell.textLabel.text]];
[self.detailView.theID setText:#"LOOK AT ME NOW!"];
self.detailView.thePrice.text = [[dict objectForKey:#"PRICE"] stringValue];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:self.detailView animated:YES];
NSString *newTitle = [[NSString alloc] initWithString: #"Viewing Attributes for: "];
newTitle = [newTitle stringByAppendingString:cell.textLabel.text];
self.detailView.title = newTitle;
[dict release];
[newTitle release];
}
Thanks for any help on this! I love this community - you guys are awesome!
I Found An Answer! I think...
After much trial and error I was able to figure out a solution, but the solution doesn't entirely make sense to me and still leaves me with questions.
Siegfried suggested that I move my "pushViewController" message to the end of the method so that the last six lines of my code were changed to:
// Pass the selected object to the new view controller.
NSString *newTitle = [[NSString alloc] initWithString: #"Viewing Attributes for: "];
newTitle = [newTitle stringByAppendingString:cell.textLabel.text];
self.detailView.title = newTitle;
[dict release];
[newTitle release];
[self.navigationController pushViewController:self.detailView animated:YES];
}
When I moved it to the last line (as shown above) my program would crash with an EXC_BAD_ACCESS error. I still don't understand why moving that line of code to the end of my method caused the program to crash?
However, this got me thinking - I was able to successfully change the title of the new detail view, but not the view's UILabel properties. So I moved my pushViewController method back to where it was and then moved my methods for setting the new view's UILabels underneath the pushViewController method and BOOM! Everything worked! The new detail view was being pushed when the user clicked on the row in my table and its properties (title and UILabels) were all being set correctly.
Why does this work?
I remember reading about how the iPhone handles views, something called "lazy loading" or "lazy instantiation"? I think this means that I can't change the properties of my detail view UNTIL I've "pushed" it, which is when the object is actually created in memory and its properties become available?
If this is correct, that would kind of explain the error I was getting when I moved the pushViewController method to the end of my code - I was trying to change the title property on a view that wasn't instantiated. However, that doesn't completely make sense because I was trying to change the UILabel properties of the view in my original code, which was before the view was actually pushed and the program wasn't crashing?
So my code that is currently working looks like the following:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
if (self.detailView == nil) {
DetailController *detail = [[DetailController alloc] initWithNibName:#"DetailController" bundle:nil];
self.detailView = detail;
[detail release];
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSDictionary *dict = [[NSDictionary alloc] initWithDictionary:[theList getItem:cell.textLabel.text]];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:self.detailView animated:YES];
// Moved the "set property" methods to happen after the detail view is pushed
[self.detailView.theID setText:#"LOOK AT ME NOW!"];
self.detailView.thePrice.text = [[dict objectForKey:#"PRICE"] stringValue];
NSString *newTitle = [[NSString alloc] initWithString: #"Viewing Attributes for: "];
newTitle = [newTitle stringByAppendingString:cell.textLabel.text];
self.detailView.title = newTitle;
[dict release];
[newTitle release];
}
I like that the code is working, but I would really like to understand what was and is actually happening. Can anyone offer additional insight here?
Thank you!
Move
[self.navigationController pushViewController:self.detailView animated:YES];
to last line.
You may try doing:
NSString *strText = [NSString stringWithFormat:#"%#",[[dict objectForKey:#"PRICE"] stringValue]];
self.detailView.thePrice.text = [NSString stringWithFormat:#"%#",strText];
Check wheather strText is containing the expected value and is not nil.

UITableView doesn't remember its scroll position

I can post code, though I'm not quite sure where to start. I have a UITableView with custom cells, in the middle tier of a three-level UINavigationView scheme. When I drill down to the bottom level and see details about my data, then use the top-left back button, my table view comes back scrolled all the way to the top, rather than scrolled down to where it was when I last saw it. Where should I look about this?
EDIT:
Here's the code in the EventsTableViewController to load an instance of EventsDetailViewController and push it on the navigation controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *events = [DataManager sharedDataManager].eventList;
Event *selectedEvent = [events objectAtIndex:[indexPath row]];
EventsDetailViewController *detailController = [[EventsDetailViewController alloc] initWithNibName:#"EventsDetailView" bundle:nil];
[detailController loadEvent:selectedEvent];
MyAppDelegate *del = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
[del.navigationController pushViewController:detailController animated:YES];
[detailController release];
}
There's nothing in my EventsTableViewController about reloading the table, as far as I can see--I haven't implemented viewWillAppear or viewDidLoad or any of those.
Are you by any chance reloading the table where you shouldn't be? In the ViewWillAppear method for example?
I was accidentally instantiating a new copy of my table view whenever I hit this level of the navigation. That's all fixed now and this isn't a problem anymore.
I have a very similar question. When I go to the chatView, which is a UIViewController belonging to the navigationController and has a tableView as a subview, and then I scroll to some point, and then I go back, and then I go back to the chatView, it starts at the top again. I am allocing the tableView in loadView of the chatView, so the tableView gets realloced everytime I come back. But, isn't this standard? How do I just always scroll to the bottom like the iPhone Messages app and show the scroll bar for a few seconds at the bottom?
Here's the code:
http://github.com/acani/acani-chat/blob/master/Lovers2/Classes/ChatViewController.m#L184
Thanks!
Matt
are you doing:
[self presentViewController:navController animated:YES completion:nil];
where self is tableviewcontroller? give this a try:
[self.navigationController presentViewController:navController animated:YES completion:nil];

How to pushviewcontroller to a viewcontroller stored in a tabbaritem?

First of all I know this is a long question. REST ASSURED I have tried to figure it out on my own (see: StackOverflow #2609318). This is driving me BATTY!
After trying and failing to implement my own EDIT feature in the standard moreNavigationController, I have decided to re-implement my own MORE feature.
I did the following:
Add a HOME view controller which I init with: initWithRootViewController
Add 3 other default tabs with:
ResortsListViewController *resortsListViewController;
resortsListViewController = [[ResortsListViewController alloc] initWithNibName:#"ResortsListView" bundle:nil];
resortsListViewController.title = [categoriesDictionary objectForKey:#"category_name"];
resortsListViewController.tabBarItem.image = [UIImage imageNamed:#"whatever.png"];
resortsListViewController.navigationItem.title=#"whatever title";
localNavigationController = [[UINavigationController alloc] initWithRootViewController:resortsListViewController];
localNavigationController.navigationBar.barStyle = UIBarStyleBlack;
[localControllersArray addObject:localNavigationController];
[localNavigationController release];
[resortsListViewController release];
Those work when i add them to the tabbar. (ie: click on them and it goes to the view controller)
Then I add my own MORE view controller to the tabbar:
MoreViewController *moreViewController;
moreViewController = [[MoreViewController alloc] initWithNibName:#"MoreView" bundle:nil];
moreViewController.title = #"More";
moreViewController.tabBarItem.image = [UIImage imageNamed:#"more.png"];
moreViewController.navigationItem.title=#"More Categories";
localNavigationController = [[UINavigationController alloc] initWithRootViewController:moreViewController];
localNavigationController.navigationBar.barStyle = UIBarStyleBlack;
[localControllersArray addObject:localNavigationController];
[localNavigationController release];
[moreViewController release];
Then
tabBarController.viewControllers = localControllersArray;
tabBarController.moreNavigationController.navigationBar.barStyle = UIBarStyleBlack;
tabBarController.customizableViewControllers = [NSArray arrayWithObjects:nil];
tabBarController.delegate = self;
That creates the necessary linkages. Okay, so far all is well. I get a HOME tab, 3 category tabs and a customized MORE tab -- which all work.
in the MORE tab view controller I implement a simple table view that displays all the other tabs I have in rows. SINCE I want to be able to switch them in and out of the tabbar I created them JUST like i did the resortslistviewcontroller above (ie: as view controllers in an array). When I pull them out to display the title in the tableview (so the user can go to that "view") i simply do the following:
// [myGizmoClass CategoryArray] holds the array of view controller tab bar items that are NOT shown on the main screen.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
... etc...
UIViewController *Uivc = [[myGizmoClass plusCategoryArray] objectAtIndex:indexPath.row];
cell.textLabel.text = [Uivc title];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
THIS is where it falls through:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MyGizmoClass *myGizmoClass= [MyGizmoClass sharedManager];
UIViewController *tbi = [[myGizmoClass plusCategoryArray] objectAtIndex:indexPath.row];
NSLog(#"%#\n",[[tbi navigationItem ]title]);
[self.navigationController pushViewController:tbi animated:YES];
}
This is the error i get ("ATMs" is the title for the clicked tableview cell) so i know the Uivc title is pulling the correct title and therefore the correct "objectatindex":
2010-04-09 11:25:48.222
MouseAddict[47485:207] ATMs 2010-04-09
11:25:48.222 MouseAddict[47485:207]
*** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason:
'Pushing a navigation controller is
not supported'
BIG QUESTION: How do i make the associated VIEW of the UIViewController *tbi show and get pushed into view?
I am GUESSING that the UIViewController is the correct class for this tbl .. i am not sure. BUT i just wanna get the view so i can push it onto the stack.
Can someone plz help?
To answer kovpas's question below: myGizmoClass is a singleton (apple's singleton myGizmo class. The array of viewcontrollers is stored in that just like it is in [localControllersArray addObject:localNavigationController]; (in the first code snippet above). AND it does put it in and pull it out correctly as evidenced by the fact that when i NSLOG the [Uivc title] the log prints ATMs. This means the plusCategoryArray is correctly storing and retrieving the viewController (if, indeed, that is what is being stored).
Pushing a navigation controller is not supported is really bothering me. Why would a viewController return a navigationController and is it possible to coerce the navigationController to get the "pushable" view out of it... or does the navigationController have some element in it that is the view?
From the error, it looks as if your Gizmo class has an array of UINavigationControllers, not UIViewControllers. So instead push with:
[self.navigationController pushViewController:[[tbi viewControllers] lastObject] animated:YES];
If the array is the same array as you called localControllers above, then this should work better. Or you could just create the array without the UINavigationControllers, they aren't needed if you are going to push them onto your more controller navigation controller.
I'm not sure, but it looks like this error appears when you are trying to push UINavigationController into another UINavigationController. Could you please provide an implementation of MyGizmoClass?