Trouble creating UINavigationController to push just one VC - objective-c

I'm having trouble on such as trivial but fundamental concept in XCode.
I just want to be able to create an app from scratch and be presented with my root view controller and push and pop some VC for practice.
Believe me, I looked at tons of posts and none of them seem to do the trick.
So my steps were, I created a new xcode project, and created an empty application. I figure hell, lets learn here, instead of creating a single view app.
I went to new file then added a obj-c class and created the XIB with it. I called it RootViewController.
So here's my app delegate h file.
#import <UIKit/UIKit.h>
#import "RootViewController.h"
UINavigationController *navVC;
#interface VCTestAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (retain, nonatomic) UINavigationController *navVC;
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
#end
Notice I'm creating my own UINavigationController called, navVC.
Then here's the beginning and relevant section of my appledelegate m file...
#import "VCTestAppDelegate.h"
#implementation VCTestAppDelegate
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
#synthesize navVC;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIViewController *VC1 = [[UIViewController alloc]initWithNibName:#"RootViewController" bundle:nil];
navVC = [[UINavigationController alloc]initWithRootViewController:VC1];
[[self window] setRootViewController:navVC];
[self.window makeKeyAndVisible];
return YES;
}
And I just created a label on that root VC to verify its showing. Well, nope. No syntax errors and no crashing, the simulator just shows a black screen. What on earth and I doing? Then once I figure this out, I want practice to push and pop view controllers.
I can't believe I can't figure this out. I recently published my own app, but using storyboards. I want to move on to another project now but I want more solid fundamentals without using storyboards. I don't want to limit users of just iOS 5 and higher.
And by the way, I even tried by starting a single view application and adding a VC and when I click my button, it "should"; push the 2nd VC. I know the syntax for that, but when I tested the other app I was messing with, it would go into the IBAction block for the button but it would not push that VC....
I tried [self navigationController] pushview... I even tried just [navVC pushview...] nothing works.
I don't understand what I'm missing and how people made apps manually (programmatically before storyboards).
Any help would be super appreciated. Thanks!

You need to create the window. If you look at the default code for an Empty Application, it will have this line:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
If you're creating a simple application for practice, what are you doing with the core data stuff?

The only I can think of is that the UIWindow *window that your app needs is currently nil.
You said you made a XIB: try associating the "window" and "NavigationController" outlets to the correct controllers in the XIB.
Control+click from the controller to the window view. It should give you an option to click on "window". This should associate the window with the controller. Try doing the same with the UINavigationController's controller and view.

Related

Reference outlet to main NSWindow in AppDelegate (OSX)

When you create a new project, I seem to remember you being able to access the basic blank window it creates in the storyboard using self.window in the AppDelegate methods.
I can't seem to set a reference outlet to the AppDelegate.h file via the storyboard either. Am I just hallucinating or is there no way to access the window/controller from AppDelegate?
Am I mixing up iOS and OSX practices?
Using storyboards, it seems impossible to connect the WindowController to AppDelegate. I used the following to get a reference to the main window:
AppDelegate.h
#property (weak) NSWindow* window;
AppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_window = [[[NSApplication sharedApplication] windows] firstObject];
}
See this question for swift solution. Hope this helps :)
It's working for me.
I added an IBOutlet to my AppDelegate.h
#property (nonatomic) IBOutlet NSWindow *window;
Then in your Interface Builder (MainMenu.xib for me) select App Delegate:
Your IBOutlet should be in the Connection inspector to the right:
Maybe the connection between your .xib and your AppDelegate is lost?

Navigating from one UITableView to another inside appDelegate

