Given an NSArray of NSStrings, is there a quick way to join them together into a single NSString (with a Separator)?
NSArray * stuff = /* ... */;
NSString * combinedStuff = [stuff componentsJoinedByString:#"separator"];
This is the inverse of -[NSString componentsSeparatedByString:].
-componentsJoinedByString: on NSArray should do the trick.
There's also this variant, if your original array contains Key-Value objects from which you only want to pick one property (that can be serialized as a string ):
#implementation NSArray (itertools)
-(NSMutableString *)stringByJoiningOnProperty:(NSString *)property separator:(NSString *)separator
{
NSMutableString *res = [#"" mutableCopy];
BOOL firstTime = YES;
for (NSObject *obj in self)
{
if (!firstTime) {
[res appendString:separator];
}
else{
firstTime = NO;
}
id val = [obj valueForKey:property];
if ([val isKindOfClass:[NSString class]])
{
[res appendString:val];
}
else
{
[res appendString:[val stringValue]];
}
}
return res;
}
#end
Related
im working on an that search in the content of allot of files, i planed to use SearchKit but i can't figure out to make Apple's sample code to work, and i can't find any other ressources (NSHipster code didn't work either), here's my code:
#define kSearchMax 1000
#interface ViewController()
#property(nonatomic) SKIndexRef mySKIndex;
#end
#implementation ViewController
#synthesize mySKIndex;
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self openIndex];
[self addDoc];
SKIndexFlush(self.mySKIndex);
// i thought that the indexation may need some time ..
sleep(2);
dispatch_async(dispatch_get_main_queue(), ^{
[self searchterm:#"var"];
});
});
}
- (void) openIndex {
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:#"index"] stringByAppendingPathExtension:#"txt"]; // 1
NSURL *url = [NSURL fileURLWithPath:path];
NSString *name = #"extension_index";
if ([name length] == 0) name = nil;
SKIndexType type = kSKIndexInverted;
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
mySKIndex = SKIndexOpenWithURL ((__bridge CFURLRef) url,
(__bridge CFStringRef) name,
true
);
}else{
self.mySKIndex = SKIndexCreateWithURL((__bridge CFURLRef) url,
(__bridge CFStringRef) name,
(SKIndexType) type,
(CFDictionaryRef) NULL);
}
}
- (void) addDoc {
SKLoadDefaultExtractorPlugIns ();
NSString *path = [NSBundle.mainBundle pathForResource:#"Products" ofType:#"rtf"]; // 1
NSURL *url = [NSURL fileURLWithPath: path]; // 2
SKDocumentRef doc = SKDocumentCreateWithURL ((__bridge CFURLRef) url);
NSString *mimeTypeHint = #"text/rtf";
BOOL added = SKIndexAddDocument ((SKIndexRef) mySKIndex,
(SKDocumentRef) doc,
(__bridge CFStringRef)mimeTypeHint,
(Boolean) true
);
NSLog(added ? #"added" : #"not added");
}
- (void) searchterm:(NSString*)query{
SKSearchOptions options = kSKSearchOptionDefault;
BOOL more = YES;
UInt32 totalCount = 0;
SKSearchRef search = SKSearchCreate (mySKIndex,
(__bridge CFStringRef) query,
options);
while (more) {
SKDocumentID foundDocIDs [kSearchMax];
float foundScores [kSearchMax];
float *scores;
Boolean unranked =
options & kSKSearchOptionNoRelevanceScores;
if (unranked) {
scores = NULL;
} else {
scores = foundScores;
}
CFIndex foundCount = 0;
more = SKSearchFindMatches (
search,
kSearchMax,
foundDocIDs,
scores,
100,
&foundCount
);
NSLog(#"%#", [NSString stringWithFormat:#"current count = %i", totalCount]);
totalCount += foundCount;
}
}
#end
it always print "current count = 0" and the loop is executed only one time.
I am using this line of code; my array contains name, email and phone no. This code only sorts namewise, but I want email and phone no with name. How can I do this that my array separeNamesByLetters contains email and phone with name?
NSMutableSet *firstCharacters = [NSMutableSet setWithCapacity:0];
for( NSString*string in [ tableDataArray valueForKey:#"name"] ){
[firstCharacters addObject:[[string substringToIndex:1] uppercaseString]];
}
NSArray *allLetters = [[firstCharacters allObjects] sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
int indexLetter = 0;
separeNamesByLetters = [NSMutableArray new];
for (NSString *letter in allLetters) {
NSMutableDictionary*userBegeinsWith = [NSMutableDictionary new];
[userBegeinsWith setObject:letter forKey:#"letter" ];
NSMutableArray *groupNameByLetters = [NSMutableArray new];
NSString *compareLetter1 = [NSString stringWithFormat:#"%#", allLetters[indexLetter]];
for (NSString*friendName in[ tableDataArray valueForKey:#"name"]) {
NSString *compareLetter2 = [[friendName substringToIndex:1] uppercaseString];
if ( [compareLetter1 isEqualToString:compareLetter2] ) {
[groupNameByLetters addObject:friendName];
}
}
indexLetter++;
[userBegeinsWith setObject:groupNameByLetters forKey:#"list"];
[separeNamesByLetters addObject: userBegeinsWith];
}
NSLog(#"%#", separeNamesByLetters);
}
Have you considered using NSSortDescriptors? See the following example:
NSMutableArray *array = [NSMutableArray new];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:#"Doe, John", #"name", #"3361231234", #"phone", #"johndoe#email.com", #"email", nil];
[array addObject:dict];
dict = [NSDictionary dictionaryWithObjectsAndKeys:#"Doe, Jane", #"name", #"9191234532", #"phone", #"janedoe#email.com", #"email", nil];
[array addObject:dict];
NSSortDescriptor *nameDescriptor =
[[NSSortDescriptor alloc]
initWithKey:#"name"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
NSSortDescriptor *phoneDescriptor =
[[NSSortDescriptor alloc]
initWithKey:#"phone"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
NSSortDescriptor *emailDescriptor =
[[NSSortDescriptor alloc]
initWithKey:#"email"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray * descriptors = [NSArray arrayWithObjects:nameDescriptor, phoneDescriptor, emailDescriptor, nil];
NSArray * sortedArray = [array sortedArrayUsingDescriptors:descriptors];
Let me know if this helps.
When I do this, I create a new NSArray of NSDictionaries. The NSDictionary has two properties: sectionTitle and records. The sectionTitle holds the first-letter of the field you are searching on. The records contain the actual objects that begin with that sectionTitle.
Add this sectionTitle method to your model class. I usually do this in an Extension to the actual model class.
- (NSString *)sectionTitle:(NSString*)sortByProperty {
NSString *propertyValue;
if ([sortByProperty isEqualToString:#"name"]) {
if (self.name == nil) return #"";
propertyValue = self.name;
} else if ([sortByProperty isEqualToString:#"email"]) {
if (self.email == nil) return #"";
propertyValue = self.phone;
} else if ([sortByProperty isEqualToString:#"phone"]) {
if (self.phone == nil) return #"";
propertyValue = phone
}
NSString *tmp;
if ([propertyValue length] > 0) {
tmp = [propertyValue substringToIndex:1];
} else {
return #"";
}
return tmp;
}
This should be quicker than iterating through your array each time you refresh your tableView.
Next, iterate through these sectionTitles, filter your array, and add the filtered results to the records attribute of the dictionary:
- (NSMutableArray*)sortedArrayForUITableView:(NSMutableArray *)tableDataArray sortBy:(NSString*)sortByProperty {
NSArray *tmp = [tableDataArray valueForKeyPath:#"#distinctUnionOfObjects.sectionTitle"];
NSArray *allLetters = [NSMutableArray arrayWithArray:[tmp sortedArrayUsingDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:#"self" ascending:YES]]]];
NSMutableArray *tree = [NSMutableArray new];
for (NSString *sectionTitle in allLetters) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.sectionTitle == %#", sectionTitle];
NSMutableArray *tmpArray = [NSMutableArray arrayWithArray:[tableDataArray filteredArrayUsingPredicate:predicate]];
NSArray *sortedArray = [tmpArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:sortByProperty ascending:YES]]];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:sectionTitle, #"sectionTitle", sortedArray, #"records", nil];
[tree addObject:dict];
}
return tree;
}
You will need to implement several UITableViewDataSource and UITableViewDelegate methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.tree count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *dict = [NSDictionary dictionaryWithDictionary:[self.tree objectAtIndex:section]];
NSArray *tmp = [NSArray arrayWithArray:[dict objectForKey:#"records"]];
return [tmp count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSDictionary *dict = [NSDictionary dictionaryWithDictionary:[self.tree objectAtIndex:section]];
return [dict objectForKey:#"sectionTitle"];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return self.allLetters;
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return index;
}
Lastly, you will build your cell in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath. Hope that helps.
I am searching into a UITableView using this:
titles = [NSArray arrayWithArray:[datamanager titlesForEntriesBetween:(NSInteger)[slider minSelectedValue] and:(NSInteger)[slider maxSelectedValue]containing:searchText]];
How can I encode array value with NSASCIIStringEncoding during the search process?
(Array contains "tĂȘte" for example.. and when I search "tete" nothing matches.. so I will encode array value just for my search)
I would add change the third parameter to your datamanager function:
- (NSArray*)titlesForEntriesBetween:(NSInteger)startIndex
and:(NSInteger)stopIndex
withFunction:(BOOL(^)(NSString*))block {
NSMutableArray *retVal = [NSMutableArray array];
for(NSInteger i = startIndex; i <= stopIndex; ++i) {
NSString *string = [array_ objectAtIndex:i];
if (block(string)) {
[retVal insertObject:string];
}
}
return retVal;
}
And then I would call the function like this:
titles = [datamanager titlesForEntriesBetween:(NSInteger)[slider minSelectedValue] and:(NSInteger)[slider maxSelectedValue] withFunction:^(BOOL)(NSString *str) {
NSData *data = [str dataUsingEncoding:NSASCIIStringEncoding];
NSString *simpleString = [[[NSString alloc] initWithData:data usingEncoding: NSASCIIStringEncoding] autorelease];
return [simpleString isEqualToString:str];
}]];
Note: I just typed this in, I haven't tried to compile/run this.
void PrintIntrospectionInfo()
{
// NSLog(#"Comes here1");
NSArray *myArray;
NSDate *aDate = [NSDate distantFuture];
NSValue *aValue = [NSNumber numberWithInt:5];
NSURL *urlObj = [NSURL URLWithString:#"http://www.yahoo.com"];
NSString *aString = #"string";
NSMutableString *mString = [NSMutableString stringWithFormat: #"Hello, %#", aString];
NSDictionary *stanford = [NSDictionary dictionaryWithObjectsAndKeys:#"http://www.stanford.edu", #"Stanford University", #"http://www.apple.com",#"Apple", #"http://cs193p.stanford.edu", #"CS193P",#"http://itunes.stanford.edu",#"Stanford on iTunesU",#"http://stanfordshop.com",#"Stanford Mall", nil];
myArray = [NSArray arrayWithObjects:aDate, aValue, aString,stanford,urlObj,mString, nil];
for(id someObject in myArray)
{
NSLog(#"Comes here");
if([someObject isKindofClass:[NSString string]])
{
}
}
}
Do
for(id someObject in myArray)
{
NSLog(#"Comes here");
if([someObject isKindOfClass:[NSString class]])
{
}
}
instead. Note that Of of isKindOfClass is in the upper case. I recommend you to use NSObject* instead of id. Then the compiler warns you that the method isKindofClass is not available. Usually, you don't need to use id unless you use something which is not an NSObject :
for(NSObject* someObject in myArray)
I need simple client-server communication with iPhone app, and XML property lists seem like quite easy and simple solution that saves me trouble of dealing with XML parser delegates and building these structures myself.
I just wonder is it wise to use NSPropertyListSerialization class with external data? Are there any obscure plist features that could be exploited?
Yes, it's safe to use NSPropertyListSerialization with untrusted data, but after you turn the bag of bytes into a hiearchy of plist types, you have to validate those types to makes sure they match your expected data format.
For example, if you expect a dictionary with string keys, and NSNumbers as values, you have to validate that with something like:
NSString *errorDescription = nil;
NSPropertyListFormat format = 0;
id topObject = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&errorDescription];
NSDictionary *validDictionary = nil;
if ([topObject isKindOfClass:[NSDictionary class]]) {
BOOL allNumbers = YES;
for(id value in [topObject allValues]) {
allNumbers = allNumbers && [value isKindOfClass:[NSNumber class]];
}
if (allNumbers) {
validDictionary = topObject;
}
}
return validDictionary;
If you don't do that, the source of the data could have placed plist values into the archive with mis matched types, or illegal values that could cause your client to misbehave and wind up being a security vulnerability.
#Jon Hess: thanks. I've created NSDictionary category to cut down on required number of isKindOfClasses.
#interface NSDictionary (TypedDictionary)
-(NSArray *)arrayForKey:(NSString*)key;
-(NSString *)stringForKey:(NSString*)key;
-(NSDictionary *)dictionaryForKey:(NSString*)key;
#end
#implementation NSDictionary (TypedDictionary)
-(NSArray *)arrayForKey:(NSString*)key {
NSArray *obj = [self objectForKey:key];
return [obj isKindOfClass:[NSArray class]] ? obj : nil;
}
-(NSString *)stringForKey:(NSString*)key {
NSString *obj = [self objectForKey:key];
return [obj isKindOfClass:[NSString class]] ? obj : nil;
}
-(NSDictionary *)dictionaryForKey:(NSString*)key {
NSDictionary *obj = [self objectForKey:key];
return [obj isKindOfClass:[NSDictionary class]] ? obj : nil;
}
#end