Programmatically create NSPopupButton and add items to list - objective-c

I've been able to programmatically create a NSPopupButton and add it to my window, and I can add items to the list from the same method, but I'd like to figure out how I can add items to it from another method.
Here's what I have so far that works:
// in my .h file:
#interface AVRecorderDocument : NSDocument
{
#private
NSPopUpButton *button;
}
#property (assign) IBOutlet NSWindow *mainWindow;
// in my .m file:
#implementation AVRecorderDocument
#synthesize mainWindow;
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
NSView *superview = [mainWindow contentView];
NSRect frame = NSMakeRect(10,10,149,22);
NSPopUpButton *button = [[NSPopUpButton alloc] initWithFrame:frame];
[superview addSubview:button];
[button release];
}
- (void)refreshDevices
{
// I'd like to add items to my popupbutton here:
// [button addItemWithTitle: #"Item 1"];
}
#end
Up in refreshDevices I don't get a compiler error, just nothing gets added to the popupbutton. The method refreshDevices is called from -(id)init. I've also tried putting the code that is inside the windowControllerDidLoadNib at the top of my init section, but it won't even create the popupbutton there.

There are a two problems with your code:
Inside windowControllerDidLoadNib:
You don't assign the newly created button to your ivar but only to a function local variable (with the same name as your ivar).
Why nothing happens inside refreshDevices
init is called before windowControllerDidLoadNib:, so your ivar is nil (and because of 1.). Sending messages to nil does nothing.
Solution:
Remove NSPopUpButton * from windowControllerDidLoadNib: so you assign the new button to your ivar and not to some function local variable.
Call refreshDevices at the end of windowControllerDidLoadNib: or at some point you know windowControllerDidLoadNib: has been called and your button is not nil.
Edit:
You should keep in mind that the moment you remove the button from the superview it is probably deallocated because you release it after creation.
The moment it is deallocated your button ivar points to an invalid/deallocated object which leads to undefined behaviour when used in this state.
I'd advise to release the button inside dealloc so you can be sure to have a valid object throughout the whole lifetime of your document object.
But nonetheless I don't know your exact use case which might require this design.

Related

NSPanel - Why can I not set my Panel title and data values before showing the window?

Simple structure:
exampleController.h :
#import <Foundation/Foundation.h>
#interface exampleController : NSWindowController {
#public
IBOutlet NSPanel *entryPanel;
#property (nonatomic, strong) IBOutlet NSPanel *entryPanel;
#end
exampleController.m :
#import "exampleController.h"
#implementation exampleController
#synthesize entryPanel;
- (id)init {
self = [super initWithWindowNibName:#"ExamplePanel"];
if (self) {
// Initialization code here.
NSLog(#"entryPanel: %#", entryPanel);
[self.entryPanel setTitle:#"TESTING!"];
}
return self;
}
randomController.m :
...
- (id) init {
self = [super init];
if (self) {
// loading our example controller if it isn't loaded yet.
if (!ourExampleController) {
ourExampleController = [exampleController alloc] init];
}
}
return self;
}
...and then later in the random controller within a method I show the NSPanel via:
[ourExampleController showWindow:self];
[ourExampleController window] makeKeyAndOrderFront:self];
My problem is that no matter what, the first time the NSPanel displays and shows itself the title is always still set to the title that it has in Interface Builder! Even though I explicitly set the title in the exampleController init method.
I've also tried throwing an NSLog(#"entryPanel: %#", entryPanel) in the init method for exampleController and at launch it is always NULL. I do not have to ALLOC all my IBOutlets in the init because I am already synthesizing them?
I've double checked everything in interface builder. The File Owner for the ExamplePanel.xib is set to the exampleController class. The window AND entryPanel outlets are both referencing the NSPanel in our xib file. What am I missing ??
Thanks in advance!
EDIT: Just to add. If I open the window (..and see the default IB title) and then close it and reopen it with a method that changes the title - it seems to work! This problem seems to only reside with the window first opening. It seems like my properties are not being alloc'd until the window first opens?
EUREKA!
As per discussion here:
IBOutlet instances are (null) after loading from NIB
I learnt that the window itself is not loaded when my controller is initialized. Found that surprising since I figured using initWithWindowNibName:#"myNibFile" would also alloc and initialize all outlet properties but since I'm new to OSX Obj-C that appears to not be the case. All the outlet properties are only alloc'd once the window itself is loaded too.
It's easy to just show the window (which also loads the window if it's not loaded yet) and then quickly set all the outlets to my desired values BUT this was an issue for me since I wanted to avoid that ever so slight "screen flicker" (for lack of a better description) that occurs as the values adjust to their new settings.
The solution was to find a way to load the controller and load the window without actually showing it first! Then I discovered this:
Can you force a NSWindow to load, i.e. before it is presented onscreen?
Steps to make that happen:
Add the following to my NSWindowController subclass init method:
// this loads the window as per link/description above
[self window]
The key seems to be though to ensure that in your NIB/XIB file that the Visible At Launch is unchecked. If it is checked (default behavior) then the [self window] call above will still show your window when your app launches. Unchecking the above option ensures the above call does not show your window until you explicitly show it yourself!
E.g. You can define an action button which loads your window:
[exampleController showWindow:self];
[[exampleController window] makeKeyAndOrderFront:self];
Hope this helps someone else out. Was a head scratcher for a couple hours!
You should set the title, etc. in -awakeFromNib or -windowDidLoad instead of an -init… method. That way the values will be set before the window is shown and you won't get the flicker.

