How to manage multiple windows in Cocoa apps with Interface Builder - xcode

I have this application with 3 classes: AppController, Profile, ProfileBuilder. I also need 3 windows: one for each class. I tried keeping all 3 as subclasses of NSObject and applying initWithNibName to an NSWindowController class WindowController variable but when I tried outputting some values on each window it wouldn't work, and also the window resulted as null using NSLog. I was wondering what was the best way to manage multiple windows, perhaps all from a same class like an AppWindowsController involving as least as possible specific code in the other classes, and keeping, if possible, the other classes as subclasses of NSObject and not NSWindowController. So if there is, maybe a way to control the behavior of the windows remotely, adding as least as possible code inside the specific classes, just to keep them as clear as possible and uniquely focused on their content. Thanks, hope I made myself clear, I'm actually pretty new to the Cocoa framework.

You should be able to load the nib files with your windows in an init method for your different classes. For example, in Profile, you could do something like this:
-(id)init {
if (self = [super init]) {
NSArray *array;
BOOL success = [[NSBundle mainBundle] loadNibNamed:#"ProfileWindow" owner: self topLevelObjects:&array];
if (success) {
for (id obj in array) {
if ([obj isKindOfClass:[NSWindow class]]) {
self.profileWindow = obj;
}
}
[self.profileWindow makeKeyAndOrderFront:self];
}
}
return self;
}
profileWindow is a property (typed as strong). In the xib file, I set the File's Owner to Profile.

I just like to improve the solution of rdelmar.
You don't need to iterate over the array to find the NSWindow class.
If you define profileWindow as an outlet and connect it in the IB, the call
[[NSBundle mainBundle] loadNibNamed:#"ProfileWindow" owner:self topLevelObjects:&array];
will assign the window object to your outlet, the array stuff is not required.
The key here is the owner object which act as interface. In the IB you can define the class type of the owner and if so, see its outlets.

Related

Multiple windows per document (Cocoa document based app)

Let's say I have created a new Cocoa application and use Document-based application when I create the project. It will have one window, the one from the NSDocument subclass. How can I make it so that two (or more) windows belong to each document?
I have created an NSWindowController subclass with a .xib file, where I have created the interface. How can I show this window? And how does communication between the NSWindowController subclass and the NSDocument subclass work?
(I use core data, so it is really an NSPersistentDocument subclass, but I don't think it matters for this particular question.)
Within your NSDocument
//Lazy instantiation of window controller
- (AdditionalWindowController *)additionalWC {
if (!_additionalWC) {
_additionalWC = [[AdditionalWindowController alloc] initWithWindowNibName:#"AdditionalWindow"];
}
return _additionalWC;
}
- (IBAction)openAdditionalWindow:(id)sender {
self.additionalWC.document = self;
[self.additionalWC showWindow:self];
}
or
- (IBAction)openAdditionalWindow:(id)sender {
//addWindowController ignores redundant invocations.
[self addWindowController:self.additionalWC];
[self.additionalWC showWindow:self];
}
Within your AdditionalWindowController you can always call
id document = [self document];
//do what ever you want e.g. somethingDidChanged | direct method call of your document

NSWindowController in Swift. Subclassing and initializing with Nib

In a test Swift project, I am subclassing NSWindowController. My NSWindowController subclass is designed to work with a particular Nib file. It is desirable, then, that when my window controller is initialized, the nib file is automatically loaded by the window controller instance. In Objective-C, this was achieved by doing:
#implementation MyWindowController
- (id)init {
self = [super initWithWindowNibName:"MyWindowNib"]
if (self) {
// whatever
}
return self
}
#end
Now, in Swift this is not possible: init() cannot call super.init(windowNibName:), because the later is declared not as a designated initializer, but as a convenience one by NSWindowController.
How can this be done in Swift? I don't see a strightforward way of doing it.
P.S.: I have seen other questions regarding this topic, but, as long as I've been able to understand, the solutions all point to initialize the Window Controller by calling init(windowNibName:). Please note that this is not the desired beheaviour. The Window Controller should be initialized with init(), and it should be the Window Controller itself who "picks up" its Nib file and loads it.
If you use the init() just to call super.init(windowNibName:), you could instead just override the windowNibName variable.
override var windowNibName: String {
get {
return "MyWindowNib"
}
}
Then there should be no need to mess with the initializers.
You can create your own convenience initializer instead:
override convenience init() {
self.init(windowNibName: "MyWindowNib")
}
You should instead opt in to replacing all designated initializers in your subclass, simply delegating to super where appropriate. Confer https://stackoverflow.com/a/24220904/1460929

How to use IKScannerDeviceView in Cocoa

How can I use IKScannerDeviceView to scan a document inside of my app?
I tried adding an IKScannerDeviceView into my view through IB and setting its delegate to my app delegate (which implements the IKScannerDeviceViewDelegate), but when I run the app I get a view with the buttons Show Details and Scan, and only Show Details is enabled and when I click it nothing happens.
I have a scanner plugged in and I can scan through Image Capture, but not through my app.
Does anybody have a good tutorial on how to use it?
I was finally able to figure out how to use IKScannerDeviceView.
Your class must implement the following delegates:
IKScannerDeviceViewDelegate, ICScannerDeviceDelegate, ICDeviceBrowserDelegate
and you need to have an IKScannerDeviceView in your window, with its delegate set to the class implementing IKScannerDeviceViewDelegate
To start using it, you must create an ICDeviceBrowser like so:
ICDeviceBrowser *mDeviceBrowser = [[ICDeviceBrowser alloc] init];
mDeviceBrowser.delegate = self;
mDeviceBrowser.browsedDeviceTypeMask = ICDeviceLocationTypeMaskLocal|ICDeviceLocationTypeMaskRemote|ICDeviceTypeMaskScanner;
[mDeviceBrowser start];
Then implement the delegate methods in a manner similar to this:
- (void)scannerDeviceDidBecomeAvailable:(ICScannerDevice*)scanner;
{
[scanner requestOpenSession];
}
- (void)deviceBrowser:(ICDeviceBrowser*)browser didAddDevice:(ICDevice*)addedDevice moreComing:(BOOL)moreComing
{
if ( (addedDevice.type & ICDeviceTypeMaskScanner) == ICDeviceTypeScanner )
{
[scannerView setScannerDevice:(ICScannerDevice*)addedDevice];
}
}
-(void)didRemoveDevice:(ICDevice*)removedDevice
{
[removedDevice requestCloseSession];
}
Then if all goes right, your IKScannerDeviceView will be able to interact with your scanner!

NSKeyedArchiver: distinguishing between different instances of the same class

I'm implementing support for Lion's "Resume" feature in my OS X app.
I have a custom subclass of NSViewController in which I implemented the method
encodeRestorableStateWithCoder: as:
#implementation MyClass (Restoration)
-(void)encodeRestorableStateWithCoder:(NSCoder*)coder {
[coder encodeObject:_dataMember forKey:#"object_key"]; // I get the warning below when this line is executed for the second time
}
- (void)restoreStateWithCoder:(NSCoder *)coder {
_dataMember = [coder decodeObjectForKey:#"object_key"];
}
#end
However, since I have multiple instances of MyClass, different values are saved into the same key ("object_key") and I get the following warning from Cocoa:
NSKeyedArchiver warning: replacing existing value for key
'object_key'; probable duplication of encoding keys in class hierarchy
What is the best practice to overcome this problem?
Edit: I found here that each instance automatically has its own namespace to avoid collisions, so the problem might be in the way I'm manually calling encodeRestorableStateWithCoder to different instances with the same NSCoder object without telling it that these are different instances. However, I still can't figure out how to do that properly.
Thanks in advance!
To overcome this problem, it is possible to create a new NSMutableData where each of which is written by a separate (new) NSKeyArchiver, and store them all in an array which is stored in the original NSCoder object.
Here is an example for encoding the restorable state of subitems. The decoding part can be straight-forward given this code.
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
// Encode subitems states:
NSArray* subitems = self.items;
NSMutableArray* states = [NSMutableArray arrayWithCapacity: subitems.count];
for (SubItemClass* item in subitems)
{
NSMutableData* state = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:state];
[item encodeRestorableStateWithCoder:archiver];
[archiver finishEncoding];
[states addObject:state];
}
[coder encodeObject:states forKey:#"subitems"];
}

How to bind a control to a singleton in Cocoa?

I have a singleton in my FTP app designed to store all of the types of servers that the app can handle, such as FTP or Amazon S3. These types are plugins which are located in the app bundle. Their path is located by applicationWillFinishLoading: and sent to the addServerType: method inside the singleton to be loaded and stored in an NSMutableDictionary.
My question is this:
How do I bind an NSDictionaryController to the dictionary inside the singleton instance? Can it be done in IB, or do I have to do it in code? I need to be able to display the dictionary's keys in an NSPopupButton so the user can select a server type.
Thanks in advance!
SphereCat1
I found / made up the answer to this: I simply override the init method so when it's called from the XIB file, it still returns the singleton. I then provide a method named realInit to do an actual initialization the first time init is called.
Code:
-(id)init
{
#synchronized(self)
{
if (_sharedInstance == nil)
{
_sharedInstance = [[VayprServerTypes alloc] realInit];
}
}
[self release];
return _sharedInstance;
}
-(id)realInit
{
if (self = [super init])
{
serverTypesArray = [[NSMutableArray alloc] init];
}
return self;
}
EDIT: Of course you'll need to define _sharedInstance as a static variable at the top of your class implementation:
static ClassTypeGoesHere *_sharedInstance;
ALSO EDIT: Since you now know for sure that your init method will be called at least once, you can go ahead and replace your normal singleton sharedInstance method with this:
+(ClassTypeGoesHere *)sharedInstance
{
return _sharedInstance;
}
If anyone sees any obvious problems with this design, please let me know!
SphereCat1

Resources