Apple iOS5 and didFinishLaunchingWithOptions - objective-c

I have an iPhone App that uses UITableViewController to show a table on the screen. The data of the table (and more data used by the App) is initialized on: didFinishLaunchingWithOptions: method of my App Delegate.
The App works perfectly on iOS4. On iOS5, the data of the table doesn't load.
I checked and found that the didFinishLaunchingWithOptions: method is now called in parallel with showing the view. For this reason, when getting the size of the table, I go to my App Delegate, which still didn't load the table data and therefore return "0".
Is there a way of solving this issue in iOS 5?
Here is the method that gets called first on my view:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
PalmsterAppDelegate *appDelegate = (PalmsterAppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"tableView: %i", [appDelegate.listOfItems count]);
return [appDelegate.listOfItems count];
}
calling: [appDelegate.listOfItems count] returns zero. And it clearly shows that the order things is:
didFinishLaunchingWithOptions is called (I print to the log on the start of the method).
Then tableView is called and prints zero. Then didFinishLaunchingWithOptions finishes (I print to the log at the end of the method as well).

Move the initialization code into a separate method in your app delegate, and then call that method from the -viewDidLoad method of your table view controller.
Make sure you only initialize once, no matter how many times your initialization method is called.

Related

Why does an empty tableView check the number of sections but a non-empty one does not?

I have set up a demo application with a simple UITableViewController with no contents, but an 'Add' button in the toolbar. This launches a modal view controller which is again empty other than a 'cancel' button. The cancel button just tells its delegate (the UITableViewController) to dismiss the modal.
I then added an NSLog statement in the UITableViewController's numberOfSectionsInTableView method.
Ordinarily, when the table view controller loads I see two calls to numberOfSectionsInTableView. When I open and dismiss the modal (which returns to the UITableViewController) I see no further calls to numberOfSectionsInTableView.
However, if I return 0 from numberOfSectionsInTableView, in addition to the two calls on display, I also see an additional numberOfSections call when the modal is dismissed.
This only happens when numberOfSectionsInTableView returns 0, and I have added no additional code to my project besides that mentioned. This is easily verifiable by setting up a couple of controllers as I've described and modifying the result from numberOfSectionsInTableView.
My questions:
Why is the UITableView calling numberOfSectionsInTableView on return from a modal view?
Why is it only doing this if numberOfSectionsInTableView returns 0?
In addition to numberOfSectionsInTableView, the UITableViewController is also calling cellForRowAtIndex: when the modal is dismissed. In fact, it is attempting to display the new contents of its dataSource. How am I meant to manually animate a row insertion if the first row added is going to already be updated automatically? Shouldn't it be left to me to make sure that my UITableView is consistent with its dataSource?
What property is the UITableViewController checking to know that there is one or more sections (and therefore ask my delegate how many sections)? It can't be numberOfSectionsInTableView itself, since I would see it called whenever I return from the modal, not only when numberOfSections = 0.
From UITableViewController docs:
When the table view is about to appear the first time it’s loaded, the
table-view controller reloads the table view’s data... The
UITableViewController class implements this in the superclass method
viewWillAppear:
If you watch in the debugger, the second call upon app launch is from UITableViewController's viewWillAppear: implementation - specifically the part referred to above, where tableView is sent the reloadData message.
Now, the first call to numberOfSectionsInTableView: on launch is also from UITableViewController's implementation of viewWillAppear: but not directly from that implementation's call to -[UITableView reloadData]. I'm not sure what the first call is all about.
But, to your question, the call to numberOfSectionsInTableView: that happens when dismissing the modal has exactly the same call stack as the second call from applicationDidFinishLaunching:withOptions:. My hypothesis then is that UITableView interprets having zero sections as being in a state where it has not loaded at all. That does make some sense actually. I'd consider an "empty" table view to be one without any rows, but one without any sections seems almost "uninitialized" to me. Furthermore the UITableViewDataSource documentation implies UITableView has by default one section. Returning zero from this method would be inconsistent with that assumption of the docs as well.
Now, to your concern about animation - if you give the table an empty section to work with, you will be able to have full control over inserting the first row with whatever animation you'd like, and not be locked in to when you need to reload.
I think the moral of the story is, don't return zero sections unless you really, really need to for some reason. The title of your post refers to this table view being "empty" as well but I think it's clear the framework finds zero sections to not be empty but unloaded.
Hope this helps! And thanks for posting the sample project for me to play around with.
Perhaps the delegate just couldn't believe its eyes. But seriously, since a table view has to have at least one section, passing 0 doesn't make any sense. Why do it? You pass it an invalid argument and it gives you back a weird response. As to why it doesn't ask for number of sections when you pass 1, I think it's because it doesn't need to know at that point (coming back from the modal view controller) -- the table view has already been populated (if there were some data) and you haven't changed anything in the model, so it doesn't need to update.
I added a few lines to your example project to slide in a row each time you return from the modal view controller, which is what I think you're trying to do. I added an int property num for the return value of numberOfRowsInSection, added an array to populate the table, and a call to insertRowsAtIndexPaths from the modal view controller dismissal method.
- (void)viewDidLoad
{
_num = 0;
self.theData = #[#"one",#"two",#"three"];
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:#selector(addRecipe)];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"# sections requested");
//when 0, this fires on return from the modal. When 1, it does not.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"in numberOfRows in section");
return _num;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"in cellForRowAtIndexPath");
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
cell.textLabel.text = [self.theData objectAtIndex:indexPath.row];
return cell;
}
- (void)addRecipe
{
//create the modal and suscribe for delegate notifications
AddRecipeViewController *addRecipeController = [[AddRecipeViewController alloc]initWithStyle:UITableViewStyleGrouped];
addRecipeController.delegate = self;
//display the modal in a navigation controller
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addRecipeController];
[self.navigationController presentModalViewController:navController animated:YES];
}
- (void)addRecipeVC:(AddRecipeViewController *)addRecipeVC didAddRecipe:(NSString *)recipe
{
[self dismissModalViewControllerAnimated:YES];
_num += 1;
[self performSelector:#selector(addRow) withObject:nil afterDelay:.5];
}
-(void)addRow {
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:_num-1 inSection:0]] withRowAnimation:UITableViewRowAnimationRight];
}
The tableview checks number of sections when it's populating the table view with data!
Since the table can be divided into sections, it has to know specifically how many sections to divide it into.
When you reload the data, the number of sections is also checked.
Because every time the table view has to take action in accessing either the data of the table, like what row you tapped, and in what section, or populating the data table, the number of sections has to be known!
Hope this helped!

