Key-Value Coding Get Element in List - ios

I have two objects:
#interface AObject : NSObject
#property NSArray *bObjects;
#end
#interface BObject : NSObject
#property NSString *name;
#end
Using key-value coding on an instance of AObject, I can get the list of bObjects (#"self.bObjects"), and a list of bObjects' names (#"self.bObjects.name").
However, what I want is the name of only the first of bObjects. My gut is that key-value coding should support list subscripting, like this: #"bObjects[0].name".
But that doesn't seem to exist. How do I get a single entity; the name of an AObject's first BObject, using key-value coding?
Footnote: I realized in my last question I was stupidly conflating NSPredicate and KV-coding.

As Martin R mentioned in the comments, currently the best option would be to create a firstBObject property in the AObject class.
AObject.h/m
#class BObject;
#interface AObject : NSObject
+ (AObject*)aObjectWithBObjects:(NSArray*)bObjects;
#property NSArray *bObjects;
#property (nonatomic, readonly) BObject *firstBObject;
#end
#implementation AObject
+ (AObject*)aObjectWithBObjects:(NSArray*)bObjects
{
AObject *ao = [[self alloc] init];
ao.bObjects = bObjects;
return ao;
}
- (BObject*)firstBObject
{
return [self.bObjects count] > 0 ? [self.bObjects objectAtIndex:0] : nil;
}
#end
BObject.h/m
#interface BObject : NSObject
+ (BObject*)bObjectWithName:(NSString*)name;
#property NSString *name;
#end
#implementation BObject
+ (BObject*)bObjectWithName:(NSString *)name
{
BObject *bo = [[self alloc] init];
bo.name = name;
return bo;
}
#end
Usage:
NSArray *aobjects = #[
[AObject aObjectWithBObjects:#[
[BObject bObjectWithName:#"A1B1"],
[BObject bObjectWithName:#"A1B2"],
[BObject bObjectWithName:#"A1B3"],
[BObject bObjectWithName:#"A1B4"]
]],
[AObject aObjectWithBObjects:#[
[BObject bObjectWithName:#"A2B1"],
[BObject bObjectWithName:#"A2B2"],
[BObject bObjectWithName:#"A2B3"],
[BObject bObjectWithName:#"A2B4"]
]],
[AObject aObjectWithBObjects:#[
[BObject bObjectWithName:#"A3B1"],
[BObject bObjectWithName:#"A3B2"],
[BObject bObjectWithName:#"A3B3"],
[BObject bObjectWithName:#"A3B4"]
]]
];
NSLog(#"%#", [aobjects valueForKeyPath:#"firstBObject.name"]);
Results
(
A1B1,
A2B1,
A3B1 )

So as it turns out, I had the fortune of being able to simply override -valueForKey: in the root class (AObject). It bears repeating that -valueForKeyPath: calls -valueForKey: on every key, which is cool.
Since that might not be applicable to everyone, and this might be too much manipulation of default, expected behavior, this definitely not the "right" answer.
But here it is anyway:
- (id)valueForKey:(NSString *)string
{
if ([string characterAtIndex: [string length] - 1] == ']') // Trying to subscript
{
NSRegularExpression *subscriptRegex = [[NSRegularExpression alloc] initWithPattern: #"([a-zA-Z]+)\\[([0-9]+)\\]"
options: (NSRegularExpressionOptions)0
error: nil];
NSString *key = [subscriptRegex stringByReplacingMatchesInString: string
options: (NSMatchingOptions)0
range: NSMakeRange(0, [string length])
withTemplate: #"$1"];
id valueForKey = [self valueForKey: key];
if (!key || !valueForKey || ![valueForKey respondsToSelector: #selector(objectAtIndexedSubscript:)])
return nil;
NSInteger index = [[subscriptRegex stringByReplacingMatchesInString: string
options: (NSMatchingOptions)0
range: NSMakeRange(0, [string length])
withTemplate: #"$2"] integerValue];
if ((index < 0) || (index >= [valueForKey count]))
return nil;
return [valueForKey objectAtIndexedSubscript: index];
}
return [super valueForKey: string];
}

Related

Replace smiles with images in NSAttributedString

I have a list of Emoji entities and each of the has property codes and I want to check string ("]:-)") if it contains any of them and then replace smile with an image.
for (Emoji *emoji in self.emojis) {
for (NSString *code in emoji.codes) {
NSString *pattern = [NSRegularExpression escapedPatternForString:code];
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:nil];
NSArray *matches = [regex matchesInString:[sourceString string] options:0 range:NSMakeRange(0, [sourceString length])];
[matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult * _Nonnull aResult, NSUInteger idx, BOOL * _Nonnull stop) {
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
[attachment setImage:[UIImage imageNamed:emoji.image]];
NSAttributedString *replacement = [NSAttributedString attributedStringWithAttachment:attachment];
[sourceString replaceCharactersInRange:[aResult range] withAttributedString:replacement];
}];
}
}
The problem is that smile with code ]:-) contains :-) and my method replacing it with next: bracket ] + [image] for :-), it is because :-) goes first in list.
How can I check for exact string?
I've tried:
]:-/), \\b]:-/)\\b, /^]:-/)$/
Maybe there is better solution to make this working.
If I understood correctly your current structure:
#interface Emoji : NSObject
#property (nonatomic, strong) NSString *image; //I'd expect a UIImage there and not an image name
#property (nonatomic, strong) NSArray *codes;
#end
A possible solution is instead to use a single couple of values: imageName/code
#interface Emoji : NSObject
#property (nonatomic, strong) NSString *image; //I'd expect a UIImage there and not an image name
#property (nonatomic, strong) NSString *code;
#end
self.emojis will have plenty of Emoji object that may have the same image name for different code, but an advantage of doing that is this little trick:
Sort self.emojis in a way that "smaller" emojis are at the end. So you'll replace first only the "lengthy" ones and the the smaller ones.
self.emojis = [arrayOfSingleEmojis sortedArrayUsingComparator:^NSComparisonResult(Emoji * _Nonnull emoji1, Emoji * _Nonnull emoji2) {
NSUInteger length1 = [[emoji1 code] length];
NSUInteger length2 = [[emoji2 code] length];
return [#(length2) compare:#(length1)]; //Or reverse length1 & length2, I never know, I always have to test, but I think it's the correct one
}];
So in your current case: ]:-) will be replace before :-) so you should have <imageFor:">:-)"] instead of ]<imageFor:":-)>

