Trouble accessing NSTextField from method called by a Custom View - objective-c

I'm almost done writing an image editing program, but ran into a problem that should have a simple solution.
Basically, I've built a set of buttons and NSTextFields and a Custom View into the main xib, just dropping them straight off the Library into the default window (and then, of course, linking them up with IBOutlets and IBActions). One of the buttons is an "Open" button that calls a function. The function does several things: it runs a NSOpenPanel, and then changes some of the NSTextFields (used for changing image name and path). That code is called as follows:
- (IBAction)openButtonPressed: (id)sender {
[self runOpenPanel];
}
Now I also happen to be running my keyDown handler from the Custom View, as I've told it to acceptFirstResponder. (I know, it's probably bad practice to not write a separate Controller class, but that's best left for another time.) So my keyDown event looks like this (simplified, as in the actual code I have has if statements to handle certain keys separately that don't pertain to this question):
- (void)keyDown: (NSEvent *)theEvent {
[self runOpenPanel];
}
So they both use [self runOpenPanel] but from different contexts. The problem I'm having is that "runOpenPanel" makes a few calls to change IBOutlets, like this:
-(void)runOpenPanel {
// Omitting some of the trivial NSOpenPanel code
[myTextField setStringValue: #"The file name NSString from the aforementioned omitted code."];
}
So myTextField updates when the function is run by an Interface Builder button, but not when run by the Custom View's keyDown handler. Is there some way to call runOpenPanel that will allow it to access myTextField? I've tried using [[super self] runOpenPanel] (don't laugh) and a number of other things. Thanks in advance!

Related

How to use NSWindowController class

I am sorry if this seems trivial, but I am sure its a reasonable question to ask here.
I worked a lot around the NSWindowController class, and it seems the only way to get it
to work fully (for my purpose), is by creating a new xib-file along with it.
My question is, would it be somehow feasible to work with MainMenu.xib and the NSWindowController class and an instantiated object controller, to get interaction with the windows' content. So far without xib the only code segments getting executed are within awakeFromNib. The purpose being, I want to save xib-file space, complexity and have it easily integrate with a bigger project. Just fyi this is not a document-based project.
Should I choose a different subclass of NSObject other than NSWindowController? Or is it not possible?
The code required to run for the class to be working fully is as follows:
- (void) tableViewSelectionDidChange:(NSNotification *)notification
{
NSInteger selectedRow = [logsTableView selectedRow];
if ([directoryList containsObject:[directoryList objectAtIndex:selectedRow]])
{
NSString *logContent = [NSString stringWithContentsOfFile:[directoryList objectAtIndex:selectedRow]
encoding:NSUTF8StringEncoding
error:NULL];
if (logContent != NULL)
{
[logsTextView setString:logContent];
} else
{
[logsTextView setString:#"No permission to read log"];
}
}
}
NSWindowController usually wants to create the window it controls, which means you either need to give it a XIB file that contains the window to create or override the various window creation methods to customize the window in code. So it's probably not feasible to use an already-instantiated window from a different XIB with your NSWindowController.
That said, I almost always create a a XIB and an NSWindowController subclass for every window in my apps. Even the preferences window gets its own window controller class. The only exception would be extremely simple windows, but even now I'm struggling to think of a good example.
Your method isn't being called because window controller instance isn't set as the table view's delegate. The typical pattern here is to create your window in a XIB, set your window controller as the custom class of the File's Owner object, and then hook up the table view's delegate and dataSource outlets to File's Owner. This makes your window controller the table view's data source and delegate, and the connections will be established automatically when the XIB is loaded.

Silence Cocoa error beep

I have a Cocoa application that captures keypresses through a custom view in the view hierarchy. This view implements the keyUp and keyDown methods, and the keypresses are received. Even so, Cocoa still insists on playing the system error sound/ding every time I press a key. Any solutions?
Note: Although I tried to make this view first responder, it didn't work. That may have something to do with it.
If you have unsuccessfully tried to make the view the first responder, it's most likely because NSView returns NO for acceptsFirstResponder. You can have your NSView subclass override acceptsFirstResponder to return YES:
- (BOOL)acceptsFirstResponder {
return YES;
}
That should eliminate the beeps. Alternatively, you could have the NSView subclass override NSResponder's performKeyEquivalent: method to return YES, which should also eliminate the NSBeeps:
- (BOOL)performKeyEquivalent:(NSEvent *)event {
return YES;
}
UPDATE:
Not sure what to suggest. I actually wrote a "Keyboard Cleaner Helper" app that's designed to basically do something similar to what you want. (I used it on my laptop when I wanted to clean the keyboard and didn't the hundreds of key presses to randomly rename files or result in repeated error beeps).
Sample project: http://www.markdouma.com/developer/KeyboardCleanerHelper.zip
Running that app, I can't get it to beep at all (notice calls are logged to Console).

Communication between UIViewControllers

I am new with Objective-C so apologies for a dumb question.
I am opening an "options" view controller from my main view controller. Both are built in the storyboard. Before and after presenting the options controller I need to stop and start a timer on my main view controller. After the options controller is closed (a button calls dismiss) I need to send some info back to my main controller or at least let my main controller know that it needs to refresh some values.
MAIN QUESTION
What's the best way of presenting a view controller and executing some presenter's methods before and after opening?
WHAT I'VE TRIED
I found a few ways to do it, but they are all cumbersome and I assume that there must be some plausible way of doing it.
Ideally I'd like to use the segue I set up in the storyboard between the two controllers.
I managed to call the options controller programmatically by accessing the storyboard and calling instantiateViewControllerWithIdentifier. It worked but looks a bit complex.
I was not able to find a delegate method on the UIViewController to handle the dismiss event
When I was trying to access the main controller in the options controller via presentingViewController and downcasting it, I got a linkage error by including my .h file twice (not sure what are the Obj-C standards of using #define).
Appreciate your help...
For communication between ViewControllers that are weakly linked, you could use the NSNotificationCenter:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html
Here you can send a message to all ViewControllers listening, which need to process some changes (for example an option to change the font size).
It's really easy to implement and it keeps certain ViewControllers less dependent on each other.
All of this can be done quite easily with storyboard and NSNotificationCenter, and NSCoding. In the viewDidLoad method of your main controller, put this code:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveNotification:)
name:#"Update"
object:nil];
Then create this method in the same controller:
(void)receiveNotification:(NSNotification*)notification
{
//...
}
When you want to make the main controller update from the options controller:
[[NSNotificationCenter defaultCenter] postNotificationName:#"Update" object:self];
Also, I would suggest using NSArchiving for Basic Data Persistence. Just found this tutorial, looks pretty good.
http://samsoff.es/posts/archiving-objective-c-objects-with-nscoding
Basically, create an object that can store information, code it using nscoding, and then uncode it whenever you need it. It has worked great for me.
Hope that helps!
MAIN QUESTION What's the best way of presenting a view controller and executing some presenter's methods before and after opening?
Just in case the answers above are a bit more involved than you'd like, I'll suggest that the easiest way to execute a presenter's methods before opening is to do so in the presenter's prepareForSegue method. If you need to send data to the destination view controller, you can access its properties this way:
ViewController *destinationVC = [segue destinationViewController];
An easy way to execute the presenter's methods after opening would be:
ViewControllerSubclass *previousVC = [self presentingViewController];
And then use the class or instance to execute your class or instance methods. You could do this in the destination's viewWillAppear.
Sorry if you already knew all this; it's often difficult to surmise what level of complexity is needed.
I have run into this with almost every app I have on the market. Difference is I have never decided to go down the storyboard path.
The way I have always been able to accomplish this is to provide accessor functions between the controllers. You get past the linker issue by defining the cross defined controller as simply a UIViewController type within your options view header, then including the main view controller' header only in the .m file. Now when you call a main view controller routine from your options view, you will have to cast it to the type of your main view controller!
You will also have to provide a routine in your options view that will allow you to set the variable that will hold a pointer to your main view controller to self.
Example for your optionsView
#interface optionsViewController : UIViewController{
UIViewController * myReactiveMainViewController;
}
-(void)setMyReactiveMainViewController:(UIViewController *)controller;
No in the .m file for the optionsView
#import "myMainViewController.h"
-(void)setMyReactiveMainViewController:(UIViewController *)controller{
myReactiveMainViewController = controller;
}
In any other call back to the main view controller you will have to do this:
-(void)returnToMain{
[(myMainViewController *)myReactiveMainViewController someCall:variable];
}
This example would of course assume that your myMainViewController implements a method called "someCall" that take on input parameter.
Thanks for replies.
I ended up with
Calling prepareForSegue to execute pre-transition code
Calling performSelector on presentingViewController when releasing presented view controller.
I am sure other suggestions would work too.

Duplicate NSLog entries

I don't know if it's possible for me to include code here that's relevant as my project is so large but are there any typical reasons why NSLog would repeat some warnings and calls to it at occasions where only one call/error is occuring?
As an example, I have a subclass of NSBox that inits an instance of another class on awakeFromNib:
- (void) awakeFromNib {
burbControllerInstance = [[BurbController alloc] init];
if (burbControllerInstance) {
NSLog(#"init ok");
}
}
I get NSLog printing "init ok" twice. I don't see why this subclass would be 'awoken' twice anywhere in my project. This is part of a larger problem where I can't get variables to return anything but nil from the class I'm creating an instance of. I'm wondering if perhaps the double values are something to do with it.
This post could be helpful, i. e. one comment:
Also important: awakeFromNib can be
called multiple times on the
controller if you use the same
controller for several nibs – say,
you’re using the app delegate as the
owner of both the app’s About Box and
preferences dialog. So you’ll need an
extra guard test if you use
awakeFromNib for anything but
initializing the nib objects
Update: Much more interesting could also be this, where the author mentions that awakeFromNib gets called twice. Unfortunately there is no real answer for this particular problem but maybe some basic ideas.
Update #2: Another potential solution from stackoverflow.com: View Controller calls awakeFromNib twice.

How to activate a custom screensaver preview in Cocoa/Obj-C?

I have created a fairly simple screensaver that runs on Mac OS 10.6.5 without issue.
The configuration screen has accumulated quite a few different options and I'm trying to implement my own preview on the configureSheet window so the user (just me, currently) can immediately see the effect of a change without having to OK and Test each change.
I've added an NSView to the configureSheet and set the custom class in Interface Builder to my ScreenSaverView subclass. I know that drawRect: is firing, because I can remove the condition for clearing the view to black, and my custom preview no longer appears with the black background.
Here is that function (based on several fine tutorials on the Internet):
- (void)drawRect:(NSRect)rect
{
if ( shouldDrawBackground )
{
[super drawRect:rect];
shouldDrawBackground = NO;
}
if (pausing == NO)
[spiroForm drawForm];
}
The spiroForm class simply draws itself into the ScreenSaverView frame using NSBezierPath and, as mentioned, is not problematical for the actual screensaver or the built-in System Preferences preview. The custom preview (configureView) frame is passed into the init method for, um, itself (since its custom class is my ScreenSaverView subclass.) The -initWithFrame method is called in configureSheet before returning the configureSheet object to the OS:
[configureView initWithFrame:[configureView bounds] isPreview:YES];
Maybe I don't have to do that? It was just something I tried to see if it was required for drawing.
I eventually added a delegate to the configureSheet to try triggering the startAnimation and stopAnimation functions of my preview via windowWillBeginSheet and windowWillEndSheet notifications, but those don't appear to be getting called for some reason. The delegate is declared as NSObject <NSWindowDelegate> and I set the delegate in the configureSheet method before returning the configureSheet object.
I've been working on this for days, but haven't been able to find anything about how the OS manages the ScreenSaverView objects (which I think is what I'm trying to emulate by running my own copy.)
Does anybody have any suggestions on how to manage this or if Apple documents it somewhere that I haven't found? This isn't really required for the screensaver to work, I just think it would be fun (I also looked for a way to use the OS preview, but it's blocked while the configureSheet is activated.)
OK, there are a couple of 'duh' moments involved with the solution:
First of all, I was setting the delegate for the sheet notifications to the sheet itself. The window that the sheet belongs to gets the notifications.
Secondly, that very window that the sheet belongs to is owned by System Preferences, I don't see any way to set my delegate class as a delegate to that window, so the whole delegate thing doesn't appear to be a viable solution.
I ended up subclassing NSWindow for the configureSheet so that I could start and stop animation on my preview by over-riding the makeKeyWindow and close methods.
- (void) makeKeyWindow
{
if (myPreview != nil)
if ( ! [myPreview isAnimating])
{
[myPreview startAnimation];
}
[super makeKeyWindow];
}
I also had to add an IBOutlet for my preview object itself and connect it in Interface Builder.
Still working out a couple of issues, but now when I click on my screensaver Options button, my configureSheet drops down and displays its own preview while you set options. Sheesh. The hoops I jump through for these little niceties. Anyway, I like it. Onward and upward.