NSWindowController awakeFromNib is being called twice

I have an application that intends to create a popup window when a button is clicked. The popup window will load from a nib file. And so, the button is clicked and the window happily pops up. BUT, its awakeFromNib method gets called twice. Here's the code;
Application Delegate:
...
-(IBAction)myButton:(id)sender{
printf("[settings]: button pressed\n");
Config_SelectorSetup *selectorSetup = [[Config_SelectorSetup alloc] initWithWindowNibName:#"Config_SelectorSetup"];
printf("about to load\n");
[[selectorSetup window] makeKeyAndOrderFront:sender];
}
Config_SelectorSetup.m
- (id) initWithWindowNibName:(NSString *)windowNibName{
printf("[initWithWindowNibName]\n");
if( self = [super initWithWindowNibName:windowNibName] ){
...
}
return self;
}
- (void)awakeFromNib{
printf("[awakeFromNib]\n");
[self startScreen];
}
And here is the output:
[settings]: button pressed
[initWithWindowNibName]
about to load
[awakeFromNib]
[awakeFromNib]
Analyzing the call stack, first time it's called by [NSObject performSelector:] the second one by [NSIBObjectData nibInstantiateWithOwner:topLevelObjects:].
Can someone tell what am I doing wrong?
Thanks
Does Config_SelectorSetup.xib contain a Config_SelectorSetup object besides File's Owner?
Try logging self in awakeFromNib -
NSLog(#"self = %p", self);
Does it print the same address each time? If it's printing different addresses, chances are you have a Config_SelectorSetup object in your nib.
How many outlets do you have in your class and what is that class subclassing? I found with certain subclasses (NSDocument for instance), if you have multiple outlets connected, each nib object will fire the awakeFromNib method upon loading. NSLog your outlets to see if they output nil or an address.
If you create an object from a nib and specify the NSWindowController as the owner, the window controller will get an awakeFromNib.
For example, a common case is where the controller is a delegate for an NSTableView and the method
(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
is making the views like this,
return [tableView makeViewWithIdentifier:tableColumn.identifier
owner:self];
Note how self (the window controller) is passed as owner, which will cause it to see an awakeFromNib message every time this line is executed.
In this case it's better to pass nil as the owner, and not rely on getting awakeFromNib for table cell views here.
I don't know what object is being created with your controller specified as the owner in your case, but this should put you on the right track.

How to get Main Window (App Delegate) from other class (subclass of NSViewController)?

I'm trying to change my windows content, from other class , that is subclass of NSViewController.I'm trying code below, but it doesn't do anything.
[NSApplication sharedApplication]mainWindow]setContentView:[self view]]; //code in NSViewController
[NSApplication sharedApplication]mainWindow] // returns null
I tried to add
[window makeMainWindow];
in App Delegate class, but it won't help.
Did I miss something?
P.S. Also I'm using code below to call any delegate function in my class,
[(appDelegate *) [[NSApplication sharedApplication]delegate]MyMethod];
but I wonder is there something better, wihtout importing delegate class. Something like this
[[NSApplication sharedApplication]delegate]MyMethod];
(it gives warning)
For the mainWindow method the docs say:
This method might return nil if the application’s nib file hasn’t finished loading, if the receiver is not active, or if the application is hidden.
I just created a quick test application and I placed the following code:
NSLog(#"%#", [[NSApplication sharedApplication] mainWindow]);
into my applicationDidFinishLaunching:aNotification method, and into an action method which I connected to a button in the main window of my application.
On startup, the mainWindow was nil, but when I click the button (after everything is up and running and displayed), the mainWindow was no longer nil.
NSApplication provides other methods which you may be useful to you:
- windows - an array of all the windows;
– keyWindow - gives the window that is receiving keyboard input (or nil);
– windowWithWindowNumber: - returns a window corresponding to the window number - if you know the number of the window whose contents you wish to replace you could use this;
– makeWindowsPerform:inOrder: - sends a message to each window - you could use this to test each window to see if it's the one you are interested in.
With regard to calling methods on the delegate, what you say gives a warning works fine for me. For example, this works with no warnings:
NSLog(#"%#", [[[NSApplication sharedApplication]delegate] description]);
What exactly is the warning you receive? Are you trying to call a method that doesn't exist?
Fighting with MacOS just figured this out.
Apple's quote:
mainWindow
Property
The app’s main window. (read-only)
Discussion
The value in this property is nil when the app’s storyboard or nib file has not yet finished loading. It might also be nil when the app is inactive or hidden.
If you have only one window in your application (which is the most used case) use next code:
NSWindow *mainWindow = [[[NSApplication sharedApplication] windows] objectAtIndex:0];
Promise it won't be nil, if application has windows.
Swift flavored approaches for getting the main window (if present)
Application Main Window
guard let window = NSApplication.shared.mainWindow,
else {
// handle no main window present
}
// ... access window here
Application Window Array
let windowArray: [NSWindow] = NSApplication.shared.windows
guard windowArray.count > 0 else {
// hand case where no windows are present
}
let window = windowArray[0]
// ... access window here
If the window property isn't set yet, try delaying things until the app has finished loading, like so:
[myObject performSelector:#selector(theSelector) withObject:nil afterDelay:0.1];

indexPath null in didSelectRowAtIndexPath in an ipad splitview navigation app

I'm porting a fairly simple iPhone Navigation based app to a Split View iPad app.
I have two nested levels of navigation on the Master view. The user picks a value from the first table and it loads the 2nd table. Selecting a value on the second table loads the Detail item for the detail view. Or it's supposed to. The didSelectRowAtIndexPath on my 2nd controller is firing but indexPath is null.
I'm following the SplitView template fairly closely. I'm only really getting off the beaten track by adding that 2nd TableViewController. My RootViewController loads the 2nd TableViewController on didSelectRowAtIndexPath, and that part's working. In my 2nd TableViewController.m I'm trying to set the detail item for the DetailView. But it's that didSelectRowAtIndexPath method that's not giving me the row. I'm fairly new to this, but it seems odd to me that the method for clicking a row would fire but not have the index for that row.
Here's the code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TrackerSplitViewAppDelegate *appDelegate = (TrackerSplitViewAppDelegate *)[[UIApplication sharedApplication] delegate];
detailViewController.thisRequest = [appDelegate.requests objectAtIndex:indexPath.row];
NSLog(#"Request Loaded: %#", detailViewController.thisRequest.Title);
NSLog(#"Index Row: %#", indexPath.row);
[appDelegate release];
}
The array of requests is loading properly (appDelegate.requests) but my objectAtIndex is failing because indexPath is null. Or at least indexPath.row is null.
EDIT: Ok, the comments below are correct, I'm not using NSLog properly. The indexPath.row is fine (I thought it was also showing up as null using the mouseover in the debugger, but I just don't know how to use the debugger properly).
The detailViewController property isn't getting set right for some reason. If I substitute with this:
//detailViewController.thisRequest = [appDelegate.requests objectAtIndex:indexPath.row];
Request *aRequest = [appDelegate.requests objectAtIndex:indexPath.row];
The aRequest object loads just fine. So the next question is why setting my property on my detailViewController object isn't working. I'm still quite fuzzy on how things persist in this environment. Any additional input would be great.
The value of indexPath.row is not a string. You can't log it with %#. Try using %i or %li, which are used for integers. Also, you haven't reaLly properly checked for the request to be loaded, afaik. Is the title property only available after a successful load?
NSLog(#"Index Row: %#", indexPath.row);
indexPath is an NSIndexPath object, you could use %# to print it.
But indexPath.row and indexPath.section are NSUInteger. So you have to use %u to print them.
So maybe your indexpath.row is not nil (as in no object) but 0 (as in zero, the number before one)
Select a row that is not the first one and you should get a crash.

Select row After UIPickerView is loaded

I have an iPhone database app that loads a UIPickerView with data from a table. I want to set the selected row to a particular row from the data in another table.
For example: Let's say I have a UIPickerView that is loaded with X number of names of the iPhone users' friends (the number of names is variable; it could be 1 or 1000 and all are entered into the database by the user). The iPhone user has a preference set that their current best friend is TED. I want the UIPickerView to be position to TED when displayed.
Where do I call selectRow?
I tried in viewDidAppear, but it was never called, in titleForRow which caused all kinds of strange behavior. viewDidLoad and viewWillAppear are out of the question, because I don't know what's in the datasource to the picker yet.
Call it after you retrieve the data. I don't know where you load the data so this is just a mockup of what should work. If this doesn't make sense, let me know where you are loading data so I can piece it together in my head :)
-(void)viewDidLoad
{
// load data from sql
// self.someDataArray = DATAFROMSQL
[picker reloadAllComponents];
[picker selectRow:0 inComponent:0 animated:YES];
}
There is a common issue when a (default) UIPickerView item needs to be selected when the UIPickerView is initially shown. The problem is related to the sequence of events. From reading previous posts, it seems that the [pickerView selectRow:n] is normally called in the view controllers ViewDidLoad event. However, the UiPickerView data is only loaded after the ViewDidLoad event, which means any selection code in ViewDidLoad will have no effect. To remedy, place the selection code in ViewDidAppear;
- (void) viewDidAppear:(BOOL)animated {
[m_pickerView selectRow:nSelectedItem inComponent:0 animated:YES]; }
Place this wherever you initialize the UIView that contains your UIPickerView as a subview:
[myPickerView selectRow:rowWithTedsName inComponent:columnWithNames animated:NO];
I have just found that on the 4.3 SDK's the component loading behaviors are different between the iPhone and the iPad. On the iPhone, I was about to invoke selectRow: right after initializing the view before or after adding to the subview hierarchy. On iPad however, the selectRow call yielded no results. I wound up using a performSelector:withObject:afterDelay: call to wait 0.1 seconds before firing selectRow:inComponent:animated:. This had predictable results on the iPhone and iPad.
For Swift it's simply:
self.picker?.selectRow(0, inComponent: 0, animated: true)
If you want the selected row permanently highlighted use this
myPickerView.subviews[0].subviews[0].subviews[2].backgroundColor = myPickerView.tintColor;