I have a multi-sectioin UITableView with different kinds of controls throughout various rows (multi-select checkboxes, single-select checkboxes, text inputs, text areas etc.). Each row could have a different data type (string, integer, date etc) and the number of rows and location are dynamic so you can't always depend on section X row Y being a certain control.
My question is what is the best way to save the data input into these fields for use in the view, grabbing the right data to show what was entered into that field when calling cellForRowAtIndexPath.
Note that I am NOT asking how to save this data persistently, I'm using CoreData for that, the question is just how to temporarily save the data while interacting with the view, so that you have it in an NSMutableArray or NSMutableDictionary ready to be saved with CoreData when the user touches the "Save" button, or completely discarded if they press "Cancel".
Currently I'm trying to implement a dictionary but it seems somewhat kludgy and I often get one row's data showing up in another row.
Here is my current method for saving the form data. It's using a name from the arguments along with a counter variable used for the view as a whole. The counter variable is also used as the tag integer for the control.
-(id)documentField:(UIView *)view withKey:(NSString *)key andValue:(id)value{
NSInteger foundTag = -1;
NSLog(#"searching dictionary for key: %#", key);
for(NSString *existingKey in fieldValues){
NSArray *keyParts = [existingKey componentsSeparatedByString:#"~"];
if( [[keyParts objectAtIndex:0] isEqualToString:key] )
{
foundTag = [[keyParts objectAtIndex:1] intValue];
NSLog(#"found key: %#, it's tag is: %d", [keyParts objectAtIndex:0], foundTag);
break;
}//end if
else{
//NSLog(#"no match: %# != %#", (NSString *)[keyParts objectAtIndex:0], key);
}
}//end for
//if we haven't tagged this element yet
//set the tag
if (foundTag == -1) {
view.tag = fieldValueCounter;
foundTag = fieldValueCounter;
fieldValueCounter++;
}//end if
NSString *fieldKey = [NSString stringWithFormat:#"%#~%d", key, foundTag];
if( ! [fieldValues objectForKey:fieldKey] ){
[fieldValues setObject:((value)? value : #"") forKey:fieldKey];
}
NSLog(#"returning fieldValue: %# = %#", fieldKey, [fieldValues objectForKey:fieldKey]);
return [fieldValues objectForKey:fieldKey];
}//end documentField:withKey:andValue:
And here is how it is being used.
((UTVCellTextField *)cell).textLabel.text = #"Door Location:";
((UTVCellTextField *)cell).textField.text = [self documentField:((UTVCellTextField *)cell).textField withKey:#"door.door_location" andValue:door.door_location];
((UTVCellTextField *)cell).textField.delegate = self;
Related
I am having an issue sending dictated text to another interface controller.
Here is my code:
- (IBAction)voiceRecognition {
[self presentTextInputControllerWithSuggestions:nil allowedInputMode:WKTextInputModePlain completion:^(NSArray *results) {
NSLog(#"results: %#", results);
NSString *wordKey = [NSString stringWithFormat:#"%#",results];
NSDictionary *dict = #{#"kWord":wordKey};
[self pushControllerWithName:#"Dictionary" context:dict];
}];
}
Logs:
Watch Extension[3185:2835671] results: ( Hello )
Getting data from other Interface controller:
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
NSDictionary *dict = (NSDictionary *)context;
[_word setText:dict[#"kWord"]];
NSLog(#"The Word is %#",[dict description]);
}
Logs:
Watch Extension[3185:2835671] The Word is {
kWord = "(\n Hello\n)";
}
Here is a screen shot that shows my problem:
The ( is supposed to show the word Hello. How can I fix this issue?
You used stringWithFormat to format an array as a string.
This took ["Hello"] and correctly converted it to the literal "(\n Hello\n)"
Because that string has a newline, it can't be displayed on a single line. Your Storyboard WKInterfaceLabel number of lines is likely set to 1, so it would only show the first line, which is (.
How can you fix this?
If you're only interested in the first word, use results.firstObject and pass that single word as the string value for your kWord key.
NSDictionary *dict = #{#"kWord": results.firstObject};
Otherwise, pass the entire array as the value, and have the destination interface controller handle the array of results as needed.
NSDictionary *dict = #{#"kWord": results};
You also may want to change the number of lines to show the entire dictation text, to handle the case where the text wouldn't fit on a single line.
Other options:
If you actually intended to send the dictated text as a single string of words, you can use
NSString *wordKey = [results componentsJoinedByString:#" "]
I have two UISlider's that represent minimum and maximum prices of items. I am passing these as well as other various data back to the previous controller.
I've used a protocol method and set the previous controller as a delegate to make it possible to pass values back to the controller.
I can easily grab the other objects out of the array because they're strings. I just use:
[_finalSelectionForRefinement containsObject:#"size"];
This is what I do in pushed controller:
// create dictionary with keys minimum and maximum that hold the
// position of the slider as a float
_dictionaryWithSliderValues =
[[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithFloat:_minSliderPosition], #"minimum",
[NSNumber numberWithFloat:_maxSliderPosition], #"maximum", nil];
// store this in the array that is retrieved in previous controller
[_finalSelectionForRefinement addObject:_dictionaryWithSliderValues];
My question is how do I now use the minimum and maximum keys to grab the slider position float objects?
Thought I could use NSPredicate but the examples I've been coming across on blogs as well as youTube are of no help to my specific needs.
Would appreciate some help here
Regards
UPDATE - Short snipped of method in previous controller where I need to retrieve the slider minimum and maximum values:
-(PFQuery *)queryForCollection
{
PFQuery *query = [PFQuery queryWithClassName:#"Garments"];
if (_selectedRowInFilterPicker == 0) {
NSLog(#"ORDER BY RECOMMENDED");
[query orderByDescending:#"recommended"];
} else if (_selectedRowInFilterPicker == 1) {
NSLog(#"ORDER BY NEWEST ITEMS");
[query orderByDescending:#"createdAt"];
} else if (_selectedRowInFilterPicker == 2) {
NSLog(#"ORDER BY DESCENDING USING PRICE");
[query orderByDescending:#"price"];
} else if (_selectedRowInFilterPicker == 3) {
NSLog(#"ORDER BY ASCENDING USING PRICE");
[query orderByAscending:#"price"];
}
if ([_selectionFromRefineResultsController count] > 0) {
NSLog(#"Selection FROM REF MADE");
// Gender
if ([_selectionFromRefineResultsController containsObject:#"Male"]) {
[query whereKey:#"gender" equalTo:#1];
}
if ([_selectionFromRefineResultsController containsObject:#"Female"]) {
[query whereKey:#"gender" equalTo:#2];
}
}
// Here I need to check there is a minimum or maximum value in the array
// If there is I can user [query whereKey:#"price" greaterThan:MINVAL] and MAXVAL
// This will return items within the correct price range.
This queryForCollection method is called from within another method called performQuery which is called when the button of the second controller is tapped to pass data back to the controller that pushed it in the first place.
You should look at the documentation for NSMutableArray , NSArray and NSDictionary
Which will explain the instance methods for each.
But in a nutshell any object that you add should be in a NSDictionary so it has a value and a key. This includes any of your strings. Doing so simplifies how you search by using keys.
If the NSMutableArray contains objects that are not KVC then you will I think find it harder it go through the objects in one sweep.
Because NSmutableArray inherites from NSArray you can then use instance method valueForKey: on a NSmutableArray whose objects values or objects objects values have keys.
valueForKey:
Returns an array containing the results of invoking
valueForKey: using key on each of the array's objects.
(id)valueForKey:(NSString *)key
Rough Example:
NSMutableArray * finalSelectionForRefinement =[[NSMutableArray alloc]init];
NSDictionary *dictionaryWithSliderValues = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithFloat:10], #"minimum", [NSNumber numberWithFloat:20], #"maximum", nil];
NSDictionary *stringValues = [[NSDictionary alloc] initWithObjectsAndKeys:#"the-size", #"size", #"the-hight", #"hight", nil];
[finalSelectionForRefinement addObject:dictionaryWithSliderValues];
[finalSelectionForRefinement addObject:stringValues];
NSLog(#"finalSelectionForRefinement = %#", [finalSelectionForRefinement valueForKey:#"maximum"] );
First off, you can of store everything in one NSDictionary which makes more sense. But I wanted to show you that the valueForKey: will search within each.
The other thing is valueForKey: will return an NSArray containing the results. any objects that it finds that do not match the key you are looking for will be returned as an NSNull object. i.e
finalSelectionForRefinement = (
20,
"<null>"
)
So you would need to still single your value out. One way is use a objectEnumerator like this:
NSEnumerator *enumerator = [[finalSelectionForRefinement valueForKey:#"maximum"] objectEnumerator];
id anObject;
while (anObject = [enumerator nextObject]) {
if(![anObject isKindOfClass:[NSNull class]])
{
NSLog(#"anObject = %#", anObject);
}
}
Which should return:
anObject = 20
There are most likely better ways of doing this. All of the above is just to give you one idea. And I suspect you could cut out a lot of the code by using bindings.
(also note this answer was being constructed before you question update)
I have a form that once a user submits, it should reload the view (but change a few things).
On this form there is a UIPickerview (_tripPicker) that has 2 locations a starting location and an end location (2 components to the pickerview).
I have it saving to the appropriate database and all that - but when it reloads (when the user clicks save) I want the pickerview to 'reset', and the first location (begSchool), to match the users second location (endSchool) that they just saved to coredata.
For example: Lets say user went from PointB to pointC (component 0 & 1 respectively in the pickerview); when they click save, I would like component 0 to display "PointC" and for the second component to just display the list of points to go to (resetting it to how it originally loads).
I've attempted to try and do some of this matching strings, but I'm running into issues with it.
Here is the logic where I do this:
//Save the data & reload View
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
//Reset the pickerview **********THIS LOGIC NEEDS WORK**********
//Check to see if there is previous UserMiles entered - if so, set beg_school to appropriate name
// Get the local context
NSArray *tripsSorted = [UserMiles MR_findAllSortedBy:#"driven_date" ascending:NO];
if (!tripsSorted || !tripsSorted.count){
//if no previous trips have been entered - set the school list to default
begSchoolLabel.text = [_schoolArray1 objectAtIndex:[_tripPicker selectedRowInComponent:0]];
} else {
UserMiles *lastTrip = [tripsSorted objectAtIndex:0];
NSString *preValue = lastTrip.end_school;
begSchoolLabel.text = preValue;
NSUInteger *currentIndex = [_schoolArray1 indexOfObject:preValue]; //Error/warning that incompatible integer to point conversion
[_tripPicker selectRow:currentIndex inComponent:0 animated:YES]; //Error/warning here that incompatible point to integer conversion
NSLog(#"LastTrip.endSchool = %#", lastTrip.end_school);
}
//Set end school labels for the second component in the picker
endSchoolLabel.text = [_schoolArray2 objectAtIndex:[_tripPicker selectedRowInComponent:1]];
NSLog (#"saveInBackground: finished!");
}];
[self.view setNeedsDisplay];
lastTrip.end_school gets me the appropriate name of the school from component 1 of the pickerview, I just need to figure out how to match that with the appropriate value from the array that is loading the pickerview and make it selected in the pickerview. The text currently displays the appropriate name, but the pickerview does not show any change.
Please let me know if I need to clarify or what other code you need to see - thanks in advance.
I was able to solve this by first finding the indexValue in the array by matching the string against the Objects in the array.
I ended up with this:
//Save the data & reload View
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
//Reset the pickerview **********THIS LOGIC NEEDS WORK**********
//Check to see if there is previous UserMiles entered - if so, set beg_school to appropriate name
// Get the local context
NSArray *tripsSorted = [UserMiles MR_findAllSortedBy:#"driven_date" ascending:NO];
if (!tripsSorted || !tripsSorted.count){
//if no previous trips have been entered - set the school list to default
begSchoolLabel.text = [_schoolArray1 objectAtIndex:[_tripPicker selectedRowInComponent:0]];
} else {
UserMiles *lastTrip = [tripsSorted lastObject];
NSString *preValue = lastTrip.end_school;
begSchoolLabel.text = preValue;
int indexValue = [_schoolArray1 indexOfObject:preValue]; //Compares the preValue string to the strings in the array and finds the indexValue of the right match
[_tripPicker selectRow:indexValue inComponent:0 animated:YES]; //Sets the picker to the appropriate value
NSLog(#"LastTrip.endSchool = %#", lastTrip.end_school);
}
//Set end school labels for the second component in the picker
endSchoolLabel.text = [_schoolArray2 objectAtIndex:[_tripPicker selectedRowInComponent:1]];
NSLog (#"saveInBackground: finished!");
}];
[self.view setNeedsDisplay];
I am working on a simple comparison of two lists to see which items in an "evaluation" list are contained in a larger "target" list. I am getting the data on-the-fly- by parsing two CSV files and storing everything as strings. I successfully import the data into the data store and I can get a list of entities no problem
The problem comes when I actually do a search. Essentially, I am looking for short ISBNs in the form of 1234 from the evaluation list in the target list, which are in the form of 1234-5. The predicate I am using is I am using the CONTAINS comparison in the form of [NSString stringWithFormat:#"%# CONTAINS %#", kOC_Target_PrintBookCode, evalIsbn]
The error I get is the following (grabbed by my NSLog)
NSInvalidArgumentException: Can't look for value (1494) in string (49885); value is not a string
I get the impression that even though the ISBN is being read from a NSString and the Core Data store has the data point spec'd as a String, that Core Data is still doing something in the background with the value for whatever reason it sees fit. Any ideas?
Here is the relevant process logic (though I use that term dubiously) code. Unless otherwise noted in the code, all values being manipulated and/or stored are NSString:
NSArray *evalBooks = [self getEntitiesByName:kOC_EntityName_EvalBook
usingPredicateValue:[NSString stringWithFormat:#"%# > \"\"", kOC_Eval_Bookcode]
withSubstitutionVariables:nil
inModel:[self managedObjectModel]
andContext:[self managedObjectContext]
sortByAttribute:nil];
if ( ( !evalBooks ) || ( [evalBooks count] == 0 ) ) {
// we have problem
NSLog(#"( !evalBooks ) || ( [evalBooks count] == 0 )");
return;
}
[evalBooks retain];
int firstEvalBook = 0;
int thisEvalBook = firstEvalBook;
int lastEvalBook = [evalBooks count]; NSLog(#"lastEvalBook: %i", lastEvalBook);
for (thisEvalBook = firstEvalBook; thisEvalBook < lastEvalBook; thisEvalBook++) {
NSManagedObject *evalBook = [[evalBooks objectAtIndex:thisEvalBook] retain];
NSString *rawIsbn = [[evalBook valueForKey:kOC_Eval_Bookcode] retain];
NSString *isbnRoot = [[self getIsbnRootFromIsbn:rawIsbn] retain];
// this is a custom method I created and use elsewhere without any issues.
NSArray *foundBooks = [self getEntitiesByName:kOC_EntityName_TargetBook
usingPredicateValue:[NSString stringWithFormat:#"%# CONTAINS %#", kOC_Target_PrintBookCode, isbnRoot]
withSubstitutionVariables:nil
inModel:[self managedObjectModel]
andContext:[self managedObjectContext]
sortByAttribute:kOC_Target_PrintBookCode];
if ( foundBooks != nil ) {
[foundBooks retain];
NSLog(#"foundBooks: %lu", [foundBooks count]);
} else {
}
If you're building your predicate as an NSString, I believe
[NSString stringWithFormat:#"%# CONTAINS %#", kOC_Target_PrintBookCode, isbnRoot]
should actually be
[NSString stringWithFormat:#"%# CONTAINS '%#'", kOC_Target_PrintBookCode, isbnRoot]
It seems that you're confusing the way predicateWithFormat: works with the way stringWithFormat: works.
Presumably either kOC_Target_PrintBookCode or isbnRoot is not an object that can be converted to a string. E.g. if either is an integer, the %# operator cannot convert the integer to a string value.
I'm working on a roguelike using Objective-C/Cocoa to learn more. I've gotten most of the basic functionality out of the way, but I still have one problem I've been trying to figure out.
Here's a breakdown of the process:
First, the map is loaded:
NSString* mapPath = [[NSBundle mainBundle] pathForResource:mapFileName ofType:mapFileType];
NSURL* mapURL = [NSURL fileURLWithPath: mapPath];
currentMap_ = [[Map alloc] initWithContentsOfURL: mapURL];
worldArray = [[NSMutableArray alloc] init];
itemArray = [[NSMutableArray alloc] init];
[self populateMap];
return;
Then, in the populateMap function, it goes through each cell of the loaded map, using NSPoints and a loop, and creates objects based on the data from the map in WorldArray. For items, normal floor is put in where the item is, and an item is then made in itemArray. Both arrays are 30x30, as determined by the height of the map.
Here is the populateMap code:
- (void)populateMap
{
NSPoint location;
for ( location.y = 0; location.y < [currentMap_ height]; location.y++ )
{
for ( location.x = 0; location.x < [currentMap_ width]; location.x++ )
{
char mapData = [currentMap_ dataAtLocation: location];
for ( GameObject *thisObject in worldDictionary )
{
//NSLog(#"char: <%c>", [thisObject single]);
if ( mapData == [thisObject single])
{
NSString* world = [thisObject className];
//NSLog(#"(%#) object created",thisObject);
[self spawnObject:world atLocation:location];
}
}
for ( Item *thisObject in itemDictionary )
{
//NSLog(#"char: <%c>", [thisObject single]);
if ( mapData == [thisObject single] )
{
NSString* item = [thisObject className];
NSString* floor = [NormalFloor className];
//NSLog(#"(%#) object created",thisObject);
[self spawnItem:item atLocation:location];
[self spawnObject:floor atLocation:location];
}
}
if ( mapData == '1'
&& [player_ stepsTaken] <= 0)
{
//NSLog(#"player spawned at (%f, %f)",location.x,location.y);
player_ = [[Player alloc] initAtLocation: location];
}
if ( mapData == '1' )
{
//NSLog(#"floor created at (%f, %f)",location.x,location.y);
[worldArray addObject:[[NormalFloor alloc] initAtLocation: location]];
}
}
}
[self setNeedsDisplay:YES];
}
This is what is called when things are spawned:
- (void)spawnObject: (NSString*) object atLocation: (NSPoint) location
{
//NSLog(#"(%#) object created",thisObject);
[worldArray addObject:[[NSClassFromString(object) alloc] initAtLocation: location]];
}
- (void)spawnItem: (NSString*) item atLocation: (NSPoint) location
{
//NSLog(#"(%#) object created",thisObject);
[itemArray addObject:[[NSClassFromString(item) alloc] initAtLocation: location]];
}
worldArray and itemArray are what the game works on from that moment onwards, including the drawing. The player is inside of worldArray as well. I'm considering splitting the player into another array of characterArray, to make it easier when I add things like monsters in the not so distant future.
Now, when I load a new level, I had first considered methods like saving them to data and loading them later, or some sort of savestate function. Then I came to the realization that I would need to be able to get to everything at the same time, because things can still happen outside of the player's current scope, including being chased by monsters for multiple floors, and random teleports. So basically, I need to figure out a good way to store worldArray and itemArray in a way that I will be able to have levels of them, starting from 0 and going onward. I do need a savestate function, but there's no point touching that until I have this done, as you shouldn't actually be allowed to save your game in roguelikes.
So to reiterate, I need to have one set of these arrays per level, and I need to store them in a way that is easy for me to use. A system of numbers going from 0-upward are fine, but if I could use something more descriptive like a map name, that would be much better in the long run.
I've figured out my problem, I'm using an NSMutableDictionary for each and storing them with the keys that correspond to each level. Works like a charm. Bigger problems elsewhere now.
I figured it out, I'm using NSMutableDictionaries, one for each array (objects, items, eventually characters). They're stored using the name of the level. Works like a charm.