ARC: How do you release a WindowController when the user closes the Window? - cocoa

I'm trying to translate some old code to ARC. The old code does this in the WindowController:
#interface PreferencesController () <NSWindowDelegate>
#end
#implementation PreferencesController
-(void)windowWillClose:(NSNotification*) notification {
[self autorelease];
}
#end
My AppDelegate has a strong pointer to the WindowController:
#property(strong) PreferencesController* preferencesCtrl;
In PreferencesController, do I need to declare a (weak) pointer back to the AppDelegate, and then do something like this:
-(void) windowWillClose:(NSNotification *)notification {
[[self appDelegate] setPreferencesCtrl:nil];
}

Well, your thoughts are right.
But I can give you make it more simple.
Set your application delegate as NSWindowDelegate.
#interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate>
#property (strong) PreferencesController* preferencesCtrl;
#end
#implementation AppDelegate
- (void)doAction
{
// create window
// ...
self.preferencesCtrl.window.delegate = self; // set window delegate
}
- (void)windowWillClose:(NSNotification *)notification
{
self.preferencesCtrl=nil;
}
#end
UPD
Since you are already using NSWindowDelegate methods, I suggest you to create another delegate protocol, say PreferenceControllerDelegate
//in PreferenceController.h before class interface
#class PreferenceControllerDelegate
#protocol PreferenceControllerDelegate <NSObject>
- (void)preferenceControllerWindowWillClose:(PreferenceControllerDelegate *)sender;
#end
#interface PreferenceController : NSWindowController
//...
#property (nonatomic,weak) id<PreferenceControllerDelegate> delegate;
//...
#end
That would be much proper.

Try using #autoreleasepool to force an immediate dealloc when you nil the pointer.
-(void) windowWillClose:(NSNotification *)notification {
//[[self appDelegate] setPreferencesCtrl:nil];
#autoreleasepool {
[[NSApp delegate] setPreferencesCtrl:nil];
}
}
You can also access the app delegate through the NSApp singleton using NSApp.delegate, which is [[NSApplication sharedApplication] delegate], though I guess you'd have to typecast it to avoid a warning. Either way.

Related

What is the proper way to declare a delegate variable?

