I'm not sure if I'm taking the right approach to this, but I want to access my model for a Shopping Cart Class from different viewControllers. My first approach was to init a Cart * object in each viewController, but I think I just ended up with multiple cart objects when all I want is one that can be accessed globally. After searching I came up with a different approach that seems better, but no luck yet.
I have a button that is supposed to add a deal to a shopping cart. but when I try to add it, the method is not getting called. Here is how I have it set up.
In my Cart class I have a NSMutableArray to hold my deals.
In my viewController I set up a property of type Cart *cart and initialized like this
#property (strong, nonatomic) Cart *cart;
...
-(id)initWithModel:(Cart *)cart {
self = [super init];
if(self){
self.cart = cart;
}
return self;
}
Then my button method is this
-(IBAction)addDealToCart {
NSLog(#"The Cart has %i items", [self.cart qtyOfItemsInCart]);
NSLog(#"Added the Deal to the Cart");
[self.cart addDealsToCart:self.deal];
NSLog(#"The Cart now has %i items", [self.cart qtyOfItemsInCart]);
self.deal.qtyInCart = self.deal.qtyInCart + 1;
NSLog(#"the deal has %i items in the Cart", self.deal.qtyInCart);
}
In this line addDealsToCart:deal never gets called.
here is my addDealsToCart:deal method in my Cart Class
-(void)addDealsToCart:(Deals *)deal {
[self.cartContents addObject:deal];
NSLog(#"the deal was added to the cart %#",deal);
}
any help would be great. thanks
Without going for a whole re-architecturing, why don't you pass your cart object to each view controller? I personally wouldn't go the singleton route that your description hints of.
Update (example):
UIViewController *yourNextVC = [[YourNextVC alloc] init];
yourNextVC.cart = self.cart //The current cart in your current vc, passing it along.
[self presentViewController:yourNextVC animated:YES completion:nil];
I you just want one Cart variable througout the app you can have it in appDelegate if you want
#property (strong, nonatomic) Cart *cart;
And taking out is simple
AppDelegate *appDelegateObject=(AppDelegate*)[[UIApplication shareApplication] delegate];
appDelegateObject.cart.qtyOfItemsInCart= 1; //initialize where you want.
Take out in another view controller like this:
AppDelegate *appDelegateObject=(AppDelegate*)[[UIApplication shareApplication] delegate];
NSLog(#"cart value: %d,appDelegateObject.cart.qtyOfItemsInCart);
Related
I am new to objective-c and trying to understand better why the following is occurring... in my view controller, I have this in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
[self createProjectData];
}
And then:
- (void)createProjectData
{
if(!self.projectData) {
self.projectData = [[NSMutableArray alloc] initWithObjects:tempProjectInfo1, tempProjectInfo2, nil];
}
projectData is a public property:
#property (nonatomic, strong) NSMutableArray *projectData;
My problem is that when I navigate to a different View Controller and return to this one, projectData is null even though I had initialized previously with the above values... so I'm really hoping someone can explain how I can retain the property value so when I return it has all of the items that I had added to the mutable array.
Check if somewhere inside viewDidDisappear your code is niling your array.
You may also have code for dealing with memory warnings doing something similar
I created a singleton in ios7 like this:
SharedData.h
#interface SharedData : NSObject
{
}
+ (id)sharedInstance;
#property (strong, nonatomic) NSMutableArray *list;
#end
SharedData.m
#import "SharedData.h"
#implementation SharedData
#synthesize list;
// Get the shared instance thread safe
+ (SharedData *)sharedInstance {
static dispatch_once_t once = 0;
static SharedData *sharedInstance = nil;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init {
self = [super init];
if (self) {
//initialize
list = [[NSMutableArray alloc] init];
}
return self;
}
#end
I always use this code to access this class:
SharedData *sharedData = [SharedData sharedInstance];
The problem is now when I switch the view in my viewDidLoad method the list is empty but in my viewDidAppear method everything is fine. Any ideas?
EDIT:
This is the code how I change the views:
SharedData *sharedData = [SharedData sharedInstance];
//clear feed and add new feed
[sharedData.list removeAllObjects];
[sharedData.list addObjectsFromArray:newList];
//show new gui
[self.navigationController performSegueWithIdentifier:#"goToMain" sender:self];
NOTE: I push from a normal ViewController to a TabBarController -> NavigationController -> TableViewController to display the list.
I guess you have the confusion between these two viewcontroller methods:
-(void)viewDidLoad{
//
}
&
-(void) viewDidAppear{
//
}
viewDidAppear is the method which is called each time your view changes but viewDidLoad is the method which is not necessarily called each time your view changes.
ViewDidLoad method is called when view loads for the first time, after that it doesn't get called until the views are removed/released.
P.S: I suggest you to put the breakpoint in your viewDidLoad and viewDidAppear method and feel it. Your answer lies there.
Hope this helps you alot.
Good Luck.
The problem was i created a segue which went from the button to the next view. Because of this the viewDidLoad gets earlier called than the list assigned. I just changed the segue to go from view to view.
How are you changing from one viewController to the other? Wich classes are the parents of your destination ViewController?,
If you are modifying properties of the view in the prepareForSegue method... you are forcing the view to load.
For example, you are setting the list of your singleton in prepareForSegue, but before setting the list you are modifying a property of your destination viewController. (doing something like destVC.view = XXX or destVC.viewControllers = XX if you are subclassing a UITabBarViewController...) Then you are triggering the viewDidLoad method , and it's executing before you have set the list to the correct value.
Or maybe you are seguing in two different places to the destinationViewController. And when the viewDidLoad happens, you still have not updated the list on the singleton.
Here is the transcription of the chat with the poster of the question: https://chat.stackoverflow.com/transcript/55218
I have 2 classes which names are A and B, I have UIScrollView with pagecontroller in class A and I have a UILabel and NSMutableArray in B.
I used this event for get pagecontroller's page and i am sending number of page to classB for use array's element.
//ClassA
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat pageWidth = self.imageScrollView.frame.size.width;
int page = floor((self.imageScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
ClassB *obj = [[ClassB alloc]init];
[obj changeDiscount:page];
}
//ClassB
- (void)viewDidLoad
{
numbers = [[NSMutableArray alloc]initWithObjects:#"15",#"25",nil];
}
-(void) changeDiscount:(int)currentPagePresentation{
NSLog(#"currentI = %i",currentPagePresentation);
_discountLabel.text = [NSString stringWithFormat:#"%# Discount",[numbers objectAtIndex:currentPagePresentation]];
}
I can call the changeDiscount method but array is coming null every time and I can't set the string to label.
What am I doing wrong?
Thanks for your answer and advice.
The reason why your _discountLabel.text's string is equal to null is because the numbers array that you are accessing has not even been initialised.
The reason why your numbers array has not been initialised is because the viewDidLoad method only gets called as the method states: WHEN the view has loaded ;)
If you want to access the array after creating an instance of your class, its best to setup the numbers array in an init method or so.
All you've done is:
//This creates a new instance of your second class B.
ClassB *obj = [[ClassB alloc]init];
//Youre trying to access the numbers array when you havent even loaded the view
//All you've done is create an instance of it and then calling a method with an empty numbers array.
[obj changeDiscount:page];
And that doesn't sit well with your existing code. Please continue to read to understand why.
You also definitely don't want to be creating new instances of your class every time your scroll view delegate method is called. I highly suggest you revisit that code and find an appropriate place for that code.
Solution
I suggest you revise the view controllers programmers guide on the apple website before doing anything else.
Follow step 1.
Follow step 1 again.
Then something you can do is:
Method 1: - lazy method In class B you could create an instance method like so:
//.h
-(void)setupArray;
//.m
-(void)setupArray{
numbers = [[NSMutableArray alloc]initWithObjects:#"15",#"25",nil];
}
//Then you can do something like this in class a
ClassB *obj = [[ClassB alloc]init];
[obj setupArray];
[obj changeDiscount:page];
Method 2: more appropriate If you want to do it in one go you can do this, and create an init method.
//.h
//in your Class B .h file you create an instance method like so:
-(void)init;
//.m file
-(id)init{
self=[super init];
if(self)
numbers = [[NSMutableArray alloc]initWithObjects:#"15",#"25",nil];
return self;
}
//Then in your class a method you can do this:
//Like before.
ClassB *obj = [[ClassB alloc]init];
[obj changeDiscount:page];
Ok, there are a few things causing issues here:
You are creating an instance of ClassB within the scope of scrollViewDidScroll of classA.
As soon as that method completes, that new object will be deallocated.
ClassB initialises the numbers array in viewDidLoad. This method will be called only when a UIViewController subclass loads it’s UIView, so ClassB must be a UIViewController subclass and you need to have presented it.
viewDidLoad is called when you first time access view property of that viewController.until then view is nil.
So your numbers array wont be initialised because you are calling changeDiscount method before viewDidLoad is executed.
So, move the initialising from viewDidLoad to init or initWithNib.
-(id)init{
self=[super init];
if(self)
numbers = [[NSMutableArray alloc]initWithObjects:#"15",#"25",nil];
return self;
}
I have an application where A View Controller (A)is called twice in close succession. Now each time it is called, an NSString object is created, and I need this value to be stored in an NSMutableArray that is a public property of ANOTHER View Controller (B).
In A, I create an instance of the second View Controller (B), and using that instance, add the NSString objects into the NSMutableArray which I've created as a public property. Later, when I am inside View Controller B and print the contents of the NSMutableArray property, the array is empty. Why? Here is the code that is inside View Controller A:
-(void)viewDidLoad {
ViewControllerA *aVC = [[ViewControllerA alloc] init];
if (aVC.stringArray == nil) {
aVC.stringArray = [[NSMutableArray alloc] init];
}
[aVC.stringArray addObject:#"hello"];
[aVC.stringArray addObject:#"world"];
for (NSString *wow in aVC.stringArray) {
NSLog(#"The output is: %#", wow);
}
}
Inside my View Controller B class, I have the following code:
- (IBAction)buttonAction:(UIButton *)sender {
NSLog(#"Button selected");
for (NSString *test in self.stringArray) {
NSLog(#"Here are the contents of the array %#", test);
}
}
Now the buttonAction method gets called, as I do see the line Button selected in the system output, but nothing else is printed. Why? One thing I want to ensure is that View Controller A is called twice, which means I would like to see in the output, "Hello World", "Hello World" (i.e. printed twice), and not "Hello World" printed just once.
The other thing I wish to point out is that View Controller B may not be called at all, or it may be called at a later point in time. In any case, whenever View Controller B is called, I would like to have the values inside the array available, and waiting for the user to access. How do I do this?
Your approach is not ideal, potentially leading to a memory cycle, with two objects holding strong pointers to each other.
You can instead achieve your goal in two ways;
Delegate Protocol
This method allows you to set delegates and delegate methods to pass data back and forth between view controllers
in viewControllerA.h
#protocol viewControllerADelegate <NSObject>
- (void)addStringToNSMutableArray:(NSString *)text;
#end
#interface viewControllerA : UIViewController
#property (nonatomic, weak) id <viewControllerADelegate> delegate;
in viewControllerB.m
// create viewControllerA class object
[self.viewControllerA.delegate = self];
- (void)addStringToNSMutableArray:(NSString *)text
{
[self.mutableArray addObject:text];
}
in viewControllerA.m
[self.delegate addStringToNSMutableArray:#"some text"];
Utility Classes
Alternatively you can use a utility class with publicly accessible methods (and temporary data storage). This allows both viewController classes to access a shared data store, also if you use class methods, you don't even need to instantiate the utility class.
in XYZUtilities.h
#import <Foundation/Foundation.h>
#interface XYZUtilities : NSObject
+ (void)addStringToNSMutableArray;
#property (strong, nonatomic) NSMutableArray *array;
#end
in XYZUtilities.m
+ (void)addStringToNSMutableArray
{
NSString *result = #"some text";
[self.array addObject:result];
}
+ (NSArray)getArrayContents
{
return self.array;
}
in viewControllerA.m
NSString *stringFromObject = [XYZUtilities addStringToNSMutableArray];
in viewControllerB.m
self.mutableArray = [[NSMutableArray alloc] initWithArray:[XYZUtilities getArrayContents]];
I'm not sure what kind of a design pattern you are trying to follow but from the looks of it IMHO that's not a very safe one. However, there are many, many ways this could be accomplished.
One thing though, you said that View Controller B may never get allocated and if it is alloc-ed, it will be down the road. So you can't set a value/property on an object that's never been created.
Since you already aren't really following traditional patterns, you could make a static NSMutableArray variable that is declared in the .m of your View Controller B Class and then expose it via class methods.
So it would look like this:
viewControllerB.h
+(void)addStringToPublicArray:(NSString *)string;
viewContrllerB.m
static NSMutableArray *publicStrings = nil;
+(void)addStringToPublicArray:(NSString *)string{
if (publicStrings == nil){
publicStrings = [[NSMutableArray alloc]init];
}
if (string != nil){
[publicStrings addObject:string];
}
}
Then it would be truly public. All instances of view controller B will have access to it. This, of course is not a traditional or recommended way of doing it—I'm sure that you will have many replies pointing that out ;).
Another idea would be to use a singleton class and store the values in there. Then, when or if view controller B is ever created, you can access them from there.
I have a Destination View Controller that allows you to edit information displayed in the TableViewController..I am attempting to set this up in a custom cell..I have my UITableViewCell file with the custom property class and I also have my Model Class for the Core Data with the attributes. I managed to get my root table view controller to show the custom label when I add a NEW player but once I click on the cell and edit it in the new view controller it goes back to the default on the table view. I believe it has something to do with this code but I can not figure it out.
my NsManagedObject Subclass
#property (nonatomic, retain) NSString *playerFirstName;
I have a pointer to my Player Class of currentPlayer in my viewcontroller.h file and the firstnameTextfield is my UITextField
-(IBAction)doneEditing:(id)sender {
_currentPlayer.playerFirstName = firstnameTextField.text;
AppDelegate *myApp = (AppDelegate *) [[UIApplication sharedApplication]delegate]'
[myApp saveContext];
}
Update
I believe this is my line of code that is the problem after messing with it
_currentPlayer.playerFirstName = firstnameTextField.text;
how do I get the currentPlayer pointer to go to my playerNameCell property in my customcell class
You should do something like:
- (IBAction)newPlayer {
_currentPlayer = (Player*) [NSEntityDescription insertNewObjectForEntityForName:#"Player" inManagedObjectContext:_managedObjectContext];
}
The _managedObjectContext should be passed to the view controller from the app delegate or some other view controller.