Confusion with UINavigationControllers in SplitViewController - objective-c

I am setting up an iPad app that uses a SplitViewController. In my app delegate I have the following in didFinishLaunchingWithOptions:
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *leftNavController = [splitViewController.viewControllers objectAtIndex:0];
LeftViewController *leftViewController = (LeftViewController*)[leftNavController topViewController];
DetailViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
NSLog(#"Detail View Ctrl >> %#", [detailViewController class]);
When I run the app, the NSLog statement returns "UINavigationController" when DetailViewController is actually a subclass of UIViewController. However, in XCode, code completion shows all the methods that are implemented in the DetailViewController subclass. Any ideas? Thanks!

I think your DetailViewController is actually embedded inside a UINavigationController, and your fourth line is in error. Take a look instead at the topViewController for the second view controller inside your split view controller, much like you do for the LeftViewController.
The reason Xcode is continuing to suggest completion for DetailViewController methods is because you've given it that type. Code completion doesn't rely on runtime behavior (how could it?) – instead, it relies on static analysis of the code that you type. If you tell Xcode that something is a DetailViewController, it'll believe you and autocomplete based on that information.

Related

Objective-C (iOS): prepareForSegue won't pass my data into destination VC

VC1 = NewGameViewController
VC2 = GameViewController
NewGameViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if( [segue.identifier isEqualToString:#"newGameSegue"]) {
GameViewController *gameVC = (GameViewController *)segue.destinationViewController;
NSArray *array = [self nameArrayForTextFieldArray:self.namePicker.textFieldArray withColon:YES];
gameVC.nameArray = [[NSArray alloc] initWithArray:array];
}
-(NSArray *)nameArrayForTextFieldArray:(NSArray *)array withColon:(BOOL *)bool
basically returns an nsarray of strings given an nsarray of textfields. withcolon is a bool of whether or not you want the strings to have a colon appended to the end.
when i debug my code, the _nameArray ivar in gameVC still reads nil after every line here is called...can anyone help me out here??
The prepareForSegue method is invoked by UIKit when a segue from one screen to another is about to be performed. It allows us to give data to the new view controller before it will be displayed. Usually you’ll do that by setting its properties.
The new view controller can be found in segue.destinationViewController. If GameViewController embed the navigation controller, the new view controller will not be GameViewController but the navigation controller that embeds it.
To get the GameViewController object, we can look at the navigation controller’s topViewController property. This property refers to the screen that is currently active inside the navigation controller.
To send an object to the new view controller you can use this solution using performSegueWithIdentifier:
For example, if we want to perform a segue pressing a UIButton we can do this:
In the MyViewController.h we create a IBAction (connected to UIButton), dragging the button from storyboard to code:
- (IBAction)sendData:(id)sender;
In MyViewController.m we implement the method:
- (IBAction)sendData:(id)sender
{
NSArray *array = [self nameArrayForTextFieldArray:self.namePicker.textFieldArray withColon:YES];
[self performSegueWithIdentifier:#"newGameSegue" sender:array];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"newGameSegue"]) {
UINavigationController *navigationController = segue.destinationViewController;
GameViewController *controller = (GameViewController *)navigationController.topViewController;
controller.nameArray = sender;
}
}
Is GameViewController embedded in a navigation controller? In that case, your destinationViewController property is of type UINavigationController, not GameViewController. You can get to GameViewController by calling [segue.destinationViewController.viewControllers lastObject].
I'm assuming that you've done a NSLog (or examine it in the debugger) of array immediately before setting gameVC.nameArray. You really want to make sure it's being set the way you think it is. It's amazing how many times I've spent debugging something like this only to realize the problem was in my equivalent to nameArrayForTextFieldArray. Or a typo in the name of the segue identifier. Or random things like that.
Assuming that's ok, then a couple of things are possible:
How is your nameArray property defined in GameViewController? If it's not a strong reference (or a copy), then when your array falls out of scope, it will be deallocated. I think this would manifest itself slightly differently, but it's worth confirming.
Also, I've seen situations where a controller like GameViewController might have some confusion between various ivars and properties (which is why I never define ivars for my properties ... I let #synthesize do that).
I assume you're not using a custom setter for nameArray. I just want to make sure. If so, though, please share that, too.
Bottom line, can you show us all references to nameArray in your #interface of GameViewController as well as in its #synthesize statement?

UISplitViewController setting viewControllers a second time crashes

I am using a UISplitViewController on ARC.
I setup the controller in my AppDelegate, then make it the rootViewController. I have made sure to make it a property:
#property (strong, nonatomic) UISplitViewController *splitViewController;
Setting the root and detail using the viewControllers property works fine when first creating. And it works fine again when setting a second time, but I get a crash on the third time I try setting the viewControllers property.
Here is how I set those:
Screens *edit = [[Screens alloc] initWithStyle:UITableViewStyleGrouped];
UINavigationController *c = [[UINavigationController alloc] initWithRootViewController:edit];
if ([Utility isIpad]) {
Map *a = (Map *)[[MyAppDelegate instance].splitViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObjects:a, c, nil];
UISplitViewController *splitView = [MyAppDelegate instance].splitViewController;
splitView.viewControllers = viewControllers;// <--- Crashes here
} else {
[self presentModalViewController:c animated:YES];
}//end
Why would it crash when I try to set the viewControllers property? It almost seems like it is released, but I know that the splitViewController is still there...
Could this be something to do with ARC?
Stacktrace:
I had the same error just now. In my case the problem was that I had originally set up the detail view controller to be a delegate of the UISplitViewController. Then I refactored to make the root view controller handle things, but I forgot to remove the connection from the .xib file.
So, when I set splitView.viewControllers the first time everything worked, but then my original detail controller would get released and the UISplitViewController was left with a bad pointer as delegate. The next time I set the viewControllers property, the UISplitViewController tried to call its delegate through the pointer and crashed.
You write that you set up the controller in your app delegate, so this may not be exactly the same problem you had. Still, double check to make sure the delegate property of the UISplitViewController is set correctly!

AppDelegate can't add subView

Since appDelegate does not have a view, just window, its hard to figure out how to load a view from it. My problem has for long been that when didReceiveLocalNotification fires i cant load a new view with that event. I have been working around it til the point that i must do something about it. When i tries to addSubview, xcode gives me the error:
Receiver tupe 'UIWindow' for instance messages does not declare a method with selector 'addSubView'
for this: (at [self.window addSubView:view];)
screwLightBulbViewController *view = [screwLightBulbViewController newMyView];
[self.window addSubView:view];
I understand that the appDelegate file does'nt have a addSubview but i want to switch to a particular view when it fires.
I have tried many other ways, like calling a function in screwLightBulbViewController and make a view from that. My function in the viewController now looks like this:
+(id)newMyView
{
UINib *nib = [UINib nibWithNibName:#"MyView" bundle:nil];
NSArray *nibArray = [nib instantiateWithOwner:self options:nil];
screwLightBulbViewController *me = [nibArray objectAtIndex: 0];
return me;
}
any help in any way would be realy appreciated and thanks for you time. :)
It's addSubview not addSubView:. UIWindow is a subclass of UIView.
Adding a view directly as a subview to the window is not usually recommended, so instead you should try and add the view as a subview to the top controller view. If you can spare some time you should look over the view programming guide and view controller programming guide, it will be useful in the future.

How to present a view controller from another view controller

I am trying to open a ViewController from within another ViewController if certain conditions are met. The code seems to run without error but the view is never shown. I am new to xcode 4 /ios 5 so I must be missing something.
Here is the code responsible for opening the second viewcontroller:
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
[self presentViewController:createUserController animated:YES completion:nil];
In my project I have a xib called, "CreateUserView". I have added a view controller to this xib and assigned it to, "CreateUserViewController".
Also I noticed in the apple documentation that is shows setting the delegate of the viewcontroller to be presented. But it seems that no property called, "delegate" is on the viewcontroller object. Is this documentation old? This is the document I am trying to use (section 9-1):
View Controller Programming
Can someone give me a hint? Thanks..
edit Adding Custom Constructor
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil keyWrapper:(KeychainItemWrapper *)keyWrapper
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self){
[self setKeyChainWrapper:keyWrapper];
}
return self;
}
Regarding CreateUserView.xib: you don't want to put a CreateUserViewController object in the nib. You want to set the custom class of the File's Owner placeholder to CreateUserViewController. Then you need to connect the view outlet of File's Owner to the top-level view in the nib.
Regarding the delegate property: The UIViewController class doesn't have its own delegate property. The idea is that you add a delegate property to your subclass of UIViewController. The delegate provides a way for your presented view controller to pass custom information back to the presenting view controller.
Why would you want to do that? Let's consider the code you posted. I'll assume you have a UserListViewController that shows a list of User objects, and has a "Create new user" button. When the user touches the "Create new user" button, you create a CreateUserViewController and present it.
The user interacts with the CreateUserViewController to set the attributes of the new User object - name, rank, hairstyle, etc. Then he touches a "Done" button. Your CreateUserViewController creates the new User object and puts it in the database. Then it needs to dismiss itself, so the UserListViewController's list of User objects will appear again.
But you want the User list to include the newly created User object and you want to scroll the list so that the new User is on the screen. So you need a way to have your CreateUserViewController tell the UserListViewController about the newly created User object. This is where the delegate comes in.
You define a protocol like this:
#protocol CreateUserViewControllerDelegate
- (void)didCreateUser:(User *)user;
#end
and you give your CreateUserViewController a delegate property:
#interface CreateUserViewController
#property (weak, nonatomic) id<CreateUserViewControllerDelegate> delegate;
// ...
When your CreateUserViewController's "Done" button is touched, you notify your delegate of the new User:
- (IBAction)doneButtonWasTouched:(id)sender {
User *user = [self createUser];
[self.delegate didCreateUser:user];
[self dismissViewControllerAnimated:YES completion:nil];
}
In your UserListViewController, you adopt and implement the protocol:
#interface UserListViewController <CreateUserViewControllerDelegate, UITableViewDelegate, UITableViewDataSource>
// ...
#end
#implementation UserListViewController
- (void)didCreateUser:(User *)user {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.users count] inSection:0];
[self.users addObject:user];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation: UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition: UITableViewScrollPositionNone animated:YES];
}
and when you need to present a CreateUserViewController, you set the new controller's delegate to the UserListViewController:
- (IBAction)createUserButtonWasTouched:(id)sender {
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
createUserController.delegate = self;
[self presentViewController:createUserController animated:YES completion:nil];
}
In iOS5 the method for pushing new view controllers was really changed around quite a bit from iOS4 and Xcode 3. In summary, storyboards are now used to create your application view controller flow. Even though you may use standalone .xib files to build an application it is much less common in iOS5.
Anyway, the main method for pushing new view controllers onto the screen is done using segues. Check out this tutorial for an introduction: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-1
It does a good job on explaining how to create a storyboard and use segues. You can still present view controllers in code "the old way" but it is much much less common now with the introduction of these new technologies. There are also some absolutely awesome tutorials on iTunes U - search for CS193P. It's the Stanford Introductory class to Objective-C and programming for iOS. This should get you started and maybe help you think of a way to push your createUserController in a way more up to speed with iOS5.
UPDATE
I just wanted to add. If you configure your program to use storyboards and segues you can use the method performSegueWithIdentifier:sender: to perform the segue to your createUserController view if the proper conditions are met. See the Apple API for UIViewController for information on how to use this method.

