Loading xib window from status menu - objective-c

I have MainMenu.Xib which has a status menu element. The MainMenu File Owner is mapped to AppDelegate.
I also have another Xib which is a Window and it's File Owner is mapped to a ViewController with the same name.
So what I have tried and it kind of works is I have created an action in the appDelegate and have mapped the menu item in the status menu to the action in the appDelete using the First Responder.
In the action I have put:
SubscriptionsViewController *vc = [[SubscriptionsViewController alloc] initWithNibName:#"Subscriptions" bundle:nil];
[vc view];
If I step through the code it the window shows up but then goes away. So I have two questions
1) I thought there was a way to load the xib with NSMenuItem without the need of the above code.
2) How do I keep the window from closing right away? Do I need to save the view pointer or something?
edit: format code.

1) I thought there was a way to load the xib with NSMenuItem without the need of the above code.
Since NSWindow is not inherited from NSView like in iOS (UIWindow:UIView), it makes no sense to use NSViewController to load window from a xib. Use subclass of NSObject instead.
#interface SubscriptionsViewController : NSObject
#property (assign, nonatomic) IBOutlet NSWindow *window;
#end
#implementation SubscriptionsViewController
- (id)init
{
self = [super init];
if (self) {
[NSBundle loadNibNamed:#"Subscriptions" owner:self];
}
return self;
}
#end
2) How do I keep the window from closing right away? Do I need to save the view pointer or something?
It depends on the context to retain the instance of subscriptionsViewController or not. You can use below code to display a window, where the instance of window is in nib -
self.subscriptionsViewController = [[SubscriptionsViewController alloc] init];
[self.subscriptionsViewController.window makeKeyAndOrderFront:self];
Remember if "Visible At Launch" is set in nib, then the window is visible when you instantiate subscriptionsViewController.

You may add your window into the MainMenu.xib instead of using an addition xib file and create an outlet in the AppDelegare.h as
#property (assign) IBOutlet NSWindow *window;
Then all you need to do is
window.isVisible = !window.isVisible;
in the necessary action method...

Related

Loading XIB file without a UIViewController

I'd like to design a UIView and some sub-views (UIWebView, UIToolbar, some UIBarButtonItems, a progress indicator and so-forth) using the Interface Builder, but I think it's unnecessary to do this traditionally, by using a UIViewController, using presentViewController:animated etc.
So, I created a custom class, with the .h file code as follows:
#interface FileInteractionManager : NSObject {
}
#property (nonatomic, retain) IBOutlet UIView *fileView;
#property (nonatomic, retain) IBOutlet UIWebView *fileWebView;
#property (nonatomic, retain) IBOutlet UIBarButtonItem *printButton;
#property (nonatomic, retain) IBOutlet UIBarButtonItem *optionsButton;
#property (nonatomic, retain) IBOutlet UIBarButtonItem *doneButton;
My .m file is as follows:
#implementation FileInteractionManager
#synthesize fileView, fileWebView, doneButton, optionsButton, printButton;
-(id)init {
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"FileInteractionView" owner:self options:nil];
NSLog(#"Load success!");
return self;
}
Finally, I create a stand-alone xib file named 'FileInteractionView.xib', change the file's owner to the custom class I created above, and wire up the IBOutlets.
When I call the init method on my class, I can see in the debugger that all my IBOutlet objects are instantiated properly.
My questions are:
Is the loadNibNamed:owner:options: method the right way to load my stand-alone .xib file? I don't like the fact that this method returns an array I have no use for (the top-level object returned matches my variable fileView, but I've already linked them through the Interface Builder).
Is my general approach correct in solving my problem? I carried out the above steps because I wanted a simple UIView object that I could add to my existing UIViewController, rather than present and dismiss a whole new UIViewController.
I use a little different approach. I create a subclass of UIView (MyCustomView i.e.) then the xib with the UI of the view and change the (main) view class the the one just defined. In the xib then you can link the outlet to the custom view itself (not the file owner).
Finally in the class definition I create a function like this:
+ (id) newFromNib
{
NSArray *nibArray = [[UINib nibWithNibName:NSStringFromClass([self class]) bundle:nil] instantiateWithOwner:nil options:nil];
return nibArray[0];
}
Just a couple of notes:
1) this's a class method, you can use "self" just for stuff like "NSStringFromClass([self class])" but the real object is the variable returned
2) this example suppose the xib have the same name of the class (via NSStringFromClass([self class]) so I can copy-paste it without changing anything ;) ) and that your view is the first one defined in the xib (the standard). If you store more than a "main" view inside one xib pick the right element.
so where I need MyCustomView I do something like:
MyCustomView* mycv = [MyCustomView newFromNib];
then set frame/center and add to superview...
I think this way is pretty usefull if you have a "library" of complex UI elements and want to design them via xib then add when needed.
il Malvagio Dottor Prosciutto answer is nice. Here is a possible alternative.
Load nib in NS_DESIGNATED_INITIALIZER and become owner of subview
If we accept the xib to only hold a subview instead of the view itself, then we can load the subview in initWithFrame: and keep an ownership construction in xib.
#interface MyCustomView ()
#property (strong, nonatomic) IBOutlet UIView *subview;
#end
#implementation MyCustomView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
[self addSubview:self.subview];
return self;
}
#end

