I'm an iOS developer and I want to create a simple desktop app. I thought the switch would go perfect but it doesn't.
I've created a cocoa app ( from the xCode template ). Now I don't want to use user interface builders and stuff so I wrote my first controller like this:
#interface MainViewController ()
#property (nonatomic, strong) NSTextView *test;
#end
#implementation MainViewController
-(instancetype) init
{
self = [super init];
if(self)
{
NSLog(#"%s", __PRETTY_FUNCTION__);
_test = [[NSTextView alloc] init];
[_test setString:#"DKDDK"];
[self.view addSubview:_test];
[_test mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
}
return self;
}
#interface MainViewController : NSViewController
#end
And I just use the NSWindow that is created by the template:
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MainViewController * mainView = [[MainViewController alloc] init];
[self.window.contentView addSubview:mainView.view];
mainView.view.frame = ((NSView*)self.window.contentView).bounds;
}
When I run the application it gives me:
[NSViewController loadView] loaded the "(null)" nib but no view was set.
I don't know how to solve this. How can I create an app without nib, just like you do on iOS?
If you aren't loading the view from a NIB then there is little need for a view controller.
Discard the view controller and subclass NSView instead, and set that as the window's content view.
Note: you are making a rod for your own back by not using IB.
Related
Running into a simple problem and not quite sure what is causing it. Linked a NSButton up to a ViewController xib. The property is referenced and then I linked up the IBAction to the view controllers view. I'm getting a crash whenever I press the button with an unrecognized selector message. I know I'm doing something wrong but on iOS this is pretty standard.
Here is the code:
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
IPVLocationViewController *mainViewController = [[IPVLocationViewController alloc]initWithNibName:#"IPVLocationViewController" bundle:nil];
self.window.contentView = mainViewController.view;
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
#import "MainViewController.h"
#interface MainViewController ()
#property (weak) IBOutlet NSButton *mainButton;
#end
#implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do view setup here.
}
- (IBAction)mainClick:(id)sender {
NSLog(#"THE BUTTON WAS CLICKED");
}
#end
In your Nib , it looks like your view controller instance is of type NSViewController instead of MainViewController.
So select the view controller in your Nib (or storyboard), and change its type to MainViewController.
Of course, if this view controller isn't being loaded from a nib or storyboard, then just check where you create it and make sure you created an instance of the correct class.
Solved it:
The accepted answer in this post helped me:
Needed to hold a reference to the view controller in the AppDelegate.
#interface AppDelegate ()
#property (nonatomic, strong) MainViewController *mainViewController;
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
self.mainViewController = [[MainViewController alloc]initWithNibName:#"MainViewController" bundle:nil];
self.window.contentView = self.mainViewController.view;
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
CONFIGURATION:
-UIviewController embedded in Navigationcontroller.
-UIviewController has a UIscrollview as subview
-UIscrollview has some views where charts are created: each view containing a chart has its own .h and .m file and from this file I want trigger a segue to a tableview controller.
-A Tableviewcontroller was added in xcode and a segue from the UIviewController to the TableViewcontroller was created as well (Xcode)
-created a protocol in the UIView to have the segue pushed from there.
PROBLEM:
delegate always nil, segue method will never be called
UIVIEW .h file
#protocol UItoUIvcDelegate <NSObject>
-(void)triggerSegue;
#end
#interface CFfirstGraph : UIView <CPTPlotDataSource , CPTPieChartDelegate,UIActionSheetDelegate>
#property(weak, nonatomic) id <UItoUIvcDelegate> delegate;
#end
UIVIEW .m file (snippet)
-(void)pieChart:(CPTPieChart *)pieChart sliceWasSelectedAtRecordIndex:(NSUInteger)index
{
if (self.delegate == nil)
{
NSLog(#"nil");
}
[self.delegate triggerSegue];
}
UIVIEWCONTROLLER .h file
#import "CFfirstGraph.h"
#interface CFMainViewController : UIViewController <UItoUIvcDelegate, UITableViewDelegate, UITableViewDataSource>
#end
UIVIEWCONTROLLER .m file (snippet)
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.contentSize = CGSizeMake(320, 1000);
[self.view addSubview:self.scrollView];
CFfirstGraph *click =[[CFfirstGraph alloc]init];
click.delegate = self ;
}
-(void)triggerSegue
{
[self performSegueWithIdentifier:#"detailedData" sender:self];
NSLog(#"estoy aqui");
}
What am I doing wrong ? why the delegate is always nil ? I tried to add the method setDelegate but still no luck.
Thanks,
dom
Make CFfirstGraph as a strong property.
#property (strong, nonatomic) CFfirstGraph * click;
- (void)viewDidLoad
{
[super viewDidLoad];
self.click =[[CFfirstGraph alloc]init];
self.click.delegate = self ;
self.scrollView.contentSize = CGSizeMake(320, 1000);
[self.view addSubview:self.scrollView];
}
ok, after many hours of sweat I found the issue.
First...delegate = nil was not the main problem.
The real issue was the protocol method triggering the segue was never called.
If i create and initialize a CFfirstGraph object (or even property) it won't be related to the view created already in x-code, and this is the main issue.
On the other hand...if I "CTRL-drag" an outlet from the UIview to the CFMainViewController i will have a property that is exactly the one i need:
#interface CFMainViewController () <UIScrollViewDelegate>
#property (weak, nonatomic) IBOutlet CFfirstGraph *FirstGraph;
Then i assign the delegate to self (CFMainViewController) in the viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.FirstGraph.delegate = self ;
}
and the delegate method "triggerSegue" will be executed being called from the UIVIEW.
Best Regards,
dom
I've got the following method on a GameScreen.m file, with its own declaration - (void) drawNumbers on a GameScreen.h file:
//GameScreen.h
#import <UIKit/UIKit.h>
#interface GameScreen : UIView
{
IBOutlet UIButton *cell00;
}
- (void) drawNumbers;
- (IBAction) onCellClick:(id)sender;
#property (nonatomic, retain) IBOutlet UIButton *cell00;
#end
//GameScreen.m
#import "GameScreen.h"
- (void) drawNumbers
{
//testing if this works, so far it doesn't
[cell00 setTitle:#"Whatever" forState:UIControlStateNormal];
[cell00 setTitle:#"Whatever" forState:UIControlStateHighlighted];
}
I'm trying to call this method from my GameScreenViewController.m file, this way:
//GameScreenViewController.m
#import "GameScreenViewController.h"
#import "GameScreen.h"
...
- (void) viewDidLoad
{
GameScreen *aGameScreen = [[GameScreen alloc] init];
[aGameScreen drawNumbers];
[aGameScreen release];
[super viewDidLoad];
}
This is supposed to change the title of a button in a GameScreen.xib file where GameScreenViewController.m is the viewController and GameScreen class is the event handler where I get all the button clicks, timers running, etc. I am trying to call [drawNumbers] from [viewDidLoad] since I want the title to be changed when the screen is brought up front (screen management is done through the AppDelegate files).
The thing is, if I call drawNumbers instance from inside the same class through
//GameScreen.m
#import GameScreen.h
-(void) onButtonClick:(id)sender
{
//some other code
[self drawNumbers];
}
it works (as to say, nothing wrong with the code implementation or the graphic interface).
I've browsed through Apple Guide and tons of pages on the Internet, but I can't seem to find any light to this. Any further help (including answers as to where exactly find the answer in the ADG) would be really appreciated.
(Edited: here goes the AppDelegate code to flip to the specific view, just in case):
//myAppAppDelegate.h
#import <UIKit/UIKit.h>
#class myAppViewController, GameScreenViewController;
#interface myAppDelegate : NSObject <UIApplicationDelegate>
{
UIWindow *window;
myAppViewController *viewController;
GameScreenViewController *gameScreenViewController;
}
- (void) flipToGameScreen;
#property (nonatomic, retain) UIWindow *window;
#property (nonatomic, retain) GameScreenViewController *gameScreenViewController;
#end
//myAppAppDelegate.m
-(void) flipToGameScreen
{
GameScreenViewController *aGameScreenView = [[GameScreenViewController alloc] initWithNibName: #"GameScreen" bundle:nil];
[self setGameScreenViewController:aGameScreenView];
[aGameScreenView release];
[gameScreenViewController.view.frame = [[UIScreen mainScreen] applicationFrame];
[viewController.view removeFromSuperview];
[self.window addSubview:[gameScreenViewController view]];
}
Since your cell00 is to be set by a NIB it will be nil if you simply do [[GameScreen alloc] init]. It will only be set if the corresponding NIB is loaded (and a connection is actually set up).
If the cell can be accessed in your viewDidLoad, create a property on GameScreen and pass it through the property (or a dedicated initWithCell: or something).
If you have something like an IBOutlet GameScreen *aGameScreen; on your GameScreenViewController (and also established a connection to cell00 in the same NIB) you should access that instead.
Like many others I started to code an experiment today where I would have two view controllers and be able to switch between them. I got this to work using a navigation controller, but I have a question about the implementation.
In my TwoViewsAppDelegate, I define the navigation controller and the rootViewController.
#interface TwoViewsAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
RootViewController *rootViewController;
}
and set them up as follows:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
rootViewController = [[RootViewController alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[window setRootViewController:navigationController];
[self.window makeKeyAndVisible];
return YES;
}
Then in my rootViewController, I define the level2ViewController that I
am going to switch to, and a button that I'm going to press to make the
switch happen:
#interface RootViewController : UIViewController {
UIButton *theButton;
Level2ViewController *level2ViewController;
}
Here's the response to the button being pressed in RootViewController.m:
-(void)level1ButtonPressed:(id)sender
{
if (level2ViewController == nil)
{
level2ViewController = [[Level2ViewController alloc] init];
}
[self.navigationController pushViewController:level2ViewController animated:YES];
}
The problem is that if there was going to be a level3ViewController,
it would have to be defined as a member of level2ViewController, etc.
for however many view controllers i wanted to push onto the stack.
It would be nice to be able to define all the view controllers in one
place, preferably the app Delegate. Is this possible?
To solve this, you can create a callback-type method which uses the delegate of the class that'll be sending the requests for the view controllers. Best explained through code...
RootViewController.h
#import "RootInterfaceView.h"
// all the other VC imports here too
#interface RootViewController : UIViewController <RootInterfaceViewDelegate>
{
RootInterfaceView *interface;
}
RootViewController.m
-(void)rootInterfaceView: (RootInterfaceView*)rootInterfaceView didSelectItem:(NSUInteger)itemTag
{
switch (itemTag)
// then create the matching view controller
}
RootInterfaceView.h
// imports here if required
#protocol RootInterfaceViewDelegate;
#interface RootInterfaceView : UIView <RootInterfaceItemViewDelegate>
{
id <RootInterfaceViewDelegate> delegate;
}
#property (nonatomic, assign) id delegate;
#end
#protocol RootInterfaceViewDelegate <NSObject>
#optional
-(void)rootInterfaceView: (RootInterfaceView*)rootInterfaceView didSelectItem:(NSUInteger)itemTag;
#end
RootInterfaceView.m
// remember to synthesize the delegate
-(void)rootInterfaceItemSelected: (RootInterfaceItemView*)rootInterfaceItemView
{
NSUInteger theTag = rootInterfaceItemView.tag;
if ([self.delegate respondsToSelector:#selector(rootInterfaceView:didSelectItem:)])
[self.delegate rootInterfaceView:self didSelectItem:theTag];
}
Alternatively, if the only options from level 2 were either back to root/pop one VC or to push controller 3, then it'd be fine for level 2 to be importing 3 to allow for it's creation.
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.