I am implementing a simple game in which the user tries to guess a randomly selected card. They select their guess from a two-component picker (in FirstViewController), and in the next screen (SecondViewController) they can check if they are correct. I am stuck right now trying to pass the guess the user selected to the screen where they can check.
In SecondViewController.h, I declare properties for both parts of the guess (number and suit) like this:
#property (nonatomic, assign) NSString * guessNumber;
#property (nonatomic, assign) NSString * guessSuit;
Then in didSelectRow in FirstViewController.h, I am trying to pass the info forward like this:
- (void) pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
SecondViewController *secondVC = [[SecondViewController alloc] init];
if (component == 0){
secondVC.guessNumber = _cardNumbers[row];
}else{
secondVC.guessSuit = _cardImages[row];
}
[self.navigationController pushViewController:secondVC animated:YES];
}
In viewDidLoad for SecondViewController, I NSLog the values of guessNumber and guessSuit, but it prints out null for both.
Clearly I am going wrong somewhere and the data isn't actually being passed, if anyone has any guidance about what I need to fix that would be amazing!
EDIT:
I have now changed the values to copy rather than assign, like this:
#property (nonatomic, copy) NSString * guessNumber;
#property (nonatomic, copy) NSString * guessSuit;
I have tried strong as well, and neither work. The weird thing is that if I print out the value right after I assign it, like this:
secondVC.guessNumber = _cardNumbers[row];
NSLog(secondVS.guessNumber);
I get the right value. Now I am even further confused as it seems to be assigning the value but not saving it when I actually go to my second view controller.
The assign tells the NSString * property setter to save the pointer address instead of the OC object itself.
Both _cardNumbers[row] and _cardImages[row] return an autoreleased object which will be released after finishing your didSelectRow method, so you couldn't get the expected string values in your viewDidLoad of SecondViewController.
Use the strong or copy instead, it tells the property setter to increase the value's retain count by 1, so SecondViewController owns (guessNumber owns) the string value, you could get it in its life cycle all the time.
NSString used copy is right . The problem is _cardNumbers and _cardImages ,let' see data source in _cardNumbers.
Related
This question already has answers here:
Objective-C declared #property attributes (nonatomic, copy, strong, weak)
(4 answers)
Closed 6 years ago.
I was trying to pass a custom object to the next view controller and I encountered this error -[ClassName copyWithZone:] unrecognized selector sent to instance
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"attemptDetails"])
{
ResultsVC *vc = segue.destinationViewController;
vc.selectedEntry = selectedEntry;
}
}
#property (nonatomic, retain) ClassName *selectedEntry; //Why is it retain and not copy?
I'm still very confused with property attributes and why certain types use certain attributes, like NSString uses (nonatomic, copy) and CLLocationCoordinate2D uses (nonatomic, readonly).
Could someone explain or link a reference to me how each property attribute works? Much thanks!
There are lots of descriptions for property attributes explanation,
Reference links,
Objective-C ARC: strong vs retain and weak vs assign
https://stackoverflow.com/a/4511004/4294543
#property and retain, assign, copy, nonatomic in Objective-C
Short & simple my understanding is like,
retain : It's working on the created object, and it just increase the reference count.
Here in your case you have already model class object so not need to copy in the second vc property,you just need to retain it to second vc property.
copy : The value you assigned to property can be copied & used for other purposes too(create shallow copy of object & need when object is mutable & need to release after finish with it).
nonatomic : Thread access is faster but you can't simultaneously access & change your property.
readonly : You can't directly assign the property new value.
Even i have run your case in the my project,
#import "ViewController.h"
#import "TestViewController.h"
#import "CustomClass.h"
#interface ViewController (){
CustomClass *classT;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
classT = [[CustomClass alloc]init];
classT.test = YES;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)btn:(id)sender {
TestViewController * vc = [self.storyboard instantiateViewControllerWithIdentifier:#"TestViewController"];
vc.className = classT;
[self presentViewController:vc animated:YES completion:nil];
}
#end
#import <UIKit/UIKit.h>
#import "CustomClass.h"
#interface TestViewController : UIViewController
#property (nonatomic,retain) CustomClass *className; // Work as i said
//#property (nonatomic,copy) CustomClass *className; // Makes a copy of an object, and returns it with retain count of 1. If you copy an object, you own the copy. This applies to any method that contains the word copy where “copy” refers to the object being returned thats why here you will get crash
#end
I have read couple of good article for memory management. According to rypress
Retain Attribute : The retain attribute is the Manual Retain Release version of strong, and it has the exact same effect: claiming ownership of assigned values. You shouldn’t use this in an Automatic Reference Counted environment.
Copy Attribute : The copy attribute is an alternative to strong. Instead of taking ownership of the existing object, it creates a copy of whatever you assign to the property, then takes ownership of that. Only objects that conform to the NSCopying protocol can use this attribute.
Even I went through some good link of stackoverflow as well. Joshua Nozzi's answer gave good explanation for retain vs copy.
Retain vs. Copy - Declared properties use retain by default (so you can simply omit it altogether) and will manage the object's reference count automatically whether another object is assigned to the property or it's set to nil; Use copy to automatically send the newly-assigned object a -copy message (which will create a copy of the passed object and assign that copy to the property instead - useful (even required) in some situations where the assigned object might be modified after being set as a property of some other object (which would mean that modification/mutation would apply to the property as well).
Also found good example here.
Code :
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:#"First",#"Second", nil];
NSMutableArray *copiedArray = [array mutableCopy];
NSMutableArray *retainedArray = [array retain];
[retainedArray addObject:#"Retained Third"];
[copiedArray addObject:#"Copied Third"];
NSLog(#"array = %#",array);
NSLog(#"Retained Array = %#",retainedArray);
NSLog(#"Copied Array = %#",copiedArray);
Output :
array = (
First,
Second,
"Retained Third"
)
2013-12-19 17:15:49.380 RetainVsCopy[2876:c07] Retained Array = (
First,
Second,
"Retained Third"
)
2013-12-19 17:15:49.381 RetainVsCopy[2876:c07] Copied Array = (
First,
Second,
"Copied Third"
)
See, both array and Retained Array are having same contents. This is because both are pointing to same memory/instance/object. Where as contents of Copied Array are different. This is because copy created a separate instance.
In Objective C you will find that each class actually has a structure behind it. The properties are shortcuts which create the value in structure, a getter and a setter. For instance:
#interface MyClass
#property id myValue;
#end
Will create:
#interface MyClass {
id _myValue;
}
#property id myValue;
#end
#implementation
- (id)myValue {
return _myValue;
}
- (void)setMyValue:(id)myValue {
_myValue = myValue;
}
#end
Now these flags such as retain and copy add additional logic to the setters and getters. Using copy will actually create a setter as:
- (void)setMyValue:(id)myValue {
_myValue = [myValue copy];
}
Which means that the value must have the copy method implemented. Since your object does not it crashes.
Why to use copy is for safety. This is rarely important for something as strings but it is important for something like an array. So for instance you create a property #property NSArray *myArray; which expects an un-mutable array but the problem is that you can set a mutable array as well: myClassInstance.myArray = [[NSMutableArray alloc] init];. Now 2 modules have the access to the same mutable array. So if the first object starts modifying the array while the other one expects the array to always be the same you may find some issues. For instance MyClass instance may use it as a data source for the table view and at some point the array is mutated but the cells are not added/removed and the table view will cause a crash.
To be honest you can simply leave all of these as default and modify them only when you really need to. The case like above is highly unlikely anyway.
i have defined variable in GroupView.h
#interface GroupView()
{
NSMutableArray *chatrooms;
}
#end
#implementation GroupView
Now i want to pass this variable in segue
#interface FriendsViewController ()
#end
#implementation FriendsViewController
else if ([segue.identifier isEqualToString:#"showGroupView"]) {
GroupView *groupView = (GroupView *)segue.destinationViewController;
groupView.chatrooms = [NSMutableArray arrayWithArray:chatrooms];
}
i know that chatrooms has to be property in header file to code this way but it is not
So is there any way to use this variable in segue.
Thanks for help.
chatrooms defined as an ivar like you have done is accessed using -> notation:
groupView->chatrooms = [NSMutableArray arrayWithArray:chatrooms]
This is generally discouraged, though. You should use a property instead:
#interface GroupView
#property (strong) NSMutableArray *chatrooms;
#end
Incidentally, if you're using an NSMutableArray, that indicates that you want to modify the element list of the array directly and not just replace the array wholesale. If you only ever want to replace the array with a whole new array every time, I suggest using NSArray instead.
Another point to make here is that you're attempting to cast the object held at segue.destinationViewController as a GroupView. You have either named a UIViewController subclass in a very misleading way, or you are not accessing the GroupView as a correct member of the UIViewController that is returned to you.
Normally, if you are not building the SDK or something. You don't really have a better reason not to expose it in the header file. However, you can expose the property in the extension and declare a private property in the host class(It's really not able to pass if you just declare a local variable). For example, You have a extension called GroupView+Helper. So, you can pass it into the property exposed in the extension. And then internally translate into the GroupView.
In GroupView.m:
#interface GroupView
#property (strong, nonatomic) NSMutableArray *chatrooms;
#end
In GroupView+Helper.h
#property (strong, nonatomic) NSMutableArray *internalChatrooms;
Also, you need to import the GroupView+Helper in the GroupView.
It will make your chatrooms private and internalChatrooms protected.
I notice that Apple has what seems to be duplicate variable names:
2 properties and two ivars. Why does Apple do this?
//.h file
#interface TypeSelectionViewController : UITableViewController {
#private
Recipe *recipe;
NSArray *recipeTypes;
}
#property (nonatomic, retain) Recipe *recipe;
#property (nonatomic, retain, readonly) NSArray *recipeTypes;
And they then update the recipe instance below. Why have two variable with the same name?
Will one affect the recipe variable of the parentViewController since that recipe variable was set when presenting this view controller the code was in from the parentViewController?
//.m file
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// If there was a previous selection, unset the accessory view for its cell.
NSManagedObject *currentType = recipe.type;
if (currentType != nil) {
NSInteger index = [recipeTypes indexOfObject:currentType];
NSIndexPath *selectionIndexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *checkedCell = [tableView cellForRowAtIndexPath:selectionIndexPath];
checkedCell.accessoryType = UITableViewCellAccessoryNone;
}
// Set the checkmark accessory for the selected row.
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryCheckmark];
// Update the type of the recipe instance
recipe.type = [recipeTypes objectAtIndex:indexPath.row];
// Deselect the row.
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
UPDATE 1
This code is from Apple's iPhoneCoreDataRecipes core data example:
First have a look at the RecipeViewController's didSelect delegate method, which will present the TypeSelectionViewController (child) view controller. Then have a look at that viewcontroller's didSelect delegate method where you will find the code implementation.
The reason I started looking at this is because I was interested how the parent's tableView cell got updated based on the selection in the ChildViewController in editing mode.
To see this for yourself, do the following:
Run the application
Select the Recipes tab
Click on a recipe - Chocolate Cake.
Click the edit button on the top right
Make note of the current category - should be on desert - then click on it.
Then you will be taken to the child view controller
Click on a different category, then click back and you will notice that the category button for that recipe has magically been updated. And I don't know how that's happening.
Does it have something to do with the private ivars and properties? which affects the parentViewController's cell?
My question i Guess is, how does selecting a category type in the child view controller's table affect the cell.text in the Parent View Controller's table? I can't see where the managedObjectcontext is saved in the child view controller for it to automatically update the parent View controller's cell text.
What you're seeing here is relatively old code, and there's not much need to do this anymore, thanks to Objective-C auto-synthesis.
Nowadays, when you issue a #property (nonatomic) NSArray *foo;, you implicitly get a #synthesize foo = _foo; in your implementation file and an instance variable declaration in your header. You don't see this, the compiler "inserts" it automatically. foo is the property and _foo is the instance variable. (In your above example, the #property and backing instance variable are both the same name, which could get confusing very quickly. With the foo property, you couldn't accidentally say self._foo, that doesn't exist. There's self.foo and _foo. With your example recipe is the ivar and self.recipe is the property. Very easy for one to quickly confuse the two when reading code.
Before the auto-synthesis, there was an intermediate step where you still needed a #synthesize, but you the backing instance variable was generated for you. These new features help you remove boilerplate code.
Answering Update 1
The code doing what you're wondering is in tableView:cellForRowAtIndexPath. There's nothing magical here. When you selected a new Category via the TypeSelectionViewController, the NSManagedObject is updated. Back in the RecipeDetailViewController, cellForRowAtIndexPath pulls the lasted information from CoreData. text = [recipe.type valueForKey:#"name"];
You might be getting confused about what an #property really is. It's just syntactic sugar. A #property these days automatically creates accessor and mutator methods and a backing ivar. Properties themselves aren't areas to store data, it's just a quick way of generating some methods and backing ivars.
Example
#interface MyClass
{
NSUInteger _foo;
}
#end
#implementation MyClass
- (NSUInteger)foo
{
return (_foo)
}
- (void)setFoo:(NSUInteger)newFoo
{
_foo = newFoo;
}
#end
is equivalent to:
#interface MyClass
#property (nonatomic, assign) NSUInteger foo;
#end
You save a lot of typing. When you get into things like NSString properties and different property modifiers like strong or copy, the amount of code you save (and memory management mistakes you avoid) in the mutators becomes much greater.
Your .h file should be your public api. You can re-declare your properties in your .m, implementation file, which are also considered private. For example
.h
#interface MyViewController : UITableViewController
#property (readonly) NSString *name;
#end
.m
#implementation MyViewController
#property (readwrite) NSString *name
#end
Here we are declaring a public name property that is readonly and in your implementation you 're re-declaring the property so that you can use the setter accessor.
I need to capture the result of NSDate when a button is pressed, so I can use it later on in the app.
What is the best way / code to do this?
Update
Sorry for not including more information. New to this site, and wasn't aware of protocol.
So I understand how to respond to a button press. However, whilst I have written the result of NSDate to the timeStampLabel, what I really want to do is save the result of NSDate at that point to a key? or similar in order that I can use it later. I require the data to be accessible to a different class, however, have no need for the data to persist if the app was closed entirely (i.e. swiped up and off the screen).
The code I currently have is:
- (IBAction)tripButton:(id)sender {
NSDate *date = [NSDate date];
self.timeStampLabel.text = [NSString stringWithFormat:#"%# ", date];
}
You can declare the variable in your App Delegate:
#interface AppDelegate : UIResponder <UIApplicationDelegate, UINavigationControllerDelegate> {
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UINavigationController *navigationController;
#property (nonatomic) NSDate* timeStamp;
#end
Then when you need to access the variable from any class, get an instance of the App Delegate...
First declare the variable right after the implementation in that class's .m:
#implementation ClassViewController
AppDelegate *mainDelegate;
then initialize it in viewDidLoad:
- (void)viewDidLoad {
mainDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
and now you can access it from anywhere. In this case...
- (IBAction)tripButton:(id)sender {
NSDate *date = [NSDate date];
self.timeStampLabel.text = [NSString stringWithFormat:#"%# ", date];
mainDelegate.timeStamp = date;
}
Then in another class, for example:
- (NSDate*)getTimeStamp {
return mainDelegate.timeStamp;
}
So, there are three methods (that I know of) to deal with passing information across different view controllers in iOS.
Lindsey Scott's answer suggests storing the information as properties on the App Delegate and reaching into the app delegate to set/retrieve the data.
Duncan C's answer explains my preferred method, using singletons.
The third method that I know of is passing the information directly between the views that need it. This is simple to manage if you just need to pass the data from one view that is presented immediately before the view you're passing the information to.
Assuming you're using segues, you'd do something like this...
Given ViewController1 and ViewController2, give ViewController2 an NSDate public property to store the date you're passing to it. We'll call it passedDate in this example. We'll also give ViewController1 a property (private, probably) to store the date temporarily, we'll call it dateToPass in this example. Both properties should be strong.
Now, in ViewController1, the IBAction that handles the button press will look like this:
-(IBAction)buttonPressed:(id)sender {
self.dateToPass = [NSDate date];
[self performSegueWithIdentifier:#"segueMySegue" sender: self];
}
So now the button press saves the date to a property and calls the segue. As a note, we don't have to do both actions in this single button press. The main thing that's important is saving the date on the button press to a property so you can access it later. You can perform the segue however/whenever you want.
Now, to actually pass the data, we're going to override the prepareForSegue: method:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"segueMySegue"]) {
ViewController2 *dest = [segue destinationViewController];
dest.passedDate = self.dateToPass;
}
}
Now we've set the property passedDate on the next view controller to point to the dateToPass date object we set on the button press. We can access this property in ViewController2 and do with it whatever we need to do.
You have 2 different issues:
How to save a date value for later
How to pass data between different objects in your app.
The first can be solved by making an NSDate or NSTimeInterval property.
The second can be solved various ways. I recommend creating a data container singleton. A singleton is an object that is designed to only be created once in the lifetime of a program and then used throughout. Typically you create a class method called something like sharedDataObject that creates the object the first time you need it, and saves it in a static variable:
header:
#interface DataContainerObject: NSObject
#property (nonatomic, strong) NSDate *dateToSave;
+ (DataContainerObject *) sharedDataObject;
#end
Implementation:
#implementation DataContainerOBject:
+ (DataContainerObject *) sharedDataObject;
{
static DataContainerObject* _sharedDataObject;
if (_sharedDataObject == nil)
_sharedDataObject = [[DataContainerObject alloc] init];
return _sharedDataObject;
}
I am making an app using a utility application template. I am trying to access the value of a UITextField from the FlipSideVewController class.
In the MainViewController.h file I have -
#interface MainViewController : UIViewController <UISplitViewControllerDelegate>{
UITextField *textField;
NSString *myText;
}
#property (retain, nonatomic) IBOutlet UITextField *textField;
#property (nonatomic, retain) NSString *myText;
-(IBAction)pressButton:(id)sender;
In the MainViewController.m file -
myText = [[NSString alloc] initWithFormat: textField.text];
NSLog(#"%#",myText);
I am creating the FlipSideViewController in the MainViewController class using the following code -
FlipsideViewController *controller = [[[FlipsideViewController alloc] initWithNibName:#"FlipsideViewController" bundle:nil] autorelease];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
This prints the value of the textfield in the console without any problems. The problem happens when I try to access the value of the textfield in the FlipSideVewController class (after the user presses the go button).
In the FlipViewController class I have -
MainViewController *obj = [[MainViewController alloc] init ];
NSString *abc = obj.textField.text;
NSLog(#"%#",abc);
The FlipSideVewController nib file is loaded fine without any problems. However the console output is (null) when in FlipSideVewController.
I will appreciate any help.
Thanks
If you use the Utility Xcode template, you should think of MainViewController and FlipSideVewController as given: the framework will instantiate them for you and make it available to the rest of the program as you define (either in your code or in IB). What I mean by this is that your line:
MainViewController *obj = [[MainViewController alloc] init ];
does not really do what you want.
In your case, what you seems to want is access a text field controlled by the existing MainViewController instance (the one that gives you the NSLog output correctly) from your other existing FlipSideVewController instance. This is a matter of "connectin" somehow those two controllers.
There are several ways to accomplish this. One is defining a "model" for your app and have it shared between the controllers. See also the Model-View-Controller pattern. A model is just a data structure that contains your "data"; you make that data structure available to both of your controllers. The easiest way to create such data structure and have it shared is through a singleton (I am not suggesting to use it as the best way, just noting that it is the easiest way, IMO).
Another, less clean way is passing a reference to MainViewController to FlipSideVewController and then accessing the text field through it. By example, you could define an ivar in your FlipSideVewController, then, where the two controllers are created, you do the assignment to the ivar.
You should go to your MainViewController and declare your textField as a property first and synthesize it, so you can access it using obj.textField. And if you have just created obj using alloc and init, you wont have any text in the textField instance Variable.
MainViewController.h
#property (retain) UITextField *textField;
MainViewController.m
#synthesize textField;
and you could use
myText=textField.text;
Now this should do it and you can access this textField by obj.textField in your other class. But you still wont get its value if you are initializing it in your other class because you will be creating a brand new obj whose textField.text will be blank( unless you have overrided its designated initializer to set the textField.text value).
Declare NSString *abc as instance variable
NSString *abc;
and then as property
#property (copy) NSString *abc;
#synthesize abc;
After you create your FlipSideViewController,
controller.abc=myText;
Remove the code where you create obj.
This will do it.