Objective-C NSMutableArray not saving objects - objective-c

I'm learning Objective-C (having come from Java) and I'm am attempting to program a small Cocoa app on OSX. I am thinking with my Java hat on and that's probably why this isn't working. Basically I am trying to get an object of type Car added to an NSMutableArray on a button click but although the rest of the code seems to work, nothing is being saved to the array. I can't figure out why - help is appreciated. Here's the code:
#import "AppDelegate.h"
#import "Car.h"
#import "PileInterface.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
PileInterface *myStack;
Car *car;
//initialise Stack
-(void)stack{
myStack = [[PileInterface alloc]init];
//initialise myStack in Pileinterface
[myStack initialise];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
//corrected code here to call the stack method above
//initialise myStack by calling stack method
[self stack];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (IBAction)addBtn:(NSButton *)sender {
car = [[Car alloc]init];
NSString *licence = [_txtField1 stringValue];
NSString *model = [_txtField2 stringValue];
NSString *colour = [_txtField3 stringValue];
if ([myStack isFull] == false) {
// add Car objects to car
//set licence
[car setLicence:licence];
// set model
[car setModel:model];
//set colour
[car setColour:colour];
//add to stack
[myStack push:car];
//append to textarea
NSString *message = licence;
[_txtArea setStringValue:message];
[_txtField2 setIntegerValue:[myStack size]];
} else {
NSString *message2 = #"Rejected. There are more than 4 cars currently for recycling!";
[_txtArea insertText:message2];
}
}
#end
Effectively Car is the object I want to get into myStack (which is a stack inside PileInterface, which also contains the NSMutableArray)
PileInterface implementation code here:
#import "PileInterface.h"
#implementation PileInterface
//arraylist of objects
NSMutableArray *list;
//initialise array
-(void)initialise{
list = [[NSMutableArray alloc]init];
}
-(void) push:(id) o{
[list insertObject:o atIndex:0];
}
-(id) pop{
if ([list count] > 0) {
NSObject *temp =[list objectAtIndex:0];
[list removeObjectAtIndex:0];
return temp;
} else {
return nil;
}
}
-(BOOL) isEmpty{
if ([list count] == 0) {
return true;
} else {
return false;
}
}
-(int) size{
return (int)[list count];
}
-(BOOL) isFull{
if ([list count] > 4) {
return true;
} else {
return false;
}
}
#end
Help Appreciated!

Related

Objective C newbie, one method can change UI elements, others can not

thanks in advance for any input. I have been trying to write a program that interprets a serial command into text change in a label. I can change the label text easily with an Action attached to a button, but for some reason all my attempts at changing the label text outside that button action result in nothing being changed.
// MasterViewController.m
//
// Created by Daniel Payne on 2/28/15.
// Copyright (c) 2015 Daniel Payne. All rights reserved.
//
#import "MasterViewController.h"
#interface MasterViewController ()
#property (strong) IBOutlet NSTextField *score;
#property (nonatomic, strong) ORSSerialPort *serialPort;
#property (nonatomic, strong) MasterViewController *masterView;
#end
static MasterViewController *serialPortHelper = nil;
void connectPort(void) {
ORSSerialPort *serialPort = [ORSSerialPort serialPortWithPath:#"/dev/tty.usbmodem1411"];
serialPortHelper = [[MasterViewController alloc] init];
serialPortHelper.serialPort = serialPort;
serialPort.delegate = serialPortHelper;
serialPort.baudRate = #9600;
NSLog(#"port open");
[serialPort open];
}
int main(int argc, const char * argv[]) {
connectPort();
return NSApplicationMain(argc, argv);
}
#implementation MasterViewController
- (IBAction)pushButton:(id)sender {
[self.score setStringValue:#"1"]; //works
}
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)awakeFromNib
{
NSLog(#"View controller instance with view: %#", self.view);
}
- (void)serialPort:(ORSSerialPort *)serialPort didReceiveData:(NSData *)data
{
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if ([string rangeOfString:#"g"].location == NSNotFound) {
NSLog(#"no goal");
} else {
[self.score setStringValue:#"1"]; //does not work
NSLog(#"GOOOOAL");
}
}
- (void)serialPortWasRemovedFromSystem:(ORSSerialPort *)serialPort
{
self.serialPort = nil;
}
- (void)serialPort:(ORSSerialPort *)serialPort didEncounterError:(NSError *)error
{
NSLog(#"%s %# %#", __PRETTY_FUNCTION__, serialPort, error);
}
- (void)serialPortWasOpened:(ORSSerialPort *)serialPort
{
NSLog(#"Serial port %s was opened", [serialPort.name UTF8String]);
}
#end
The instance of MasterViewController that is listening to your serial port is not the same one that you are seeing on screen.
You create a new one and assign it to a static variable - this is not the same one you have loaded from a nib with all its outlets connected.
Log self in both methods to confirm.

Object loses reference

I have a IKImageBrowserView which has its own controller - BrowserController.h + .m
#interface BrowserController : NSWindowController{
NSMutableArray *_images;
}
#property (strong,nonatomic) IBOutlet IKImageBrowserView *imageBrowser;
-(void)addMultipleImages;
When I run the app for the first time, the staring image loads, but when I click a button to add other images and call a method from another class I don't get any results. I have noticed that my _imageBrowser loses the reference and becomes nil.
How could I solve this issue?
AppDelegate.m
#import "AppDelegate.h"
#import "BrowserController.h"
#implementation AppDelegate{
BrowserController *imageBrowserController;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
imageBrowserController = [BrowserController sharedManager];
}
- (IBAction)doSomething:(id)sender {
[imageBrowserController addMultipleImages];
}
#end
BrowserController.m
#import "BrowserController.h"
#interface myImageObject : NSObject
{
NSString *_path;
}
#end
#implementation myImageObject
/* our datasource object is just a filepath representation */
- (void)setPath:(NSString *)path
{
if(_path != path)
{
_path = path;
}
}
/* required methods of the IKImageBrowserItem protocol */
#pragma mark -
#pragma mark item data source protocol
/* let the image browser knows we use a path representation */
- (NSString *)imageRepresentationType
{
return IKImageBrowserPathRepresentationType;
}
/* give our representation to the image browser */
- (id)imageRepresentation
{
return _path;
}
/* use the absolute filepath as identifier */
- (NSString *)imageUID
{
return _path;
}
#end
#interface BrowserController ()
#end
#implementation BrowserController
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
}
- (void)awakeFromNib{
myImageObject *p;
p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[self updateDataSource];
}
- (void)updateDataSource{
[_imageBrowser reloadData];
}
-(NSUInteger) numberOfItemsInImageBrowser:(IKImageBrowserView *)aBrowser{
return [_images count];
};
-(id)imageBrowser:(IKImageBrowserView *)aBrowser itemAtIndex:(NSUInteger)index{
return [_images objectAtIndex:index];
};
- (void)updateDatasource
{
[_imageBrowser reloadData];
}
- (void)addMultipleImages{
myImageObject *p;
p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[_images addObject:p];
[_images addObject:p];
[_imageBrowser reloadData];
}
+ (id)sharedManager {
static BrowserController *sharedMyManager = nil;
#synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
#end
There is some confusion as to the name of the class where you mention it's called ImageBrowserController at the start of your question and it looks like it's called BrowserController at the end of your question.
The issue is that you allocate _images in awakeFromNib which is never called given the class is created via the singleton pattern (sharedInstance) and not loaded from a .nib file.
Therefore move the code from awakeFromNib into init and dump awakeFromNib:
BrowserController.m:
- (id)init
{
self = [super init];
if (self) {
myImageObject *p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[self updateDataSource];
}
return self;
}
Further confusion: you have implemented an initWithFrame method. Why?

No visible #interface for 'BNRItemStore' declares the selector 'deleteImageForKey;'

I'm new to objective C and have been learning with the book "IOS Programming Big Nerd Ranch Guide 4th edition". I keep getting a recurring error and I have been working to resolve it for the last few hours. After researching for a bit, I came here. Any help would be greatly appreciated.
"No visible #interface for 'BNRItemStore' declares the selector 'deleteImageForKey;'"
BNRItemStore.h
#import <Foundation/Foundation.h>
#class BNRItem;
#interface BNRItemStore : NSObject
#property (nonatomic, readonly) NSArray *allItems;
// Notice that this is a class method and prefixed with a + instead of a -
+ (instancetype)sharedStore;
- (BNRItem *)createItem;
- (void)removeItem:(BNRItem *)item;
- (void)moveItemAtIndex:(NSInteger)fromIndex
toIndex:(NSInteger)toIndex;
#end
BNRItemStore.m
#import "BNRItemStore.h"
#import "BNRItem.h"
#interface BNRItemStore ()
#property (nonatomic) NSMutableArray *privateItems;
#end
#implementation BNRItemStore
+ (instancetype)sharedStore
{
static BNRItemStore *sharedStore = nil;
// Do I need to create a sharedStore?
if (!sharedStore) {
sharedStore = [[super allocWithZone:nil] init];
}
return sharedStore;
}
// If a programmer calls [[BNRItemStore alloc] init], let him
// know the error of his ways
- (instancetype)init
{
#throw [NSException exceptionWithName:#"Singleton"
reason:#"Use +[BNRItemStore sharedStore]"
userInfo:nil];
return nil;
}
// Here is the real (secret) initializer
- (instancetype)initPrivate
{
self = [super init];
if (self) {
_privateItems = [[NSMutableArray alloc] init];
}
return self;
}
- (NSArray *)allItems
{
return [self.privateItems copy];
}
- (BNRItem *)createItem
{
BNRItem *item = [BNRItem randomItem];
[self.privateItems addObject:item];
return item;
}
- (void)removeItem:(BNRItem *)item
{
NSString *key = item.itemKey;
if (key) {
[[BNRItemStore sharedStore] deleteImageForKey:key];
}
[self.privateItems removeObjectIdenticalTo:item];
}
- (void)moveItemAtIndex:(NSInteger)fromIndex
toIndex:(NSInteger)toIndex
{
if (fromIndex == toIndex) {
return;
}
// Get pointer to object being moved so you can re-insert it
BNRItem *item = self.privateItems[fromIndex];
// Remove item from array
[self.privateItems removeObjectAtIndex:fromIndex];
// Insert item in array at new location
[self.privateItems insertObject:item atIndex:toIndex];
}
#end
BNRImageStore.h
#import <Foundation/Foundation.h>
#interface BNRImageStore : NSObject
+ (instancetype)sharedStore;
- (void)setImage:(UIImage *)image forKey:(NSString *)key;
- (UIImage *)imageForKey:(NSString *)key;
- (void)deleteImageForKey:(NSString *)key;
#end
BNRImageStore.m
#import "BNRImageStore.h"
#interface BNRImageStore ()
#property (nonatomic, strong) NSMutableDictionary *dictionary;
#end
#implementation BNRImageStore
+ (instancetype)sharedStore
{
static BNRImageStore *sharedStore = nil;
if (!sharedStore) {
sharedStore = [[self alloc] initPrivate];
}
return sharedStore;
}
// No one should call init
- (instancetype)init
{
#throw [NSException exceptionWithName:#"Singleton"
reason:#"Use +[BNRImageStore sharedStore]"
userInfo:nil];
return nil;
}
// Secret designated initializer
- (instancetype)initPrivate
{
self = [super init];
if (self) {
_dictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)setImage:(UIImage *)image forKey:(NSString *)key
{
self.dictionary[key] = image;
}
- (UIImage *)imageForKey:(NSString *)key
{
return self.dictionary[key];
}
- (void)deleteImageForKey:(NSString *)key
{
if (!key) {
return;
}
[self.dictionary removeObjectForKey:key];
}
#end
You are declaring the method deleteImageForKey in your BNRImageStore class, not in your BNRItemStore.
Check your implementation of removeItem: in BNRItemStore.m
- (void)removeItem:(BNRItem *)item
{
NSString *key = item.itemKey;
if (key) {
[[BNRItemStore sharedStore] deleteImageForKey:key];
}
[self.privateItems removeObjectIdenticalTo:item];
}
I assume you meant to refer to "BNRImageStore" and not BNRItemStore.
Apart from the typo, you should understand that Objective-C objects respond to selectors. Every Objective-C object has an array of selectors that it responds to. When you see the error: "No visible #interface for 'BNRItemStore' declares the selector 'deleteImageForKey;'" you should understand that the compiler does not see the selector you specified as being understood by the class in the error.

Issue initializing an object of a custom class

I'm trying to make a custom HashTable for my program. Yes, I know there is already a HashTable class in Xcode, but for this case I have a custom one. It's supposed to be simple, but when I'm trying to use it in my view controller, the debugger shows its value to be "0x0", even after calling the initialization method. Here's the code:
//header file HashTable.h
#import <Foundation/Foundation.h>
#interface HashTable : NSObject
{
}
-(void)initWithLength:(int)capacity;
-(void)add:(NSObject*)object withName:(NSString*)name;
-(id)getObjectFromIndex:(int)index;
-(id)getObjectWithName:(NSString*)name;
#end
//main file HashTable.m
#import "HashTable.h"
#implementation HashTable
NSMutableArray* values;
NSMutableArray* markers;
-(id)initWithLength:(int)capacity //Apparently, this never gets called
{
self = [super init];
if (self)
{
values = [[NSMutableArray alloc] initWithCapacity:capacity];
markers = [[NSMutableArray alloc] initWithCapacity:capacity];
}
return self;
}
-(void)add:(NSObject*)object withName:(NSString*)name
{
[values addObject:object];
[markers addObject:name];
}
-(id)getObjectFromIndex:(int)index
{
return [values objectAtIndex:index];
}
-(id)getObjectWithName:(NSString*)name
{
for (int i = 0; i < [markers count]; i++)
{
if ([[markers objectAtIndex:i] isEqualToString:name]) {return [values objectAtIndex:i];}
}
return [NSObject new];
}
-(void)removeObjectFromIndex:(int)index
{
[values removeObjectAtIndex:index];
[markers removeObjectAtIndex:index];
}
-(void)removeObjectWithName:(NSString*)name
{
for (int i = 0; i < [markers count]; i++)
{
if ([[markers objectAtIndex:i] isEqualToString:name])
{
[values removeObjectAtIndex:i];
[markers removeObjectAtIndex:i];
return;
}
}
}
-(BOOL)isEmpty
{
return [values count] == 0;
}
-(void)dealloc
{
[values release];
[markers release];
[super dealloc];
}
#end
Then I have the segments of the view controller that uses HashTable:
//header file
#import <UIKit/UIKit.h>
#import "HashTable.h"
#interface Circuitry_LabViewController : UIViewController
{
HashTable* table;
}
#property(nonatomic, retain) HashTable* table;
#end
//main file
#import "Circuitry_LabViewController.h"
#implementation Circuitry_LabViewController
#synthesize table;
- (void)viewDidLoad
{
[table initWithLength:10];
[super viewDidLoad];
}
I can't see what I'm missing here. Can anyone help?
You meant to do this in -viewDidLoad:
table = [[HashTable alloc] initWithLength:10];
Something's telling me that you're doing it wrong.

NSValueTransformer for NSPopupButton on CoreData integer attribute

I have a CoreData/SQLite app with an NSPopupButton presenting the possible values for some entity attribute (an integer). It automatically shows the attribute value in the database. The user must be able to assign another value so my code generates an array of all possible values on start-up. In my XIB, I have a NSObject for the button and an NSArrayController for the possible values. Bindings are done and everything works fine.
Of course, using only numbers is not clear for the user, so I created a value transformer to replace numbers by words on the popup button: 1=>Red, 2=>Green, 3=>Blue.
The problem is that only the selected Popup item is replaced by a word. And when I choose another value with the Popup button to modify the entity attribute, it doesn't save it. I see it by selecting another object in the TableView then coming back to the modified one and the Popup will indicate NoValue.
I don't understand why. Thank you for your help.
Here is the code of the header file "ColorOption.h":
#import <Foundation/Foundation.h>
#interface ColorOption : NSArrayController {
IBOutlet NSPopUpButton *colorPopup;
NSArray *theNumbers;
}
#property (retain) NSPopUpButton *colorPopup;
#property (readwrite, copy) NSArray *theNumbers;
#end
#interface StatusTransformer : NSValueTransformer {
}
#end
Here is the code of the implementation file "ColorOption.m":
#import "ColorOption.h"
#implementation ColorOption
#synthesize colorPopup;
#synthesize theNumbers;
-(void)awakeFromNib {
[self setTheNumbers:[NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2],[NSNumber numberWithInt:3],nil]];
StatusTransformer *statusTrans = [[[StatusTransformer alloc] init] autorelease];
[NSValueTransformer setValueTransformer:statusTrans forName:#"StatusTransformer"];
}
#end
#implementation StatusTransformer
+ (Class)transformedValueClass { return [NSString class]; }
+ (BOOL)allowsReverseTransformation { return YES; }
- (id)transformedValue:(id)value {
switch ([value intValue]) {
case 1:
return #"Red";
break;
case 2:
return #"Green";
break;
case 3:
return #"Blue";
break;
default:
return #"";
break;
}
}
-(id)reverseTransformedValue:(id)value {
if ([#"Red" isEqualToString:value]) return [NSNumber numberWithInt:1];
if ([#"Green" isEqualToString:value]) return [NSNumber numberWithInt:2];
if ([#"Blue" isEqualToString:value]) return [NSNumber numberWithInt:3];
return nil;
}
#end
I'm reviewing my questions and see that this one has been left unanswered. However in the meantime I had found the answer which I'll post now so it may help others.
The mistake I did was to define the array of integer values while what I should have done is to define the array of words populating the popup button. Here is the full solution:
Header file :
#import <Cocoa/Cocoa.h>
#interface ColorOption : NSArrayController {
IBOutlet NSPopUpButton *colorPopup;
NSArray *theNames;
}
#property (strong) NSPopUpButton *colorPopup;
#property (readwrite, copy) NSArray *theNames;
#end
#interface StatusTransformer : NSValueTransformer {
}
#end
Implementation file
#import "ColorOption.h"
#implementation ColorOption
#synthesize colorPopup = _colorPopup;
#synthesize theNames = _theNames;
-(void)awakeFromNib {
[self setTheNames:[NSArray arrayWithObjects:[NSString stringWithFormat:#"Red"],[NSString stringWithFormat:#"Green"],[NSString stringWithFormat:#"Blue"],nil]];
StatusTransformer *statusTrans = [[[StatusTransformer alloc] init] autorelease];
[NSValueTransformer setValueTransformer:statusTrans forName:#"StatusTransformer"];
}
#end
#implementation StatusTransformer
+ (Class)transformedValueClass {
return [NSString class];
}
+ (BOOL)allowsReverseTransformation {
return YES;
}
- (id)transformedValue:(id)value {
switch ([value intValue]) {
case 1: {
return #"Red";
break;
}
case 2: {
return #"Green";
break;
}
case 3: {
return #"Blue";
break;
}
default: {
return nil;
break;
}
}
}
-(id)reverseTransformedValue:(id)value {
if ([#"Red" isEqualToString:value]) return [NSNumber numberWithInt:1];
if ([#"Green" isEqualToString:value]) return [NSNumber numberWithInt:2];
if ([#"Blue" isEqualToString:value]) return [NSNumber numberWithInt:3];
return nil;
}
#end
Now in the bindings inspector of Xcode, the value transformer must be applied to "Content Values" and "Selected Object" for the popup button.