How to set NSStatusBar title from other class? - objective-c

In one of my classes i setup my NSStatusBar like:
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain];
[statusItem setMenu:statusMenu];
Is it possible to somehow call something like:
[statusItem setTitle:#"Waiting for data..."];
From another class? I want to change the title as a user has entered information into a preferences window that is being handled by a different class.
I tried to make a class function:
+(void)ChangeTitel
Which i called from the preferences class, it seemed to be able to call it but somehow not access the statusItem object.
Any ideas? :)

If you want to change the title from another class, that other class needs to have a reference to that status Item.
So what you have to do, is write a method that returns the pointer of this status item, ( its an instance variable right? ) and then call that method, to get the NSStatusItem object in the other class.
I think you are new to OOP coding, judging from your question. A class is a set of code.. sorta, if you [[Class alloc] init] you allocate some memory for the class, and create a new object of that class in it. A new instance.
If you call something that starts with a - then you are calling an instance method, that requires you to have an instance. If you call something with a + you are calling a CLASS method, which has no instance, so no access to the instance variable of you status item.
- (NSStatusItem *)statusItem
{ return statusItem; }
Don't forget to declare this method in your header file as well, otherwise you will get a compiler warning.
Should be in the class that manages the status item.
Then in the class where you want to use the status item:
#import "ManagerClass.h" // on top, so we have the method declared
Then:
ManagerClass *someInstanceToIt = [[ManagerClass alloc] init];
[(NSStatusItem *)[someInstanceToIt statusItem] setTitle:#"New Title"];
If the instance of the manager class is an interface builder outlet, or has been created before, then you shouldn't do that first line with alloc] init]
If you need any more help, post a comment.

Related

Trouble calling NSOpenPanel from another class and attaching it to its parent window

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

Is there a way to turn a weak reference into a strong one?

I have an object that is set as the delegate of another object, whose delegate property is weak.
- (YYService *)service
{
XXHandler *handler = [[XXHandler alloc] init];
// YYService's "delegate" property is weak
return [[YYService alloc] initWithDelegate:handler];
// The XXHandler is deallocated because there are no strong references to it
}
Since nothing else references the delegate it ends up getting deallocated, but I want it to live for as long as the parent object does as if the parent had a strong reference to its delegate. Is there a simple way to accomplish this?
The easy why to "solve" that problem is to subclass YYService, giving the subclass an additional strong property and set that one in -initWithDelegate:.
But this "solution" would deepen a problem in your design instead of solving that.
Let's have a look, why delegates are usually hold weakly:
The delegating class has a general – or no – behavior which might not fit in the class' user's case, i. e. if something happens. (An operation completes, an error occurs, $whatever) So the delegating class gives you the opportunity to customize the behavior including running custom code. Delegating is in competition with subclassing, but in difference to subclassing is on a per instance basis (instead of a per class basis) and at run time (instead of compile time).
Because it works on per instance basis, the instance creating the delegate typically holds the delegating instance strongly. This code knows the customization that should apply to the delegating instance:
-(void)createDelegate
{
self.delegating = [Delegating new]; // I create and hold the instance strongly
delegating.delegate = self; // I customize it
}
Then the delegating instance cannot hold the delegate strongly, because this would be a retain cycle.
In your snippet that does not work, because -service returns the newly created delegating instance. And even it would be possible to return both instances, I wouldn't like it, because creating the delegating object and installing the delegate would be a two-step operation, even it is semantically a one-stepper. So If you do not have self as the delegate, you should do the whole installation process in one method:
-(void)installService
{
self.handler = [[XXHandler alloc] init]; // Hold the handler strongly
self.service = [[YYService alloc] initWithDelegate:handler];
}
If you do not know the concrete instance object acting as delegate, pass it as argument:
-(void)installServiceWithDelegate:(id)delegate
{
self.delegate = delegate;
self.service = [[YYService alloc] initWithDelegate:delegate];
}
…
[self installServiceWithDelegate:[YourConcreteClass new]];
But you should not try to turn things upside down or inside out.

My object's isKindOfClass executed with unexpected answer

I've been trying to get this script print out the one of the two types of object that was inserted. However, it always prints both types while only one thing is inserted. There are two classes, VKJItem and VKJBox. VKJBox is a subclass of VKJItem.
This is the main.m:
VKJBox *box1 = [[VKJBox alloc] init];
VKJBox *box2 = [[VKJBox alloc] init];
[box1 addItem:box2];
and this is the implementation of my VKJBox's addItem method:
if ([item isKindOfClass:[VKJBox class]]) {
NSLog(#"BOX");
}
if ([item isKindOfClass:[VKJItem class]]) {
NSLog(#"ITEM");
}
The problem is that the script prints both BOX and ITEM to the console.
VKJBox is a subclass of of VKJItem and therefore VKJBox is a kind of VKJItem and a kind of VKJBox.
-isKindOfClass: is used to determine whether an object is an instance of a class or an instance of a class which inherits from the class.
For example:
isKindOfClass[UIView class] will be true for UIImageView, UILabel, etc.
Use the -isMemberOfClass: to check if the object is an instance of the specified class exactly.
On of your Classes must be a subclass of Other.
isKindClass:
Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class.
isMemberOfClass:
Returns a Boolean value that indicates whether the receiver is an instance of a given class.
You need to use isMemberOfClass
Source

Setting/getting global variables in objective-C

I am writing an app which is a sort of dictionary - it presents the user with a list of terms, and when clicked on, pops up a dialog box containing the definition. The definition itself may also contain terms, which in turn the user can click on to launch another definition popup.
My main app is stored in 'myViewController.m'. It calls a custom UIView class, 'CustomUIView.m' to display the definition (this is the dialog box that pops up). This all works fine.
The text links from the CustomUIView then should be able to launch more definitions. When text is tapped in my CustomUIView, it launches another CustomUIView. The problem is, that this new CustomUIView doesn't have access to the hash map which contains all my dictionary's terms and definitions; this is only available to my main app, 'myViewController.m'.
Somehow, I need to make my hash map, dictionaryHashMap, visible to every instance of the CustomUIView class. dictionaryHashMap is created in myViewController.m when the app opens and doesn't change thereafter.
I don't wish to limit the number of CustomUIViews that can be opened at the same time (I have my reasons for doing this!), so it would be a little resource intensive to send a copy of the dictionaryHashMap to every instance of the CustomUIView. Presumably, the solution is to make dictionaryHashMap a global variable.
Some of my code:
From myViewController.m:
- (void)viewDidLoad
{
self.dictionaryHashMap = [[NSMutableDictionary alloc] init]; // initialise the dictionary hash map
//... {Code to populate dictionaryHashMap}
}
// Method to pop up a definition dialog
- (void)displayDefinition:(NSString *) term
{
NSArray* definition = [self.dictionaryHashMap objectForKey:term]; // get the definition that corresponds to the term
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
[definitionPopup setTitle: term];
[definitionPopup setMessage: definition];
[definitionPopup show];
}
// Delegation for sending URL presses in CustomUIView to popupDefinition
#pragma mark - CustomUIViewDelegate
+ (void)termTextClickedOn:(CustomUIView *)customView didSelectTerm:(NSString *)term
{
myViewController *t = [[myViewController alloc] init]; // TODO: This instance has no idea what the NSDictionary is
[t displayDefinition:term];
}
From CustomUIView.m:
// Intercept clicks on links in UIWebView object
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
[myViewController termTextClickedOn:self didSelectTerm:request];
return NO;
}
return YES;
}
Any tips on how to make the dictionaryHashMap visible to CustomUIView would be much appreciated.
I have tried making the dictionaryHashMap global by doing the following:
Changing all instances of 'self.dictionaryHashMap' to 'dictionaryHashMap'
Adding the line 'extern NSMutableDictionary *dictionaryHashMap;' to CustomUIView.h
Adding the following outside of my implementation in myViewController.m: 'NSMutableDictionary *dictionaryHashMap = nil;'
However, the dictionaryHashMap remains invisible to CustomUIView. As far as I can tell, it actually remains a variable which is local to myViewController...
It's not resource-intensive to pass around the reference (pointer) to dictionaryHashMap. A pointer to an object is only 4 bytes. You could just pass it from your view controller to your view.
But I don't know why you even need to do that. Your view is sending a message (termTextClickedOn:didSelectTerm:) to the view controller when a term is clicked. And the view controller already has a reference to the dictionary, so it can handle the lookup. Why does the view also need a reference to the dictionary?
Anyway, if you want to make the dictionary a global, it would be more appropriate to initialize it in your app delegate, in application:didFinishLaunchingWithOptions:. You could even make the dictionary be a property of your app delegate and initialize it lazily.
UPDATE
I didn't notice until your comment that termTextClickedOn:didSelectTerm: is a class method. I assumed it was an instance method because myViewController starts with a lower-case letter, and the convention in iOS programming is that classes start with capital letters. (You make it easier to get good help when you follow the conventions!)
Here's what I'd recommend. First, rename myViewController to MyViewController (or better, DefinitionViewController).
Give it a property that references the dictionary. Whatever code creates a new instance of MyViewController is responsible for setting this property.
Give CustomUIView properties for a target and an action:
#property (nonatomic, weak) id target;
#property (nonatomic) SEL action;
Set those properties when you create the view:
- (void)displayDefinition:(NSString *)term {
NSArray* definition = [self.dictionaryHashMap objectForKey:term];
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
definitionPopup.target = self;
definitionPopup.action = #selector(termWasClicked:);
...
In the view's webView:shouldStartLoadWithRequest: method, extract the term from the URL request and send it to the target/action:
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
NSString *term = termForURLRequest(request);
[self.target performSelector:self.action withObject:term];
return NO;
}
return YES;
}
In the view controller's termWasClicked: method, create the new view controller and set its dictionary property:
- (void)termWasClicked:(NSString *)term {
MyViewController *t = [[MyViewController alloc] init];
t.dictionary = self.dictionary;
[t displayDefinition:term];
}
Create a class that will be used as singleton. Example.
You Should always keep your data in separate class as the mvc pattern suggest and that could be achieved by using a singleton class for all your dictionary terms and accesing them from every custom view when needed.

