Accessing object from array within an array (defined in model class) - ios

I suspect the solution to what I'm trying to do is fairly straight forward--yet, I'm unable to get it working myself.
Here's what I'm trying to do: I've built a card game that is initialized with the cards when the game loads. Now, some of those cards have certain properties (card type, card name, as well as a special array). The card type and card name objects are fairly easy to retrieve since they're just one object (and I can call using objectatindex). However, the special array contains several keywords that fluctuate depending on which card is chosen. So instead of initializing these keywords one by one (like I did for card type and card name), I put them into their own special array.. or an array within an array. Here's my code:
itemObjects class:
#synthesize cardName=_cardName;
#synthesize cardType=_cardType;
-(id) initWithCardName:(NSString*)cardName initWithCardType:(NSString*)cardType initWithSpecialArray:(NSArray*) specialArray{
self=[super init];
if (self){
_cardName=cardName;
_cardType=cardType;
}
return self;
}
model class
-(NSMutableArray*)deck{
if (_deck==nil){
_deck=[[NSMutableArray alloc]initWithObjects:
[[itemObjects alloc]initWithCardName:#"The Long Way" initWithCardType:#"bill" initWithSpecialArray:[[NSArray alloc]initWithObjects:#"fast", #"high", nil]],
[[itemObjects alloc]initWithCardName:#"A Short Cut" initWithCardType:#"bill" initWithTrendArray:[[NSArray alloc]initWithObjects:#"small", #"tall", nil]],nil];
View Controller class (this is where I'm trying to call one of the objects, "fast" for example, but with no success
NSString* testing=[[[self.model.deck objectAtIndex:indexPath.row]arrayForKey:#"specialArray"]objectAtIndex:0];
NSLog(#"%#",testing);
I believe I've initialized my "specialArray" correctly and the issue is with how I'm attempting to call it but if I've made a mistake there, any advice would be much appreciated. Thanks!
EDIT: This particular issue has been solved thanks to WendiKidd. It turned out that I wasn't initializing my specialArray correctly. This has led to a separate issue which I have linked to here. I've also posted my corrected code below for those interested in the future:
#synthesize cardName=_cardName;
#synthesize cardType=_cardType;
#synthesize specialArray=_specialArray;
-(id) initWithCardName:(NSString*)cardName initWithCardType:(NSString*)cardType initWithSpecialArray:(NSArray*) specialArray{
self=[super init];
if (self){
_cardName=cardName;
_cardType=cardType;
_specialArray=specialArray;
}
return self;
}

You aren't initializing your special array at all. You pass it to initWithCardName, but you're never setting it to anything. You need to store the special array inside that class, just like you do with cardName and cardType.
Secondly, I can't make heads or tails of the line where you're trying to access the special array. You haven't given us the proper information to see where the object is being stored to tell if you're even properly accessing a card object, but I definitely don't see where you've ever used the key #"specialArray" before, so there's no reason to expect that will return anything. However the rest of your data structure works, at some point you're going to have an object of whatever class the initWithCardName function initializes (for example purposes I'm going to go ahead and call it CardObject). To get the info from the special array, you're going to have to save an object called specialArray into the CardObject class, as already mentioned. Then you can write something like this:
CardObject* card = //[whatever you have to do to access the right card object]
for(NSString* specialAttribute in card.specialArray)
{
NSLog(#"Special Attribute: %#", specialAttribute);
}
And that should print all the special attributes of the card quite nicely.
If you really do just want the first item in the list, as your example was trying to access, this should work just fine:
CardObject* card = //[whatever you have to do to access the right card object]
NSLog(#"Special Attribute: %#", [card.specialArray objectAtIndex:0]);

Related

Managing an enum within a PFObject - iOS Swift/Parse

I'm new to Parse and Swift.
I have an app where people play against each other.
I want to create an activity feed where game results, cheers, heckles (anything really) can show up in a list.
My thought is to create an Activity class that subclasses PFObject and I'd like to have an enum ActivityType to determine what kind of Activity is being created.
Can I set up the Activity object in Parse and the PFObject in Swift so that each Activity is set up with the correct ActivityType?
My thinking is that I need a "Type" column in Parse that's just a number and an init method that reads that number and sets the correct type.
Does that sound about right?
Thanks
You could use a type column in parse, though I'd expect each of your different kinds of feed item to be different classes in parse as they all have different data and relationships, so you could use the class type (name).
In either case this is just a way to identify the type coming from the server. Once you have that you want an organised and common approach to displaying the feed items. To do that you should have a protocol which defines what a feed item needs to provide in order to be displayed on the feed. Then you have a set of classes, each conforming to that protocol, and each dealing with one of the different types of feed item to 'mutate' them into the common format for display.
Using an enum in your app would work, but it could lead you to have one big switch statement dealing with everything. So long as you just use the enum and switch to deal with deciding which class to create to handle the feed item then your code should be well structured.
I've solved this problem. It was 'free' functionality from Parse. I think first of all, you should consider subclassing PFObject (for many reasons). Once you do this, all you have to do is add the enum as a property to your subclass. It's taken care of automatically by Parse.
Parse knows how to convert to NSNumber and vice versa for an enum, no need to worry about that.
Note, in your .m file:
#implementation MyParseObjectSubclass
#dynamic aPropertyIWantPersisted; // declare your properties as dynamic to be managed by Parse
#synthesize aLocalTransientProperty; // if you have transient properties that you don't want persisted to the server.
+ (void)load
{
[self registerSubclass];
}
+ (NSString*)parseClassName
{
return "MySubclass";
}
#end

Retrieve text from array

I am working on an app where I send a query to a webpage and get the information and save it into my array. Now, all this works except for then I come to take the value from my array and display it in my text view. using this code
-(void)viewDidAppear:(BOOL)animated
{
self.tvSoilN.text = [[_soilNotes objectAtIndex:0] objectForKey:#"crop"];
}
However, I get the error of the array being empty.
Now that makes sense because using a breakpoint I can see that the code above actually runs before the code be in my model.m file which I use to write the data in the array
I cant post images but when I use a break point on the if(self.delegate) line It shows me
_locations (and when i drill down i see)
_crop_soil(this is the field in my database)=#"Blah blah blah")
which makes me realize that my data is indeed being retreived from online. But i am just having difficulty in displaying it from my array or anywhere.
Would like some help in especially where to call my array as I also think i am doing it wrong.Thanks you
IN Implementation file
-(void)itemsDownloaded:(NSArray *)items
{
// This delegate method will get called when the items are finished downloading
// Set the downloaded items to the array
_soilN = items;
NSLog(#"soil%#",_soilNotes);
self.tvSoilN.text=[[_soil objectAtIndex:0]objectForKey:#"crop"];
}
Use valueForKey not objectForKey. Got there in the end!

Structuring a basic app iOS app - single view - best practice

I am currently working on a single view app that does the following:
Grabs users location using CoreLocation
Sends their lon/lat to an API
Returns tide data as JSON
Dive in to objects and keys within the JSON to return: tide levels, locations, etc and display them via text labels.
I have the majority of this working, but it is more so crammed in to viewDidLoad - I am trying to see the best way to organize something like this up. Would I break it in to different methods such as:
setTideData
displayTideData
Or would it be more broken down than that? (And yes im sure it all depends on the details too). I would be displaying probably 8-10 different stats on the view.
Things like
On viewDidLoad do I grab the users location or do it prior on viewDidAppear, etc
Would I then call my displayTideData method inside of viewDidLoad
Just looking for some type of general best practice, was trying to scan for some items but nothing was what I was looking for.
EDIT: Here is an example of my returned data -- https://gist.github.com/ryancoughlin/8043604
Any ideas?
Thanks
This is pretty much a matter of preference and style, but you could simplify your logic in a number of ways by separating certain parts into their own classes, eg:
You could use something like JSON model to deserialize the data into a class, and access it in a more convenient way
Loading the data and deserializing could be handled in a different class that would only return the object with the data you need
Etc.
It's hard to give a better advice without reviewing your code directly, but this should give you some pointers how to simplify things.
EDIT: A bit of code example how to separate the the deserialization and data handling into a custom object:
- (id)initWithJSON:(NSDictionary *)JSON
{
self = [super init];
if (self) {
self.gender = [JSON objectForKey:#"gender"];
self.email = [JSON objectForKey:#"email"];
self.firstName = [JSON objectForKey:#"first_name"];
self.lastName = [JSON objectForKey:#"last_name"];
self.personId = [[JSON objectForKey:#"person_id"] integerValue];
}
return self;
}
You would then simply create an object with the data like this:
Person *person = [[Person alloc] initWithJSON:JSON];
and simply access whatever data you need in a cleaner way:
self.myLabel.text = [NSString stringWithFormat:#"Name: %#", person.firstName];
It sounds like you have a Massive View Controller, which is a bad thing. Try to reorganise your code so you have a fat model and a skinny controller. Controllers should only be responsible for creating and refreshing the view, bootstrapping the model and updating the model based on user interaction. Some possible model objects:
Fetching data from the server.
Parser for converting JSON objects into typed objects (thus encapsulating the knowledge of the JSON structure).
A simple interface (e.g. one object) for your view controller to pull data from and push data to.
Your model should contain all the business logic for the app.

Is it possible to assign a new value to a returned pointer?

I have a form that I'm creating and to simplify things, I'm trying to create a form field mapper to an object. As such, I create the following dictionary:
self.fieldPropertyMapper = #{
#(CompanyFieldName):self.company,
#(CompanyFieldDescription):self.company.description,
#(CompanyFieldWebsite):self.company.website,
#(CompanyFieldTwitter):self.company.twitter,
#(CompanyFieldAddress):self.company.address,
#(CompanyFieldAddress2):self.company.address2,
#(CompanyFieldCity):self.company.city,
#(CompanyFieldState):self.company.state,
#(CompanyFieldZipcode):self.company.zipcode,
#(CompanyFieldPhone):self.company.phone
};
The keys here are members of the CompanyFieldType enum.
My goal here is to later in my form to assign a value to the returned pointer. Here's what I mean: when a text field in one of my forms stops editing, I'm looking to set the value. Here's what I'd like to accomplish:
- (void)textFieldDidEndEditing:(UITextField *)textField
{
CompanyFieldType fieldType = [self fieldTypeForTag:textField.tag];
// Set the value of the respective company property
// In theory it would be something like:
// self.fieldPropertyMapper[#(fieldType)] = textField.text;
}
I'm assuming there's a way to assign by reference but I'm forgetting how to do this. (Is it using the & symbol or **?) I don't remember. Help appreciated! If I'm messing up my terminology, feel free to let me know.
You can't do exactly what you want to do. That is to say, there is no pointer magic that will do what you want.
You can get essentially the same effect, though, with key-value coding. Instead of storing the result of accessing the property (e.g. self.company.website), instead you want to just store the key path to the value you're interested in as a string — e.g. #"company.website". Then you can do like so:
[self setValue:textField.text forKey:self.fieldPropertyMapper[textField.tag]];
Using NSMapTable initialized with NSPointerFunctionsStrongMemory for the keys and NSPointerFunctionsOpaqueMemory for the values. Then you could store the addresses of your iVars backing your properties as the values in the table.
[self.mapTable setObject:&_company forKey:#(CompanyFieldName)];
Haven't tested this but this should get you started.

NSArray containsObject method inconsistent results

I have an NSMutableArray called selectedUsers to which I am adding objects using a method called addUser. The objects being added are most often of type PLManagedUser (a core data managed object) although sometimes the object could be a string. In any case, if the array already contains the object, I do not want to add it to the array. Here is the code:
- (void)addUser:(id)user withTitle:(NSString *)title {
if (![_selectedUsers containsObject:user]) {
[_selectedUsers addObject:user];
}
}
I have noticed that if I try to add the same user back to back using the above method, the containsObject catches it, and duplicates are not added. However, if I add the same user (with the same memory address) after having added other objects in between, the duplicate will be added.
I am printing the contents of the array each time I add something to confirm that the duplicate objects are in the array.
My question is, is there any obvious reason why containsObject isn't consistently working here?
You need to provide the ability for an object to identify itself as equal to another object of the same class, and to do this you implement the isEqual: and hash methods.
Having said that, the explanation in your question is the opposite of what I would have expected.

Resources