The first UITableView is presented inside a Popover that is called from the RootViewController of the application.
I need to navigate to another UITableView inside the same popover. This is easy to do if you just instance an object of the second UITableView and push it from the first one.
In the next paragraph I write as taking for granted some facts, please correct me if I'm wrong.
The problem here is that this process should be done inside the appDelegate. This is because I'm implementing Dropbox API and I need the pushViewController to be done immediately after the login process is done, which means the navigation through UITableViews has to be done inside of the application:handleOpenURL. I asume that application:handleOpenURL has to be called right there and that's why I also asume the pushViewController has to be done there in order to have the navigation done after the Dropbox API validation window is presented, without having to make the user do anything else.
This is how my code looks like:
AppDelegate.h
#interface AppDelegate : NSObject <UIApplicationDelegate>{
UINavigationController *navigationController;
NSString *relinkUserId;
UIWindow *window;
TableViewControllerForStorageList *rootViewController;
ViewController *viewController;
}
#property (nonatomic, strong) IBOutlet UIWindow *window;
#property (nonatomic, strong) IBOutlet UINavigationController *navigationController;
#property (nonatomic, strong) IBOutlet TableViewControllerForStorageList *rootViewController;
#property (nonatomic, strong) IBOutlet ViewController *viewController;
AppDelegate.m
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if ([[DBSession sharedSession] handleOpenURL:url]) {
if ([[DBSession sharedSession] isLinked]) {
[(TableViewControllerForStorageList *)self.window.rootViewController PushView];
}
return YES;
}
return NO;
}
TableViewControllerForStorageList.h
-(void)PushView;
TableViewControllerForStorageLost.m
-(void)PushView
{
TableViewControllerIpadStorage *tableViewControllerIpadStorage = [[TableViewControllerIpadStorage alloc]initWithNibName:#"TableViewControllerIpadStorage" bundle:Nil];
[self.navigationController pushViewController:tableViewControllerIpadStorage animated:YES];
}
Off course I got sure that Application:HandleOpenURL is running, but when calling PushView from there the error is [ViewController PushView]: unrecognized selector sent to instance
So, how can make the navigation be done from there? Which basics about objective c am I missing?
It is not clear from your question how your app is structured, so this answer may not be the best solution for your problem but hopefully it gives you some idea of how your view controller hierarchy is likely built up from your app delegate.
Lets say your first view controller class is named FirstViewController. Either your app delegate has a direct reference to an instance of this view controller, or it can access it through a parent view controller (perhaps via window.rootViewController).
Now lets say you have a method in FirstViewController named pushNextViewController that performs the task of pushing the second table view controller.
You can call that method from within the application:handleOpenURL: method of your app delegate.
This might look something like:
[self.window.rootViewController.firstViewController pushNextViewController];
There are other ways you could get a reference to your instance of FirstViewController and it would be cleaner if your rootViewController was a custom subclass so your could create a pushNextViewController method there and from that method tell your FirstViewController instance to pushNextViewController:
[self.window.rootViewController pushNextViewController];
Note that in both examples above, you will need to cast the rootViewController to whatever class it is actually an instance of or the compiler will warn you that it does not have the property firstViewController (example 1) or the method pushNextViewController (example 2).
EDIT: If your rootViewController is a UINavigationController, then your code might look more like:
UINavigationController* navController = (UINavigationController*)window.rootViewController;
FirstViewController* vc = navController.viewControllers[0];
[vc pushNextViewController];
EDIT 2: OK, It looks like the confusion here is that the window object has a rootViewController property (which appears to be pointing to your navigationController) and then you also have a rootViewController instance variable in your app delegate. These are two different objects, making your naming convention a bit confusing, but if I am right then the following should work:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if ([[DBSession sharedSession] handleOpenURL:url]) {
if ([[DBSession sharedSession] isLinked]) {
[rootViewController PushView];
}
return YES;
}
return NO;
}
You should consider changing the name of your app delegate's reference to your TableViewControllerForStorageList to something other than rootViewController to alleviate some confusion.

Error message " Applications are expected to have a root view controller at the end of application launch"

