My Setup
XCode 4.3.2
MacRuby 0.12 (ruby 1.9.2) [universal-darwin10.0, x86_64]
Latest nightly as of 4 Jun, 2012
OS 10.7.3
Goal
Have a window with some controls in a separate XIB from MainMenu.xib and be able to open that window programmatically. I do not want it to open at launch.
Attempts
I made a new xib (Woot.xib) and created a window in it
I made a new Ruby class
class WootController < NSWindowController
attr_accessor :window
def windowNibName
return 'Woot'
end
end
I tried to set the class of File's Owner in the Woot.xib to be WootController, but found that it will not if < NSWindowController is in my class definition. If I remove the < NSWindowController from the class definition, then the outlets populate and I can link the window in XIB to the window outlet in my class.
From inside my AppDelegate's applicationDidFinishLaunching method, I've tried
Attempt
newWind = WootController.new
puts newWind #outputs "#<AddCredentialsDialog:0x400191180>"
newWind.window().makeKeyAndOrderFront(self) # results in no method error for nil
Attempt 2
newWind = WootController.initWithWindowNibName 'AddWindow'
puts newWind #outputs "#<AddCredentialsDialog:0x400191180>"
newWind.window().makeKeyAndOrderFront(self) # results in no method error for nil
Questions
Why don't either of my attempts work? I've ready just about everything I can find on macruby and using NSWindowController.
Why can't I link my class WootController if I have it inheriting from NSWindowController
Is there a different way to do this other than putting it all in MainMenu.xib?
This solution works
nib = NSNib.alloc.initWithNibNamed('Woot', bundle: nil)
newWind = WootController.new
nib.instantiateNibWithOwner(newWind, topLevelObjects:nil)
newWind.showWindow(self)
Some things to note
In Macruby, if there are named parameters to a method signature, you must use them even if you just specify nil or the method signatures don't match up and you get a no method error.
ie. obj.foo('hello', to_whom: nil) is not the same as obj.foo('hello')
If there are named parameters you must use parentheses.
ie. this obj.foo('hello', to_whom: nil) will work, not this obj.foo 'hello', to_whom: nil
Related
I am running into a problem with the NSOpenPanel and calling it form another class.
I currently have one main window with a button that opens up a second window that is setup as an image editor using ImageKit. That works well. I would also like for when that image editor window opens up (as a result of the button push) the NSOpenPanel is launched. Basically I want to bypass making the user click a button to open the image editor and then click "Open" in the menu or command-O to open an image. We know that if the user is opening the image editor they will need to open an image to edit... I'd like the open panel to open when the window is displayed.
In my appDelegate.m I have this code to launch the image editor window "_imageWindow" and call the "openImage" method:
[_imageWindow makeKeyAndOrderFront:self];
Controller *controllerOpenImage = [[Controller alloc] init];
[controllerOpenImage openImage];
This works EXCEPT that the open panel which is supposed to be modal is launched as a separate window and not attached to the Image Editor window (_imageWindow) so when a user selects an image it's not opened... I've tried adding a delay to allow the _imageWindow window time to open to no avail. I've tried both IBAction(openImage) and void(openImage) with and without sender with the same result...
Here's the code to open an image in my Controller.m:
- (IBAction)openImage:(id)sender
{
// present open panel...
NSString * extensions = #"tiff/tif/TIFF/TIF/jpg/jpeg/JPG/JPEG";
NSArray * types = [extensions pathComponents];
NSString *url=[[NSUserDefaults standardUserDefaults] objectForKey:#"photoPath"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy"];
NSString *yearString = [formatter stringFromDate:[NSDate date]];
NSString *photoUrl =[NSString stringWithFormat:#"%#%#%#",url,#"/",yearString];
// Let the user choose an output file, then start the process of writing samples
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel setDirectoryURL:[NSURL fileURLWithPath:photoUrl]];
[openPanel setAllowedFileTypes:types];
[openPanel setCanSelectHiddenExtension:YES];
[openPanel beginSheetModalForWindow:_imageWindow completionHandler:^(NSInteger result) {
if (result)
{
// user did select an image...
[self openImageURL: [openPanel URL]];
}
[_imageView zoomImageToFit: self];
}];
}
Is there an issue with the sender being null when called from my appDelegate.m as opposed to sender having an identity when called from the Image Editor window (_imageEditor) or am I asking to do something that just can't be done.
Let's look at your code. First in some method/function you execute:
[_imageWindow makeKeyAndOrderFront:self];
This accesses _imageWindow which by common naming convention is probably an instance variable or property backing variable, but could be a global - you don't say.
Controller *controllerOpenImage = [[Controller alloc] init];
This creates a brand new object storing a reference to it in a local variable.
[controllerOpenImage openImage];
This calls a method on this brand new object, and in that method you do:
[openPanel beginSheetModalForWindow:_imageWindow ...
So where does _imageWindow come from? By common naming convention it is an instance/property variable, and if so it belongs to the Controller instance and is in no way connected with the similarly named variable in the first line above. As you've not shown the init method we've no idea if you initialised this variable, if you didn't it is nil.
On the other hand this could reference a global, and if it does it is presumably the same global referenced in this first code line above. If this is the case your code would probably work, but it doesn't...
So it's a fair guess that both references are to distinct, but similarly named instance/property variables, that they are connected in no way, that the second is nil, and that therefore openPanel is passed a nil window reference and opens a standalone dialog.
Addendum
In your fourth comment you end with:
does that make any sense?
Unfortunately not.
When you link an object, such as your window, to an IBOutlet of another object, such as an instance of your Controller class, then you are making a connection between the specific instances created by your NIB (the IB document).
When the system evaluates your NIB at runtime it creates a window, being an instance of NSWindow, and an instance of your Controller class and places a reference to that specific window instance into the IBOutlet variable/property of that specific Controller instance.
This has no impact on any other windows you might create, they are not automatically linked to Controller instances; or on any other Controller instances, they are not automatically linked to the window.
When you write:
Controller *controllerOpenImage = [[Controller alloc] init];
You are creating a new Controller instance, which is in no way connected to the Controller instance created by your NIB. Initially the latter instance, the one you are not using, might reference your window; the former, which you are using, certainly does not.
If you are going to create a Controller instance in your NIB then in your code you need to use that specific instance, not create a new one. To use it you need a reference to it. One way to do that would be to add a property to your appDelegate class which references it, setting up the link in your NIB.
In an earlier comment you wrote:
but I'm not really sure how to extrapolate a solution from the second comment
The comment you refer to was suggesting you get your window reference into your Controller class by passing it as a parameter. This is a very basic mechanism in any programming language. In this case it is suggesting you write a method such as:
- (instancetype) initWithWindow:(NSWindow *)aWindow { ... }
And create a Controller instance using:
[[Controller alloc] initWithWindow:_imageWindow];
It sounds like you need to go back and study the basics of instances, methods, parameter passing; and then how a NIB is evaluated.
HTH
I am trying to access a function in an objective C category that extends a UIViewController from the original controller. When I do this one of the properties is nil and the method doesn't work properly. When accessed from inside the category (using a UITapGestureRecogniser) the property has a value in memory and works fine. Am I missing something this seems very odd?
calling from the UIViewController:
[self dismissCurrentPopinControllerAnimated:YES completion:nil];
from the category: (UIViewController_MaryPoppin)
- (void)dismissCurrentPopinControllerAnimated:(BOOL)animated completion:(void(^)(void))completion
{
UIViewController *presentedPopin = self.presentedPopinViewController;
The presentedPopin controller is nil when called from outside the category.
My mistake, it seems that with this category (UIViewController+MaryPoppin) you need to send messages to the presented controller (in this case a UINavigationController) so self was incorrect although the compiler still didn't complain.
[self navigationController]dismissCurrentPopinControllerAnimated:YES completion:nil];
This seems to have solved the problem
I'm getting some weird behavior, I Set a Label in Interface Builder, then I connect the label to a file as an Referencing Outlet.
#property (weak) IBOutlet NSTextField *TitleLabel;
When I access that label in the file (cell.TitleLabel.stringValue = title) and run the application, it doesn't recognize it.I get this:
-[NSApplication TitleLabel]: unrecognized selector sent to instance 0x608000101680
The weird thing is that it doesn't always do this, sometimes it works and displays correctly, other times it doesn't.
I've just started messing with IB so I'm probably missing something. Any help?
Is the property really on your NSApplication subclass? or is it on you application delegate class? It's not impossible for it to be on the application object, but it would be a pretty uncommon (and arguably ill-advised) pattern.
In short, I suspect you're probably connecting it to the wrong object.
EDIT: Ah. I see. You're trying to access things via the topLevelObjects array, but in practice, you can't count on the order of topLevelObjects. What you need to rely on the owner's outlets getting populated, but you're passing nil for the owner. topLevelObjects only exists to give the caller "ownership" (in the reference counting sense) of the top level objects in the xib for memory-mangement purposes, it's not really meant to be "used" directly like you're doing here. (In fairness, I can imagine situations where you might need to introspect that array, but this hardly rises to that level.)
The canonical way to do this would be to use an NSViewController subclass as the owner. In Xcode, if you add a subclass of NSViewController to your project, it will give you the option to create a xib file at the same time that will have everything hooked up. Then you just initialize the NSViewController subclass at runtime and the view outlet property of that class will be filled with the root view. You can obviously add more outlets and plug in whatever you like.
This post appears to cover the basics, if your looking for more details. Apple's docs on xib files and how they work are here.
The problem was that the View would sometimes get assigned to NSApplication. I'm not sure if the way that I am initiating the view is the common way of doing it but the problem was within this block of code:
NSArray * views;
[[NSBundle mainBundle] loadNibNamed:#"CollapseClickViewController" owner:nil topLevelObjects:&views];
CollapseClickCell * cell = [[CollapseClickCell alloc] initWithFrame:CGRectMake(0,0,320,50)];
cell = [views objectAtIndex:0];
the problem was that [views objectAtIndex:0] would sometimes return NSApplication. To fix it I just checked the class against itself and returned that object via:
-(CollapseClickCell*)assignCell:(CollapseClickCell*)cell withArray:(NSArray*)array{
for ( int i = 0; i< [array count]; i++) {
if ([[array objectAtIndex:i] class] == [CollapseClickCell class]) {
return [array objectAtIndex:i];
}
}
return nil;
}
I then assign that to the object:
cell = [cell assignCell:cell withArray:views];
It may not be the conventional way of doing it but it works. If there is a better technique or a more common approach please enlighten me! :)
I'm trying out UIBlurEffect and UIVibrancyEffect for a view.
Apple docs here claim a "notificationCenterVibrancyEffect", but even after importing UIKit in my class, it throws No known class method for selector notificationCenterVibrancyEffect.
Code looks like this, every other class method resolves just fine:
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVibrancyEffect *vibrancyEffect = [UIVibrancyEffect notificationCenterVibrancyEffect];
UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
UIVisualEffectView *vibrancyEffectView = [[UIVisualEffectView alloc] initWithEffect:vibrancyEffect];
This is only available for "Today" extensions. If you create a new target in your project of the class "Today" extensions and you add your code to the view controller, it will build fine.
The method + (UIVibrancyEffect *)notificationCenterVibrancyEffect; is declared in a category of the class UIVibrancyEffect named NotificationCenter, and declared in the NotificationCenter framework, in a header file called NCWidgetProviding.h
Try: #import <NotificationCenter/NotificationCenter.h> in your view controller, I was able to build a sample project with your code this way.
I have a class BrowserWindowController that extends NSWindowController. My app delegate has a BrowserWindowController that it allocates, initializes, and points an instance variable at when the app is launched. Then, it displays its window. The goal is to have a window from a NIB show up.
However, the code I am using ends up allocating TWO BrowserWindowControllers and initializes both. I have used the debugger to track down when BWC's initWithWindow method is called:
browser = [[BrowserWindowController alloc] initWithWindowNibName:#"BrowserWindow"]; //this calls initWithWindow as expected
[browser showWindow:nil]; //this allocates ANOTHER BWC and calls initWithWindow on it!
showWindow is making a new BrowserWindowController. I don't know what points to the new object it makes. It's a huge problem for me. Any way to get around this or make the window show up using a different method? Or could I at least get a pointer to the controller that showWindow creates for whatever reason?
Did you check the condition like this and try?
if !(browser)
{
browser = [[BrowserWindowController alloc] initWithWindowNibName:#"BrowserWindow"]; //this calls initWithWindow as expected
[browser showWindow:nil];
}
Worst solution ever. The problem was that I had a property in my controller called "owner" that was an NSString. NSWindowController already has an "owner" property, and I overlooked that. Somehow, that caused the NIB loader to make a second controller with no accessible pointer to it and do some other weird things.
So I renamed it, and it works now. Thank goodness... I was tearing my hair out with this problem.