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).
Related
I am developing a watchkit application and i have got a table view which must have functionality to delete rows. I saw a tutorial using content menu. But no idea how to delete row from the table. Please help me.
#import "HomeInterfaceController.h"
#import "HomeTableRowController.h"
#import "DetailHomeInterfaceController.h"
#interface HomeInterfaceController ()
#property (unsafe_unretained, nonatomic) IBOutlet WKInterfaceTable *homeTable;
#property (nonatomic,strong) NSArray *nameArr;
#end
#implementation HomeInterfaceController
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
self.nameArr = [NSArray arrayWithObjects: #"Jill Valentine", #"Peter Griffin", #"Meg Griffin", #"Jack Lolwut",
#"Mike Roflcoptor", #"Cindy Woods", #"Jessica Windmill", #"Alexander The Great",
#"Sarah Peterson", #"Scott Scottland", #"Geoff Fanta", #"Amanda Pope", #"Michael Meyers",
#"Richard Biggus", #"Montey Python", #"Mike Wut", #"Fake Person", #"Chair",
nil];
[self setupTable];
// Configure interface objects here.
}
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
[super willActivate];
}
- (void)didDeactivate {
// This method is called when watch view controller is no longer visible
[super didDeactivate];
}
- (void)setupTable {
[self.homeTable setNumberOfRows:[self.nameArr count] withRowType:#"HomeTableRowController"];
for (NSInteger i = 0; i < self.nameArr.count; i++)
{
HomeTableRowController *row = [self.homeTable rowControllerAtIndex:i];
NSString *thisBook = [self.nameArr objectAtIndex:i];
[row.reminderHeadlineLabel setText:thisBook];
// [row.imageRow setImage:[UIImage imageNamed:#"abc.jpg"]];
}
}
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndex:(NSInteger)rowIndex
{
NSDictionary *d=[NSDictionary dictionaryWithObject:#"hi" forKey:#"nm"];
[self presentControllerWithName:#"DetailHome" context:d];
}
- (IBAction)deleteButtonAction {
}
#end
You need to call removeRowsAtIndexes in your deleteButtonAction function:
// first create an index set with the index of the row you want to delete
NSIndexSet *indexes = [[NSIndexSet alloc] initWithIndex:index];
// then call the function
[self.homeTable removeRowsAtIndexes:indexes];
There is more info in the docs
**Note: I have updated the code based off of aroth's suggestions - however it is still crashing. The code in the below post is the updated code.
I am trying to create an iPhone app based off of the table-view XCode template (XCode 4). The table view gets populated with the correct data in the proper order - however when I go to scroll through the table the app crashes (sometimes I can scroll through 5 or 10 more cells, sometimes it freezes right away). The table view is being fed from 'Artist' objects within an NSArray which is an IVAR of another 'iPodLibraryParser' object. I believe the problem is that the 'iPodLibraryParser' object is being released prematurely - but I don't understand why.
I have created an iPodLibraryParser object with the following header file:
#import <Foundation/Foundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import <CoreLocation/CoreLocation.h>
#import "ArtistClass.h"
#interface iPodLibraryParser : NSObject {
//Location stuff
CLLocationManager *locationManager;
IBOutlet UITextField *latitudeTextField;
IBOutlet UITextField *longitudeTextField;
IBOutlet UILabel *latitudeLabel;
IBOutlet UILabel *longitudeLabel;
//Music Library Stuff
NSString *currentArtist;
NSString *currentAlbum;
NSMutableArray *artistArray;
NSMutableArray *sortedArtistArray;
}
#property (nonatomic, retain) NSMutableArray *sortedArtistArray;
-(void)parseiPodLibrary;
-(id) initializeiPodLibraryParser;
#end
The relevant code in the .m file of this Class:
#implementation iPodLibraryParser
#synthesize sortedArtistArray;
-(id) initializeiPodLibraryParser{
[super init];
sortedArtistArray = [[NSMutableArray alloc] initWithObjects:nil];
return self;
}
-(void)parseiPodLibrary{
.....
NSArray *sortingArray = [[NSArray alloc] initWithObjects:artistTrackCountSorter,nil];
NSArray *tempSortedArray = [artistArray sortedArrayUsingDescriptors:sortingArray];
[sortedArtistArray removeAllObjects];
[sortedArtistArray addObjectsFromArray:tempSortedArray];
}
Artist object header:
#import <UIKit/UIKit.h>
#import "iPodLibraryParser.h"
#interface ArtistClass : NSObject {
NSString *artistName;
int numberOfTracks;
id artistClassViewController;
}
-(id) initializeArtistObjectWithDocument:(id)myDocument withArtist:(NSString*) artist;
#property (nonatomic, retain) NSString *artistName;
#property (nonatomic, assign) int numberOfTracks;
#end
Table View Controller (called RootViewController from the template being used)
Header:
#import <UIKit/UIKit.h>
#import "iPodLibraryParser.h"
#import "ArtistClass.h"
#interface RootViewController : UITableViewController {
iPodLibraryParser *iPodLibrary;
}
#property (nonatomic, retain) iPodLibraryParser *iPodLibrary;
#end
Relevant code
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"In viewDidAppear");
iPodLibrary = [[iPodLibraryParser alloc] initializeiPodLibraryParser];
[iPodLibrary parseiPodLibrary];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"there are %i artists in the array", [[iPodLibrary sortedArtistArray] count]);
return [[iPodLibrary sortedArtistArray] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"in tableView blah");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell.
NSLog(#"getting row: %i",indexPath.row);
cell.textLabel.text = [[[iPodLibrary sortedArtistArray] objectAtIndex:indexPath.row] artistName];
return cell;
}
The error is at this line:
cell.textLabel.text = [[[iPodLibrary sortedArtistArray] objectAtIndex:indexPath.row] artistName];
I tried creating an array with Artist objects that I created within the RootViewController - and that works perfectly (can scroll the entire table with no crashes)
Thanks again!
--Edit:
It is interesting to note that I get different errors at different times:
Most of the times it's just EXC_BAD_ACCESS at the line:
cell.textLabel.text = [[[iPodLibrary sortedArtistArray] objectAtIndex:indexPath.row] artistName];
Sometimes it's:
-[UIAutoRotatingWindow isEqualToString:]: unrecognized selector sent to instance 0x1baa80
And another had to do with an unrecognized RGB Selector (very strange).
You problem is when you do this:
sortedArtistArray = [artistArray sortedArrayUsingDescriptors:sortingArray];
The sortedArrayUsingDescriptors method is returning an autoreleased object, so you shouldn't be surprised when it gets released on you without warning.
Moreover, because your init does:
sortedArtistArray = [[NSArray alloc] init];
...and because you don't release it before you assign to it, your code is leaking NSArray instances. I would suggest the following revisions:
Make sortedArtistArray an NSMutableArray*, like:
NSMutableArray* sortedArtistArray;
Don't overwrite the array reference in parseiPodLibrary. Instead just copy the sorted dataset into the mutable array, like:
-(void)parseiPodLibrary{
//.....
NSArray* temp = [artistArray sortedArrayUsingDescriptors:sortingArray];
[sortedArtistArray removeAllObjects];
[sortedArtistArray addObjectsFromArray:temp];
}
Don't call [iPodLibrary retain]; in viewDidAppear. You've already called alloc/init on it, so there is no need to retain it again.
Ensure that you call [iPodLibrary release]; in viewWillDisappear, and [sortedArtistArray release]; in dealloc so that you don't leak memory.
I think it's much more likely that your iPodLibrary variable is over-released than that it's the array. You should troubleshoot by splitting your code into a few lines and seeing where it fails:
NSArray *artistArray = [iPodLibrary sortedArtistArray];
Artist *artist = [artistArray objectAtIndex:indexPath.row];
NSString *name = [artist artistName];
cell.textLabel.text = name;
Is it possible that your implementation of initializeiPodLibraryParser returns an autoreleased object? It shouldn't, as that violates the convention. But it would make sense that that would lead to this problem.
When you call this:
iPodLibrary = [[iPodLibraryParser alloc] initializeiPodLibraryParser];
you're directly assigning the iPodLibrary ivar, so its mutator isn't called and it doesn't matter that the property is marked as retain. If you think it's being implicitly retained, it's likely you're over-releasing it somewhere else. I find you'll understand your own memory management if you use this style to assign properties:
iPodLibraryParser *parser = [[iPodLibraryParser alloc] initializeiPodLibraryParser]; // retain count should be 1 if not autoreleased
[self setIpodLibrary:parser]; // retain count is now 2
[parser release]; // retain count is now 1, will go to zero when you release it in dealloc or viewDidUnload
Finally, I'd move your iPodLibrary initialization from viewDidAppear to viewDidLoad:. That's where you should be initializing your view. And you should be cleaning up anything you initialize there in viewDidUnload, which will be called in a low memory situation.
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).
I'm new to objective-c and I'm finding that I don't know how to correctly assert that a text property on some given label is equal to a raw string value. I'm not sure if I just need to cast the label as NSString or if I need to modify my assert statement directly.
#interface MoreTest : SenTestCase {
MagiczzTestingViewController* controller;
}
- (void) testObj;
#end
#implementation MoreTest
- (void) setUp
{
controller = [[MagiczzTestingViewController alloc] init];
}
- (void) tearDown
{
[controller release];
}
- (void) testObj
{
controller.doMagic;
STAssertEquals(#"hehe", controller.label.text, #"should be hehe, was %d instead", valtxt);
}
#end
The implementation of my doMagic method is below
#interface MagiczzTestingViewController : UIViewController {
IBOutlet UILabel *label;
}
#property (nonatomic, retain) UILabel *label;
- (void) doMagic;
#end
#implementation MagiczzTestingViewController
#synthesize label;
- (void) doMagic
{
label.text = #"hehe";
}
- (void)dealloc {
[label release];
[super dealloc];
}
#end
The build is fine when I modify the assert to compare a raw NSString to another but when I try to capture the text value (assuming it's of type NSString) it fails. Any help would be much appreciated!
STAssertEquals() checks for identity of the two values provided, so it's equivalent to doing this:
STAssertTrue(#"hehe" == controller.label.text, ...);
Instead, you want STAssertEqualObjects(), which will actually run an isEqual: check like the following:
STAssertTrue([#"hehe" isEqual:controller.label.text], ...);
You need to load the nib of the view controller. Otherwise there won't be any objects for the label outlet to be hooked up to.
One way to do this is to add an ivar for the view controller's view to your test case:
#interface MoreTest : SenTestCase {
MagiczzTestingViewController *controller;
UIView *view;
}
#end
#implementation MoreTest
- (void)setUp
{
[super setUp];
controller = [[MagiczzTestingViewController alloc] init];
view = controller.view; // owned by controller
}
- (void)tearDown
{
view = nil; // owned by controller
[controller release];
[super tearDown];
}
- (void)testViewExists
{
STAssertNotNil(view,
#"The view controller should have an associated view.");
}
- (void)testObj
{
[controller doMagic];
STAssertEqualObjects(#"hehe", controller.label.text,
#"The label should contain the appropriate text after magic.");
}
#end
Note that you also need to invoke super's -setUp and -tearDown methods appropriately from within yours.
Finally, do not use dot syntax for method invocation, it is not a generic replacement for bracket syntax in message expressions. Use dot syntax only for getting and setting object state.
I've been stuck on this for days and each time I come back to it I keep making my code more and more confusing to myself, lol. Here's what I'm trying to do. I have table list of charges, I tap on one and brings up a model view with charge details. Now when the model is presented a object is created to fetch a XML list of users and parses it and returns a NSMutableArray via a custom delegate. I then have a button that presents a picker popover, when the popover view is called the user array is used in an initWithArray call to the popover view. I know the data in the array is right, but when [pickerUsers count] is called I get an EXC_BAD_ACCESS. I assume it's a memory/ownership issue but nothing seems to help. Any help would be appreciated.
Relevant code snippets:
Charge Popover (Charge details model view):
#interface ChargePopoverViewController .....
NSMutableArray *pickerUserList;
#property (nonatomic, retain) NSMutableArray *pickerUserList;
#implementation ChargePopoverViewController
#synthesize whoOwesPickerButton, pickerUserList;
- (void)viewDidLoad {
JEHWebAPIPickerUsers *fetcher = [[JEHWebAPIPickerUsers alloc] init];
fetcher.delegate = self;
[fetcher fetchUsers];
}
-(void) JEHWebAPIFetchedUsers:(NSMutableArray *)theData {
[pickerUserList release];
pickerUserList = theData;
}
- (void) pickWhoPaid: (id) sender {
UserPickerViewController* content = [[UserPickerViewController alloc] initWithArray:pickerUserList];
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:content];
[popover presentPopoverFromRect:whoPaidPickerButton.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
content.delegate = self;
}
User Picker View Controller
#interface UserPickerViewController .....
NSMutableArray *pickerUsers;
#property(nonatomic, retain) NSMutableArray *pickerUsers;
#implementation UserPickerViewController
#synthesize pickerUsers;
-(UserPickerViewController*) initWithArray:(NSMutableArray *)theUsers {
self = [super init];
if ( self ) {
self.pickerUsers = theUsers;
}
return self;
}
- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component {
// Dies Here EXC_BAD_ACCESS, but NSLog(#"The content of array is%#",pickerUsers); shows correct array data
return [pickerUsers count];
}
I can provide additional code if it might help. Thanks in advance.
You declare the ivar holding the array as this...
#property (nonatomic, retain) NSMutableArray *pickerUserList;
But then you have a method implemented like this:
-(void) JEHWebAPIFetchedUsers:(NSMutableArray *)theData {
[pickerUserList release];
pickerUserList = theData;
}
You aren't retaining theData and you aren't calling the synthesized setter. If you did Build and Analyze, it should catch this problem and tell you about it. If not, file a bug.