Mantle 2.0 cannot parse JSON for the notation in array nested

The following codes parsing JSON don't work any more for me after I update Mantle to 2.0. They can work fine on an older Mantle version( I don't remember the correct version number. What I know is I downloaded it in Nov of 2013.)
Here is the JSON content:
{
date = "2015-05-21";
error = 0;
results = (
{
currentCity = "beijing";
index = (
{
des = "desc1";
tipt = "tipt1";
title = "title1";
zs = "zs1";
},
{
des = "desc2";
tipt = "tipt2";
title = "title2";
zs = "zs2";
},
{
des = "desc3";
tipt = "tipt3";
title = "title3";
zs = "zs3";
}
);
}
);
status = success;
}
The Model I defined:
// .h
#import "MTLModel.h"
#import "Mantle.h"
#interface BaiduWeatherResults : MTLModel<MTLJSONSerializing>
#property (nonatomic, strong) NSNumber *error;
#property (nonatomic, strong) NSString *status;
#property (nonatomic, strong) NSString *date;
#property (nonatomic, strong) NSString *currentCity;
#end
// .m
#import "BaiduWeatherResults.h"
#implementation BaiduWeatherResults
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
#"error" : #"error",
#"status" : #"status",
#"date" : #"date",
#"currentCity" : #"results.currentCity",
};
}
+ (NSValueTransformer *) currentCityJSONTransformer
{
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSArray *values) {
return [values firstObject];
} reverseBlock:^(NSString *str) {
return #[str];
}];
}
Parse JSON to Model
id results =[MTLJSONAdapter modelOfClass:[BaiduWeatherResults class]
fromJSONDictionary:responseObject
error:nil];
NSLog(#"results:%#", results);
My Question:
The codes can work on an older Mantle. On the Mantle 2.0, the parse failed once I added #"currentCity" : #"results.currentCity" into the dictionary returned by JSONKeyPathsByPropertyKey . Anyone know what I missed for the parsing?
BTW, the currentCityJSONTransformer did call when the parse began. But the transformer is never used, because the line "return [values firstObject];" is never executed.
Thanks in advance.
Try this -
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
#"error" : #"error",
#"status" : #"status",
#"date" : #"date",
#"currentCity" : #"results",
};
}
+ (NSValueTransformer *) currentCityJSONTransformer
{
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSArray *values) {
NSDictionary *cityInfo = [values firstObject];
return cityInfo[#"currentCity"];
} reverseBlock:^(NSString *str) {
return #[#{#"currentCity" : str}];
}];
}
Since results is an array of dictionaries, you can't access currentCity via dot syntax in JSONKeyPathsByPropertyKey. Instead the currentCityJSONTransformer finds the first dictionary in the results array and returns its value for currentCity. You might want to add type-checking and define the #"currentCity" key in a single place.

Adding Custom Objects to NSMutableArray