Show NSWindow from separate nib (not modal)

How to do it? I simply want to load a window and show it in front of the main window.
NSWindowController* controller = [[NSWindowController alloc] initWithWindowNibName: #"MyWindow"];
NSWindow* myWindow = [controller window];
[myWindow makeKeyAndOrderFront: nil];
This code shows the window for one moment and then hides it. IMHO this is because I don't keep reference to the window (I use ARC). [NSApp runModalForWindow: myWindow]; works perfectly but I don't need to show it modally.
Yes, with ARC if you don't hold a reference to the window it will be torn down as soon you as you exit the routine you were in. You need to hold a strong reference to it in an ivar. [NSApp runModalForWindow: myWindow] is different because the NSApplication object holds a reference to the window as long as it is being run modally.
You should likely do something similar to the following, which creates a strong reference to the NSWindowController instance you create:
.h:
#class MDWindowController;
#interface MDAppDelegate : NSObject <NSApplicationDelegate> {
__weak IBOutlet NSWindow *window;
MDWindowController *windowController;
}
#property (weak) IBOutlet NSWindow *window;
#property (strong) MDWindowController *windowController;
- (IBAction)showSecondWindow:(id)sender;
#end
.m:
#import "MDAppDelegate.h"
#import "MDWindowController.h"
#implementation MDAppDelegate
#synthesize window;
#synthesize windowController;
- (IBAction)showSecondWindow:(id)sender {
if (windowController == nil) windowController =
[[MDWindowController alloc] init];
[windowController showWindow:nil];
}
#end
Note that rather than sending the makeKeyAndOrderFront: method directly to the NSWindowController's NSWindow, you can just use NSWindowController's built-in showWindow: method.
While the above code (and sample project below) use a custom subclass of NSWindowController, you also use a generic NSWindowController and create the instance using initWithWindowNibName: (just make sure the File's Owner of the nib file is set to NSWindowController rather than a custom subclass like MDWindowController).
Sample project:
http://www.markdouma.com/developer/MDWindowController.zip

One App Delegate, Two Windows

I have a simple Cocoa app. It has two windows, each in a separate xib file:
MainMenu.xib
SecondaryWindow.xib
I have an AppDelegate class, which has a reference to the window in MainMenu.xib. I'm trying to make it have a reference to the window in SecondaryWindow.xib. I'm confused about how to do this. I have made an outlet, as such:
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (assign) IBOutlet NSWindow *secondaryWindow;
#end
Here's the implementation:
#implementation AppDelegate
#synthesize window = _window;
#synthesize secondaryWindow = _secondaryWindow;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSBundle loadNibNamed:#"SecondaryWindow" owner:self];
NSLog(#"_window = %#", _window);
NSLog(#"_secondaryWindow = %#", _secondaryWindow);
}
#end
_secondaryWindow is always (null)
I've add an outlet from in SecondaryWindow.xib connection the second window to the outlet in AppDelegate. What else do I need to do in SecondaryWindow.xib to make the connection complete?
EDIT: added [NSBundle loadNibNamed...]
You need to load it...
By default, MainWindow.xib is loaded by the framework, which creates its own instance of the app delegate.
You should load your second window from your app delegate (try [NSBundle laodNibNamed:#"SecondaryWindow" owner:self]. when you do this, the File's owner will be the application delegate - change the class of the file's owner in interface builder to reflect that and make your connections to it)
Did you set the type of File's Owner in the secondary window's .xib to the type of your app delegate? And did you then connect the window in that .xib to the secondaryWindow outlet of File's Owner?
If you did those things, and if the .xib is properly included in the project and you've specified the name of the file correctly in the +loadNibNamed:owner: message, then your secondaryWindow property should be populated.

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

Cocoa - Link IBOutlet to Separate Nib

I have a nib file in which I load at a certain point in my application. Would it be legal for me to link a NSWindow IBOutlet from my AppDelegate to the 2nd nib file's window? In other words, my IBOutlet is not being connected to the MainMenu xib file that Xcode creates on default. If this was legal, can I have access to the NSWindow's frame and other features?
Yes you can do that. In your second nib file, I would use a NSWindowController as the file's owner to the nib. Then in your AppDelegate, create an instance of the NSWindowController and then load the nib. From there, you can inspect the properties of the window owned by NSWindowController or do whatever you want with the window.
Here is an example
#interface MyAppDelegate : NSObject
{
NSWindowController *myWindowController;
}
#end
#implementation MyAppDelegate
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
myWindowController = [[NSWindowController alloc] initWithWindowNibName:#"MySecondWindow"];
[[myWindowController window] center];
[[myWindowController window] makeKeyAndOrderFront:self];
}
#end
In your second nib, set the File's Owner to be your app delegate class. Then attach the outlets as needed within IB. At run time, call [NSBundle loadNibNamed:owner:] and be sure to pass self as the owner.
Yes, this would be legal as long as the App Delegate is the File's Owner of the nib you are loading. That said, if you unload the nib later, you have to make sure that all top level objects in the nib are properly released (otherwise you'll create a memory leak).