Changes not reflected across view when using binding in cocoa

I am creating some sample applications to understand the concepts of view navigation, binding etc in cocoa.
Here is the scenario:
I have a window that has a tab view(2 tabs) in MainMenu.Xib.
I have a text field in the first tab and label in the second tab. I want both of them to reflect the same value and I want to do this using binding. Also, I don't want to use the views provided to me along with the tab view.
These are the steps I have done.
The view of each tab view item is set separately in the applicationDidFinishLaunching: method using the following code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
//initialize view controllers
view1=[[ViewTab1 alloc] initWithNibName:#"ViewTab1" bundle:nil];
view2=[[ViewTab2 alloc] initWithNibName:#"ViewTab2" bundle:nil];
//set views
[[[myTabView tabViewItems] objectAtIndex:0]setView:view1.view];
[[[myTabView tabViewItems] objectAtIndex:1]setView:view2.view];
}
myTabView is the outlet reference of the tab view from MainMenu.xib in AppDelegate.
ViewTab1 is the name of the first view controller (and the xib).
ViewTab2 is the name of the second view controller (and the xib).
ViewTab1 has one single text field (and an associated label). I have bound this to a variable(name) declared in AppDelegate.
ViewTab2 has a label. I have bound this also to the same variable in AppDelegate.
The variable, 'name' is initialized in the init method of AppDelegate.
AppDelegate.h
....
NSString *name;
....
#property(strong) ViewTab1 *view1;
#property(strong) ViewTab2 *view2;
#property (assign) IBOutlet NSTabView *myTabView;
#property (strong) NSString *name;
....
AppDelegate.m
....
#synthesize myTabView;
#synthesize view1,view2;
#synthesize name;
....
- (id)init {
self = [super init];
if (self) {
name=#"dummy";
}
return self;
....
Apart from this I haven't done any coding in my program.
In the ViewTab1.xib I got an object and made it an instance of AppDelegate and then connected the delegate reference of the Application object(NSApplication) to the same object. (I hope this is the right way of getting the AppDelegate object.)
I did the same in ViewTab2.xib
Then I bound the text field in ViewTab1 and label in ViewTab2 to this variable in AppDelegate.
When I run the program both the text field and label shows "dummy". But when I change the value in the text field, its not reflected in the label in the second tab( i.e. ViewTab2).
Please tell me what I'm doing wrong.
How to establish binding to the same App delegate object from any loaded Nib?
Yes, I know this frustrated situation as described in question... after many weeks and hundreds pages of documentation for KVO - Notifications - Bindings I think there is one very simple solution for that.
As we can find in some information sources the nib-loading process produce new instances of members... and we need to use binding connection to the old one.
Note that bindings made in InterfaceBuilder are redirect to these new instances automatically after loading nib
Why not redirect the pointer of App delegate to the old instance?
In method where you loads your nib you can test which object is app delegate before and just after nib load.
If the new one isn’t the same as the previous one you can redirect it as you want.
This simple example works for me in Xcode3 under 10.5.8 with target to OSX10.5 / i386:
// ***** SOMEWHERE IN DEFAULT APP-DELEGATE.m IMPLEMENTATION
- (IBAction) createOtherWindowFromNib: (id)sender
{
// ensure that app delegate is set as you want...
[NSApp setDelegate:self];
NSLog(#"APP-DELEGAT **** CREATE-TEST-WINDOW ***** WHO IS APP-DELEGATE BEFORE NIB LOAD: %# ", [[NSApp delegate] description]);
// we can bind members of the nib to this controller over proxy object named "File’s Owner"
NSWindowController *otherWinCapo = [[NSWindowController alloc] initWithWindowNibName: #"OtherTestWindow"];
NSLog(#"APP-DELEGAT **** CREATE-TEST-WINDOW ***** WHO IS APP-DELEGATE AFTER NIB LOAD: %# ", [[NSApp delegate] description]);
// make some test for delegates before/after here if you need ...
// usually your bindings made inside "OtherTestWindow.xib" by IB doesn’t works in this moment
// ... and some redirection if needed
[NSApp setDelegate:self];
// afer that the bind made in IB inside "OtherTestWindow.xib"
// referred to (proxy object) "Application.delegate.myBOOL" (Bind to:Application, Model Key Path:delegate.myBOOL)
// react to changes of myBOOL placed in default app delegate object as expected
// simultaneously in every open instance of "OtherTestWindow.xib"
[otherWinCapo showWindow: otherWinCapo.window]; // we need populate the window instance on screen to see it
}
I think the problem is that the objects in your xibs that you set to the app delegate class create 2 different instances of the app delegate, so changing the value of the text field changes the value of name in one instance but not in the other. That's what you're doing wrong, unfortunately, I can't think of a solution at this time.
Have you turned on 'Continuously Updates Value' in the NSTextField controls?
See this example.

Xcode 4.2 & Storyboard, how to access data from another class?

I was wondering how I could access data from another class using Xcode 4.2 and Storyboard?
Say for instance how would I access the text of a text field from another class?
Google hasn't helped and the lesson on MyCodeTeacher.com about this is outdated and doesn't work anymore...
Thanks for bearing with me!
-Shredder2794
Not sure if this is the only or best way, but you can create a property in the destination view's .h file and set it to a value before the segue is performed
in the destination view controller's .h file:
#interface YourDestinationViewController : UIViewController
{
NSString* _stringToDisplay;
//...
}
#property (nonatomic, retain) NSString* stringToDisplay;
//...
and in the presenting view's .m file
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
YourDestinationViewController*viewController = segue.destinationViewController;
viewController.delegate = self;
viewController.stringToDisplay = #"this is the string";
}
Then you can do what you want with the property in whichever of the viewWillAppear/viewDidLoad/viewDidAppear/etc. methods best suits your purpose in the destination view's .m file
And then to check if it works, in the destination view controller's .m file:
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"self.stringToDisplay = %#", self.stringToDisplay);
...
//and if a label was defined as a property already you could set the
//label.text value here
}
Edit: Added more code, and made it less generic
This isn't specific to Storyboard. There are several ways to do what you are trying to do. You could declare a variable in your AppDelegate (an NSString) and set that in your first class. Then in your second class access the AppDelegate variable and use that to set your label. The code to do this is:
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
label.text = appDelegate.myString;
Another way to do it (probably the easiest) is to declare an NSString in your second class. Then in your first class, before you push the second view set that string variable. Something like this:
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"" bundle:nil];
vc.myString = #"";
The third way to do this is using delegates. This is the most 'complicated' way but is the best. You would create a delegate which gets called when your second view appears. The delegate could then return the value from the first class to you.
You may also be able to use the new completion handler block on the iOS 5 pushViewController: method.
Edit:
Custom init method:
- (void)initWithNibName:(NSString *)nibName bundle:(NSString *)bundle string:(NSString *)myString
And then when you are pushing the view just class this method and set the string through it.

Objective-C: Adding UIButtons programmatically from another class

I'm having trouble connecting the dots between code and .xib files.
If I want to add a series of UIButtons programmatically to my view controller, how would I go about doing this from a separate class?
For instance, if I have MainViewController.m, which is set as the root view controller in Xcode, how can I add a UIButton to that view controller from SecondViewController.m? Is this even possible?
I would essentially like to place all of my "user interface" code in a separate class.
Thanks
To do this, create a UIButton *myButton programmatically and then call [mainViewController addSubview:myButton];. This may mean you need to store a MainViewController * property in your SecondViewController class.
Important methods and properties for a UIButton instance (essentially, just take a look at the documentation, but here's a minimal set of stuff to get you started):
+[UIButton buttonWithType:buttonType] - Make sure if you're doing anything remotely custom to use UIButtonTypeCustom here (it doesn't give you any default background images or otherwise to have to nil out)
setFrame: - Position the button relative to its container and set the size, for usability reasons the width and height should be at least 44 pixels (as mentioned here).
setTitle:forState: - UIControlStateNormal will act as the default properties for other states too, so you may only need to set the text here
setBackgroundImage:forState: - use UIControlStateNormal and UIControlStateHighlighted/UIControlStateSelected primarily, UIControlStateDisabled if you wish to show it grayed out or inaccessible at any point.
setImage:forState: - Use for an icon next to the button text (like an arrow pointing down for Save or up for Load, etc)
setEnabled:, setHidden:, setSelected: - Transition between different button states. setHighlighted: happens automatically when you tap the button.
addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside - TouchUpInside is almost always what you want for a simple button press, I'm using a method named buttonClicked: here to handle my button press.
Oh, and if you use [[UIButton alloc] initWith...] don't forget to [myButton release] once it's added to the mainViewController :)
use this
#import "MainViewController.h"
#interface SecondViewController
{
MainViewController *mainView;
}
#property(nonatomic, retain) MainViewController *mainView;
-(void)addButtons;
In your implementation
#synthesize mainView;
-(void)addButtons
{
UIButton *add = [UIButton alloc] init];
//do necessary stuff on button here
[self.mainView addSubview:add];
[add release];
}
In your MainViewcontroller.m
#import "SecondViewController.h"
-(void)viewDidLoad
{
[self superViewDidLoad];
SecondViewController *second = [SecondViewController alloc] init];
second.mainView = self;
[second addButton];
[second release];
}