I mainly program in Java and can't understand why this isn't working. I'm trying to create a temporary object "Judge" in my for loop. I then want to add that object to an NSMutableArray so in the end I have an array filled with different Judge objects. After the for loop I run through all the objects in the Array and they're all the last Judge Object.
The NSLog shows that "JudgeTemp" object is being assigned the right values while in the for loop. My guess is that it's not creating a new object called JudgeTemp every time but referencing the old already created JudgeTemp.
NSMutableArray *Judges = [NSMutableArray arrayWithCapacity:30];
for (int i=0; i<[courtinfoarray count]; i++) {
Judge1= [[courtinfoarray objectAtIndex:i] componentsSeparatedByString:#"|"];
Judge *JudgeTemp=[[Judge alloc]init];
[JudgeTemp setName:[Judge1 objectAtIndex:0] picture:[Judge1 objectAtIndex:1] courtroom:[Judge1 objectAtIndex:2] phone:[Judge1 objectAtIndex:3] undergrad:[Judge1 objectAtIndex:4] lawschool:[Judge1 objectAtIndex:5] opdasa:[Judge1 objectAtIndex:6] career:[Judge1 objectAtIndex:7] judgecode:[Judge1 objectAtIndex:8]];
NSLog(#"%#",[JudgeTemp getName]);
[Judges addObject:JudgeTemp];
NSLog(#"%#",[[Judges objectAtIndex:i]getName]);
}
Judges Class
#implementation Judge
NSString *name;
NSString *picture;
NSString *courtroom;
NSString *phone;
NSString *undergrad;
NSString *lawschool;
NSString *opdasa;
NSString *career;
NSString *judgecommentcode;
-(void) setName:(NSString *)n picture:(NSString *) p courtroom:(NSString *)c phone:(NSString *)ph undergrad: (NSString *) u lawschool: (NSString *)l opdasa: (NSString *) o career: (NSString *)ca judgecode: (NSString *)jcode{
name = n;
picture = p;
courtroom = c;
phone = ph;
undergrad = u;
lawschool = l;
opdasa = o;
career = ca;
judgecommentcode = jcode;
}
-(NSString*) getName{
return name;
}
The problem is with your Judge class. When you define variables directly in your #implementation they have global scope and are not instance variables. What you need to do is put those variable declarations in your #interface instead:
#interface Judge : NSObject {
NSString *name;
NSString *picture;
NSString *courtroom;
NSString *phone;
NSString *undergrad;
NSString *lawschool;
NSString *opdasa;
NSString *career;
NSString *judgecommentcode;
}
// ...
#end
Edit: Apparently you can declare them in your #implementation, you just have to wrap them in { }. See: Instance variables declared in ObjC implementation file

Objective-C - Translate string to different characters using a dictionary

I have an iOS program that takes a string from the user, splits the string up by character, and then uses each character as a key to grab a seperate (morse code) character from a dictionary. I'm having issues regarding the char*s and strings, and I am receiving errors having to do with improper types and not finding the key in the array. Thoughts please? Thanks in advance.
Here is the code:
//
// ViewController.m
// MorseCodeTranslator
//
// Created by Mitch on 4/30/14.
// Copyright (c) 2014 Mitch . All rights reserved.
//
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UITextField *userInput;
#property (weak, nonatomic) IBOutlet UILabel *outputField;
- (IBAction)translateUserString:(UIButton *)sender;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (IBAction)translateUserString:(UIButton *)sender {
BOOL stringPresent = (self.userInput.text.length > 0);
if (stringPresent) {
NSDictionary *morseCharacterKey = #{
#"A" : #".-",
#"B" : #"-...",
#"C" : #"-.-.",
#"D" : #"-..",
#"E" : #".",
#"F" : #"..-.",
#"G" : #"--.",
#"H" : #"....",
#"I" : #"..",
#"J" : #".---",
#"K" : #"-.-",
#"L" : #".-..",
#"M" : #"--",
#"N" : #"-.",
#"O" : #"---",
#"P" : #".--.",
#"Q" : #"--.-",
#"R" : #".-.",
#"S" : #"...",
#"T" : #"-",
#"U" : #"..-",
#"V" : #"...-",
#"W" : #".--",
#"X" : #"-..-",
#"Y" : #"-.--",
#"Z" : #"--..",
#"1" : #".----",
#"2" : #"..---",
#"3" : #"...--",
#"4" : #"....-",
#"5" : #".....",
#"6" : #"-....",
#"7" : #"--...",
#"8" : #"---..",
#"9" : #"----.",
#"0" : #"-----",
#" " : #"/",
#"," : #"--..--",
#"." : #".-.-.-",
#"?" : #"..--..",
#"\'" : #".----.",
#"!" : #"-.-.--",
#"/" : #"-..-.",
#"(" : #"-.--.",
#")" : #"-.--.-",
#"&" : #".-...",
#":" : #"---...",
#";" : #"-.-.-.",
#"=" : #"-...-",
#"+" : #".-.-.",
#"-" : #"-....-",
#"_" : #"..--.-",
#"\"" : #".-..-.",
#"$" : #"...-..-",
#"#" : #".--.-."
};
NSString *userString = self.userInput.text;
NSString *outputString;
for (int i = 0; userString.length > i; i++){
char userCharacter = [userString characterAtIndex:i];
char morseCharacter = [morseCharacterKey objectForKey:morseCharacterKey[userCharacter]];
outputString stringByAppendingString:[morseCharacter];
self.outputField.text = outputString;
}
}
}
#end
Your code should use NSString:
for (int i = 0; userString.length > i; i++) {
NSString *userCharacter = [userString substringWithRange:NSMakeRange(i, 1)];
NSString *morseCharacter = morseCharacterKey[userCharacter];
outputString = [outputString stringByAppendingString:morseCharacter];
NSLog(outputString);
}
}
So:
#"A"
Is an NSString literal. So your dictionary keys:
NSDictionary *morseCharacterKey = #{
#"A" : #".-",
...
Are NSString*s. Which is fine, because keys are required to be objects.
You're getting the character as a char, which is not an object, and even if it was it isn't the same object as you used for your keys.
Instead what you want to do is something like this:
NSRange range;
range.location = i;
range.length = 1;
NSString* userCharacter = [userString substringWithRange:range];
And use userCharacter as your key. Note that your values are also NSString*s, so you should store the result of your dictionary lookup in an NSString as well.
You shouldn't be working with char. Try this:
NSString *userString = self.userInput.text;
NSMutableString *outputString = [NSMutableString string];
for (NSUInteger i = 0; i < userString.length; i++) {
NSString *letter = [userString substringWithRange:NSMakeRange(i, 1)];
NSString *morse = morseCharacterKey[letter];
[outputString appendingString:morse];
}
self.outputField.text = outputString;
Block based enumeration would clean this up a little bit. Also a NSMutableString would be better then using stringByAppendingString. Also since I use a mutableString we don't have to use the __block specify in order to append to it like we would if stringByAppending string was used.
NSString *string = #"...";
NSMutableString *output = [[NSMutableString alloc] init];
[string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
NSString *value = morseCharacterKey[substring];
[output appendString:value];
}];