Calling subclass methods - Cocos2d

I'm working through the 'Learning Cocos2d' book, and I'm stuck on something basic.
So, theres a parent Class: GameplayLayer. In it, there's an 'init' method which creates an instance of the main character in here - 'Viking'. Viking is a subclass of 'GameCharacter', which is a subclass of 'GameObject'.
#pragma mark INIT PLAYER
Viking *viking = [[Viking alloc]
initWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:#"viking.png"]];
[viking setJoystick:leftJoystick];
[viking setFireButton:fireButton];
[viking setSecondaryButton:secondaryButton];
[viking setCollisionLayer:collidableLayer]; // send collision layer to player
[viking setPosition:ccp(screenSize.width * 0.35f, screenSize.height * 0.14f)];
[viking setHealth:100];
[sceneSpriteBatchNode addChild:viking z:1000 tag:kPlayerSpriteTagValue];
Now, Viking has an update method which is called every frame by GameplayLayer. It's parent class, GameObject also has this update method, which brings up an error message if it is accidentally called - "GameObject update should be overridden'.
So in my code, I'm calling the update method of 'Viking' with the following method:
#pragma mark UPDATE_METHOD
-(void) update:(ccTime)deltaTime
{
CCArray *listOfGameObjects =
[sceneSpriteBatchNode children];
for (GameObject *tempChar in listOfGameObjects) {
CCLOG(#"engine found %#",tempChar);
[tempChar updateStateWithDeltaTime:deltaTime
andListOfGameObjects:listOfGameObjects];
}
}
So, this is supposed to call the 'updateStateWithDeltaTime' method in Viking. But somehow, it's calling the parent method in GameObject which says 'updatestate should be overridden'. How do I override the parent method?
Many thanks,
Carl
You need to cast tempChar to a Viking.
for (GameObject *tempChar in listOfGameObjects)
{
[(Viking *) tempChar updateStateWithDeltaTime:deltaTime
andListOfGameObjects:listOfGameObjects];
}
Because you're doing a for loop with fast enumeration of GameObjects, the local variable assumes that all objects are GameObjects. You need to explicitly cast tempChar to a Viking so that the program knows which class to look for the method in.
As an interesting side note, if GameObject didn't have the same method as Viking did, you would be getting a warning in XCode telling you it couldn't find the method you're asking for (because it needs to know that every object this could be called on has it).
You might want to check that the object you're calling this on is the correct class (if you only want to call this on Viking objects). You'd add if ([GameObject isKindOfClass[Viking class]) above your update method call.