In MyModel.h, I declared a delegate variable like this:
#property(weak) IBOutlet id <MyProtocol> delegate;
I've also seen a delegate variable declared like this:
#property(weak) IBOutlet NSObject <MyProtocol>* delegate;
I'm wondering which I should use.
Also, Xcode 6.2 indicates I'm doing something wrong because when I connect the delegate outlet in IB, Xcode still shows an empty circle to the left of the declaration instead of a filled in circle. This is what I did:
1) In IB, I dragged on Object out of the Library onto the dock, and I changed its class to: MyModel.
2) In IB, I dragged another Object onto the dock, and I changed its class to: MyController. I declared the MyController class like this:
#interface MyController : NSObject <MyProtocol>
#property(strong) IBOutlet MyModel* model;
#end
3) In IB, I hooked up the delegate outlet for the MyModel Object to the MyController Object.
But in Xcode, it still shows an empty circle to the left of the line:
#property(weak) IBOutlet id <MyProtocol> delegate;
In other words, Xcode is saying the outlet is not connected to anything--yet my app is able to communicate with the controller using the delegate property.
If I delete <MyProtocol> from that line, the circle to the left of the line fills in, i.e. Xcode is saying the outlet is now connected to something. Is that an Xcode bug?
Here are the files for my HelloDelegate app:
MyProtocol.h:
//
// MyProtocol.h
// HelloDelegate
//
#class MyModel; //#import "MyModel.h" doesn't work for some reason
#protocol MyProtocol
-(void)sayHello:(MyModel*)model;
#end
MyModel.h:
//
// MyModel.h
// HelloDelegate
//
#import <Foundation/Foundation.h>
#import "MyController.h"
#interface MyModel : NSObject
#property NSString* name;
-(id)initWithName:(NSString*)name;
-(void)doStuff;
#end
MyModel.m:
//
// MyModel.m
// HelloDelegate
//
#import "MyModel.h"
#interface MyModel()
#property(weak) IBOutlet id <MyProtocol> delegate;
#end
#implementation MyModel
-(void)doStuff {
[[self delegate] sayHello:self];
}
-(id) init {
return [self initWithName:#"world"];
}
//Designated initializer:
-(id) initWithName:(NSString *)name {
if (self = [super init]) {
[self setName:name];
}
return self;
}
#end
MyController.h:
//
// MyController.h
// HelloDelegate
//
#import <Foundation/Foundation.h>
#import "MyProtocol.h"
#interface MyController : NSObject <MyProtocol>
#property(strong) IBOutlet MyModel* model;
#end
MyController.m:
//
// MyController.m
// HelloDelegate
//
#import "MyController.h"
#import "MyModel.h"
#import <Cocoa/Cocoa.h>
#interface MyController()
#property(weak) IBOutlet NSTextField* label;
#end
#implementation MyController
-(void)sayHello:(MyModel*)model {
NSString* labelText = [NSString stringWithFormat:#"Hello, %#!", [model name]];
[[self label] setStringValue:labelText];
}
#end
AppDelegate.m:
//
// AppDelegate.m
// HelloDelegate
//
#import "AppDelegate.h"
#import "MyController.h"
#import "MyModel.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) IBOutlet MyController* controller;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[[[self controller] model] doStuff];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
The main difference comes along when you type something as id <SomeProtocol> and then try to send it a message such as respondsToSelector: and the compiler won't let you. It comes as a surprise - or at least it sure came as a surprise to me - that id <SomeProtocol> is not a form of id. The only messages you can send to such a beast without casting are those defined in the protocol. That's in stark contrast to id plain and simple, which can be sent any known message.
Thus, in my view, as in that of those who know better than I, NSObject <SomeProtocol>* is better, because now this thing is seen by the compiler as an NSObject, and can be sent all the messages declared for NSObject.

Mac OS X Cocoa multiview application navigation

I've already spent 2 full days trying to figure out how to use NSViewControllers in order to create a multiview application.
Here is what I do.
I have 2 View Controllers and the MainMenu.xib's Window.
I also have an AppController that is the delegate for both View Controllers.
When I launch the app, I'm first greeted with the MainMenu.xib's Window's view which holds a button. On clicking this button, an IBAction is sent to the appController and asks for the SecondViewController to display it's nib. So far, everything's fine and the nib file is displayed correctly.
On the secondViewController, there's another button that sends another IBAction to the appController and asks for the FirstViewController to be displayed but nothing happens,
no crash, no warning... Any help would be much appreciated...
Thanks in advance for your patience...
Here is the code for the AppController.h :
#import <Foundation/Foundation.h>
#import "SecondViewController.h"
#import "FirstViewController.h"
#interface AppController : NSObject
#property (strong) IBOutlet NSWindow *mainWindow;
#property (strong) IBOutlet SecondViewController *secondViewController;
#property (strong) IBOutlet FirstViewController *firstViewController;
- (IBAction)secondButtonfromsecondViewControllerClicked:(id)sender;
- (IBAction)buttonClicked:(id)sender;
#end
and here is the code for the AppController.m :
#import "AppController.h"
#implementation AppController
#synthesize mainWindow = mainwindow;
#synthesize secondViewController;
#synthesize firstViewController;
- (IBAction)buttonClicked:(id)sender {
NSLog(#"button from second View Controller clicked");
self.secondViewController = [[SecondViewController
alloc]initWithNibName:#"SecondViewController" bundle:nil];
self.mainWindow.contentView = self.secondViewController.view;
[self.secondViewController.view setAutoresizingMask:NSViewWidthSizable |
NSViewHeightSizable];
}
- (IBAction)secondButtonfromsecondViewControllerClicked:(id)sender {
NSLog(#"button from first ViewController clicked");
self.firstViewController = [[FirstViewController
alloc]initWithNibName:#"FirstViewController" bundle:nil];
self.mainWindow.contentView = [self.firstViewController view];
}
#end
Well, anyone can help me, I just need a single view application that displays a first ViewController with a button on the first viewController that takes me to a second view controller with a second button that takes me back to my first viewcontroller... I've already spent more than a week on that... in vain... PS : I don't want any button on the mainMenu.xib window nor tabs.
here is the solution to my question then.
Here's the code for the AppDelegate.h:
// AppDelegate.h
#import <Cocoa/Cocoa.h>
#import "FirstViewController.h"
#import "SecondViewController.h"
//We need to declare the AppDelegate class as being the delegate for both
//FirstViewController and SecondViewController
#interface AppDelegate : NSObject <NSApplicationDelegate,
FirstViewControllerDelegate, SecondViewControllerDelegate>
#property (strong, nonatomic) NSWindow *window;
#property (strong) FirstViewController *firstViewController;
#property (strong) SecondViewController *secondViewController;
-(void) goToSecondView;
-(void) goToFirstView;
#end
Now, here's the AppDelegate.m:
// AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
#synthesize firstViewController;
#synthesize secondViewController;
-(void) awakeFromNib {
[self goToFirstView];
self.firstViewController.delegate = self;
}
-(void) goToSecondView {
if (self.secondViewController ==nil) {
self.secondViewController =[[SecondViewController alloc]
initWithNibName:#"SecondViewController" bundle:nil];
}
self.window.contentView = [self.secondViewController view];
}
-(void) goToFirstView {
if (self.firstViewController ==nil) {
self.firstViewController =[[FirstViewController alloc]
initWithNibName:#"FirstViewController" bundle:nil];
}
self.window.contentView = [self.firstViewController view];
}
#end
Next we need to set delegates in the FirstViewController and the SecondViewController
// FirstViewController.h
#import <Cocoa/Cocoa.h>
#import "SecondViewController.h"
//We declare the delegation protocole:
#protocol FirstViewControllerDelegate <NSObject>
-(void)goToSecondView;
#end
#interface FirstViewController : NSViewController
- (IBAction)firstViewControllerButtonClicked:(id)sender;
#property (nonatomic, strong) id <FirstViewControllerDelegate> delegate;
#end
And here is the FirstViewController.m:
// FirstViewController.m
#import "FirstViewController.h"
#implementation FirstViewController
#synthesize delegate;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.delegate = [NSApp delegate];
}
return self;
}
- (IBAction)firstViewControllerButtonClicked:(id)sender {
NSLog(#"button from first View Controller clicked");
if ([self.delegate respondsToSelector:#selector(goToSecondView)]) {
[self.delegate goToSecondView];
}
}
#end
Now, same thing for the SecondViewController:
// SecondViewController.h
#import <Cocoa/Cocoa.h>
#protocol SecondViewControllerDelegate <NSObject>
-(void)goToFirstView;
#end
#interface SecondViewController : NSViewController
#property (nonatomic, strong) id <SecondViewControllerDelegate> delegate;
- (IBAction)goToFirstViewControllerButtonClicked:(id)sender;
#end
And here's the SecondViewController.m:
// SecondViewController.m
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.delegate = [NSApp delegate];
}
return self;
}
- (IBAction)goToFirstViewControllerButtonClicked:(id)sender {
NSLog(#"button from Second View Controller clicked");
if ([self.delegate respondsToSelector:#selector(goToFirstView)]) {
[self.delegate goToFirstView];
}
}
#end
Well, I guess this code may be improved and if you have any suggestion, feel free to let me know. Hope it will help others.
THE PROBLEM: When the user presses a button in View2, you want View1 to appear. It's not.
STEP 1: You say that the button should be invoking an action on your AppController. Set a breakpoint (or add a diagnostic log) in that action, just to verify that it is, in fact, being invoked.
STEP 2: Think about what you want that action to do, precisely. My guess is that you want to hide View2 and show View1. Perhaps
[view2 setHidden: YES];
[view1 setHidden: NO];
(I'm not using your names here, of course.) Or you might animate the transitions, either cross-fading the views or moving them.
STEP 3: My guess is that STEP 2 will solve your problem. If it doesn't, use the debugger again to verify that view1 and view2 are not null. (If they're null, you probably have weak variables where you need them to be strong.)
STEP 4: In the unlikely event that you're still stuck, check the frames of view1 and view2. Perhaps view1 isn't where you think it is.
STEP 5: If you're still stuck, check the alphaValue of view1. Maybe you set it to be transparent, and it's being drawn transparently in the right place.
STEP 6: I bet there is no step 6!
This isn't much of an answer at the moment, however I have some concerns about your code that I wanted to work through with you.
Are you sure you have connected the outlets and actions in Interface Builder. Please verify this.
You don't need mainWindow as there is already a window property that points to the main window (verify this in Interface Builder). Also this looks wrong:
#synthesize mainWindow = mainwindow;
^
W
So dump that and just use the existing window outlet provided by Xcode.
Don't re-create the view controllers if they already exist:
if (self.secondViewController == nil)
{
self.secondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController"
bundle:nil];
}
self.window.contentView = self.secondViewController.view;

Cocoa : Load NSViewController with nib

I have tried many possibilities I found on this site and read some explanations
from apple developer page, but seems like i couldn't resolve my problem about
loading NSViewController with/form NIB.
Files on Xcode Project look a bit like this :
SecondViewController.h
SecondViewController.m
SecondViewController.xib
AppDelegate.h
AppDelegate.m
MainMenu.xib
The main problem is how could i create the SecondViewController programatically with
the initial nib on SecondViewController.xib
Custom class of FileOwner on MainMenu.xib is NSApplication
Custom class of FileOwner on SecondViewController.xib is SecondViewController
There are some panels and window in MainMenu.xb (about window and preference panel)
This application has no main window (using notification icon on status bar)
SecondViewController.h
#interface SecondViewController : NSViewController {
BOOL fullScreenMode;
NSImageView *fullScreenbg;
NSImageView *imageView1;
NSImageView *imageView2;
NSPanel *imageWindow;
}
#property (assign) IBOutlet NSImageView *fullScreenbg;
#property (assign) IBOutlet NSImageView *imageView1;
#property (assign) IBOutlet NSImageView *imageView2;
#property (assign) IBOutlet NSPanel *imageWindow;
SecondViewController.m
#implementation SecondViewController {
NSImageView *nv1;
NSImageView *nv2;
NSSize curImgSize;
}
#synthesize fullScreenbg;
#synthesize imageView1;
#synthesize imageView2;
#synthesize imageWindow;
......
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
fullScreenMode = YES;
}
return self;
}
AppDelegate.h
#interface AppDelegate : NSObject <NSApplicationDelegate,NSWindowDelegate> {
NSPanel *aboutWindow;
IBOutlet NSMenu *myStatusMenu;
IBOutlet NSMenuItem *toggleFullScreen;
}
AppDelegate.m
#implementation AppDelegate {
SecondViewController *controller;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
controller = [[SecondViewController alloc]
initWithNibName:#"SecondViewController"
bundle:nil];
//Not work (fullscreenbg, imageView1, imageView2,imageWindow = nil)
//controller = [[SecondViewController alloc] init]; ?? <-- didn't work either
}
Even if using initWithNibName or just init, all the IBOutlet properties seems to be nil
on debug.
i've tried other solustions like "NSBundle loadNibNamed" or using loadView but it didn't work (warning message : "NSObject my not respond to loadView").
The main purpose of the secondViewController is to display notification message including
graphics and web element.
I hope someone could give me a best suggestion. Thanks.
This is normal behavior. IBOutlets are not connected in the constructor.
You can override viewDidLoad, call super and then do any initialization.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
//added this line :
if (nibBundleOrNil!=nil || ![nibBundleOrNil isEqualtoString:#""]) {
[NSBundle loadNibNamed:#"SecondViewController" owner:self];
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
fullScreenMode = YES;
}
return self;
}

hide an object from another class

I'm trying to hide an object in my viewController, with code executed from a custom class, but the object is nil.
FirstViewController.h
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController {
IBOutlet UILabel *testLabel;
}
#property (nonatomic, retain) IBOutlet UILabel *testLabel;
- (void) hideLabel;
FirstViewController.m
I synthesize testLabel and I have a function to hide it. If I call the function from viewDidAppear it works, but I want to call it from my other class. When called from the other class, testLabel is nil
#import "FirstViewController.h"
#import "OtherClass.h"
#implementation FirstViewController
#synthesize testLabel;
- (void) hideLabel {
self.testLabel.hidden=YES;
NSLog(#"nil %d",(testLabel==nil)); //here I get nil 1 when called from OtherClass
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
OtherClass *otherClass = [[OtherClass alloc] init];
[otherClass hideThem];
//[self hideLabel]; //this works, it gets hidden
}
OtherClass.h
#class FirstViewController;
#import <Foundation/Foundation.h>
#interface OtherClass : NSObject {
FirstViewController *firstViewController;
}
#property (nonatomic, retain) FirstViewController *firstViewController;
-(void)hideThem;
#end
OtherClass.m
calls the hideLabel function in FirstViewController. In my original project, (this is an example obviously, but the original project is at work) I download some data here and I want to hide my loading label and indicator when download is done
#import "OtherClass.h"
#import "FirstViewController.h"
#implementation OtherClass
#synthesize firstViewController;
-(void)hideThem {
firstViewController = [[FirstViewController alloc] init];
//[firstViewController.testLabel setHidden:YES]; //it doesn't work either
[firstViewController hideLabel];
}
Any ideas?
Your UILabel is nil because you just initialized your controller but didn't load it's view. Your controller`s IBoutlets are instantiated from the xib or storyboard automatically when you ask access to the bound view for the first time, so in order to access them you first have to load its view by some means.
EDIT (after OP comments):
Since your FirstViewController is already initialized and your OtherClass is instantiated by that controller, you could just hold a reference to it and not try to initialize a new one.
So try something like this:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
OtherClass *otherClass = [[OtherClass alloc] init];
otherClass.firstViewController = self;
[otherClass hideThem];
}
In your OtherClass.m:
-(void)hideThem {
[self.firstViewController hideLabel];
}

Cocoa Drag and Drop delegates not working the way I would expect

I made a very simple class that subclasses NSTextField so I could have more control over the drag and drop behavior, but I'm running into confusion. According to what I think I understand in the apple.developer docs on Dragging destinations, I should be able to get the delegates to fire when I enter or exit the bounds of the text field, but what I get instead is that the delegates don't fire until I have dropped something into the textField and then I try to drag it out.
I have a MyController class which is an NSObject and I have a CustomFields class of type NSTextField; There is nothing in the controller class.
Here is the code:
// CustomFields.h
// Drags
//
#import <AppKit/AppKit.h>
#import <Foundation/foundation.h>
#interface CustomFields : NSTextField{
NSString *tempStorage_;
unsigned long last_;
}
#property(readwrite, retain) NSString *tempStorage;
#end
//
// CustomFields.m
// Drags
//
#import "CustomFields.h"
#implementation CustomFields
#synthesize tempStorage = tempStorage_;
- (id)init{
if (self) {
self = [super init];
}
return self;
}
-(void)dealloc{
[self.tempStorage release ];
[super release];
}
-(void)awakeFromNib{
NSLog(#"Awake from nib called");
self.tempStorage = #"";
self->last_ = 0;
[self setBackgroundColor:[NSColor lightGrayColor]];
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSPasteboardTypeString , NSPasteboardTypeString, nil]];
[super awakeFromNib];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
NSLog(#"Inside dragging entered");
return NSDragOperationCopy;
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
NSLog(#" draggingUpdated called");
return NSDragOperationCopy;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
NSLog(#"Inside dragging exited");
}
#end
//
// MyController.h
// Drags
//
#import <Foundation/Foundation.h>
#class CustomFields;
#interface MyController : NSObject{
IBOutlet NSButton *myButton_;
IBOutlet CustomFields *field1_;
IBOutlet CustomFields *field2_;
}
#end
//
// MyController.m
// Drags
//
#import "MyController.h"
#import "CustomFields.h"
#implementation MyController
#end
I made sure I assigned the correct class to the fields(CustomFields) in IB and connected them. As you can see, for the moment I would be happy to get log statements at the proper time. I also tried changing the MyController class to NSViewController and NSView so I could try registerForDraggedTypes there. That did'nt help anything and I don't think I should expect it to. I think the behaviors I want should be confined to the CustomFields Class which is one of the reasons the MyController class can be empty for the moment.
So any help is much apprecieted.

Resources