What is a better way to check if string is a member of a const struct?

I want to check if a string is in a const struct. The way I do it is like this:
In my MyClass.h:
extern const struct MyAttributes {
__unsafe_unretained NSString *attribute1;
__unsafe_unretained NSString *attribute2;
__unsafe_unretained NSString *attribute3;
__unsafe_unretained NSString *attribute4;
__unsafe_unretained NSString *attribute5;
} MyAttributes;
Then in my MyClass.m I have:
const struct MyAttributes MyAttributes = {
.attribute1 = #"attribute1",
.attribute2 = #"attribute2",
.attribute3 = #"attribute3",
.attribute4 = #"attribute4",
.attribute5 = #"attribute5"
};
...
- (void)someMethod
{
BOOL test1 = [self check:#"test"];
BOOL test2 = [self check:#"attribute1"];
}
- (BOOL)check:(NSString *)string
{
return [string isEqualToString:MyAttributes.attribute1] ||
[string isEqualToString:MyAttributes.attribute2] ||
[string isEqualToString:MyAttributes.attribute3] ||
[string isEqualToString:MyAttributes.attribute4] ||
[string isEqualToString:MyAttributes.attribute5];
}
Well, this works. But, is there a better way to implement - (void)check so that, if I update MyAttributes, I won't have to update - (void)check?
You can convert it to an Objective-C array and then see if it contains the string you're looking for:
- (BOOL) check: (NSString*) string {
// I renamed the variable MyAttributes to myAttributes, following naming conventions
__unsafe_unretained id* ptr;
struct MyAttributes* attrPtr= &myAttributes;
memcpy(&ptr, &attrPtr, sizeof(id*));
NSArray* array= [NSArray arrayWithObjects: ptr count: sizeof(MyAttributes)/sizeof(NSString*)];
return [array containsObject: string];
}
The C-style way is to treat the struct as a C-array:
- (BOOL)check:(NSString *)string {
BOOL result= NO;
// I renamed the variable MyAttributes to myAttributes, following naming conventions
NSString* __unsafe_unretained * strPtr;
struct MyAttributes* ptr= &myAttributes;
memcpy(&strPtr, &ptr, sizeof(NSString**));
for(size_t i=0; i<sizeof(MyAttributes)/sizeof(NSString*) && !result;i++) {
result= [string isEqualToString: strPtr[i] ];
}
return result;
}
PS: I used memcpy to avoid bridge-casting, since the strings are already retained in myAttributes.
you can dig in the direction of keeping struct items in NSArray
Possible answer:
Cocoa structs and NSMutableArray
then in - check method use containsObject: or array iteration to check if string is there.

Resources