universal array in objective C - iphone

I am making my first app, and already made it on android, and am now trying to make it on iphone, but have no objective c experience. The app is super simple except for one part, the array.
The app has a button, that when pressed, needs to store info into an array. The problem I am running into is that when I create the array in the method where the button-click actions take place, every time I click the button it creates a new array, defeating the point of the array. When I make the array outside of the method, it either doesn't pass into the method (error says undefined) or, when I declare the object in the .h file, the program compiles, but when I hit the button it crashes.
Any help would be greatly appreciated. Examples would be great, but even if someone could point me in the right direction of things to look up, that would save me from going bald.

Try something like this (this isn't ARC) -
#interface MyViewController : UIViewController {
NSMutableArray *myArray;
}
#implementation MyViewController
-(id)init {
self = [super init];
if (self) {
myArray = [[NSMutableArray alloc] init];
}
return self;
}
-(void)dealloc {
[myArray release];
[super dealloc];
}
-(IBAction)buttonPressed {
[myArray addObject:someObject];
}
#end

You need to declare your array as an instance variable (AKA "ivar") inside the curly braces section of the the interface declaration in your .h file, and also initialize it in your designated initializer.
In the .h file:
#interface MyClass : NSObject {
NSMutableArray *myArray
}
// methods
#end
In the .m file:
-(id)init {
self = [super init];
if (self) {
myArray = [NSMutableArray array];
}
return self;
}
Now you can use myArray in all instance methods of your class.
EDIT: This sample assumes that you are using automated reference counting. Since this is your first app, using ARC is a good idea (XCode asks you if you would like to use it when you create a new project).

Related

How to access a method in an object within another object, from the outside?

I am in ViewController, trying to access a method in object "cat" owned by object "backgroundpicture". ViewController has an instance of backgroundpicture.
The method/message in "cat.h":
#interface Cat : NSObject
-(BOOL)checkIfTouchHit:(float) xx :(float) yy;
#end
"Cat.m":
- (BOOL)checkIfTouchHit:(float) xx :(float) yy{
NSLog(#"Inside checkIfTouchHit");
return YES;
}
"BackGroundPicture.h":
#import "Cat.h"
#interface BackGroundPicture : NSObject
#property (strong) Cat * katt;
#end
"BackGroundPicture.m":
#implementation BackGroundPicture
#synthesize katt = _katt
#end
"ViewController.m":
#interface ViewController ()
#property (strong) BackGroundPicture * bakgrunnsbilde;
#end
#implementation BackGroundPicture
#synthesize bakgrunnsbilde = _bakgrunnsbilde;
- (void)viewDidLoad
{...
[[self.bakgrunnsbilde katt] checkIfTouchHit :(float)touchLocation.x :(float)touchLocation.y]
...}
The string inside the method "checkIfInside" in cat will not show up. I also tried
[_bakgrunnsbilde katt]...
but with the same lack of result, and I believe this is compiled the same way. I am wondering what I am missing here, and hope someone can help. Thanks :)
edit I forgot to add a few lines from my BackGroundPicture.m. It is a method run on start from the ViewDidLoad in ViewController. It is like this in BackGroundPicture.m:
- (void)createObjects {
Cat * katt = [[Cat alloc] init];
}
it is called from ViewController.m like so:
- (void)viewDidLoad
{
[_bakgrunnsbilde createObjects];
}
I know that this get executed. I hope this edit makes sense, my head is ruined after a long day :) Going to check back tomorrow morning.
It will work, but BackGroundPicture.m needs to allocate a cat first.
So in BackGroundPicture.m, do this:
- (id)init {
self = [super init];
if (self) {
_katt = [[Cat alloc] init];
}
return self;
}
In general, remember to allocate objects before you use them. You may also need to create a BackGroundPicture, too as Valentin points out. In viewDidLoad, do this:
bakgrunnsbilde = [[BackGroundPicture alloc] init];
As far as I can see you're accessing the method correctly. You could use the property, for readability sake (you also don't need the cast)
[self.bakgrunnsbilde.katt checkIfTouchHit:touchLocation.x :touchLocation.y]
, but your way of doing it should work as well.
You should check if your -viewDidLoad method gets called and if self.bakgrunnsbilde or self.bakgrunnsbilde.katt is not nil when -viewDidLoad gets called. One of this should get you on the right track.