I'm trying to simply view an empty table on my iPhone simulator (with Xcode 4.2), but somehow I don't know what I did wrong. Right now it shows nothing than a plain white page. Actually It should show me a table.
For note: I'm following the instructions of Big Nerd Ranch's Guide "iPhone Programming", Chapter 10.
So right now I have 4 files of my Homepwner-App:
HomepwnerAppDelegate.m
HomepwnerAppDelegate.h
ItemsViewController.h
ItemsViewController.m
the "ItemsViewController" is a Subclass of the UITableViewController.
ItemsViewController.h
# import <UIKit/UIKit.h>
#interface ItemsViewController : UITableViewController
#end
ItemsViewController.m
isn't filled with interesting stuff right now
HomepwnerAppDelegate.h
# import <UIKit/UIKit.h>
#class ItemsViewController;
#interface HomepwnerAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
ItemsViewController *itemsViewController; }
#property (nonatomic, retain) IBOutlet UIWindow *window;
#end
HomepwnerAppDelegate.m
#import "HomepwnerAppDelegate.h"
#import "ItemsViewController.h"
#implementation HomepwnerAppDelegate
#synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Create a ItemsViewController
itemsViewController = [[ItemsViewController alloc] init];
//Place ItemsViewController's table view in the window hierarchy
[window addSubview:[itemsViewController view]];
[window makeKeyAndVisible];
return YES;
}
#end
The console says: Homepwner[2400:207] Applications are expected to have a root view controller at the end of application launch
I know that there are already other threads with same error message and lots of them link at the "didFinishLaunchingWithOptions:"- Method, but since they offered so many solutions, I'm quite confused now, especially because I just followed the instructions of the book and nothing more... I've just got the feeling that I declared something wrong.
How to solve the problem?
I don't know what it's worth but, did you try
self.window.rootViewController = itemsViewController;
This is how I've solve it when I had this problem.

Setting the initial value of a UILABEL

