Initialising array before it gets called by NSTableView - objective-c

I'm having trouble getting an NStableview to populate. I've been working through examples and have been having some success using a test array to feed it. My only problem is that the file often crashes on running (sometimes it doesn't, it's temperamental) and I think I've whittled it down to it sometimes returning nothing for array count. I'm going to guess this is because the array is sometimes not fully loaded by the time it's called by the tableview controller. Any ideas how to solve this? Thanks in advance.
tableController.h:
#import <Cocoa/Cocoa.h>
NSArray *testArray;
#interface tableController : NSObject {
IBOutlet NSTableView *jobsTable;
}
#end
tableController.m:
#import "tableController.h"
#implementation tableController
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
testArray = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
}
- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
if ([testArray count] > 0) {
return [testArray count];
} else {
return 0;
}
}
- (id)tableView:(NSTableView *)tableView
objectValueForTableColumn:(NSTableColumn *)tableColumn
row:(int)row
{
if ([testArray count] > 0) {
return [testArray objectAtIndex:row];
} else {
return 0;
}
}
#end

I am pretty sure testArray could be an instance variable, and there is no need for a global variable.
If you do this you could use a custom getter for testArray which lazy loads the array.
like this:
- (NSArray *)testArray {
if (!testArray) {
testArray = [[NSArray alloc] initWithObjects:#"foo",#"bar",#"baz",nil];
}
return testArray;
}
and all your tableView datasource methods would use self.testArray instead of testArray
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [self.testArray count];
}

Try using init instead of applicationDidFinishLaunching for initialization. According to the documentation of NSApplicationDelegate, applicationDidFinishLaunching is called "after the application has been launched and initialized", but the TableView may well try to fetch its entries from the two delegate methods before that method is called.
If you absolutely have to use applicationDidFinishLaunching, you will have to check if testArray != nil before you access it in your delegate methods.

Related

Redefintition of SidebarDelegate as different kind of symbol