referencing an instantiated object in another class in objective C

I am working in xcode on an ipod app in objective C, and I have a field (navigationController) in one of my classes (rootViewController). How do I reference the instantiated rootViewController's navigationController from another class? For example, how would I do this if I want to reference the navigationController from the FirstViewController.m class?
I seem to only be able to find references for this to reference the application delegate.
If I want to reference the application delegate with FirstViewController, I just say:
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[delegate.navigationController pushViewController:childController animated:YES];
how do I do this for the rootViewController class instead of MyAppDelegate?
Based on the question you are asking, it seems that you want to call methods on the UINavigationController that is higher up in the stack. All UIViewControllers already have a property of navigationController which is the UINavigationController that is an ancestor to this ViewController on the stack.
So if you had a RootViewController, called root, that at some point did this
FirstViewController * first = [[FirstViewController alloc] initWithNibName:#"FirstView" bundle:nil];
[self.navigationController pushViewController:first animated:YES];
[first release];
Then first.navigationController == root.navigationController
So inside of first calling
[self navigationController]
will give you the same navigationController that first was pushed onto in the first place.
One object needs a reference to the other. There are many ways to make this happen. For example:
Have the same object create both and, since it knows them both, tell them about each other
Create them in the same nib and hook them up with normal connections
Have the owner of the nib one of the objects is in know about the other
Essentially, this is just a matter of designing your application properly so that the objects can be told about each other. There is no one magic thing you do.