Array of NSViews in Cocoa? - objective-c

I am working on a Mac application. One of the windows can load several NSView objects that are in the same NIB/XIB file.
But my code looks like this:
#interface TheWindowController : NSWindowController {
//Interface objects
IBOutlet NSTableView *detailsTree;
IBOutlet NSView *bigView;
IBOutlet NSView *subView1;
IBOutlet NSView *subView2;
IBOutlet NSView *subView3;
IBOutlet NSView *subView4;
IBOutlet NSView *subView5;
}
My question is if that is possible to hold all these IBOutlets inside an Array, Dictionary or something alike. So in the future I could do something like this in my implementation:
- (IBAction)traceTableViewClick:(id)sender {
//having now a NSArray called subviewsArray
[[[bigView subviews] objectAtIndex:0] removeFromSuperview];
[rightView addSubview: [subviewsArray objectAtIndex:[detailsTree selectedRow]]];
}
Is it possible? How? Any examples?

I have done something similar with a custom view that contains many controls that I want to manipulate en masse. You need to keep them separate in the #interface declaration so that the IBOutlet property works correctly with Interface Builder, but in your init method you can organize them into an array or NSArray yourself:
- (id)init
{
self = [super init];
if (self != nil)
{
_viewArray = [[NSArray alloc] initWithObjects:subView1, subView2,
subView3, subView4, subView5, nil];
}
return self;
}
- (void)dealloc
{
[_viewArray release];
...etc...
[super dealloc];
}
You can then work on them as you desire:
- (void)doThing
{
for (id view in _viewArray)
{
[view doSomething];
}
}

Just add your NSViews to a NS(Mutable)Array or a NS(Mutable)Dictionary the same way you would add any other object to them.

Related

Cocoa app doesn't show textview