I have the following Objective C code that keeps error complaining I am redefining it when I not!! I am trying to implement a NSTableViewDelegate thing but xcode keep complaining
#import "AppDelegate.h"
#import <AppKit/AppKit.h>
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// add Hello World to sidebar
[self.Sidebar.tableView insertValue:0 atIndex:0 inPropertyWithKey:#"Hello World!"];
[self.Sidebar.tableView setDelegate: SidebarDelegate()]
// reload data
[self.Sidebar.tableView reloadData];
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
return YES;
}
#end
// Sidebar Delage
#implementation SidebarDelegate : NSObject <NSTableViewDelegate, NSTableViewDataSource>;
// setting data
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
anObject = #"Apples";
}
// height
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
return 20;
}
- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
{
NSLog(#"%ld", (long)rowIndex);
return ((long)rowIndex % 2) == 0;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return 4;
}
#end
replace
[self.Sidebar.tableView setDelegate: SidebarDelegate()];
with
SidebarDelegate *sidebarDelegate = [[SidebarDelegate alloc] init];
[self.Sidebar.tableView setDelegate:sidebarDelegate];
This line:
[self.Sidebar.tableView setDelegate: SidebarDelegate()];
implicitly creates a function named SidebarDelegate() of the form
int SidebarDelegate(void)
Thus, when you try to define a class of the same name, you overwrite the symbol in an unacceptable way. You need to initialize an instance of SidebarDelegate and pass that as the TableView's delegate not, I will presume, just pass the name of the class.

Objective C - Adding a new row to empty NSTableView

I have a View-Based NSTableView which should be initially empty. I also have "add" and "remove" buttons for adding and deleting rows from same NSTableView.
My delegate methods and method for adding new row look like this:
#import "PreferencesApiKeysViewController.h"
#import "UserKeys.h"
#interface PreferencesApiKeysViewController ()
#property (weak) IBOutlet NSTableView *keysTableView;
#end
#implementation PreferencesApiKeysViewController
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// Get a new ViewCell
NSTableCellView *cellView = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
if([tableColumn.identifier isEqualToString:#"userKeysColumn"]) {
UserKeys *allKeys = [self.allKeys objectAtIndex:row];
cellView.textField.stringValue = allKeys.userKeyName;
return cellView;
}
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
NSLog(#"Initial rows: %li", (unsigned long)[self.allKeys count]);
return [self.allKeys count];
}
- (IBAction)addKey:(id)sender {
UserKeys *newKey = [[UserKeys alloc] initWithKeyName:#""
apiID:#""
apiCode:#"" ];
[self.allKeys addObject:newKey];
NSLog(#"Total rows: %li", (unsigned long)self.allKeys.count);
NSInteger newRowIndex = self.allKeys.count;
if (newRowIndex == 0) {
NSLog(#"No rows.");
newRowIndex = 0;
} else {
NSLog(#"Has rows.");
newRowIndex = self.allKeys.count-1;
}
NSLog(#"New Index: %ld", (long)newRowIndex);
[self.keysTableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:newRowIndex] withAnimation:NSTableViewAnimationSlideDown];
[self.keysTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRowIndex] byExtendingSelection:NO];
[self.keysTableView scrollRowToVisible:newRowIndex];
}
#end
and in my AppDelegate.m, I'm calling my view like this:
#import "AppDelegate.h"
#import "UserKeys.h"
#include "PreferencesApiKeysViewController.h"
#interface AppDelegate()
#property (nonatomic,strong) IBOutlet PreferencesApiKeysViewController *prefsViewController;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.prefsViewController = [[PreferencesApiKeysViewController alloc] initWithNibName:#"PreferencesApiKeysViewController" bundle:nil];
// Create few rows as dummy data
/*
UserKeys *key1 = [[UserKeys alloc] initWithKeyName:#"key one"
apiID:#"123"
apiCode:#"xxx" ];
UserKeys *key2 = [[UserKeys alloc] initWithKeyName:#"key two"
apiID:#"456"
apiCode:#"yyy" ];
NSMutableArray *tempKeys = [NSMutableArray arrayWithObjects:key1, key2, nil];
self.prefsViewController.allKeys = tempKeys;
*/
// done.
[self.window.contentView addSubview:self.prefsViewController.view];
self.prefsViewController.view.frame = ((NSView*)self.window.contentView).bounds;
}
#end
Now, the thing here is that if I uncomment those few lines for adding dummy data in AppDelegate and launch my app, everything works fine. I can add/remove rows without any problem, I can even delete all of them and add a new one after that.
But, if I comment those lines again, my app starts with an empty table (which is what I need), and when I want to add new row I get an error:
*** Assertion failure in -[NSTextFieldCell _objectValue:forString:errorDescription:], /SourceCache/AppKit/AppKit-1187.34/AppKit.subproj/NSCell.m
Looking further the thread, I see only one line referencing to my app:
0x0000000100003116 -[PreferencesApiKeysViewController tableView:viewForTableColumn:row:] + 406
I'm guessing that my TableView is not properly instantiated if allKeys array doesn't contain any single object really, but I'm not sure how to fix that? How to create an empty NSTableView and have the ability to add the first row by myself when "add" button is clicked (without adding any dummy data to allKeys)?
Add this to the implementation of PreferencesApiKeysViewController:
#implementation PreferencesApiKeysViewController
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
self.allKeys = [NSMutableArray array];
}
return self;
}
// add the following only if not using ARC
- (void)dealloc {
[_allKeys release];
[super dealloc];
}
#end
What's happening when you allow that commented-out code to be compiled in is that you assign an NSMutableArray instance to the allKeys instance variable. As a result, adding and removing items works as you would expect.
Generally, in a case like this, the class that manages the NSMutableArray should override the init method to make sure the array is properly initialized to an empty array. (By default, non-IBOutlet instance variables are initialized to nil, which won't allow you to properly add or remove items from it).

how to move an object from one array to another

I understand that this topic has been done before but I wanted to bring it up again for a specific reason, I have a function designed to move an item from one array to another, removing the item from the array it was originally in, but whenever I test it, it doesnt seem to work
-(void) moveOpperand: (NSMutableArray *) moveFrom :(NSMutableArray *) moveTo{
NSString *opperandObject = [moveFrom lastObject];
if (opperandObject) {
[moveTo addObject:moveFrom.lastObject];
[moveFrom removeLastObject];
}
}
the method above is called in this method
-(NSMutableArray *) giveHand: (NSMutableArray *) hand : (NSMutableArray *)refrenceDeck{
for (int i=0; i<6; i++) {
[self moveOpperand:refrenceDeck :hand];
}
return hand;
}
the error when testing seems to be located when I try to implement the currentHand method
-(NSMutableArray *) currentHand{
if (_currentHand == nil) {
self.currentHand = [self.myDeck giveHand:self.currentHand :self.myDeck.currentDeck];
}
return _currentHand;
}
If you're trying to do what I think here is a very very simple category that should do it for any object.
NSObject+Move.h
#import <Foundation/Foundation.h>
#interface NSObject (Move)
/*
Returns true if it sucessfully moves the object between arrays.
*/
- (BOOL)moveFromArray:(NSMutableArray *)arrayA toArray:(NSMutableArray *)arrayB;
#end
NSObject+Move.m
#import "NSObject+Move.h"
#implementation NSObject (Move)
- (BOOL)moveFromArray:(NSMutableArray *)arrayA toArray:(NSMutableArray *)arrayB
{
if ([arrayA containsObject:self]) {
[arrayA removeObject:self];
[arrayB addObject:self];
return YES;
} else {
return NO;
}
}
#end
It's possible that you aren't setting the _currentHand variable to nil at the appropriate time, thus you may not have the correct current hand which may make it 'seem' like an issue with your array swapping.

ocjective-c Obtain return value from public method

I'm pretty new to objective-C (and C in general) and iPhone development and am coming from the java island, so there are some fundamentals that are quite tough to learn for me.
I'm diving right into iOS5 and want to use storyboards.
For now I am trying to setup a list in a UITableViewController that will be filled with values returned by a web service in the future. For now, I just want to generate some mock objects and show their names in the list to be able to proceed.
Coming from java, my first approach would be to create a new Class that provides a global accessible method to generate some objects for my list:
#import <Foundation/Foundation.h>
#interface MockObjectGenerator : NSObject
+(NSMutableArray *) createAndGetMockProjects;
#end
Implementation is...
#import "MockObjectGenerator.h"
// Custom object with some fields
#import "Project.h"
#implementation MockObjectGenerator
+ (NSMutableArray *) createAndGetMockObjects {
NSMutableArray *mockProjects = [NSMutableArray alloc];
Project *project1 = [Project alloc];
Project *project2 = [Project alloc];
Project *project3 = [Project alloc];
project1.name = #"Project 1";
project2.name = #"Project 2";
project3.name = #"Project 3";
[mockProjects addObject:project1];
[mockProjects addObject:project2];
[mockProjects addObject:project3];
// missed to copy this line on initial question commit
return mockObjects;
}
And here is my ProjectTable.h that is supposed to control my ListView
#import <UIKit/UIKit.h>
#interface ProjectsTable : UITableViewController
#property (strong, nonatomic) NSMutableArray *projectsList;
#end
And finally ProjectTable.m
#import "ProjectsTable.h"
#import "Project.h"
#import "MockObjectGenerator.h"
#interface ProjectsTable {
#synthesize projectsList = _projectsList;
-(id)initWithStyle:(UITableViewStyle:style {
self = [super initWithStyle:style];
if (self) {
_projectsList = [[MockObjectGenerator createAndGetMockObjects] copy];
}
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// only one section for all
return 1;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"%d entries in list", _projectsList.count);
return _projectsList.count;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// the identifier of the lists prototype cell is set to this string value
static NSString *CellIdentifier = #"projectCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Project *project = [_projectsList objectAtIndex:indexPath.row];
cell.textLabel.text = project.name
return cell;
}
So while I think everything is correctly set, I expect the tableView to show my three mock objects in its rows. But it stays empty and the NSLog method prints "0 entries in list" into the console. So what am I doing wrong?
Any help is appreciated.
Best regards
Felix
Update 1: missed to copy the two return statements into this box ("return mockObjects" and "return cell") which were already in my code, now inserted.
You have at least two problems in your code:
In
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
You are supposed to return a valid UITableViewCell (just return cell;)
You should also init the objects you allocate:
NSMutableArray *mockProjects = [NSMutableArray alloc];
Project *project1 = [Project alloc];
Project *project2 = [Project alloc];
Project *project3 = [Project alloc];
To do so write mockProjects = [[NSMutableArray alloc]init]; and same for the Project objects.
In the method you also need to return the object you create: return mockProjects;
I would strongly recommend that you check up on the Automatic reference Counting system, or try to learn the good ol' fashioned memory management model of iOS. It is very different from the Java way of doing things.
You're missing
return mockProjects;
in your method
+ (NSMutableArray *) createAndGetMockObjects {
EDIT
Also, like everyone else is pointing out, you need to make sure to call init on all of the objects you are allocating. Simply allocating it does not initialize the object, and you can't really use it until you do so
First: always call alloc and init to create an object.
Second: You don't return the mutable array in your class method.
Add a return mockProjects; statement at the end

Where does this method's arguments get passed from?

Simple question: where do the tableView and section arguments get passed from? The actual code in the method return [self.listData count]; doesn't even mention them.
Here's my interface code:
#interface Simple_TableViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource>
{
NSArray *listData;
}
#property (nonatomic, retain) NSArray *listData;
#end
And this is all the implementation code:
#import "Simple_TableViewController.h"
#implementation Simple_TableViewController
#synthesize listData;
- (void)viewDidLoad {
NSArray *array = [[NSArray alloc] initWithObjects:#"Sleepy", #"Sneezy",
#"Bashful", #"Happy", #"Doc", #"Grumpy", #"Dopey", #"Thorin",
#"Dorin", #"Nori", #"Ori", #"Balin", #"Dwalin", #"Fili", #"Kili",
#"Oin", #"Gloin", #"Bifur", #"Bofur", #"Bombur", nil];
self.listData = array;
[array release];
[super viewDidLoad];
}
- (void)viewDidUnload {
self.listData = nil;
}
- (void)dealloc {
[listData release];
[super dealloc];
}
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.listData count];
}
I just want to know how does the method (NSInteger)tableView: (UITableView *)numberOfRowsInSection: receive those arguments? Of course this happens everywhere; I just want to understand it.
The Simple_TableViewController class is likely meant to manage a single table with a single section. Given that, the tableView and section parameters aren't important because they can only be one thing: a pointer to the table and 0, respectively.
Your view controller class is adding support for these callback methods through UITableViewDelegate and UITableViewDataSource. You are adding this support in your .h file through <UITableViewDelegate, UITableViewDataSource>. These classes are built in to the Cocoa Touch framework and you are just using them. When the table is (re)loaded, this callback methods are called if you have defined them (some are required, others are optional).