Subclassing UIButton but can't access my properties

I've created a sub class of UIButton:
//
// DetailButton.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface MyDetailButton : UIButton {
NSObject *annotation;
}
#property (nonatomic, retain) NSObject *annotation;
#end
//
// DetailButton.m
//
#import "MyDetailButton.h"
#implementation MyDetailButton
#synthesize annotation;
#end
I figured that I can then create this object and set the annotation object by doing the following:
MyDetailButton* rightButton = [MyDetailButton buttonWithType:UIButtonTypeDetailDisclosure];
rightButton.annotation = localAnnotation;
localAnnotation is an NSObject but it is really an MKAnnotation. I can't see why this doesn't work but at runtime I get this error:
2010-05-27 10:37:29.214 DonorMapProto1[5241:207] *** -[UIButton annotation]: unrecognized selector sent to instance 0x445a190
2010-05-27 10:37:29.215 DonorMapProto1[5241:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[UIButton annotation]: unrecognized selector sent to instance 0x445a190'
'
I can't see why it's even looking at UIButton because I've subclassed that so it should be looking at the MyDetailButton class to set that annotation property. Have I missed something really obvious. It feels like it :)
Thanks in advance for any help you can provide
Ross
UIButton is a class cluster, which implies that Apple's implementation of buttonWithType: probably looks something like this:
+(id)buttonWithType:(UIButtonType)t {
switch (t) {
case UIButtonTypeDetailDisclosure:
return [[[PrivateDetailDisclosureButtonClass alloc] init] autorelease];
case ...
}
}
So when you call [MyDetailButton buttonWithType:UIButtonTypeDetailDisclosure]; you don't get an instance of MyDetailButton, you get an instance of PrivateDetailDisclosureButtonClass (or whatever Apple actually calls it).
Note, however, that you can get buttonWithType to instantiate a subclass if you call it with UIButtonTypeCustom (At least in the simulator running v3.0):
// LGButton is a straightforward subclass of UIButton
LGButton *testBtn = [LGButton buttonWithType:UIButtonTypeCustom];
LGButton *testBtn2 = [LGButton buttonWithType:UIButtonTypeDetailDisclosure];
NSLog(#"testBtn: %#, testBtn2: %#", [testBtn class], [testBtn2 class]);
// Output: testBtn: LGButton, testBtn2: UIButton
I made the same attempt as original poster but it seems that subclassing UIButton to do something like this is hard.
What I did, which is a hack - but works for me now, is add a UITextField as subview of the UIButton. The UITextField has no frame and is hidden, but I can freely store text strings in the textfield's text property. Which is what I wanted to do...
UITextField* tf = [[UITextField alloc] init];
tf.text = #"the text that I wanna store";
tf.hidden = YES;
tf.tag = TAGOFBUTTONSUBTEXTFIELD;
[previouslyCreatedButton addSubview:tf];
[tf release];
I defined TAGOFBUTTONSUBTEXTFIELD as 99 somewhere. Global. It's ugly but...
Then later, to get that text string use something like this:
+(NSString*)getStoredStringFromButton:(UIButton*)button {
UITextField* tf = (UITextField*)[button viewWithTag:TAGOFBUTTONSUBTEXTFIELD];
return tf.text;
}
So this assumes that no one else tries to add a subview with tag 99 to the button.
Lol :-)
That exception is because the actual button that you are trying to get the annotation from is not of class MyDetailButton, it is a UIButton. Verify that you set the class in IB for that particular button. Select the button in IB and press ⌘4 to see its identity, change the Class Identity to MyDetailButton.
Just making a subclass isn't enough; the subclass does not take the place of its superclass. The same way not all UIControls, not all UIViews, not all UIResponders, and not all NSObjects have the behavior of UIButton, not all UIButtons have the behavior of your custom subclass.
What you need is an instance of your subclass. What you have is an instance of UIButton.
The solution is to make that instance an instance of your subclass instead. If you created the button in Interface Builder, select the button and press ⌘6, then change the instance's Custom Class there. If you're creating the button in code, send your alloc message to your custom subclass, not directly to UIButton.