I'm making little utility to help me generate code for an app I'm making. I like to have constants for my NSUserDefaults settings, so that my code is more readable and easier to maintain. The problem is, that making constants for everything takes some time, so I'm trying to write a utility to generate code for me. I'd like to be able to enter a string and have it converted to camel case, like so:
- (NSString *)camelCaseFromString:(NSString *)input{
return inputAsCamelCase;
}
Now, the input string might be composed of multiple words. I'm assuming that I need some sort of regular expression here, or perhaps there is another way to do it. I'd like to input something like this:
#"scrolling direction"
or this:
#"speed of scrolling"
and get back something like this:
kScrollingDirection
or this:
kSpeedOfScrolling
How would you go about removing spaces and replacing the character following the space with the uppercase version?
- (NSString *)camelCaseFromString:(NSString *)input {
return [#"k" stringByAppendingString:[[input capitalizedString] stringByReplacingOccurrencesOfString:#" " withString:#""]];
}
Capitalize each word.
Remove whitespace.
Insert "k" at the beginning. (Not literally, but a simplification using stringByAppendingString.)
The currently accepted answer has a bug. (also pointed out by #jaydee3)
Words already having proper camelCasing, PascalCasing, or capitalized TLA acronyms will have their correct casing "destroyed" by having non-first characters lowercased via the call to capitalizedString.
So "I prefer camelCasing to PascalCasing for variables" would look like "iPreferCamelcasingToPascalcasingForVariables" but according to the question, should be "iPreferCamelCasingToPascalCasingForVariables"
Use the below category on NSString to create non-destructive camel and pascal casing. It also properly leaves ALL_CAPS words/acronyms in place, although that wasn't really part of the orig question.
An acronym as a first word for a camelCased string would be weird. "WTH" becomes "wTH" which looks weird. Anyway, that's an edge case, and not addressed. However, since question asker is prefixing with "k" then it "k IBM" becomes "kIBM" which looks ok to me, but would look be "kIbm" with currently accepted answer.
Use:
NSString *str = #"K computer manufacturer IBM";
NSLog(#"constant: %#", str.pascalCased);
// "constant: kComputerManufacturerIBM"
Category (class extension) code.
#implementation NSString (MixedCasing)
- (NSString *)camelCased {
NSMutableString *result = [NSMutableString new];
NSArray *words = [self componentsSeparatedByString: #" "];
for (uint i = 0; i < words.count; i++) {
if (i==0) {
[result appendString:((NSString *) words[i]).withLowercasedFirstChar];
}
else {
[result appendString:((NSString *)words[i]).withUppercasedFirstChar];
}
}
return result;
}
- (NSString *)pascalCased {
NSMutableString *result = [NSMutableString new];
NSArray *words = [self componentsSeparatedByString: #" "];
for (NSString *word in words) {
[result appendString:word.withUppercasedFirstChar];
}
return result;
}
- (NSString *)withUppercasedFirstChar {
if (self.length <= 1) {
return self.uppercaseString;
} else {
return [NSString stringWithFormat:#"%#%#",[[self substringToIndex:1] uppercaseString],[self substringFromIndex:1]];
}
}
- (NSString *)withLowercasedFirstChar {
if (self.length <= 1) {
return self.lowercaseString;
} else {
return [NSString stringWithFormat:#"%#%#",[[self substringToIndex:1] lowercaseString],[self substringFromIndex:1]];
}
}
#end
Just use: #"This is a sentence".capitalizedString;
Becomes: >> "This Is A Sentence"
Replace spaces and manipulate...
Uppercases first char of word, and lowers the other letters for each word.
Related
I am working with a Objective-C Application, specifically I am gathering the dictionary representation of NSUserDefaults with this code:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *userDefaultsDict = [defaults dictionaryRepresentation];
While enumerating keys and objects of the resulting dict, sometimes I find a kind of opaque string that you can see in the following picture:
So it seems like an encoding problem.
If I try to print description of the string, the debugger correctly prints:
Printing description of obj:
tsuqsx
However, if I try to write obj to a file, or use it in any other way, I get an unreadable output like this:
What I would like to achieve is the following:
Detect in some way that the string has the encoding problem.
Convert the string to UTF8 encoding to use it in the rest of the program.
Any help is greatly appreciated. Thanks
EDIT: Very Hacky possible Solution that helps explaining what I am trying to do.
After trying all possible solutions based on dataUsingEncoding and back, I ended up with the following solution, absolutely weird, but I post it here, in the hope that it can help somebody to guess the encoding and what to do with unprintable characters:
- (BOOL)isProblematicString:(NSString *)candidateString {
BOOL returnValue = YES;
if ([candidateString length] <= 2) {
return NO;
}
const char *temp = [candidateString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
if (ctr != 1 && ctr < [candidateString length]) {
if (temp[ctr] < 0x10 || temp[ctr] > 0x1F) {
returnValue = NO;
}
}
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
free(dest);
return returnValue;
}
- (NSString *)utf8StringFromUnknownEncodedString:(NSString*)originalUnknownString {
const char *temp = [originalUnknownString UTF8String];
long length = temp[0];
char *dest = malloc(length + 1);
long ctr = 1;
long usefulCounter = 0;
for (ctr = 1;ctr <= length;ctr++) {
if ((ctr - 1) % 3 == 0) {
memcpy(&dest[ctr - usefulCounter - 1],&temp[ctr],1);
} else {
usefulCounter += 1;
}
}
memset(&dest[length],0,1);
NSString *returnValue = [[NSString alloc] initWithUTF8String:dest];
free(dest);
return returnValue;
}
This returns me a string that I can use to build a full UTF8 string. I am looking for a clean solution. Any help is greatly appreciated. Thanks
We're talking about a string which comes from the /Library/Preferences/.GlobalPreferences.plist
(key com.apple.preferences.timezone.new.selected_city).
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
NSLog(#"%#", city); // \^Zt\^\\^]s\^]\^\u\^V\^_q\^]\^[s\^W\^Zx\^P
(lldb) p [city description]
(__NSCFString *) $1 = 0x0000600003f6c240 #"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10"
What I would like to achieve is the following:
Detect in some way that the string has the encoding problem.
Convert the string to UTF8 encoding to use it in the rest of the program.
&
After trying all possible solutions based on dataUsingEncoding and back.
This string has no encoding problem and characters like \x1a, \x1c, ... are valid characters.
You can call dataUsingEncoding: with ASCII, UTF-8, ... but all these characters will still be
present. They're called control characters (or non-printing characters). The linked Wikipedia page explains what these characters are and how they're defined in ASCII, extended ASCII and unicode.
What you're looking for is a way how to remove control characters from a string.
Remove control characters
We can create a category for our new method:
#interface NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters;
#end
#implementation NSString (ControlCharacters)
- (NSString *)stringByRemovingControlCharacters {
// TODO Remove control characters
return self;
}
#end
In all examples below, the city variable is created in this way ...
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
... and contains #"\x1at\x1c\x1ds\x1d\x1cu\x16\x1fq\x1d\x1bs\x17\x1ax\x10". Also all
examples below were tested with the following code:
NSString *cityWithoutCC = [city stringByRemovingControlCharacters];
// tsuqsx
NSLog(#"%#", cityWithoutCC);
// {length = 6, bytes = 0x747375717378}
NSLog(#"%#", [cityWithoutCC dataUsingEncoding:NSUTF8StringEncoding]);
Split & join
One way is to utilize the NSCharacterSet.controlCharacterSet.
There's a stringByTrimmingCharactersInSet:
method (NSString), but it removes these characters from the beginning/end only,
which is not what you're looking for. There's a trick you can use:
- (NSString *)stringByRemovingControlCharacters {
NSArray<NSString *> *components = [self componentsSeparatedByCharactersInSet:NSCharacterSet.controlCharacterSet];
return [components componentsJoinedByString:#""];
}
It splits the string by control characters and then joins these components back. Not a very efficient way, but it works.
ICU transform
Another way is to use ICU transform (see ICU User Guide).
There's a stringByApplyingTransform:reverse:
method (NSString), but it only accepts predefined constants. Documentation says:
The constants defined by the NSStringTransform type offer a subset of the functionality provided by the underlying ICU transform functionality. To apply an ICU transform defined in the ICU User Guide that doesn't have a corresponding NSStringTransform constant, create an instance of NSMutableString and call the applyTransform:reverse:range:updatedRange: method instead.
Let's update our implementation:
- (NSString *)stringByRemovingControlCharacters {
NSMutableString *result = [self mutableCopy];
[result applyTransform:#"[[:Cc:] [:Cf:]] Remove"
reverse:NO
range:NSMakeRange(0, self.length)
updatedRange:nil];
return result;
}
[:Cc:] represents control characters, [:Cf:] represents format characters. Both represents the same character set as the already mentioned NSCharacterSet.controlCharacterSet. Documentation:
A character set containing the characters in Unicode General Category Cc and Cf.
Iterate over characters
NSCharacterSet also offers the characterIsMember: method. Here we need to iterate over characters (unichar) and check if it's a control character or not.
Let's update our implementation:
- (NSString *)stringByRemovingControlCharacters {
if (self.length == 0) {
return self;
}
NSUInteger length = self.length;
unichar characters[length];
[self getCharacters:characters];
NSUInteger resultLength = 0;
unichar result[length];
NSCharacterSet *controlCharacterSet = NSCharacterSet.controlCharacterSet;
for (NSUInteger i = 0 ; i < length ; i++) {
if ([controlCharacterSet characterIsMember:characters[i]] == NO) {
result[resultLength++] = characters[i];
}
}
return [NSString stringWithCharacters:result length:resultLength];
}
Here we filter out all characters (unichar) which belong to the controlCharacterSet.
Other ways
There're other ways how to iterate over characters - for example - Most efficient way to iterate over all the chars in an NSString.
BBEdit & others
Let's write this string to a file:
NSString *city = [[NSUserDefaults standardUserDefaults]
stringForKey:#"com.apple.preferences.timezone.new.selected_city"];
[city writeToFile:#"/Users/zrzka/city.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
It's up to the editor how all these controls characters are handled/displayed. Here's en example - Visual Studio Code.
View - Render Control Characters off:
View - Render Control Characters on:
BBEdit displays question marks (upside down), but I'm sure there's a way how to
toggle control characters rendering. Don't have BBEdit installed to verify it.
I'm trying to write Custom Keyboard Extension.
I'm looking for the way to know where the cursor is on UITextField,UITextView...etc in CustomKeyboardExtension ... but I don't see anything like that.
I saw SwiftKey app (http://swiftkey.com) can do that (or do something like that). When I change the cursor, suggestion-text will change (see below pictures).
Q: How can we get current text selection?
...
UPDATE: 29/09/2014
Ok, I'm so foolish. We can use documentContextBeforeInput, documentContextAfterInput methods of textDocumentProxy property. I thought that "Before","After" are about the time. Actually it's about the position.
Sorry all! I wasted your time :(
Create lastWordBeforeInput method...
-(NSString *) lastWordBeforeInput{
NSArray *arrayOfSplitsString = [self.textDocumentProxy.documentContextBeforeInput componentsSeparatedByString:#" "];
int countIndex = arrayOfSplitsString.count - 1;
NSCharacterSet *ChSet = [NSCharacterSet alphanumericCharacterSet];
NSCharacterSet *invertedChSet = [ChSet invertedSet];
while (countIndex > 0) {
NSString *lastWordOfSentance = [arrayOfSplitsString objectAtIndex:countIndex--];
if ([[lastWordOfSentance stringByTrimmingCharactersInSet:invertedChSet] rangeOfCharacterFromSet:ChSet].location != NSNotFound) {
return [lastWordOfSentance stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
}
return #"";
}
Then call it with textWillChange/textDidChange as per requirement.
- (void)textWillChange:(id<UITextInput>)textInput {
// The app is about to change the document's contents. Perform any preparation here.
NSLog(#"%#",[self lastWordBeforeInput]);
}
Hope this will help you.
I'm quite new to Cocoa programming and I'm tiring my best to create a program which will have the user input text into a text field and then press a button. When the button is pressed the text is supposed to replace certain substrings to certain characters. None of the substrings are longer than 2 characters, though some are a single character long. After the replacement has been performed the newly acquired text is to be put into another textfield.
Examples of substring replacements may be that "n" is supposed to be changed to "5", "nj" is supposed to be changed to "g" and "ng" is to be changed to "s". So the text "Inject the syringe now!" would be changed to "Igect the syrise 5ow!"
How can I achieve this in a simple and elegant way? I have tried the following code but it doesn't seem to work.
- (IBAction)convert:(id)sender {
NSMutableString *x;
[x setString:[self.input stringValue]];
NSMutableString *output1;
[output1 setString:#""];
NSMutableString *middle;
middle = [[NSMutableString alloc] init];
int s;
unsigned long length = [x length];
for (s = 0; s < length; s = s + 1) {
if (s + 2 <= length) { // if more than or equal to two characters left
[middle setString:[x substringWithRange:NSMakeRange(s, 2)]];
if ([middle isEqualToString:#"nj"]) {
[output1 appendToString:#"g"];
s = s+1;
} else if ([middle isEqualToString:#"ng"]) {
[output1 appendToString:#"s"];
s = s+1;
} else { // if no two-character sequence matched
[middle setString:[x substringWithRange:NSMakeRange(s, 1)]];
if ([middle isEqualToString:#"n"]) {
[output1 appendString:#"5"];
}
}
} else { // if less than two characters left
[middle setString:[x substringWithRange:NSMakeRange(s, 1)]];
if ([middle isEqualToString:#"n"]) {
[output1 appendString:#"5"];
}
}
}
[self.output setStringValue:output1];
}
Here, *x is where the text from input goes, *output1 is where the result is stored, *middle consists of the piece of text being tested, and input and output are the NSTextFields.
I guess you could achieve what you want with a quite a few different methods. Here is a simple one:
Define a map for values/replacements
Sort them by length (largest length first)
Match and replace
Something like this perhaps:
#import <Foundation/Foundation.h>
NSString * matchAndReplace(NSString *input, NSDictionary *map){
NSMutableString *_input = [input mutableCopy];
// Get all keys sorted by greatest length
NSArray *keys = [map.allKeys sortedArrayUsingComparator: ^(NSString *key1, NSString *key2) {
return [#(key2.length) compare:#(key1.length)];
}];
for (NSString *key in keys) {
[_input replaceOccurrencesOfString:key
withString:map[key]
options:NSLiteralSearch
range:NSMakeRange(0,_input.length)];
}
return [_input copy];
};
int main(int argc, char *argv[]) {
#autoreleasepool {
NSDictionary *mapping = #{
#"n": #"5",
#"nj": #"g",
#"ng": #"s"
};
NSString *input = #"Inject the syringe now!";
NSLog(#"Output: %#", matchAndReplace(input, mapping));
}
}
Which will produce:
Output: Igect the syrise 5ow!
Note: This is an over-simplified way to achieve what you want (obviously) and maybe requires a few adjustments to cover every edge case, but it's simpler than your version and I hope that will be helpful to you.
This has given me quite a big headache. For whatever reason, when I use this code, the if statement always evaluates to false:
while(!feof(file))
{
NSString *line = [self readNSString:file];
NSLog(#"%#", line);
NSLog(#"%#", search);
NSRange textRange;
textRange =[line rangeOfString:search];
if(textRange.location != NSNotFound)
{
NSString *result = [line substringFromIndex:NSMaxRange([line rangeOfString:search])];
resultView.text = result;
}
else
{
resultView.text = #"Not found";
}
}
When the functions execute, the two NSLogs tell me that the "line" and "search" strings are what they should be, so then why does the if statement always evaluate to false? I must be missing something simple, having another set of eyes would be great. Thanks
edit: (function "readNSString")
- (NSString*)readNSString:(FILE*) file
{
char buffer[300];
NSMutableString *result = [NSMutableString stringWithCapacity:256];
int read;
do
{
if(fscanf(file, "%299[^\n]%n%*c", buffer, &read) == 1)
[result appendFormat:#"%s", buffer];
else
break;
} while(r == 299);
return result;
}
edit 2:
search is set with a call to the first function, with an NSString* variable as a parameter, like this:
NSString *textFieldText = [[NSString alloc]
initWithFormat:#"%#", textField.text];
[self readFile:textFieldText];
edit 3 (NSLogs output)
line: Germany Italy France
search: Italy
I think that you are using the rangeOfString and the NSNotFound etc. correctly, so the problem is possibly to do with the creation of the string from the data read from the file using the appendFormat:#"%s".
I suspect there may be an encoding issue between your two string formats - I would investigate whether the "%s" encodes the null terminated C string properly into the same format as a unicode NSString with the appropriate encoding.
Try hard coding the value you are getting from the readNSString function as a string literal in code just for testing and see if that comparison works, if so this would tend to indicate it probably is something to do with the encoding of the string created from the file.
Hey folks, beneath is a piece of code i used for a school assignment.
Whenever I enter a word, with an O in it (which is a capital o), it fails!
Whenever there is one or more capital O's in this program, it returns false and logs : sentence not a palindrome.
A palindrome, for the people that dont know what a palindrome is, is a word that is the same read left from right, and backwards. (e.g. lol, kayak, reviver etc)
I found this bug when trying to check the 'oldest' palindrome ever found: SATOR AREPO TENET OPERA ROTAS.
When I change all the capital o's to lowercase o's, it works, and returns true.
Let me state clearly, with this piece of code ALL sentences/words with capital O's return false. A single capital o is enough to fail this program.
-(BOOL)testForPalindrome:(NSString *)s position:(NSInteger)pos {
NSString *string = s;
NSInteger position = pos;
NSInteger stringLength = [string length];
NSString *charOne = [string substringFromIndex:position];
charOne = [charOne substringToIndex:1];
NSString *charTwo = [string substringFromIndex:(stringLength - 1 - position)];
charTwo = [charTwo substringToIndex:1];
if(position > (stringLength / 2)) {
NSString *printableString = [NSString stringWithFormat:#"De following word or sentence is a palindrome: \n\n%#", string];
NSLog(#"%# is a palindrome.", string);
[textField setStringValue:printableString];
return YES;
}
if(charOne != charTwo) {
NSLog(#"%#, %#", charOne, charTwo);
NSLog(#"%i", position);
NSLog(#"%# is not a palindrome.", string);
return NO;
}
return [self testForPalindrome:string position:position+1];
}
So, is this some weird bug in Cocoa?
Or am I missing something?
B
This of course is not a bug in Cocoa, as you probably knew deep down inside.
Your compare method is causing this 'bug in Cocoa', you're comparing the addresses of charOne and charTwo. Instead you should compare the contents of the string with the isEqualToString message.
Use:
if(![charOne isEqualToString:charTwo]) {
Instead of:
if(charOne != charTwo) {
Edit: tested it in a test project and can confirm this is the problem.
Don't use charOne != charTwo
Instead use one of the NSString Compare Methods.
if ([charOne caseInsensitiveCompare:charTwo] != NSOrderedSame)
It may also have to do with localization (but I doubt it).