I'm trying to create a simple Quiz app (I'm a beginner), when I launch the app I want a UILabel to show the first question (of an array of questions). I'm having some trouble with setting the initial value.
I've done a couple of attempts, whiteout success. I my QuizAppDelegate.h file I declare my UILabel like this:
IBOutlet UILabel * questionField;
In my main .m file I have tried the following:
- (id)init {
[super init];
questions = [[NSMutableArray alloc] init];
// Not working
questionField = [[UILabel alloc] init];
[questionField setText:#"Hello"];
// Working
NSLog(#"Hello");
[self defaultQuestions];
// [self showQuestion];
return self;
}
Another thing I have tried is the following in QuizAppDelegate:
#property (nonatomic, retain) IBOutlet UILabel *questionField;
- (void)changeTitle:(NSString *)toName;
And in the .m file:
#synthesize questionField;
- (id)init {
[super init];
questions = [[NSMutableArray alloc] init];
// Not working
[self changeTitle:#"Hello"];
// Working
NSLog(#"Hello");
[self defaultQuestions];
// [self showQuestion];
return self;
}
-(void)changeTitle:(NSString *)toName {
[questionField setText:toName];
}
Any tips on how to solve this would be great!
// Anders
Hopefully you're not actually putting code into main.m. On iOS, you rarely modify that file.
Since you're doing everything in the AppDelegate, let's keep it there (as opposed to creating a new UIViewController). Let's start with the basics.
Adding the Label as an instance variable
You're doing this correctly—inside the curly braces of the .h file, put the line
IBOutlet UILabel * questionField;
Then, declare the corresponding property, and make sure to synthesize it in the .m file.
#property (nonatomic, retain) IBOutlet UILabel *questionField;
#synthesize questionField // in the .m file
Adding the UILabel in Interface Builder
Open up MainWindow.xib. Drag a UILabel from the Library to the Window that represents your app's window. Then Control-Drag from the AppDelegate object (the third icon on the left in Xcode 4; it'll be labelled in the Document window in IB 3). You'll see a little black window come up—select the option called questionField to make the connection.
See this link for screenshots and how to make connections in IB. The same applies in Xcode 4.
Changing the text
You don't need a separate method to change the text—just modify the label's text property.
Pick a method that'll be called when the app launches (applicationDidFinishLaunching:WithOptions: is a good place to do it in), and put the following code:
questionField.text = #"Hello";
And that's it!
Code
QuizAppDelegate.h
#import <UIKit/UIKit.h>
#interface QuizAppDelegate : NSObject <UIApplicationDelegate> {
IBOutlet UILabel *questionField;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UILabel *questionField;
#end
QuizAppDelegate.m
#import "QuizAppDelegate.h"
#implementation QuizAppDelegate
#synthesize window=_window;
#synthesize questionField;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the tab bar controller's current view as a subview of the window
[self.window addSubview:self.questionField];
[self.window makeKeyAndVisible];
self.questionField.text = #"Hello";
return YES;
}
- (void)dealloc
{
[_window release];
[questionField release];
[super dealloc];
}
#end
If you're creating the label programmatically, then you have to add the label to the view:
[self.view addSubview:questionField];
This assumes that you have a ViewController. If not, and you're doing this directly in the AppDelegate (a very bad idea, by the way), then do
[self.window addSubview:questionField];
If you're creating it in the IB, make sure you set up the connections.
You should not both add the UILabel in the IB and instantiate it programmatically. Only call alloc if you are creating it programmatically. Otherwise, if using the IB, skip that part. You created it already with the xib.
I suspect that you have either not created your Interface Builder layout properly - either you have missed the control out all together or more likely you have not connected that control to the questionField outlet in yout header file.
You need to drag a UILabel view into the main view and then connect it to the correct line in your header file.
You shouldn't be using your main.m like that at all. In fact, you should almost certainly never do anything with it. Try creating a UIViewController subclass and practicing your quiz with that. (Add the UILabel to the IB file and then connect the outlet.) Perhaps use the View-Based Application template while you are practicing.
This is a good answer:
"You're doing this correctly—inside the curly braces of the .h file, put the line
IBOutlet UILabel * questionField;"
I was trying to change the value of mylabel.text and the screen didn't update the label with this.value. I included the {IBOutlet UILabel * mylabel} and it works like a charm!
So this answer is valid to change the text of a label programmatically!
Thanks

Programmatically changing a UILabel from the App Controller in a Navigation Based iOS App

I'm having a lot of trouble with what seems like a very simple thing. I cannot update a UILabel programmatically from a Navigation-based iOS App. I don't want to use a button as this label is designed to report the status of an external system, and should update on launch. There is no need to make the user go though the extra step on touching the button if I don't have to.
The following is a somewhat exhaustive list of the steps I've taken. I'm sorry if some of this seems unnecessary, but in my experience even the smallest forgotten step can be the cause of the issue.
From a fresh Navigation-based App in Xcode here are the steps I'm taking:
Replace UITableView with a generic UIView class
Re-wire File's Owner's view outlet to the new UIView
Add a UILabel to the center of the UIView, make the text centered, and leave the default text.
Save and Exit Interface Builder
RootViewController.h
#import <UIKit>
#interface RootViewController : UIViewController {
UILabel *myLabel;
}
#property (nonatomic, retain) IBOutlet UILabel *myLabel;
#end
RootViewController.m
#import "RootViewController.h"
#implementation RootViewController
#synthesize myLabel;
...
Removed TableView stuff from RootViewController.m
Wire IBOutlet myLabel to the Label in RootViewController.xib
Save and Exit Interface Builder
tempNavAppAppDelegate.m
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
RootViewController *rootViewCont = navigationController.visibleViewController;
rootViewCont.myLabel.text = #"test";
NSLog(#"Label Text: %#", rootViewCont.myLabel.text);
return YES;
}
...
Build/Run
The Label shows as "Label" not "test". And the log reports:tempNavApp[94186:207] Label Text: (null)
I've tried a number of different ways to get this done, but any help would be appreciated.
The Journey
After discovering that my rootViewCont.myLabel was also nil, thanks to the help of mprudhom, I decided to test and see if I could assign myLabel.text a value in RootViewController.m's - (void)viewDidLoad method.
It worked, I was able to change the text directly from the RootViewController. But while this proved my View Controller wasn't broken, it did not solve my initial desire to change the UILabel from tempNavAppAppDelegate.m.
Elliot H. then suggested that navigationController.visibleViewController wasn't actually returning a view controller. I had tested for the value of rootViewCont and it came back as a RootViewController, but Elliot's suggestion got me thinking about the app's lifecycle and when the different parts of my code was actually loaded up.
So I started printing an NSLog at each step of the launch process (application:didFinishLaunchingWithOptions:, applicationDidBecomeActive:, viewDidLoad, viewDidAppear:), and discovered to my surprise that [self.window makeKeyAndVisible]; does not mean that the view will load before application:didFinishLaunchingWithOptions: is complete.
With that knowledge in hand I knew where the problem was. The solution (or at least my solution) seems to be NSNotificationCenter. I have now registered for notifications in tempNavAppAppDelegate and I am broadcasting a notification in RootViewController's viewDidAppear: method.
The Pertinent Code
RootViewController.h:
#interface RootViewController : UIViewController {
IBOutlet UILabel *myLabel;
}
#property (nonatomic, retain) UILabel *myLabel;
#end
RootViewController.m:
#implementation RootViewController
#synthesize myLabel;
- (void)viewDidLoad {
[super viewDidLoad];
NSParameterAssert(self.myLabel);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:#"viewDidAppear" object:self];
}
tempNavAppAppDelegate.h:
#interface tempNavAppAppDelegate : NSObject {
UIWindow *window;
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
- (void)viewDidAppearNotification:(id)notification;
#end
tempNavAppAppDelegate.m:
#implementation tempNavAppAppDelegate
#synthesize window;
#synthesize navigationController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(viewDidAppearNotification:) name:#"viewDidAppear" object:nil];
return YES;
}
- (void)viewDidAppearNotification:(id)notification
{
NSString *noteClass = [NSString stringWithFormat:#"%#", [[notification object] class]];
if ([noteClass isEqualToString:#"RootViewController"]) {
RootViewController *noteObject = [notification object];
noteObject.myLabel.text = #"Success!";
}
}
If this code is printing nil:
rootViewCont.myLabel.text = #"test";
NSLog(#"Label Text: %#", rootViewCont.myLabel.text);
Then almost certainly it is because rootViewCont.myLabel itself is nil. Try logging the value of rootViewCont.myLabel as well and you'll see.
Are you sure you wired up the label to your UILabel IBOutput declaration in Interface Builder? That's most commonly the problem.
I personally always assert all my expected outlets in viewDidLoad so that I catch early on when the outlets have been (accidentally or not) been decoupled in Interface Builder. E.g.:
- (void)viewDidLoad {
[super viewDidLoad];
NSParameterAssert(rootViewCont.myLabel);
}
your interface should look like this
#import <UIKit>
#interface RootViewController : UIViewController {
// IBOutlet here...
IBOutlet UILabel *myLabel;
}
#property (nonatomic, retain) UILabel *myLabel;
#end
Is visibleViewController actually returning the view controller? My guess is since application:didFinishLaunchingWithOptions: hasn't returned yet, it's possible UINavigationController hasn't properly configured that property to return yet, even though you've added the navigation controller's subview to the view hierarchy, it's probably that visibleViewController isn't valid until after viewDidAppear: is called on the view controller in question.
Try having an IBOutlet to the RootViewController directly, or create it programmatically, and then assign the label text.
Just a general reminder: If an object is nil (in this case visibleViewController would be returning nil), and you send it a message, you won't crash, because messages to nil are valid and won't do anything. When you call the myLabel accessor on the rootViewCont object, if rootViewCont is nil, myLabel will return nil always.