diff --git a/Classes/NSArray+ObjectiveSugar.h b/Classes/NSArray+ObjectiveSugar.h index 05a13bf..f3dc7a7 100644 --- a/Classes/NSArray+ObjectiveSugar.h +++ b/Classes/NSArray+ObjectiveSugar.h @@ -36,22 +36,23 @@ /// Alias for -sample - (id)anyObject; - /** Allow subscripting to fetch elements within the specified range - @param key An NSString or NSValue wrapping an NSRange. It's intended to behave like Ruby's array range accessors. + @param key An NSString or NSValue wrapping an NSRange. It's intended to behave + like Ruby's array range accessors. - Given array of 10 elements, e.g. [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], you can perform these operations: + Given array of 10 elements, e.g. [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], you + can perform these operations: array[@"1..3"] will give you [2, 3, 4] array[@"1...3"] will give you [2, 3] (last value excluded) - array[@"1,3"] implies NSRange(location: 1, length: 3), and gives you [2, 3, 4] + array[@"1,3"] implies NSRange(location: 1, length: 3), and gives you [2, + 3, 4] @return An array with elements within the specified range */ -- (id)objectForKeyedSubscript:(id )key; - +- (id)objectForKeyedSubscript:(id)key; /** A simpler alias for `enumerateObjectsUsingBlock` @@ -77,14 +78,15 @@ - (void)each:(void (^)(id object))block options:(NSEnumerationOptions)options; /** - A simpler alias for `enumerateObjectsWithOptions:usingBlock:` which also passes in an index + A simpler alias for `enumerateObjectsWithOptions:usingBlock:` which also passes + in an index @param block A block with the object in its arguments. @param options Enumerating options. */ -- (void)eachWithIndex:(void (^)(id object, NSUInteger index))block options:(NSEnumerationOptions)options; - +- (void)eachWithIndex:(void (^)(id object, NSUInteger index))block + options:(NSEnumerationOptions)options; /** An alias for `containsObject` @@ -136,7 +138,6 @@ */ - (id)detect:(BOOL (^)(id object))block; - /** Alias for `detect`. Iterate through current array returning the first element meeting a criteria. @@ -149,13 +150,15 @@ /** Iterate through current array asking whether to remove each element. - @param block A block that returns YES/NO for whether the object should be removed + @param block A block that returns YES/NO for whether the object should be + removed @return An array of elements not rejected */ - (NSArray *)reject:(BOOL (^)(id object))block; /** - Recurse through self checking for NSArrays and extract all elements into one single array + Recurse through self checking for NSArrays and extract all elements into one + single array @return An array of all held arrays merged */ @@ -183,18 +186,19 @@ - (NSString *)join:(NSString *)separator; /** - Run the default comparator on each object in the array + Run the default comparator on each object in the array on the given key @return A sorted copy of the array */ -- (NSArray *)sort; +- (NSArray *)sortedArrayByKey:(NSString *)key ascending:(BOOL)ascending; /** - Sorts the array using the the default comparator on the given key + Sorts the array using the the default comparator on the given keys @return A sorted copy of the array */ -- (NSArray *)sortBy:(NSString *)key; +- (NSArray *)sortedArrayByKeys:(NSArray *)keys + ascending:(NSArray *)ascendingArray; /** Alias for reverseObjectEnumerator.allObjects @@ -233,14 +237,17 @@ Return all the objects that are unique to each array individually Alias for Ruby's ^ operator. Equivalent of a - b | b - a - @return An array of elements which are in either of the arrays and not in their intersection. + @return An array of elements which are in either of the arrays and not in their + intersection. */ - (NSArray *)symmetricDifference:(NSArray *)array; /** - Return a single value from an array by iterating through the elements and transforming a running total. + Return a single value from an array by iterating through the elements and + transforming a running total. - @return A single value that is the end result of apply the block function to each element successively. + @return A single value that is the end result of apply the block function to + each element successively. **/ - (id)reduce:(id (^)(id accumulator, id object))block; @@ -251,10 +258,9 @@ /** Produces a duplicate-free version of the array - + @return a new array with all unique elements **/ - (NSArray *)unique; @end - diff --git a/Classes/NSArray+ObjectiveSugar.m b/Classes/NSArray+ObjectiveSugar.m index 82c28f5..74b9d8d 100644 --- a/Classes/NSArray+ObjectiveSugar.m +++ b/Classes/NSArray+ObjectiveSugar.m @@ -10,223 +10,251 @@ #import "NSMutableArray+ObjectiveSugar.h" #import "NSString+ObjectiveSugar.h" -static NSString * const OSMinusString = @"-"; +static NSString *const OSMinusString = @"-"; @implementation NSArray (ObjectiveSugar) - (id)sample { - if (self.count == 0) return nil; + if (self.count == 0) + return nil; - NSUInteger index = arc4random_uniform((u_int32_t)self.count); - return self[index]; + NSUInteger index = arc4random_uniform((u_int32_t)self.count); + return self[index]; } - (id)objectForKeyedSubscript:(id)key { - if ([key isKindOfClass:[NSString class]]) - return [self subarrayWithRange:[self rangeFromString:key]]; + if ([key isKindOfClass:[NSString class]]) + return [self subarrayWithRange:[self rangeFromString:key]]; - else if ([key isKindOfClass:[NSValue class]]) - return [self subarrayWithRange:[key rangeValue]]; + else if ([key isKindOfClass:[NSValue class]]) + return [self subarrayWithRange:[key rangeValue]]; - else - [NSException raise:NSInvalidArgumentException format:@"expected NSString or NSValue argument, got %@ instead", [key class]]; + else + [NSException raise:NSInvalidArgumentException + format:@"expected NSString or NSValue argument, got %@ instead", + [key class]]; - return nil; + return nil; } - (NSRange)rangeFromString:(NSString *)string { - NSRange range = NSRangeFromString(string); + NSRange range = NSRangeFromString(string); - if ([string containsString:@"..."]) { - range.length = isBackwardsRange(string) ? (self.count - 2) - range.length : range.length - range.location; + if ([string containsString:@"..."]) { + range.length = isBackwardsRange(string) ? (self.count - 2) - range.length + : range.length - range.location; - } else if ([string containsString:@".."]) { - range.length = isBackwardsRange(string) ? (self.count - 1) - range.length : range.length - range.location + 1; - } + } else if ([string containsString:@".."]) { + range.length = isBackwardsRange(string) ? (self.count - 1) - range.length + : range.length - range.location + 1; + } - return range; + return range; } - (void)each:(void (^)(id object))block { - [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - block(obj); - }]; + [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + block(obj); + }]; } - (void)eachWithIndex:(void (^)(id object, NSUInteger index))block { - [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - block(obj, idx); - }]; + [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + block(obj, idx); + }]; } - (void)each:(void (^)(id object))block options:(NSEnumerationOptions)options { - [self enumerateObjectsWithOptions:options usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - block(obj); - }]; + [self enumerateObjectsWithOptions:options + usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + block(obj); + }]; } -- (void)eachWithIndex:(void (^)(id object, NSUInteger index))block options:(NSEnumerationOptions)options { - [self enumerateObjectsWithOptions:options usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - block(obj, idx); - }]; +- (void)eachWithIndex:(void (^)(id object, NSUInteger index))block + options:(NSEnumerationOptions)options { + [self enumerateObjectsWithOptions:options + usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + block(obj, idx); + }]; } - (BOOL)includes:(id)object { - return [self containsObject:object]; + return [self containsObject:object]; } - (NSArray *)take:(NSUInteger)numberOfElements { - return [self subarrayWithRange:NSMakeRange(0, MIN(numberOfElements, [self count]))]; + return [self + subarrayWithRange:NSMakeRange(0, MIN(numberOfElements, [self count]))]; } - (NSArray *)takeWhile:(BOOL (^)(id object))block { - NSMutableArray *array = [NSMutableArray array]; + NSMutableArray *array = [NSMutableArray array]; - for (id arrayObject in self) { - if (block(arrayObject)) - [array addObject:arrayObject]; + for (id arrayObject in self) { + if (block(arrayObject)) + [array addObject:arrayObject]; - else break; - } + else + break; + } - return array; + return array; } - (NSArray *)map:(id (^)(id object))block { - NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count]; + NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count]; - for (id object in self) { - [array addObject:block(object) ?: [NSNull null]]; - } + for (id object in self) { + [array addObject:block(object) ?: [NSNull null]]; + } - return array; + return array; } - (NSArray *)select:(BOOL (^)(id object))block { - return [self filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + return [self + filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL( + id evaluatedObject, + NSDictionary *bindings) { return block(evaluatedObject); - }]]; + }]]; } - (NSArray *)reject:(BOOL (^)(id object))block { - return [self filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + return [self + filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL( + id evaluatedObject, + NSDictionary *bindings) { return !block(evaluatedObject); - }]]; + }]]; } - (id)detect:(BOOL (^)(id object))block { - for (id object in self) { - if (block(object)) - return object; - } - return nil; + for (id object in self) { + if (block(object)) + return object; + } + return nil; } - (id)find:(BOOL (^)(id object))block { - return [self detect:block]; + return [self detect:block]; } - (NSArray *)flatten { - NSMutableArray *array = [NSMutableArray array]; - - for (id object in self) { - if ([object isKindOfClass:NSArray.class]) { - [array concat:[object flatten]]; - } else { - [array addObject:object]; - } + NSMutableArray *array = [NSMutableArray array]; + + for (id object in self) { + if ([object isKindOfClass:NSArray.class]) { + [array concat:[object flatten]]; + } else { + [array addObject:object]; } + } - return array; + return array; } - (NSArray *)compact { - return [self select:^BOOL(id object) { - return object != [NSNull null]; - }]; + return [self select:^BOOL(id object) { + return object != [NSNull null]; + }]; } - (NSString *)join { - return [self componentsJoinedByString:@""]; + return [self componentsJoinedByString:@""]; } - (NSString *)join:(NSString *)separator { - return [self componentsJoinedByString:separator]; + return [self componentsJoinedByString:separator]; } -- (NSArray *)sort { - return [self sortedArrayUsingSelector:@selector(compare:)]; +- (NSArray *)sortedArrayByKey:(NSString *)key ascending:(BOOL)ascending { + return [self sortedArrayByKeys:@[ key ] ascending:@[ @(ascending) ]]; } -- (NSArray *)sortBy:(NSString*)key; { - NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:key ascending:YES]; - return [self sortedArrayUsingDescriptors:@[descriptor]]; +- (NSArray *)sortedArrayByKeys:(NSArray *)keys + ascending:(NSArray *)ascendingArray { + if (keys.count != ascendingArray.count) { + return self; + } + NSMutableArray *sortDescriptors = [NSMutableArray array]; + NSInteger i, count = keys.count; + for (i = 0; i < count; i++) { + NSString *key = keys[i]; + BOOL ascending = [ascendingArray[i] boolValue]; + NSSortDescriptor *descriptor = + [NSSortDescriptor sortDescriptorWithKey:key ascending:ascending]; + [sortDescriptors addObject:descriptor]; + } + return [self sortedArrayUsingDescriptors:sortDescriptors]; } - (NSArray *)reverse { - return self.reverseObjectEnumerator.allObjects; + return self.reverseObjectEnumerator.allObjects; } - (id)reduce:(id (^)(id accumulator, id object))block { - return [self reduce:nil withBlock:block]; + return [self reduce:nil withBlock:block]; } - (id)reduce:(id)initial withBlock:(id (^)(id accumulator, id object))block { - id accumulator = initial; + id accumulator = initial; - for(id object in self) - accumulator = accumulator ? block(accumulator, object) : object; + for (id object in self) + accumulator = accumulator ? block(accumulator, object) : object; - return accumulator; + return accumulator; } -- (NSArray *)unique -{ +- (NSArray *)unique { return [[NSOrderedSet orderedSetWithArray:self] array]; } #pragma mark - Set operations - (NSArray *)intersectionWithArray:(NSArray *)array { - NSPredicate *intersectPredicate = [NSPredicate predicateWithFormat:@"SELF IN %@", array]; - return [self filteredArrayUsingPredicate:intersectPredicate]; + NSPredicate *intersectPredicate = + [NSPredicate predicateWithFormat:@"SELF IN %@", array]; + return [self filteredArrayUsingPredicate:intersectPredicate]; } - (NSArray *)unionWithArray:(NSArray *)array { - NSArray *complement = [self relativeComplement:array]; - return [complement arrayByAddingObjectsFromArray:array]; + NSArray *complement = [self relativeComplement:array]; + return [complement arrayByAddingObjectsFromArray:array]; } - (NSArray *)relativeComplement:(NSArray *)array { - NSPredicate *relativeComplementPredicate = [NSPredicate predicateWithFormat:@"NOT SELF IN %@", array]; - return [self filteredArrayUsingPredicate:relativeComplementPredicate]; + NSPredicate *relativeComplementPredicate = + [NSPredicate predicateWithFormat:@"NOT SELF IN %@", array]; + return [self filteredArrayUsingPredicate:relativeComplementPredicate]; } - (NSArray *)symmetricDifference:(NSArray *)array { - NSArray *aSubtractB = [self relativeComplement:array]; - NSArray *bSubtractA = [array relativeComplement:self]; - return [aSubtractB unionWithArray:bSubtractA]; + NSArray *aSubtractB = [self relativeComplement:array]; + NSArray *bSubtractA = [array relativeComplement:self]; + return [aSubtractB unionWithArray:bSubtractA]; } #pragma mark - Private static inline BOOL isBackwardsRange(NSString *rangeString) { - return [rangeString containsString:OSMinusString]; + return [rangeString containsString:OSMinusString]; } #pragma mark - Aliases - (id)anyObject { - return [self sample]; + return [self sample]; } - (id)first DEPRECATED_ATTRIBUTE { - return [self firstObject]; + return [self firstObject]; } - (id)last DEPRECATED_ATTRIBUTE { - return [self lastObject]; + return [self lastObject]; } @end - diff --git a/Classes/NSMutableArray+ObjectiveSugar.h b/Classes/NSMutableArray+ObjectiveSugar.h index cecd5e8..37a7ff1 100644 --- a/Classes/NSMutableArray+ObjectiveSugar.h +++ b/Classes/NSMutableArray+ObjectiveSugar.h @@ -21,7 +21,6 @@ */ - (id)pop; - /** Removes the last n items of the array, and returns that item Note: This method changes the length of the array! @@ -31,7 +30,6 @@ - (NSArray *)pop:(NSUInteger)numberOfElements; - (void)concat:(NSArray *)array; - /** Removes the first item of the array, and returns that item Note: This method changes the length of the array! @@ -40,7 +38,6 @@ */ - (id)shift; - /** Removes N first items of the array, and returns that items Note: This method changes the length of the array! @@ -49,7 +46,6 @@ */ - (NSArray *)shift:(NSUInteger)numberOfElements; - /** Deletes every element of the array for which the given block evaluates to NO. @@ -57,5 +53,14 @@ @return An array of elements */ - (NSArray *)keepIf:(BOOL (^)(id object))block; +/** + Sorts the array by a given key + */ +- (void)sortArrayByKey:(NSString *)key ascending:(BOOL)ascending; +/** + Sorts the array by the given keys (each value in ascendingArray (YES/NO) + corresponds to a key in the keys keys array. + */ +- (void)sortArrayByKeys:(NSArray *)keys ascending:(NSArray *)ascendingArray; @end diff --git a/Classes/NSMutableArray+ObjectiveSugar.m b/Classes/NSMutableArray+ObjectiveSugar.m index 7d0451e..ea5d7d3 100644 --- a/Classes/NSMutableArray+ObjectiveSugar.m +++ b/Classes/NSMutableArray+ObjectiveSugar.m @@ -6,55 +6,73 @@ // Copyright (c) 2012 @supermarin | supermar.in. All rights reserved. // -#import "NSMutableArray+ObjectiveSugar.h" #import "NSArray+ObjectiveSugar.h" +#import "NSMutableArray+ObjectiveSugar.h" @implementation NSMutableArray (ObjectiveSugar) - (void)push:(id)object { - [self addObject:object]; + [self addObject:object]; } - (id)pop { - id object = [self lastObject]; - [self removeLastObject]; + id object = [self lastObject]; + [self removeLastObject]; - return object; + return object; } - (NSArray *)pop:(NSUInteger)numberOfElements { - NSMutableArray *array = [NSMutableArray arrayWithCapacity:numberOfElements]; + NSMutableArray *array = [NSMutableArray arrayWithCapacity:numberOfElements]; - for (NSUInteger i = 0; i < numberOfElements; i++) - [array insertObject:[self pop] atIndex:0]; + for (NSUInteger i = 0; i < numberOfElements; i++) + [array insertObject:[self pop] atIndex:0]; - return array; + return array; } - (void)concat:(NSArray *)array { - [self addObjectsFromArray:array]; + [self addObjectsFromArray:array]; } - (id)shift { - NSArray *result = [self shift:1]; - return [result firstObject]; + NSArray *result = [self shift:1]; + return [result firstObject]; } - (NSArray *)shift:(NSUInteger)numberOfElements { - NSUInteger shiftLength = MIN(numberOfElements, [self count]); + NSUInteger shiftLength = MIN(numberOfElements, [self count]); - NSRange range = NSMakeRange(0, shiftLength); - NSArray *result = [self subarrayWithRange:range]; - [self removeObjectsInRange:range]; + NSRange range = NSMakeRange(0, shiftLength); + NSArray *result = [self subarrayWithRange:range]; + [self removeObjectsInRange:range]; - return result; + return result; } - (NSArray *)keepIf:(BOOL (^)(id object))block { - [self filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { - return block(evaluatedObject); - }]]; - return self; + [self filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL( + id evaluatedObject, + NSDictionary *bindings) { + return block(evaluatedObject); + }]]; + return self; +} + +- (void)sortArrayByKey:(NSString *)key ascending:(BOOL)ascending { + [self sortArrayByKeys:@[ key ] ascending:@[ @(ascending) ]]; +} + +- (void)sortArrayByKeys:(NSArray *)keys ascending:(NSArray *)ascendingArray { + NSMutableArray *sortDescriptors = [NSMutableArray array]; + for (NSInteger i = 0; i < keys.count && i < ascendingArray.count; i++) { + NSString *key = keys[i]; + BOOL ascending = [ascendingArray[i] boolValue]; + NSSortDescriptor *descriptor = + [NSSortDescriptor sortDescriptorWithKey:key ascending:ascending]; + [sortDescriptors addObject:descriptor]; + } + [self sortUsingDescriptors:sortDescriptors]; } @end diff --git a/Classes/NSString+ObjectiveSugar.h b/Classes/NSString+ObjectiveSugar.h index 9560aa6..ece876f 100644 --- a/Classes/NSString+ObjectiveSugar.h +++ b/Classes/NSString+ObjectiveSugar.h @@ -8,27 +8,28 @@ #import -NSString *NSStringWithFormat(NSString *format, ...) NS_FORMAT_FUNCTION(1,2); +NSString *NSStringWithFormat(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2); -@interface NSString(ObjectiveSugar) +@interface NSString (ObjectiveSugar) /** - Returns an array containing substrings from the receiver that have been divided by a whitespace delimiter + Returns an array containing substrings from the receiver that have been divided + by a whitespace delimiter - @return An array containing substrings that have been divided by a whitespace delimiter + @return An array containing substrings that have been divided by a whitespace + delimiter */ - (NSArray *)split; - /** - Returns an array containing substrings from the receiver that have been divided by a given delimiter + Returns an array containing substrings from the receiver that have been divided + by a given delimiter @param delimiter The delimiter string @return An array containing substrings that have been divided by delimiter */ - (NSArray *)split:(NSString *)delimiter; - /** Returns a new string made by converting a snake_case_string to CamelCaseString @@ -36,36 +37,71 @@ NSString *NSStringWithFormat(NSString *format, ...) NS_FORMAT_FUNCTION(1,2); */ - (NSString *)camelCase; - /** - Returns a new string made by converting a snake_case_string to lowerCamelCaseString + Returns a new string made by converting a snake_case_string to + lowerCamelCaseString @return A string made by converting a snake_case_string to CamelCaseString */ - (NSString *)lowerCamelCase; - /** - Returns a Boolean value that indicates whether a given string is a substring of the receiver + Returns a Boolean value that indicates whether a given string is a substring of + the receiver @return YES if 'string' is a substring of the receiver, otherwise NO */ - (BOOL)containsString:(NSString *)string; - /** - Returns a new string made by removing whitespaces and newlines from both ends of the receiver + Returns a new string made by removing whitespaces and newlines from both ends + of the receiver @return A string without trailing or leading whitespaces and newlines */ - (NSString *)strip; -/** +/** Returns a new string that matches the passed in pattern - + @return A String matching the regex or nil if no match is found */ - (NSString *)match:(NSString *)pattern; +/** + Returns an NSDictionary or an NSArray assuming the string is in JSON +*/ +- (id)toJSON; +/** + Returns the language the string is written in + */ +- (NSString *)language; +/** + Returns the minimum height for the given width and font of this string + */ +- (double)multipleLinesHeightForWidth:(double)width font:(UIFont *)font; +/** + Returns the minimum width for the given height and font of this string + */ +- (double)dynamicWidthForHeight:(double)height font:(UIFont *)font; +/** + Returns the unescaped unicode string + */ +- (NSString *)unescapedUnicodeString; +/** + Returns the escaped unicode string + */ +- (NSString *)escapedUnicodeString; +/** + Returns the url encoded string + */ +- (NSString *)URLEncodedString; +/** + Returns the url decoded string + */ +- (NSString *)URLDecodedString; +/** + Returns the string trimmed + */ +- (NSString *)trim; @end - diff --git a/Classes/NSString+ObjectiveSugar.m b/Classes/NSString+ObjectiveSugar.m index cfeb455..7665544 100644 --- a/Classes/NSString+ObjectiveSugar.m +++ b/Classes/NSString+ObjectiveSugar.m @@ -6,69 +6,251 @@ // Copyright (c) 2012 @supermarin | supermar.in. All rights reserved. // -#import "NSString+ObjectiveSugar.h" #import "NSArray+ObjectiveSugar.h" +#import "NSString+ObjectiveSugar.h" static NSString *const UNDERSCORE = @"_"; static NSString *const SPACE = @" "; static NSString *const EMPTY_STRING = @""; NSString *NSStringWithFormat(NSString *formatString, ...) { - va_list args; - va_start(args, formatString); + va_list args; + va_start(args, formatString); - NSString *string = [[NSString alloc] initWithFormat:formatString arguments:args]; + NSString *string = + [[NSString alloc] initWithFormat:formatString arguments:args]; - va_end(args); + va_end(args); #if defined(__has_feature) && __has_feature(objc_arc) - return string; + return string; #else - return [string autorelease]; + return [string autorelease]; #endif } - -@implementation NSString(Additions) +@implementation NSString (Additions) - (NSArray *)split { - NSArray *result = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - return [result select:^BOOL(NSString *string) { - return string.length > 0; - }]; + NSArray *result = + [self componentsSeparatedByCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + return [result select:^BOOL(NSString *string) { + return string.length > 0; + }]; } - (NSArray *)split:(NSString *)delimiter { - return [self componentsSeparatedByString:delimiter]; + return [self componentsSeparatedByString:delimiter]; } - (NSString *)camelCase { - NSString *spaced = [self stringByReplacingOccurrencesOfString:UNDERSCORE withString:SPACE]; - NSString *capitalized = [spaced capitalizedString]; + NSString *spaced = + [self stringByReplacingOccurrencesOfString:UNDERSCORE withString:SPACE]; + NSString *capitalized = [spaced capitalizedString]; - return [capitalized stringByReplacingOccurrencesOfString:SPACE withString:EMPTY_STRING]; + return [capitalized stringByReplacingOccurrencesOfString:SPACE + withString:EMPTY_STRING]; } - (NSString *)lowerCamelCase { - NSString *upperCamelCase = [self camelCase]; - NSString *firstLetter = [upperCamelCase substringToIndex:1]; - return [upperCamelCase stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstLetter.lowercaseString]; + NSString *upperCamelCase = [self camelCase]; + NSString *firstLetter = [upperCamelCase substringToIndex:1]; + return [upperCamelCase + stringByReplacingCharactersInRange:NSMakeRange(0, 1) + withString:firstLetter.lowercaseString]; } -- (BOOL)containsString:(NSString *) string { - NSRange range = [self rangeOfString:string options:NSCaseInsensitiveSearch]; - return range.location != NSNotFound; +- (BOOL)containsString:(NSString *)string { + NSRange range = [self rangeOfString:string options:NSCaseInsensitiveSearch]; + return range.location != NSNotFound; } - (NSString *)strip { - return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + return [self + stringByTrimmingCharactersInSet:[NSCharacterSet + whitespaceAndNewlineCharacterSet]]; } - (NSString *)match:(NSString *)pattern { - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil]; - NSTextCheckingResult *match = [regex firstMatchInString:self options:0 range:NSMakeRange(0, self.length)]; - - return (match != nil) ? [self substringWithRange:[match range]] : nil; + NSRegularExpression *regex = + [NSRegularExpression regularExpressionWithPattern:pattern + options:0 + error:nil]; + NSTextCheckingResult *match = + [regex firstMatchInString:self + options:0 + range:NSMakeRange(0, self.length)]; + + return (match != nil) ? [self substringWithRange:[match range]] : nil; +} + +- (id)toJSON { + NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; + id JSON = [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingAllowFragments + error:nil]; + return JSON; +} + +- (NSString *)language { + NSArray *tagschemes = + [NSArray arrayWithObjects:NSLinguisticTagSchemeLanguage, nil]; + NSLinguisticTagger *tagger = + [[NSLinguisticTagger alloc] initWithTagSchemes:tagschemes options:0]; + [tagger setString:self]; + NSString *language = [tagger tagAtIndex:0 + scheme:NSLinguisticTagSchemeLanguage + tokenRange:NULL + sentenceRange:NULL]; + return language; +} + +- (double)multipleLinesHeightForWidth:(double)width font:(UIFont *)font { + CGRect rect = [self boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) + options:NSStringDrawingUsesLineFragmentOrigin + attributes:@{ + NSFontAttributeName : font + } + context:nil]; + return rect.size.height; +} + +- (double)dynamicWidthForHeight:(double)height font:(UIFont *)font { + CGRect rect = [self boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, height) + options:NSStringDrawingUsesLineFragmentOrigin + attributes:@{ + NSFontAttributeName : font + } + context:nil]; + return rect.size.width; +} + +- (NSString *)unescapedUnicodeString { + NSString *string = [NSString stringWithString:self]; + // unescape quotes and backwards slash and slash + NSString *unescapedString = [string + stringByReplacingOccurrencesOfString:@"\\\"" + withString:@"\""]; /* Replaces \" + with " */ + unescapedString = [unescapedString + stringByReplacingOccurrencesOfString:@"\\\\" + withString:@"\\"]; /* Replaces \\ + with \ */ + unescapedString = + [unescapedString + stringByReplacingOccurrencesOfString:@"\\/" + withString:@"/"]; /* Replaces \/ + with / */ + + // tokenize based on unicode escape char + NSMutableString *tokenizedString = [NSMutableString string]; + NSScanner *scanner = [NSScanner scannerWithString:unescapedString]; + while ([scanner isAtEnd] == NO) { + // read up to the first unicode marker + // if a string has been scanned, it's a token + // and should be appended to the tokenized string + NSString *token = @""; + [scanner scanUpToString:@"\\u" intoString:&token]; + if (token != nil && token.length > 0) { + [tokenizedString appendString:token]; + continue; + } + + // skip two characters to get past the marker + // check if the range of unicode characters is + // beyond the end of the string (could be malformed) + // and if it is, move the scanner to the end + // and skip this token + NSUInteger location = [scanner scanLocation]; + NSInteger extra = scanner.string.length - location - 4 - 2; + if (extra < 0) { + NSRange range = {location, -extra}; + [tokenizedString appendString:[scanner.string substringWithRange:range]]; + [scanner setScanLocation:location - extra]; + continue; + } + + // move the location pas the unicode marker + // then read in the next 4 characters + location += 2; + NSRange range = {location, 4}; + token = [scanner.string substringWithRange:range]; + unichar codeValue = (unichar)strtol([token UTF8String], NULL, 16); + [tokenizedString appendString:[NSString stringWithFormat:@"%C", codeValue]]; + + // move the scanner past the 4 characters + // then keep scanning + location += 4; + [scanner setScanLocation:location]; + } + + // done + return tokenizedString; +} + +- (NSString *)escapedUnicodeString { + // lastly escaped quotes and back slash + // note that the backslash has to be escaped before the quote + // otherwise it will end up with an extra backslash + NSString *string = [NSString stringWithString:self]; + NSString *escapedString = + [string stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; + escapedString = [escapedString stringByReplacingOccurrencesOfString:@"\"" + withString:@"\\\""]; + + // convert to encoded unicode + // do this by getting the data for the string + // in UTF16 little endian (for network byte order) + NSData *data = + [escapedString dataUsingEncoding:NSUTF16LittleEndianStringEncoding + allowLossyConversion:YES]; + size_t bytesRead = 0; + const char *bytes = data.bytes; + NSMutableString *encodedString = [NSMutableString string]; + + // loop through the byte array + // read two bytes at a time, if the bytes + // are above a certain value they are unicode + // otherwise the bytes are ASCII characters + // the %C format will write the character value of bytes + while (bytesRead < data.length) { + uint16_t code = *((uint16_t *)&bytes[bytesRead]); + if (code > 0x007E) { + [encodedString appendFormat:@"\\u%04X", code]; + } else { + [encodedString appendFormat:@"%C", code]; + } + bytesRead += sizeof(uint16_t); + } + // done + return encodedString; +} + +- (NSString *)URLEncodedString { + __autoreleasing NSString *encodedString; + NSString *originalString = (NSString *)self; + encodedString = + (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes( + NULL, (__bridge CFStringRef)originalString, NULL, + (CFStringRef) @":!*();@/&?#[]+$,='%’\"", kCFStringEncodingUTF8); + return encodedString; +} + +- (NSString *)URLDecodedString { + __autoreleasing NSString *decodedString; + NSString *originalString = (NSString *)self; + decodedString = (__bridge_transfer NSString *) + CFURLCreateStringByReplacingPercentEscapesUsingEncoding( + NULL, (__bridge CFStringRef)originalString, CFSTR(""), + kCFStringEncodingUTF8); + return decodedString; +} + +- (NSString *)trim { + return [self + stringByTrimmingCharactersInSet:[NSCharacterSet + whitespaceAndNewlineCharacterSet]]; } @end