I'm an iOS developer and I want to create a simple desktop app. I thought the switch would go perfect but it doesn't.
I've created a cocoa app ( from the xCode template ). Now I don't want to use user interface builders and stuff so I wrote my first controller like this:
#interface MainViewController ()
#property (nonatomic, strong) NSTextView *test;
#end
#implementation MainViewController
-(instancetype) init
{
self = [super init];
if(self)
{
NSLog(#"%s", __PRETTY_FUNCTION__);
_test = [[NSTextView alloc] init];
[_test setString:#"DKDDK"];
[self.view addSubview:_test];
[_test mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
}
return self;
}
#interface MainViewController : NSViewController
#end
And I just use the NSWindow that is created by the template:
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MainViewController * mainView = [[MainViewController alloc] init];
[self.window.contentView addSubview:mainView.view];
mainView.view.frame = ((NSView*)self.window.contentView).bounds;
}
When I run the application it gives me:
[NSViewController loadView] loaded the "(null)" nib but no view was set.
I don't know how to solve this. How can I create an app without nib, just like you do on iOS?
If you aren't loading the view from a NIB then there is little need for a view controller.
Discard the view controller and subclass NSView instead, and set that as the window's content view.
Note: you are making a rod for your own back by not using IB.

Unable to set content in NSPopover

I'm showing an NSPopover in an NSView, originating from a point on an NSBezierPath. I'm able to show the popover without a problem, but I can't seem to set the string value of the two text fields in it. The popover and the content view are both a custom subclass of NSPopover and NSViewController, respectively. The NSPopover subclass is also the NSPopover's delegate, although I don't implement any delegate methods, so I'm not sure I even need to do that.
Here is my subclass of NSViewController:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverViewController : NSViewController
#end
#import "WeightPopoverViewController.h"
#interface WeightPopoverViewController ()
#end
#implementation WeightPopoverViewController
- (id)init {
self = [super initWithNibName:#"WeightPopoverViewController" bundle:nil];
if (self) {
}
return self;
}
#end
And my subclass of NSPopover:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverController : NSPopover <NSPopoverDelegate> {
NSTextField *dateLabel;
NSTextField *weightLabel;
}
#property (strong) IBOutlet NSTextField *dateLabel;
#property (strong) IBOutlet NSTextField *weightLabel;
#end
#import "WeightPopoverController.h"
#implementation WeightPopoverController
#synthesize weightLabel;
#synthesize dateLabel;
#end
This is the code in my NSView subclass that opens up the popover:
#interface WeightGraphViewController () {
WeightPopoverController *popover;
WeightPopoverViewController *vc;
}
...
-(void)mouseEntered:(NSEvent *)theEvent {
// initialize the popover and its view controller
vc = [[WeightPopoverViewController alloc] init];
popover = [[WeightPopoverController alloc] init];
// configure popover
[popover setContentViewController:vc];
[popover setDelegate:popover];
[popover setAnimates:NO];
// set labels
for (id key in (id)[theEvent userData]) {
[popover.weightLabel setStringValue:[(NSDictionary*)[theEvent userData] objectForKey:key]];
[popover.dateLabel setStringValue:key];
}
// set the location
(redacted, irrelevant)
// show popover
[popover showRelativeToRect:rect ofView:[self window].contentView preferredEdge:NSMaxYEdge];
}
-(void)mouseExited:(NSEvent *)theEvent {
[popover close];
popover = nil;
}
In WeightPopoverViewController.xib, I've set the File's Owner to WeightPopoverViewController and connected the view to the custom NSView. In this xib I also have an Object set to WeightPopoverController with the dateLabel and weightLabel connected to their text fields and the contentViewController set to File's Owner.
I think where I am going wrong is likely related to how I have configured my class / instance variables for the NSPopover, but from the research I've done and documentation I've read I can't seem to crack where I've gone wrong. Any help would be appreciated.
UPDATE:
I removed the NSPopover subclass from code and from IB. I put my outlets in my NSViewController and connected them in IB. However, I'm still not able to set the string values. The following won't compile with the error "Property 'weightLabel' not found on object of type NSPopover*'".
#interface WeightGraphViewController () {
NSPopover *popover;
...
}
-(void)mouseEntered:(NSEvent *)theEvent {
vc = [[WeightPopoverViewController alloc] init];
popover = [[NSPopover alloc] init];
[popover setContentViewController:vc];
[popover.dateLabel setStringValue:#"test"];
}
I have the property definition exactly as I had it in my NSPopover subclass, but now in my NSViewController. This is actually what I had before, and since I wasn't able to set the properties from the NSViewController, I figured I needed to do it through a subclass of NSPopover. This is why I thought I am having an issue with how I have configured my class / instance variables.
You seem to be creating two popovers, one in code (popover = [[WeightPopoverController alloc] init]) and one in Interface Builder (In this xib I also have an Object set to WeightPopoverController). Have a think about what you’re trying to achieve.
I would also advise against subclassing NSPopover. I believe this is causing confusion and is unnecessary. Instead, put the outlets to your dateLabel and weightLabel in the popover’s content view controller.
I've experienced something that I think is similar. The root problem is that the "outlets" connecting your view (XIB) to your controller are not initialized until after the view has been displayed. If the controller tries to set properties on any UI controls in the view before the popover has been opened, those changes are ignored (since all the controls will be nil).
Luckily, there's an easy solution (as mentioned in this answer): just invoke the view getter on your controller, and it will force the view to initialize sooner.
In other words:
popover = [NSPopover new];
myController = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
popover.contentViewController = myController;
[myController view]; // force view to initialize
...set some values on myController... // works because view is now loaded
[popover showRelativeToRect: ...];

Method of one class not executing when called form a different class

I am working on a front end for a project in objective-c and I am having some trouble getting methods of my class Window which is a subclass of NSViewController to fully execute when called from a different class.
I have a method of the class Window that is called setColor which changes the color of my NSTableView variable which is linked to a bordered scroll view in my interface. I am able to successfully change the color by calling the setColor method like this from the init method in Window: [self setColor :self];
However when I do this [window1 setColor: window1] with window1 being an object of the class Window that I have declared in class Door, nothing seems to happen since the color of the boarded scroll view remains the same.
My Window.h file looks like this:
#interface Window : NSViewController {
#public
IBOutlet NSTableView *dataTableView;
}
#property (retain) IBOutlet NSTableView *tableView;
- (IBAction)SetColor:(id)sender;
#end
My Window.m looks like this:
#synthesize tableView;
- (void) awakeFromNib {
// [self SetColor :self];
}
- (IBAction)SetColor:(id)sender;
{
NSLog(#"changing the color");
[self->tableView setBackgroundColor: NSColor.blueColor];
}
Door.h looks like this
#interface Door : NSViewController {
Window* window1;
}
-(IBAction)buttonPress:(id)sender;
#property (retain) Window* window1;
#end
Door.m looks like this:
-(void) dealloc{
[window1 release];
}
-(id)init{
self = [super init];
if(self){
window1 = [Window alloc];
}
-(IBAction)buttonPress :(id)Sender;
{
[window1 setColor: window1];
}
I am using Xcode 3.2 so I cannot use ARC.
window1 = [Window alloc] will not load a Nib or storyboard and connect the outlet IBOutlet NSTableView *dataTableView to the table view inside it.
If "Window" is a view controller, you need to initialize it and the outlets in it a more standard way. View controllers need the proper initialization or the outlets are nil, and in Objective-C, if you send a method to nil, it just does nothing.

changing UILabel text on a subview from main view

Ok, so I'm a relative noob with Objective-C/iOS programming, so hopefully someone with more knowledge here can help me out.
I have an iPad application using the SplitViewController template (with Core Data). I created another UIViewController (with xib file) called PlayerViewController. This View has several UILabel components on it.
I have a list of players that show up in the RootViewController (UITableView) and when you select a player, I programmatically create a PlayerViewController (in DetailViewController), pass it the NSManagedObject that was passed to the DetailViewController, try to set the text of one of the labels on the PlayerViewController's view, and then add it as a subview to the DetailViewController.
All of this works great except for the setting the text of the label on the PlayerViewController's view. I'm not sure what I'm doing wrong. I have used NSLog to confirm that the NSManagedObject is not nil and that the NSManagedObject property I'm trying to use has the correct text.
I'm at a loss here. Any help would be greatly appreciated. (Code follows):
This method is in the DetailViewController.m file:
- (void)configureView {
// Update the user interface for the detail item.
PlayerViewController *player = [[PlayerViewController alloc] init];
player.player = detailItem;
[self.view addSubview:player.view];
}
This method is called when the user selects an item from the RootViewController (This functionality, calling of configureView, is setup by the template and I haven't changed it).
Setting the player property of the PlayerViewController to object detailItem is handled in the setPlayer method of that class.
- (void)setPlayer:(NSManagedObject *)managedObject {
if (player != managedObject) {
[player release];
player = [managedObject retain];
// Update the view.
[self configureView];
}
}
I then have a configureView method as well in PlayerViewController that sets the text of the label:
- (void)configureView {
nickName.text = [[player valueForKey:#"Nickname"] description];
NSLog(#"Nickname %#", [[player valueForKey:#"Nickname"] description]);
NSLog(#"Nickname %#", nickName.text);
}
Ok, so the first NSLog statement prints the desired value, but the text of the UILabel (called nickName) returns nil.
The following is the full PlayerViewController.h & .m files:
PlayerViewController.h:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#interface PlayerViewController : UIViewController {
NSManagedObject *player;
IBOutlet UILabel *nickName;
IBOutlet UILabel *goalCount;
IBOutlet UILabel *assistCount;
IBOutlet UILabel *timeInGame;
}
#property (nonatomic, retain) IBOutlet UILabel *nickName;
#property (nonatomic, retain) IBOutlet UILabel *goalCount;
#property (nonatomic, retain) IBOutlet UILabel *assistCount;
#property (nonatomic, retain) IBOutlet UILabel *timeInGame;
#property (nonatomic, retain) NSManagedObject *player;
#end
PlayerViewController.m:
#import "PlayerViewController.h"
#implementation PlayerViewController
#synthesize nickName, goalCount, assistCount, timeInGame, player;
#pragma mark -
#pragma mark Managing the detail item
/*
When setting the player item, update the view
*/
- (void)setPlayer:(NSManagedObject *)managedObject {
if (player != managedObject) {
[player release];
player = [managedObject retain];
// Update the view.
[self configureView];
}
}
- (void)configureView {
nickName.text = [[player valueForKey:#"Nickname"] description];
NSLog(#"Nickname %#", [[player valueForKey:#"Nickname"] description]);
NSLog(#"Nickname %#", nickName.text);
}
/*
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// Custom initialization
}
return self;
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return YES;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
I'm sure I'm just missing something trivial, but I can't figure it out, and haven't been able to find any answers searching the web.
Thanks for any help!
Ok, so after playing with this for a bit and searching and searching around, I have gotten the answer to my problem. It turns out all the code I had was fine except the location of one statement. My call to configureView in PlayerViewController.m needed to be in viewDidLoad() not in the setPlayer() method. It all works great now.
Change the configureView method to that :
- (void)configureView {
nickName.text = (NSString*)[player valueForKey:#"Nickname"];
}
Yes, better place to call method is
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self configureView];
}
(void)setPlayer:(NSManagedObject *)managedObject called before your nib files loaded.

Why isn't my UIButton responding to touches?

I'm sure I'm overlooking the obvious as I've got countless working buttons...but...for whatever reason this one is not cooperating...
I've added a UIButton (Rounded Rect) to a UIView subclass (DialogView) which is a subview of my view controller's view. This subview is created almost entirely in IB. I've wired up the button to (IBAction)okButtonPressed:(id)sender in IB to Touch Up Inside and created a corresponding method in DialogView. However when I "touch" this button it doesn't trigger the method. userInteractionEnabled is true for the VC's view, DialogView and the UIButton.
Thinking maybe initWithCoder had to do some frame manipulation or something I added the following which successfully logs to console.
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super initWithCoder:decoder]) {
NSLog(#"DialogView initWithCoder called");
}
return self;
}
In further exploration I wired up an IBOutlet to the button and then if I try to change the titleLabel from the view controller I notice that it get's severely truncated. Default text of say "Press Me!" set in IB displays fine when view is first drawn. But if I change the text...
self.DialogView.okButton.titleLabel.text = #"Not Working";
...it gets truncated to "N..."
Dunno if this is related. Probably...
Anyone see what I've screwed up here?
Edit (adding code related to showing UIButton):
From the View Controller:
self.DialogView = [[[NSBundle mainBundle] loadNibNamed:#"DialogView" owner:self options:nil] objectAtIndex:0];;
self.DialogView.myVC = self;
self.DialogView.backgroundColor = [UIColor clearColor];
self.DialogView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
self.DialogView.nameLabel.text = loan.fullName;
self.DialogView.noteLabel.text = loan.summaryOfLoan;
self.DialogView.amountLabel.text = [currencyFormatter stringFromNumber:loan.originalAmount];
self.DialogView.alpha = 0.0;
[self.view addSubview:DialogView];
The UILabels all displaying as expected. As is the problem UIButton. I can see it I just can't interact with it!?!
DialogView's interface:
#class MyViewController;
#interface DialogView : UIView {
IBOutlet UILabel *nameLabel, *noteLabel, *amountLabel;
IBOutlet UIImageView *arrowView;
IBOutlet UIButton *okButton;
MyViewController *myVC;
}
#property (nonatomic, retain) UILabel *nameLabel, *noteLabel, *amountLabel;
#property (nonatomic, retain) UIImageView *arrowView;
#property (nonatomic, assign) MyViewController *myVC;
#property (nonatomic, retain) UIButton *okButton;
- (IBAction)okButtonPressed:(id)sender;
#end
And DialogView's implementation:
#import "DialogView.h"
#import "MyViewController.h"
#implementation DialogView
#synthesize nameLabel, noteLabel, amountLabel, arrowView, okButton;
#synthesize myVC;
- (void)dealloc {
[nameLabel release];
[noteLabel release];
[amountLabel release];
[arrowView release];
[okButton release];
[super dealloc];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super initWithCoder:decoder]) {
NSLog(#"DialogView initWithCoder called");
}
return self;
}
- (IBAction)okButtonPressed:(id)sender {
NSLog(#"pressed DialogView OK button");
[self.myVC.navigationController popViewControllerAnimated:YES];
}
- (void)drawRect:(CGRect)rect {
// Drawing code
}
#end
I thought that we should use -setTitle:forState: in order to set button's title ?
An other thought, did you check that the button's frame is not CGRectZero ? And by the way, all the frames for the view in the hierarchy ? And check that one superview in the hierarchy is not user interaction disabled ?
And, I think imageView does not respond to touches, do you have one in your code ?
I was just having more or less the same problem and I found that my containing view did not have "User Interaction Enabled".
Hope this helps.
Do you maybe have two buttons on top of one another? Change the IB project window to the detail view and see if your view has more buttons than you are expecting. Maybe you've wired up a button that's not actually getting the press you're expecting.