Sharing data/string with a singleton between views

I'm trying to share a string between two views on my iPhone project. It currently works if I use the actual #"something here" for the string, but if I want to use something like label.text, it doesn't even though it is still a string.
I'll show you what I have to make it clearer.
First View: Info_ViewController.h
#import <UIKit/UIKit.h>
#interface Info_ViewController : UIViewController {
IBOutlet UITextField *locationField;
}
#property (nonatomic, retain) NSString *locationString;
+ (id)sharedInfoVC;
#end
First View: Info_ViewController.m
#import "Info_ViewController.h"
static Info_ViewController *sharedInfoVC = nil;
#implementation Info_ViewController
#synthesize locationString;
#pragma mark Singleton Methods
+ (id)sharedInfoVC {
#synchronized(self) {
if (sharedInfoVC == nil)
sharedInfoVC = [[self alloc] init];
}
return sharedInfoVC;
}
- (id)init {
if (self = [super init]) {
locationString = [[NSString alloc] initWithString:locationField.text]; //This is there part I mentioned earlier, when using #"something" instead of locationField.text works.
}
return self;
}
Second View: Confirm_ViewController.m
#import "Confirm_ViewController.h"
#import "Info_ViewController.h"
#implementation Confirm_ViewController
- (IBAction)buttonZ:(id)sender
{
Info_ViewController *infoVCmanager = [Info_ViewController sharedInfoVC];
locationLabel.text = infoVCmanager.locationString;
}
I put it under a button for now, but it will eventually be under viewDidLoad.
If you replace locationField.text with a string (#"blahblahblah") it won't crash and works.
When it crashes I get the error: Program received signal: "SIGABRT"
EDIT: I tried changing
initWithString:locationField.text
to
initWithFormat:#"%#",locationField.text
and now it my label in the second view prints "(NULL)"
Thanks for taking the time to give advice, I really appreciate it.
It is an error to pass nil as the format string to -[NSString initWithString].
So how are you passing nil? You actually have two instances of Info_ViewController. You have the one instance which is the normal part of your app, and then you also have a second instance which is your "singleton" (which really isn't a singleton any more).
So in your "singleton" instance, the UITextField is nil (and will always be nil) and so locationField.text is nil and you are passing that to initWithString:, which is a crash. In fact the "singleton" isn't even fully baked as view controller's go.
If you want a singleton to share data elsewhere in your app, it really should not be a Info_ViewController or any type of view controller. It should be of some other class that you use to manage your data. I would create another class and implement that as a singleton.
Hope that helps you understand what's happening here.
Pre-pend "self." to your location string.
self.locationString = [[NSString alloc] initWithString:locationField.text];
From what I understand of your code, you have got the value for locationString when you from the textfield when you initialize the viewController. At this point of time, your textfield would not be visible. After it becomes visible and you enter something, you don't have the code to store it to locationString.
What you should do is wait for Info_ViewController object to be initialized and displayed. Then on the press of some button or some other event, assign locationLabel.text from the locationString or even directly from locationField.text.
I would provide code, but I have no clue as to how you are structuring this. If you still need help, please provide the details.

where do I instantiate my class so I can access it throughout my app?

I've been working through some objective-c/ iOS dev books and I've hit a stumbling block. I get the feeling that I'm missing something dumb here, so I'm sure you guys can help, because you're all so damn smart :-).
I've got a very simple app that consists of 1 button and 1 label. Pushing the button puts a message in the label. I've created a class that includes a method to create said message. Here is the problem:
#import "classTestViewController.h"
#implementation classTestViewController
#synthesize myLabel;
- (void)viewDidLoad
{
}
-(IBAction) pressGo:(id)sender{
MyClass * classTester = [[MyClass alloc] init];
classTester.count = 15;
NSString *newText = [classTester makeString ];
myLabel.text = newText;
}
- (void)dealloc
{
[classTester release];
[myLabel release];
[super dealloc];
}
The output of this app, in my label, is "Yay 15". So you can see the problem, the only way I can get this to work is to instantiate the class right there, in the "pressGo" method. This isn't desirable because another method can't access or change the class variable count. Also I get a warning that local declaration of classTester hides instance variable. If I move the class instantiation to the viewDidLoad method, which seems right, the other methods can't access it anymore.
#import "classTestViewController.h"
#implementation classTestViewController
#synthesize myLabel;
- (void)viewDidLoad
{
MyClass * classTester = [[MyClass alloc] init];
}
-(IBAction) pressGo:(id)sender{
classTester.count = 15;
NSString *newText = [classTester makeString ];
myLabel.text = newText;
}
- (void)dealloc
{
[classTester release];
[myLabel release];
[super dealloc];
}
The output of that is nada. If I try to access just one variable, classTester.count, for example, even after setting it, I get a 0 value. I also get the override warning here as well.
So my question is, how can i get access to that class instance throughout my app and not just in one method? I'm using a view based application.
Declare classTester in your interface file with:
#class MyClass
#interface classTestViewController : UIViewController
{
MyClass *classTester;
}
// Any other custom stuff here
#end
Then instantiate it in your viewDidLoad method with:
classTester = [[MyClass alloc] init];
And you should be able to access the ivar from any method within this class. If you want it to be accessible to your entire app, #Waqas link will point you in the right direction.
You need to create a singleton class which instantiate once and is available inside whole project
Have a look
http://projectcocoa.com/2009/10/26/objective-c-singleton-class-template/

Memory Management in Objective-C

I wanna ask if I allocated an instance variable for private use in that class, should i release it immediately on site, or i can depend on dealloc function. (because maybe i will need it on other function) ?
//Player.h
#interface Player : NSObject
{
NSMutableArray * objectArray;
}
- (void)awake;
- (void)add;
#end
//Player.m
#implementation Player : NSObject
{
-(id) init {
self = [super init];
if (self != nil ){
[self awake];
[self add];
}
return self;
}
- (void) awake {
objectArray = [[NSMutableArray alloc] init]; //is it cause leakage?
[objectArray addObject:#"foobar"];
}
- (void) add {
[objectArray addObject:#"foobar2"];
}
- (void) dealloc {
[objectArray release];
[super dealloc];
}
}
#end
or should i using property to set the objectArray iVar?
//Player.h
#interface Player : NSObject
{
NSMutableArray * objectArray;
}
#property (nonatomic,retain)NSMutableArray* objectArray;
- (void)awake;
- (void)add;
#end
//Player.m
#implementation Player : NSObject
{
-(id) init {
self = [super init];
if (self != nil ){
[self awake];
[self add];
}
return self;
}
- (void) awake {
self.objectArray = [[NSMutableArray alloc] init autorelease]; //cause leakage?
[objectArray addObject:#"foobar"];
}
- (void) add {
[objectArray addObject:#"foobar2"];
}
- (void) dealloc {
[objectArray release];
[super dealloc];
}
}
#end
if both of them doesn't cause a leakage, what type should i use?
should i always set iVar property, and access iVar value with self even if i only want to use it in this class?
I like to take the stance that if the instance variable should not be visible outside of the class then it should not be implemented as a property. But it's a personal thing that other developers may not agree with.
Either way you would need to release the objectArray in your classes dealloc method - which is what you're currently doing.
However you need to be careful with your awake method - if it's invoked multiple times then objectArray is leaked. This is the downside of not using properties. A use of self.objectArray = [[NSMutableArray alloc] init] here would have released the previous object.
In my opinion, you should only declare properties in your header if other objects are allowed to use them. There is no good reason why you would provide an -add: method (as in your example) that adds something to your array while also providing a getter for your array so other objects can manipulate it directly. It's called encapsulation.
If you do want to have the benefits of generated getters/setters for your implementation file, you can always use a class continuation (a nameless category) inside your implementation file and include your property declarations there. That way you get real, auto-generated properties that are only visible to your class' implementation.
Personally, I wouldn't use any getter or setter methods in your example. Just allocate the NSArray in your -init and release it in -dealloc. If this -awake method of yours might be called multiple times, just add an [objectArray removeAllObjects] call and you're sure to have an empty array without worrying about memory management.
It is very likely that memory will leak in your first example because you are not sending release to the previously set instance variable (if it already existed).
This is why you should use property setters - they handle all of this stuff for you.
Also, since you are obtaining ownership of the instance variable through the property (which is defined with the retain keyword), you will definitely leak memory if you don't send the instance variable the -release message in your -dealloc method.
So the verdict is that you should use the second example, not the first.

Confused why one piece of code works and another doesn't in Objective C

Sorry about the title being extremely vague, I'm new to Objective C and struggling a little with it. Basically I have the following section of code:
Graph *graph1 = [[Graph alloc] init];
[graph1 addNode:#"TEST"];
which is working to a degree. But I want to change it because the above code happens on a button press, and therefore I assume I am creating a new "*graph1" every time I do this. I thought I could simply change it to this:
if(self = [super init])
{
[self setGraph: [[Graph alloc] init]];
}
return self;
Where the above is in the init method, and below is the modified function:
[graph addNode:#"TEST"];
However when debugging I've found addNode method is never called when it's like this.
Thanks
Zac
This is testViewController.h
#import <UIKit/UIKit.h>
#class Graph;
#class Node;
#interface testViewController : UIViewController {
Graph *graph;
UILabel *label;
}
#property (nonatomic, retain) IBOutlet UILabel *label;
#property (nonatomic, retain) Graph *graph;
- (IBAction) buttonPressed:(id)sender;
#end
This is textViewController.m
#import "testViewController.h"
#import "Graph.h"
#implementation testViewController
#synthesize label, graph;
- (id)init
{
if(self = [super init])
{
[self setGraph: [[Graph alloc] init]];
}
return self;
}
- (IBAction)buttonPressed:(id)sender
{
//Graph *graph1 = [[Graph alloc] init];
[graph addNode:#"TEST"];
Node *node1 = [[Node alloc] initWithLabel: #"LABEL"];
label.text = node1.label;
}
The first thing that comes to mind is that graph is nil and thus invoking a method (sending a message) to it will result in nothing. An unwanted release will cause an EXC_BAD_ACCESS, and this seems to be not the case.
I suppose you are calling all of this in a UIViewController subclass, right? Are you sure the right init is called? If you are using a NIB you should override the -(id)initWithNibName:bundle: and place you code there. I guess the code is probably in the plain -(id)init, since you are calling [super init] and not [super initWithNibName:nameOrNil bundle:bundleOrNil], but this way, if initialize the controller with the NIB you custom code is never called and thus graph is nil.
By the way, if the graph property is (retain) you are also causing a memory leak in the init.
I'm sure you're through this problem now, but I agree that the reason "add" is "not being called" is that your graph object is nil at that moment
I'd say, first put a test message around your "addNode" call
NSLog(#"We tried to add here");
[graph addNode:#"TEST"];
That will show you that the add is being called -- I bet it is.
Then, where you had your previous call to initialize "graph" right before your add call, try conditionally initializing it:
if(graph == nil) graph = [[Graph alloc] init];
[graph addNode:#"TEST"];
Note, this is all just to find the problem. Finally I'd say you have some challenges in here with how you are dealing with memory. You may not have reference issues, but later leaks. And depending upon how often this code is executed it could be an issue.
But at least you may get to your issue easier with the above tests.
Have you declared the graph variable in the header? ie: Graph *graph; and the corresponding #property (nonatomic, retain) Graph *graph;
Then I would do this:
-(id) init {
graph = [[Graph alloc] init];
[graph retain];
}
that might help (the only reason I think it wouldn't would be because a) if your variable wasn't declared then you would get a warning like graph may not respond to addNode and if it wasn't retained then your app would crash when it runs)... other than that, I can't see what would be the problem. If that doesn't work, can you please post all your code from your .h and .m files?
Then I would do this:
-(id) init {
graph = [[Graph alloc] init];
[graph retain];
}
that might help
This would result in a memory leak. The retain count of the object pointed to by graph will have a retain count of 2. Not ideal. If you declare the property with the retain attribute then
[self setGraph:[[[Graph alloc] init] autorelease]];
should do it. I go with -
self.graph = [[[Graph alloc] init] autorelease];
There could be many reasons the addNode: method is not being called. Put break points in the enclosing method and see if everything is working as you expect it to.