Why would subclassing NSFormatter prevent me from editing NSTextField's input? - objective-c

I'm trying to format two NSTextFields for SMPTE time codes, which have the format:
HH:MM:SS:FF. However, when the user switches the SMPTE code to drop-frame, the delimiter between SS and FF needs to switch to a ; (HH:MM:SS;FF). To do this, I've subclassed NSFormatter, and have it mostly working except for one very stubborn problem.
The text field accepts input just fine, but if I highlight-replace, backspace, delete, or insert any new characters into the text field, I get an NSBeep and I can't switch focus away from the text field. I can input new text if I delete the whole text field first, but not if I try to edit the existing input. Here are my implemented methods/overrides:
- (NSString*)stringForObjectValue:(id)obj
{
if ( ! [obj isKindOfClass:[NSNumber class]])
{
return nil;
}
NSMutableString *string = [NSMutableString stringWithString:#"00:00:00:00"];
int length = (int)[[obj stringValue] length];
int insertLocation = 9;
if (length == 1)
{
[string replaceCharactersInRange:NSMakeRange(10, 1) withString:[obj stringValue]];
}
else
{
while (length > 1)
{
NSString *temp = [[obj stringValue] substringFromIndex:length-2];
[string replaceCharactersInRange:NSMakeRange(insertLocation, 2) withString:temp];
obj = [NSNumber numberWithInt:[obj intValue]/100];
length -= 2;
insertLocation -= 3;
}
if (length == 1)
{
[string replaceCharactersInRange:NSMakeRange(insertLocation+1, 1) withString:[obj stringValue]];
}
}
return string;
}
- (BOOL)getObjectValue:(out __autoreleasing id *)obj forString:(NSString *)string errorDescription:(out NSString *__autoreleasing *)error
{
int valueResult;
NSScanner *scanner;
BOOL returnValue = NO;
scanner = [NSScanner scannerWithString: string];
[scanner scanString:#":" intoString:NULL];
[scanner scanString:#";" intoString:NULL];
if ([scanner scanInt:&valueResult] && ([scanner isAtEnd])) {
returnValue = YES;
if (obj)
{
*obj = [NSNumber numberWithInt:valueResult];
}
}
return returnValue;
}
At least at this point, I don't need to validate the input during editing, only when editing is finished. I tried implementing isPartialStringValid and just returning YES, but that didn't seem to help either. Any help would be greatly appreciated!

Ok, I just solved it by doing some more testing. It appears as though why it was failing is because getObjectValue was receiving the string with the delimiters in them and was not correctly removing them. I simply replaced the method with this:
- (BOOL)getObjectValue:(out __autoreleasing id *)obj forString:(NSString *)string errorDescription:(out NSString *__autoreleasing *)error
{
NSString *newString = [string stringByReplacingOccurrencesOfString:#":" withString:#""];
if (obj)
{
*obj = [NSNumber numberWithInt:[newString intValue]];
return YES;
}
return NO;
}
Now it works perfectly.

Related

run applescript from cocoa app stopped working

This code had been working fine until just recently. I hadn't' changed anything nor upgraded my system and I'm completely flummoxed.
I've been using it for 6 years and now it dies on me.
Is there an easier or better way of running an applescript from within a cocoa application? At this point I'm happy to pay to fix this problem!
utils.h
#import <Foundation/Foundation.h>
#interface Utils : NSObject
// Runs an applescript with a given map of variables (name/value)
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables;
// Runs an applescript from a file pathwith a given map of variables
// (name/value)
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables;
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor;
// String is empty or only has white characters (space, tab...)
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string;
#end
Utils.M
#import "Utils.h"
#implementation Utils
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor {
// Enumerate the apple descriptors (lists) returned by the applescript and
// make them into arrays
NSMutableArray *returnArray = [NSMutableArray array];
NSInteger counter, count = [descriptor numberOfItems];
for (counter = 1; counter <= count; counter++) {
NSAppleEventDescriptor *desc = [descriptor descriptorAtIndex:counter];
if (nil != [desc descriptorAtIndex:1]) {
[returnArray addObject:[Utils arrayFromDescriptor:desc]];
} else {
NSString *stringValue = [[descriptor descriptorAtIndex:counter] stringValue];
if (nil != stringValue) {
[returnArray addObject:stringValue];
} else {
[returnArray addObject:#""];
}
}
}
return returnArray;
}
+ (NSString *)escapeCharacters:(NSString *)string {
return [string stringByReplacingOccurrencesOfString:#"\"" withString:#"\\\""];
}
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables {
NSString *input = #"";
NSArray *variableNames = [variables allKeys];
// Transform the dictionary of names/values to set sentences of applescript
for (NSString *variableName in variableNames) {
NSObject *variableValue = [variables objectForKey:variableName];
if ([variableValue isKindOfClass:[NSString class]]) {
input =
[input stringByAppendingString:[NSString stringWithFormat:#"set %# to (\"%#\" as text)\n", variableName,
[Utils escapeCharacters:variableValue], nil]];
} else if ([variableValue isKindOfClass:[NSNumber class]]) {
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to (%# as integer)\n",
variableName, variableValue, nil]];
} else if ([variableValue isKindOfClass:[NSArray class]]) {
// Initialize a list
NSString *entry;
NSArray *values = (NSArray *)variableValue;
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to {", variableName]];
BOOL first = TRUE;
for (entry in values) {
if (!first) {
input = [input stringByAppendingString:#", "];
}
input = [input
stringByAppendingString:[NSString stringWithFormat:#"\"%#\"", [Utils escapeCharacters:entry], nil]];
first = FALSE;
}
input = [input stringByAppendingString:#"}\n"];
}
}
NSString *finalScript = [input stringByAppendingString:[NSString stringWithFormat:#"\n\n%#", source]];
NSLog(#"Final script: %#", finalScript);
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:finalScript];
NSDictionary *error;
NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&error];
NSLog(#"applescript error: %#", [error description]);
// Transform the return value of applescript to nested nsarrays
return [Utils arrayFromDescriptor:descriptor];
}
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables {
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:scriptName ofType:#"applescript"];
NSString *scriptSource =
[[NSString alloc] initWithContentsOfFile:scriptPath encoding:NSASCIIStringEncoding error:nil];
return [Utils runApplescript:scriptSource withVariables:variables];
}
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string {
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [string isEqualToString:#""];
}
#end
Easier, yes, although whether that’s your actual problem is another question.
http://appscript.sourceforge.net/asoc.html
I assume you’ve already got other details, including sandboxing and hardening settings and plist entries, taken care of. (Recent Xcode upgrades also had a habit of breaking it when auto-upgrading your project files, by turning on hardening for you so Apple events can’t get out.)

obj-c dictionary to url parameter string [duplicate]

With all the URL-handling objects lying around in the standard Cocoa libraries (NSURL, NSMutableURL, NSMutableURLRequest, etc), I know I must be overlooking an easy way to programmatically compose a GET request.
Currently I'm manually appending "?" followed by name value pairs joined by "&", but all of my name and value pairs need to be manually encoded so NSMutableURLRequest doesn't fail entirely when it tries to connect to the URL.
This feels like something I should be able to use a pre-baked API for.... is there anything out of the box to append an NSDictionary of query parameters to an NSURL? Is there another way I should approach this?
Introduced in iOS8 and OS X 10.10 is NSURLQueryItem, which can be used to build queries. From the docs on NSURLQueryItem:
An NSURLQueryItem object represents a single name/value pair for an item in the query portion of a URL. You use query items with the queryItems property of an NSURLComponents object.
To create one use the designated initializer queryItemWithName:value: and then add them to NSURLComponents to generate an NSURL. For example:
NSURLComponents *components = [NSURLComponents componentsWithString:#"http://stackoverflow.com"];
NSURLQueryItem *search = [NSURLQueryItem queryItemWithName:#"q" value:#"ios"];
NSURLQueryItem *count = [NSURLQueryItem queryItemWithName:#"count" value:#"10"];
components.queryItems = #[ search, count ];
NSURL *url = components.URL; // http://stackoverflow.com?q=ios&count=10
Notice that the question mark and ampersand are automatically handled. Creating an NSURL from a dictionary of parameters is as simple as:
NSDictionary *queryDictionary = #{ #"q": #"ios", #"count": #"10" };
NSMutableArray *queryItems = [NSMutableArray array];
for (NSString *key in queryDictionary) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:key value:queryDictionary[key]]];
}
components.queryItems = queryItems;
I've also written a blog post on how to build URLs with NSURLComponents and NSURLQueryItems.
You can create a category for NSDictionary to do this -- there isn't a standard way in the Cocoa library that I could find either. The code that I use looks like this:
// file "NSDictionary+UrlEncoding.h"
#import <cocoa/cocoa.h>
#interface NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString;
#end
with this implementation:
// file "NSDictionary+UrlEncoding.m"
#import "NSDictionary+UrlEncoding.h"
// helper function: get the string form of any object
static NSString *toString(id object) {
return [NSString stringWithFormat: #"%#", object];
}
// helper function: get the url encoded string form of any object
static NSString *urlEncode(id object) {
NSString *string = toString(object);
return [string stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
}
#implementation NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString {
NSMutableArray *parts = [NSMutableArray array];
for (id key in self) {
id value = [self objectForKey: key];
NSString *part = [NSString stringWithFormat: #"%#=%#", urlEncode(key), urlEncode(value)];
[parts addObject: part];
}
return [parts componentsJoinedByString: #"&"];
}
#end
I think the code's pretty straightforward, but I discuss it in some more detail at http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html.
I wanted to use Chris's answer, but it wasn't written for Automatic Reference Counting (ARC) so I updated it. I thought I'd paste my solution in case anyone else has this same issue. Note: replace self with the instance or class name where appropriate.
+(NSString*)urlEscapeString:(NSString *)unencodedString
{
CFStringRef originalStringRef = (__bridge_retained CFStringRef)unencodedString;
NSString *s = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,originalStringRef, NULL, (CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ", kCFStringEncodingUTF8);
CFRelease(originalStringRef);
return s;
}
+(NSString*)addQueryStringToUrlString:(NSString *)urlString withDictionary:(NSDictionary *)dictionary
{
NSMutableString *urlWithQuerystring = [[NSMutableString alloc] initWithString:urlString];
for (id key in dictionary) {
NSString *keyString = [key description];
NSString *valueString = [[dictionary objectForKey:key] description];
if ([urlWithQuerystring rangeOfString:#"?"].location == NSNotFound) {
[urlWithQuerystring appendFormat:#"?%#=%#", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
} else {
[urlWithQuerystring appendFormat:#"&%#=%#", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
}
}
return urlWithQuerystring;
}
The other answers work great if the values are strings, however if the values are dictionaries or arrays then this code will handle that.
Its important to note that there is no standard way of passing an array/dictionary via the query string but PHP handles this output just fine
-(NSString *)serializeParams:(NSDictionary *)params {
/*
Convert an NSDictionary to a query string
*/
NSMutableArray* pairs = [NSMutableArray array];
for (NSString* key in [params keyEnumerator]) {
id value = [params objectForKey:key];
if ([value isKindOfClass:[NSDictionary class]]) {
for (NSString *subKey in value) {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)[value objectForKey:subKey],
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#[%#]=%#", key, subKey, escaped_value]];
}
} else if ([value isKindOfClass:[NSArray class]]) {
for (NSString *subValue in value) {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)subValue,
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#[]=%#", key, escaped_value]];
}
} else {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)[params objectForKey:key],
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, escaped_value]];
[escaped_value release];
}
}
return [pairs componentsJoinedByString:#"&"];
}
Examples
[foo] => bar
[translations] =>
{
[one] => uno
[two] => dos
[three] => tres
}
foo=bar&translations[one]=uno&translations[two]=dos&translations[three]=tres
[foo] => bar
[translations] =>
{
uno
dos
tres
}
foo=bar&translations[]=uno&translations[]=dos&translations[]=tres
I refactored and converted to ARC answer by AlBeebe
- (NSString *)serializeParams:(NSDictionary *)params {
NSMutableArray *pairs = NSMutableArray.array;
for (NSString *key in params.keyEnumerator) {
id value = params[key];
if ([value isKindOfClass:[NSDictionary class]])
for (NSString *subKey in value)
[pairs addObject:[NSString stringWithFormat:#"%#[%#]=%#", key, subKey, [self escapeValueForURLParameter:[value objectForKey:subKey]]]];
else if ([value isKindOfClass:[NSArray class]])
for (NSString *subValue in value)
[pairs addObject:[NSString stringWithFormat:#"%#[]=%#", key, [self escapeValueForURLParameter:subValue]]];
else
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, [self escapeValueForURLParameter:value]]];
}
return [pairs componentsJoinedByString:#"&"];
}
- (NSString *)escapeValueForURLParameter:(NSString *)valueToEscape {
return (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) valueToEscape,
NULL, (CFStringRef) #"!*'();:#&=+$,/?%#[]", kCFStringEncodingUTF8);
}
If you are already using AFNetworking (as was the case with me), you can use it's class AFHTTPRequestSerializer to create the required NSURLRequest.
[[AFHTTPRequestSerializer serializer] requestWithMethod:#"GET" URLString:#"YOUR_URL" parameters:#{PARAMS} error:nil];
In case you only require the URL for your work, use NSURLRequest.URL.
Here is a simple example in Swift (iOS8+):
private let kSNStockInfoFetchRequestPath: String = "http://dev.markitondemand.com/Api/v2/Quote/json"
private func SNStockInfoFetchRequestURL(symbol:String) -> NSURL? {
if let components = NSURLComponents(string:kSNStockInfoFetchRequestPath) {
components.queryItems = [NSURLQueryItem(name:"symbol", value:symbol)]
return components.URL
}
return nil
}
I took Joel's recommendation of using URLQueryItems and turned into a Swift Extension (Swift 3)
extension URL
{
/// Creates an NSURL with url-encoded parameters.
init?(string : String, parameters : [String : String])
{
guard var components = URLComponents(string: string) else { return nil }
components.queryItems = parameters.map { return URLQueryItem(name: $0, value: $1) }
guard let url = components.url else { return nil }
// Kinda redundant, but we need to call init.
self.init(string: url.absoluteString)
}
}
(The self.init method is kinda cheesy, but there was no NSURL init with components)
Can be used as
URL(string: "http://www.google.com/", parameters: ["q" : "search me"])
I've got another solution:
http://splinter.com.au/build-a-url-query-string-in-obj-c-from-a-dict
+(NSString*)urlEscape:(NSString *)unencodedString {
NSString *s = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)unencodedString,
NULL,
(CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ",
kCFStringEncodingUTF8);
return [s autorelease]; // Due to the 'create rule' we own the above and must autorelease it
}
// Put a query string onto the end of a url
+(NSString*)addQueryStringToUrl:(NSString *)url params:(NSDictionary *)params {
NSMutableString *urlWithQuerystring = [[[NSMutableString alloc] initWithString:url] autorelease];
// Convert the params into a query string
if (params) {
for(id key in params) {
NSString *sKey = [key description];
NSString *sVal = [[params objectForKey:key] description];
// Do we need to add ?k=v or &k=v ?
if ([urlWithQuerystring rangeOfString:#"?"].location==NSNotFound) {
[urlWithQuerystring appendFormat:#"?%#=%#", [Http urlEscape:sKey], [Http urlEscape:sVal]];
} else {
[urlWithQuerystring appendFormat:#"&%#=%#", [Http urlEscape:sKey], [Http urlEscape:sVal]];
}
}
}
return urlWithQuerystring;
}
You can then use it like so:
NSDictionary *params = #{#"username":#"jim", #"password":#"abc123"};
NSString *urlWithQuerystring = [self addQueryStringToUrl:#"https://myapp.com/login" params:params];
-(NSString*)encodeDictionary:(NSDictionary*)dictionary{
NSMutableString *bodyData = [[NSMutableString alloc]init];
int i = 0;
for (NSString *key in dictionary.allKeys) {
i++;
[bodyData appendFormat:#"%#=",key];
NSString *value = [dictionary valueForKey:key];
NSString *newString = [value stringByReplacingOccurrencesOfString:#" " withString:#"+"];
[bodyData appendString:newString];
if (i < dictionary.allKeys.count) {
[bodyData appendString:#"&"];
}
}
return bodyData;
}
Yet another solution, if you use RestKit there's a function in RKURLEncodedSerialization called RKURLEncodedStringFromDictionaryWithEncoding that does exactly what you want.
Simple way of converting NSDictionary to url query string in Objective-c
Ex: first_name=Steve&middle_name=Gates&last_name=Jobs&address=Palo Alto, California
NSDictionary *sampleDictionary = #{#"first_name" : #"Steve",
#"middle_name" : #"Gates",
#"last_name" : #"Jobs",
#"address" : #"Palo Alto, California"};
NSMutableString *resultString = [NSMutableString string];
for (NSString* key in [sampleDictionary allKeys]){
if ([resultString length]>0)
[resultString appendString:#"&"];
[resultString appendFormat:#"%#=%#", key, [sampleDictionary objectForKey:key]];
}
NSLog(#"QueryString: %#", resultString);
Hope will help :)
If you are already using AFNetwork, you can use their built in serializer to to produce an encoded URL;
NSString *baseURL = #"https://api.app.com/parse";
NSDictionary *mutableParameters = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"true",#"option1", data, #"option2", token, #"token", #"3.0", #"app", nil];
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"GET" URLString:baseURL parameters:mutableParameters error:nil];
NSString *urlPath = request.URL.absoluteString;
NSLog(#"%#", urlPath); // https://api.app.com/parse?option1=true&option2=datavalue&token=200%3ATEST%3AENCODE ....
Note; this is an extension to an above answer. The edit queue is full so cannot be added to the existing answer.

Check that a input to UITextField is numeric only

How do I validate the string input to a UITextField? I want to check that the string is numeric, including decimal points.
You can do it in a few lines like this:
BOOL valid;
NSCharacterSet *alphaNums = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *inStringSet = [NSCharacterSet characterSetWithCharactersInString:myInputField.text];
valid = [alphaNums isSupersetOfSet:inStringSet];
if (!valid) // Not numeric
-- this is for validating input is numeric chars only. Look at the documentation for NSCharacterSet for the other options. You can use characterSetWithCharactersInString to specify any set of valid input characters.
There are a few ways you could do this:
Use NSNumberFormatter's numberFromString: method. This will return an NSNumber if it can parse the string correctly, or nil if it cannot.
Use NSScanner
Strip any non-numeric character and see if the string still matches
Use a regular expression
IMO, using something like -[NSString doubleValue] wouldn't be the best option because both #"0.0" and #"abc" will have a doubleValue of 0. The *value methods all return 0 if they're not able to convert the string properly, so it would be difficult to distinguish between a legitimate string of #"0" and a non-valid string. Something like C's strtol function would have the same issue.
I think using NSNumberFormatter would be the best option, since it takes locale into account (ie, the number #"1,23" in Europe, versus #"1.23" in the USA).
I use this code in my Mac app, the same or similar should work with the iPhone. It's based on the RegexKitLite regular expressions and turns the text red when its invalid.
static bool TextIsValidValue( NSString* newText, double &value )
{
bool result = false;
if ( [newText isMatchedByRegex:#"^(?:|0|[1-9]\\d*)(?:\\.\\d*)?$"] ) {
result = true;
value = [newText doubleValue];
}
return result;
}
- (IBAction) doTextChanged:(id)sender;
{
double value;
if ( TextIsValidValue( [i_pause stringValue], value ) ) {
[i_pause setTextColor:[NSColor blackColor]];
// do something with the value
} else {
[i_pause setTextColor:[NSColor redColor]];
}
}
If you want a user to only be allowed to enter numerals, you can make your ViewController implement part of UITextFieldDelegate and define this method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *resultingString = [textField.text stringByReplacingCharactersInRange: range withString: string];
// The user deleting all input is perfectly acceptable.
if ([resultingString length] == 0) {
return true;
}
NSInteger holder;
NSScanner *scan = [NSScanner scannerWithString: resultingString];
return [scan scanInteger: &holder] && [scan isAtEnd];
}
There are probably more efficient ways, but I find this a pretty convenient way. And the method should be readily adaptable to validating doubles or whatever: just use scanDouble: or similar.
#pragma mark - UItextfield Delegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ([string isEqualToString:#"("]||[string isEqualToString:#")"]) {
return TRUE;
}
NSLog(#"Range ==%d ,%d",range.length,range.location);
//NSRange *CURRANGE = [NSString rangeOfString:string];
if (range.location == 0 && range.length == 0) {
if ([string isEqualToString:#"+"]) {
return TRUE;
}
}
return [self isNumeric:string];
}
-(BOOL)isNumeric:(NSString*)inputString{
BOOL isValid = NO;
NSCharacterSet *alphaNumbersSet = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *stringSet = [NSCharacterSet characterSetWithCharactersInString:inputString];
isValid = [alphaNumbersSet isSupersetOfSet:stringSet];
return isValid;
}
Here are a few one-liners which combine Peter Lewis' answer above (Check that a input to UITextField is numeric only) with NSPredicates
#define REGEX_FOR_NUMBERS #"^([+-]?)(?:|0|[1-9]\\d*)(?:\\.\\d*)?$"
#define REGEX_FOR_INTEGERS #"^([+-]?)(?:|0|[1-9]\\d*)?$"
#define IS_A_NUMBER(string) [[NSPredicate predicateWithFormat:#"SELF MATCHES %#", REGEX_FOR_NUMBERS] evaluateWithObject:string]
#define IS_AN_INTEGER(string) [[NSPredicate predicateWithFormat:#"SELF MATCHES %#", REGEX_FOR_INTEGERS] evaluateWithObject:string]
For integer test it'll be:
- (BOOL) isIntegerNumber: (NSString*)input
{
return [input integerValue] != 0 || [input isEqualToString:#"0"];
}
You can use the doubleValue of your string like
NSString *string=#"1.22";
double a=[string doubleValue];
i think this will return a as 0.0 if the string is invalid (it might throw an exception, in which case you can just catch it, the docs say 0.0 tho). more info here
Hi had the exact same problem and I don't see the answer I used posted, so here it is.
I created and connected my text field via IB. When I connected it to my code via Control+Drag, I chose Action, then selected the Editing Changed event. This triggers the method on each character entry. You can use a different event to suit.
Afterwards, I used this simple code to replace the text. Note that I created my own character set to include the decimal/period character and numbers. Basically separates the string on the invalid characters, then rejoins them with empty string.
- (IBAction)myTextFieldEditingChangedMethod:(UITextField *)sender {
NSCharacterSet *validCharacterSet = [NSCharacterSet characterSetWithCharactersInString:#".0123456789"];
NSCharacterSet *invalidCharacterSet = validCharacterSet.invertedSet;
sender.text = [[sender.text componentsSeparatedByCharactersInSet:invalidCharacterSet] componentsJoinedByString:#""];
}
Credits:
Remove all but numbers from NSString
Late to the game but here a handy little category I use that accounts for decimal places and the local symbol used for it. link to its gist here
#interface NSString (Extension)
- (BOOL) isAnEmail;
- (BOOL) isNumeric;
#end
#implementation NSString (Extension)
/**
* Determines if the current string is a valid email address.
*
* #return BOOL - True if the string is a valid email address.
*/
- (BOOL) isAnEmail
{
NSString *emailRegex = #"[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", emailRegex];
return [emailTest evaluateWithObject:self];
}
/**
* Determines if the current NSString is numeric or not. It also accounts for the localised (Germany for example use "," instead of ".") decimal point and includes these as a valid number.
*
* #return BOOL - True if the string is numeric.
*/
- (BOOL) isNumeric
{
NSString *localDecimalSymbol = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator];
NSMutableCharacterSet *decimalCharacterSet = [NSMutableCharacterSet characterSetWithCharactersInString:localDecimalSymbol];
[decimalCharacterSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
NSCharacterSet* nonNumbers = [decimalCharacterSet invertedSet];
NSRange r = [self rangeOfCharacterFromSet: nonNumbers];
if (r.location == NSNotFound)
{
// check to see how many times the decimal symbol appears in the string. It should only appear once for the number to be numeric.
int numberOfOccurances = [[self componentsSeparatedByString:localDecimalSymbol] count]-1;
return (numberOfOccurances > 1) ? NO : YES;
}
else return NO;
}
#end
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(string.length > 0)
{
NSCharacterSet *numbersOnly = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
NSCharacterSet *characterSetFromTextField = [NSCharacterSet characterSetWithCharactersInString:string];
BOOL stringIsValid = [numbersOnly isSupersetOfSet:characterSetFromTextField];
return stringIsValid;
}
return YES;
}
IMO the best way to accomplish your goal is to display a numeric keyboard rather than the normal keyboard. This restricts which keys are available to the user. This alleviates the need to do validation, and more importantly it prevents the user from making a mistake. The number pad is also much nicer for entering numbers because the keys are substantially larger.
In interface builder select the UITextField, go to the Attributes Inspector and change the "Keyboard Type" to "Decimal Pad".
That'll make the keyboard look like this:
The only thing left to do is ensure the user doesn't enter in two decimal places. You can do this while they're editing. Add the following code to your view controller. This code removes a second decimal place as soon as it is entered. It appears to the user as if the 2nd decimal never appeared in the first place.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.textField addTarget:self
action:#selector(textFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
}
- (void)textFieldDidChange:(UITextField *)textField
{
NSString *text = textField.text;
NSRange range = [text rangeOfString:#"."];
if (range.location != NSNotFound &&
[text hasSuffix:#"."] &&
range.location != (text.length - 1))
{
// There's more than one decimal
textField.text = [text substringToIndex:text.length - 1];
}
}
#property (strong) NSNumberFormatter *numberFormatter;
#property (strong) NSString *oldStringValue;
- (void)awakeFromNib
{
[super awakeFromNib];
self.numberFormatter = [[NSNumberFormatter alloc] init];
self.oldStringValue = self.stringValue;
[self setDelegate:self];
}
- (void)controlTextDidChange:(NSNotification *)obj
{
NSNumber *number = [self.numberFormatter numberFromString:self.stringValue];
if (number) {
self.oldStringValue = self.stringValue;
} else {
self.stringValue = self.oldStringValue;
}
}
Old thread, but it's worth mentioning that Apple introduced NSRegularExpression in iOS 4.0. (Taking the regular expression from Peter's response)
// Look for 0-n digits from start to finish
NSRegularExpression *noFunnyStuff = [NSRegularExpression regularExpressionWithPattern:#"^(?:|0|[1-9]\\d*)(?:\\.\\d*)?$" options:0 error:nil];
// There should be just one match
if ([noFunnyStuff numberOfMatchesInString:<#theString#> options:0 range:NSMakeRange(0, <#theString#>.length)] == 1)
{
// Yay, digits!
}
I suggest storing the NSRegularExpression instance somewhere.
I wanted a text field that only allowed integers. Here's what I ended up with (using info from here and elsewhere):
Create integer number formatter (in UIApplicationDelegate so it can be reused):
#property (nonatomic, retain) NSNumberFormatter *integerNumberFormatter;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Create and configure an NSNumberFormatter for integers
integerNumberFormatter = [[NSNumberFormatter alloc] init];
[integerNumberFormatter setMaximumFractionDigits:0];
return YES;
}
Use filter in UITextFieldDelegate:
#interface MyTableViewController : UITableViewController <UITextFieldDelegate> {
ictAppDelegate *appDelegate;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
// Make sure the proposed string is a number
NSNumberFormatter *inf = [appDelegate integerNumberFormatter];
NSString* proposedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSNumber *proposedNumber = [inf numberFromString:proposedString];
if (proposedNumber) {
// Make sure the proposed number is an integer
NSString *integerString = [inf stringFromNumber:proposedNumber];
if ([integerString isEqualToString:proposedString]) {
// proposed string is an integer
return YES;
}
}
// Warn the user we're rejecting the change
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
return NO;
}
Not so elegant, but simple :)
- (BOOL) isNumber: (NSString*)input
{
return [input doubleValue] != 0 || [input isEqualToString:#"0"] || [input isEqualToString:#"0.0"];
}
Accept decimal values in text fields with single (.)dot working with iPad and iPhone in Swift 3
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inverseSet = NSCharacterSet(charactersIn:"0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
if filtered == string {
return true
} else {
if string == "." {
let countdots = textField.text!.components(separatedBy:".").count - 1
if countdots == 0 {
return true
}else{
if countdots > 0 && string == "." {
return false
} else {
return true
}
}
}else{
return false
}
}
}
To be more international (and not only US colored ;-) ) just replace in the code above by
-(NSNumber *) getNumber
{
NSString* localeIdentifier = [[NSLocale autoupdatingCurrentLocale] localeIdentifier];
NSLocale *l_en = [[NSLocale alloc] initWithLocaleIdentifier: localeIdentifier] ;
return [self getNumberWithLocale: [l_en autorelease] ];
}
This answer uses NSFormatter as said previously. Check it out:
#interface NSString (NSNumber)
- (BOOL) isNumberWithLocale:(NSLocale *) stringLocale;
- (BOOL) isNumber;
- (NSNumber *) getNumber;
- (NSNumber *) getNumberWithLocale:(NSLocale*) stringLocale;
#end
#implementation NSString (NSNumber)
- (BOOL) isNumberWithLocale:(NSLocale *) stringLocale
{
return [self getNumberWithLocale:stringLocale] != nil;
}
- (BOOL) isNumber
{
return [ self getNumber ] != nil;
}
- (NSNumber *) getNumber
{
NSLocale *l_en = [[NSLocale alloc] initWithLocaleIdentifier: #"en_US"] ;
return [self getNumberWithLocale: [l_en autorelease] ];
}
- (NSNumber *) getNumberWithLocale:(NSLocale*) stringLocale
{
NSNumberFormatter *formatter = [[ [ NSNumberFormatter alloc ] init ] autorelease];
[formatter setLocale: stringLocale ];
return [ formatter numberFromString:self ];
}
#end
I hope it helps someone. =)
#import "NSString+Extension.h"
//#interface NSString (Extension)
//
//- (BOOL) isAnEmail;
//- (BOOL) isNumeric;
//
//#end
#implementation NSString (Extension)
- (BOOL) isNumeric
{
NSString *emailRegex = #"[0-9]+";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", emailRegex];
return [emailTest evaluateWithObject:self];
// NSString *localDecimalSymbol = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator];
// NSMutableCharacterSet *decimalCharacterSet = [NSMutableCharacterSet characterSetWithCharactersInString:localDecimalSymbol];
// [decimalCharacterSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
//
// NSCharacterSet* nonNumbers = [decimalCharacterSet invertedSet];
// NSRange r = [self rangeOfCharacterFromSet: nonNumbers];
//
// if (r.location == NSNotFound)
// {
// // check to see how many times the decimal symbol appears in the string. It should only appear once for the number to be numeric.
// int numberOfOccurances = [[self componentsSeparatedByString:localDecimalSymbol] count]-1;
// return (numberOfOccurances > 1) ? NO : YES;
// }
// else return NO;
}
In Swift 4:
let formatString = "12345"
if let number = Decimal(string:formatString){
print("String contains only number")
}
else{
print("String doesn't contains only number")
}
This covers: Decimal part control (including number of decimals allowed), copy/paste control, international separators.
Steps:
Make sure your view controller inherits from UITextFieldDelegate
class MyViewController: UIViewController, UITextFieldDelegate {...
In viewDidLoad, set your control delegate to self:
override func viewDidLoad() {
super.viewDidLoad();
yourTextField.delegate = self
}
Implement the following method and update the "decsAllowed" constant with the desired amount of decimals or 0 if you want a natural number.
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let decsAllowed: Int = 2
let candidateText = NSString(string: textField.text!).replacingCharacters(in: range, with: string)
let decSeparator: String = NumberFormatter().decimalSeparator!;
let splitted = candidateText.components(separatedBy: decSeparator)
let decSeparatorsFound = splitted.count - 1
let decimalPart = decSeparatorsFound > 0 ? splitted.last! : ""
let decimalPartCount = decimalPart.characters.count
let characterSet = NSMutableCharacterSet.decimalDigit()
if decsAllowed > 0 {characterSet.addCharacters(in: decSeparator)}
let valid = characterSet.isSuperset(of: CharacterSet(charactersIn: candidateText)) &&
decSeparatorsFound <= 1 &&
decsAllowed >= decimalPartCount
return valid
}
If afterwards you need to safely convert that string into a number, you can just use Double(yourstring) or Int(yourstring) type cast, or the more academic way:
let formatter = NumberFormatter()
let theNumber: NSNumber = formatter.number(from: yourTextField.text)!

Objective-C: Reading a file line by line

What is the appropriate way of dealing with large text files in Objective-C? Let's say I need to read each line separately and want to treat each line as an NSString. What is the most efficient way of doing this?
One solution is using the NSString method:
+ (id)stringWithContentsOfFile:(NSString *)path
encoding:(NSStringEncoding)enc
error:(NSError **)error
and then split the lines with a newline separator, and then iterate over the elements in the array. However, this seems fairly inefficient. Is there no easy way to treat the file as a stream, enumerating over each line, instead of just reading it all in at once? Kinda like Java's java.io.BufferedReader.
This will work for general reading a String from Text.
If you would like to read longer text (large size of text), then use the method that other people here were mentioned such as buffered (reserve the size of the text in memory space).
Say you read a Text File.
NSString* filePath = #""//file path...
NSString* fileRoot = [[NSBundle mainBundle]
pathForResource:filePath ofType:#"txt"];
You want to get rid of new line.
// read everything from text
NSString* fileContents =
[NSString stringWithContentsOfFile:fileRoot
encoding:NSUTF8StringEncoding error:nil];
// first, separate by new line
NSArray* allLinedStrings =
[fileContents componentsSeparatedByCharactersInSet:
[NSCharacterSet newlineCharacterSet]];
// then break down even further
NSString* strsInOneLine =
[allLinedStrings objectAtIndex:0];
// choose whatever input identity you have decided. in this case ;
NSArray* singleStrs =
[currentPointString componentsSeparatedByCharactersInSet:
[NSCharacterSet characterSetWithCharactersInString:#";"]];
There you have it.
That's a great question. I think #Diederik has a good answer, although it's unfortunate that Cocoa doesn't have a mechanism for exactly what you want to do.
NSInputStream allows you to read chunks of N bytes (very similar to java.io.BufferedReader), but you have to convert it to an NSString on your own, then scan for newlines (or whatever other delimiter) and save any remaining characters for the next read, or read more characters if a newline hasn't been read yet. (NSFileHandle lets you read an NSData which you can then convert to an NSString, but it's essentially the same process.)
Apple has a Stream Programming Guide that can help fill in the details, and this SO question may help as well if you're going to be dealing with uint8_t* buffers.
If you're going to be reading strings like this frequently (especially in different parts of your program) it would be a good idea to encapsulate this behavior in a class that can handle the details for you, or even subclassing NSInputStream (it's designed to be subclassed) and adding methods that allow you to read exactly what you want.
For the record, I think this would be a nice feature to add, and I'll be filing an enhancement request for something that makes this possible. :-)
Edit: Turns out this request already exists. There's a Radar dating from 2006 for this (rdar://4742914 for Apple-internal people).
This should do the trick:
#include <stdio.h>
NSString *readLineAsNSString(FILE *file)
{
char buffer[4096];
// tune this capacity to your liking -- larger buffer sizes will be faster, but
// use more memory
NSMutableString *result = [NSMutableString stringWithCapacity:256];
// Read up to 4095 non-newline characters, then read and discard the newline
int charsRead;
do
{
if(fscanf(file, "%4095[^\n]%n%*c", buffer, &charsRead) == 1)
[result appendFormat:#"%s", buffer];
else
break;
} while(charsRead == 4095);
return result;
}
Use as follows:
FILE *file = fopen("myfile", "r");
// check for NULL
while(!feof(file))
{
NSString *line = readLineAsNSString(file);
// do stuff with line; line is autoreleased, so you should NOT release it (unless you also retain it beforehand)
}
fclose(file);
This code reads non-newline characters from the file, up to 4095 at a time. If you have a line that is longer than 4095 characters, it keeps reading until it hits a newline or end-of-file.
Note: I have not tested this code. Please test it before using it.
Mac OS X is Unix, Objective-C is C superset, so you can just use old-school fopen and fgets from <stdio.h>. It's guaranteed to work.
[NSString stringWithUTF8String:buf] will convert C string to NSString. There are also methods for creating strings in other encodings and creating without copying.
You can use NSInputStream which has a basic implementation for file streams. You can read bytes into a buffer (read:maxLength: method). You have to scan the buffer for newlines yourself.
The appropriate way to read text files in Cocoa/Objective-C is documented in Apple's String programming guide. The section for reading and writing files should be just what you're after. PS: What's a "line"? Two sections of a string separated by "\n"? Or "\r"? Or "\r\n"? Or maybe you're actually after paragraphs? The previously mentioned guide also includes a section on splitting a string into lines or paragraphs. (This section is called "Paragraphs and Line Breaks", and is linked to in the left-hand-side menu of the page I pointed to above. Unfortunately this site doesn't allow me to post more than one URL as I'm not a trustworthy user yet.)
To paraphrase Knuth: premature optimisation is the root of all evil. Don't simply assume that "reading the whole file into memory" is slow. Have you benchmarked it? Do you know that it actually reads the whole file into memory? Maybe it simply returns a proxy object and keeps reading behind the scenes as you consume the string? (Disclaimer: I have no idea if NSString actually does this. It conceivably could.) The point is: first go with the documented way of doing things. Then, if benchmarks show that this doesn't have the performance you desire, optimise.
A lot of these answers are long chunks of code or they read in the entire file. I like to use the c methods for this very task.
FILE* file = fopen("path to my file", "r");
size_t length;
char *cLine = fgetln(file,&length);
while (length>0) {
char str[length+1];
strncpy(str, cLine, length);
str[length] = '\0';
NSString *line = [NSString stringWithFormat:#"%s",str];
% Do what you want here.
cLine = fgetln(file,&length);
}
Note that fgetln will not keep your newline character. Also, We +1 the length of the str because we want to make space for the NULL termination.
Just like #porneL said, the C api is very handy.
NSString* fileRoot = [[NSBundle mainBundle] pathForResource:#"record" ofType:#"txt"];
FILE *file = fopen([fileRoot UTF8String], "r");
char buffer[256];
while (fgets(buffer, 256, file) != NULL){
NSString* result = [NSString stringWithUTF8String:buffer];
NSLog(#"%#",result);
}
To read a file line by line (also for extreme big files) can be done by the following functions:
DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
NSString * line = nil;
while ((line = [reader readLine])) {
NSLog(#"read line: %#", line);
}
[reader release];
Or:
DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
[reader enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
NSLog(#"read line: %#", line);
}];
[reader release];
The class DDFileReader that enables this is the following:
Interface File (.h):
#interface DDFileReader : NSObject {
NSString * filePath;
NSFileHandle * fileHandle;
unsigned long long currentOffset;
unsigned long long totalFileLength;
NSString * lineDelimiter;
NSUInteger chunkSize;
}
#property (nonatomic, copy) NSString * lineDelimiter;
#property (nonatomic) NSUInteger chunkSize;
- (id) initWithFilePath:(NSString *)aPath;
- (NSString *) readLine;
- (NSString *) readTrimmedLine;
#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL *))block;
#endif
#end
Implementation (.m)
#import "DDFileReader.h"
#interface NSData (DDAdditions)
- (NSRange) rangeOfData_dd:(NSData *)dataToFind;
#end
#implementation NSData (DDAdditions)
- (NSRange) rangeOfData_dd:(NSData *)dataToFind {
const void * bytes = [self bytes];
NSUInteger length = [self length];
const void * searchBytes = [dataToFind bytes];
NSUInteger searchLength = [dataToFind length];
NSUInteger searchIndex = 0;
NSRange foundRange = {NSNotFound, searchLength};
for (NSUInteger index = 0; index < length; index++) {
if (((char *)bytes)[index] == ((char *)searchBytes)[searchIndex]) {
//the current character matches
if (foundRange.location == NSNotFound) {
foundRange.location = index;
}
searchIndex++;
if (searchIndex >= searchLength) { return foundRange; }
} else {
searchIndex = 0;
foundRange.location = NSNotFound;
}
}
return foundRange;
}
#end
#implementation DDFileReader
#synthesize lineDelimiter, chunkSize;
- (id) initWithFilePath:(NSString *)aPath {
if (self = [super init]) {
fileHandle = [NSFileHandle fileHandleForReadingAtPath:aPath];
if (fileHandle == nil) {
[self release]; return nil;
}
lineDelimiter = [[NSString alloc] initWithString:#"\n"];
[fileHandle retain];
filePath = [aPath retain];
currentOffset = 0ULL;
chunkSize = 10;
[fileHandle seekToEndOfFile];
totalFileLength = [fileHandle offsetInFile];
//we don't need to seek back, since readLine will do that.
}
return self;
}
- (void) dealloc {
[fileHandle closeFile];
[fileHandle release], fileHandle = nil;
[filePath release], filePath = nil;
[lineDelimiter release], lineDelimiter = nil;
currentOffset = 0ULL;
[super dealloc];
}
- (NSString *) readLine {
if (currentOffset >= totalFileLength) { return nil; }
NSData * newLineData = [lineDelimiter dataUsingEncoding:NSUTF8StringEncoding];
[fileHandle seekToFileOffset:currentOffset];
NSMutableData * currentData = [[NSMutableData alloc] init];
BOOL shouldReadMore = YES;
NSAutoreleasePool * readPool = [[NSAutoreleasePool alloc] init];
while (shouldReadMore) {
if (currentOffset >= totalFileLength) { break; }
NSData * chunk = [fileHandle readDataOfLength:chunkSize];
NSRange newLineRange = [chunk rangeOfData_dd:newLineData];
if (newLineRange.location != NSNotFound) {
//include the length so we can include the delimiter in the string
chunk = [chunk subdataWithRange:NSMakeRange(0, newLineRange.location+[newLineData length])];
shouldReadMore = NO;
}
[currentData appendData:chunk];
currentOffset += [chunk length];
}
[readPool release];
NSString * line = [[NSString alloc] initWithData:currentData encoding:NSUTF8StringEncoding];
[currentData release];
return [line autorelease];
}
- (NSString *) readTrimmedLine {
return [[self readLine] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL*))block {
NSString * line = nil;
BOOL stop = NO;
while (stop == NO && (line = [self readLine])) {
block(line, &stop);
}
}
#endif
#end
The class was done by Dave DeLong
As others have answered both NSInputStream and NSFileHandle are fine options, but it can also be done in a fairly compact way with NSData and memory mapping:
BRLineReader.h
#import <Foundation/Foundation.h>
#interface BRLineReader : NSObject
#property (readonly, nonatomic) NSData *data;
#property (readonly, nonatomic) NSUInteger linesRead;
#property (strong, nonatomic) NSCharacterSet *lineTrimCharacters;
#property (readonly, nonatomic) NSStringEncoding stringEncoding;
- (instancetype)initWithFile:(NSString *)filePath encoding:(NSStringEncoding)encoding;
- (instancetype)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding;
- (NSString *)readLine;
- (NSString *)readTrimmedLine;
- (void)setLineSearchPosition:(NSUInteger)position;
#end
BRLineReader.m
#import "BRLineReader.h"
static unsigned char const BRLineReaderDelimiter = '\n';
#implementation BRLineReader
{
NSRange _lastRange;
}
- (instancetype)initWithFile:(NSString *)filePath encoding:(NSStringEncoding)encoding
{
self = [super init];
if (self) {
NSError *error = nil;
_data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedAlways error:&error];
if (!_data) {
NSLog(#"%#", [error localizedDescription]);
}
_stringEncoding = encoding;
_lineTrimCharacters = [NSCharacterSet whitespaceAndNewlineCharacterSet];
}
return self;
}
- (instancetype)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding
{
self = [super init];
if (self) {
_data = data;
_stringEncoding = encoding;
_lineTrimCharacters = [NSCharacterSet whitespaceAndNewlineCharacterSet];
}
return self;
}
- (NSString *)readLine
{
NSUInteger dataLength = [_data length];
NSUInteger beginPos = _lastRange.location + _lastRange.length;
NSUInteger endPos = 0;
if (beginPos == dataLength) {
// End of file
return nil;
}
unsigned char *buffer = (unsigned char *)[_data bytes];
for (NSUInteger i = beginPos; i < dataLength; i++) {
endPos = i;
if (buffer[i] == BRLineReaderDelimiter) break;
}
// End of line found
_lastRange = NSMakeRange(beginPos, endPos - beginPos + 1);
NSData *lineData = [_data subdataWithRange:_lastRange];
NSString *line = [[NSString alloc] initWithData:lineData encoding:_stringEncoding];
_linesRead++;
return line;
}
- (NSString *)readTrimmedLine
{
return [[self readLine] stringByTrimmingCharactersInSet:_lineTrimCharacters];
}
- (void)setLineSearchPosition:(NSUInteger)position
{
_lastRange = NSMakeRange(position, 0);
_linesRead = 0;
}
#end
This answer is NOT ObjC but C.
Since ObjC is 'C' based, why not use fgets?
And yes, I'm sure ObjC has it's own method - I'm just not proficient enough yet to know what it is :)
from #Adam Rosenfield's answer, the formatting string of fscanf would be changed like below:
"%4095[^\r\n]%n%*[\n\r]"
it will work in osx, linux, windows line endings.
Using category or extension to make our life a bit easier.
extension String {
func lines() -> [String] {
var lines = [String]()
self.enumerateLines { (line, stop) -> () in
lines.append(line)
}
return lines
}
}
// then
for line in string.lines() {
// do the right thing
}
I found response by #lukaswelte and code from Dave DeLong very helpful. I was looking for a solution to this problem but needed to parse large files by \r\n not just \n.
The code as written contains a bug if parsing by more than one character. I've changed the code as below.
.h file:
#import <Foundation/Foundation.h>
#interface FileChunkReader : NSObject {
NSString * filePath;
NSFileHandle * fileHandle;
unsigned long long currentOffset;
unsigned long long totalFileLength;
NSString * lineDelimiter;
NSUInteger chunkSize;
}
#property (nonatomic, copy) NSString * lineDelimiter;
#property (nonatomic) NSUInteger chunkSize;
- (id) initWithFilePath:(NSString *)aPath;
- (NSString *) readLine;
- (NSString *) readTrimmedLine;
#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL *))block;
#endif
#end
.m file:
#import "FileChunkReader.h"
#interface NSData (DDAdditions)
- (NSRange) rangeOfData_dd:(NSData *)dataToFind;
#end
#implementation NSData (DDAdditions)
- (NSRange) rangeOfData_dd:(NSData *)dataToFind {
const void * bytes = [self bytes];
NSUInteger length = [self length];
const void * searchBytes = [dataToFind bytes];
NSUInteger searchLength = [dataToFind length];
NSUInteger searchIndex = 0;
NSRange foundRange = {NSNotFound, searchLength};
for (NSUInteger index = 0; index < length; index++) {
if (((char *)bytes)[index] == ((char *)searchBytes)[searchIndex]) {
//the current character matches
if (foundRange.location == NSNotFound) {
foundRange.location = index;
}
searchIndex++;
if (searchIndex >= searchLength)
{
return foundRange;
}
} else {
searchIndex = 0;
foundRange.location = NSNotFound;
}
}
if (foundRange.location != NSNotFound
&& length < foundRange.location + foundRange.length )
{
// if the dataToFind is partially found at the end of [self bytes],
// then the loop above would end, and indicate the dataToFind is found
// when it only partially was.
foundRange.location = NSNotFound;
}
return foundRange;
}
#end
#implementation FileChunkReader
#synthesize lineDelimiter, chunkSize;
- (id) initWithFilePath:(NSString *)aPath {
if (self = [super init]) {
fileHandle = [NSFileHandle fileHandleForReadingAtPath:aPath];
if (fileHandle == nil) {
return nil;
}
lineDelimiter = #"\n";
currentOffset = 0ULL; // ???
chunkSize = 128;
[fileHandle seekToEndOfFile];
totalFileLength = [fileHandle offsetInFile];
//we don't need to seek back, since readLine will do that.
}
return self;
}
- (void) dealloc {
[fileHandle closeFile];
currentOffset = 0ULL;
}
- (NSString *) readLine {
if (currentOffset >= totalFileLength)
{
return nil;
}
#autoreleasepool {
NSData * newLineData = [lineDelimiter dataUsingEncoding:NSUTF8StringEncoding];
[fileHandle seekToFileOffset:currentOffset];
unsigned long long originalOffset = currentOffset;
NSMutableData *currentData = [[NSMutableData alloc] init];
NSData *currentLine = [[NSData alloc] init];
BOOL shouldReadMore = YES;
while (shouldReadMore) {
if (currentOffset >= totalFileLength)
{
break;
}
NSData * chunk = [fileHandle readDataOfLength:chunkSize];
[currentData appendData:chunk];
NSRange newLineRange = [currentData rangeOfData_dd:newLineData];
if (newLineRange.location != NSNotFound) {
currentOffset = originalOffset + newLineRange.location + newLineData.length;
currentLine = [currentData subdataWithRange:NSMakeRange(0, newLineRange.location)];
shouldReadMore = NO;
}else{
currentOffset += [chunk length];
}
}
if (currentLine.length == 0 && currentData.length > 0)
{
currentLine = currentData;
}
return [[NSString alloc] initWithData:currentLine encoding:NSUTF8StringEncoding];
}
}
- (NSString *) readTrimmedLine {
return [[self readLine] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL*))block {
NSString * line = nil;
BOOL stop = NO;
while (stop == NO && (line = [self readLine])) {
block(line, &stop);
}
}
#endif
#end
I am adding this because all other answers I tried fell short one way or another. The following method can handle large files, arbitrary long lines, as well as empty lines. It has been tested with actual content and will strip out newline character from the output.
- (NSString*)readLineFromFile:(FILE *)file
{
char buffer[4096];
NSMutableString *result = [NSMutableString stringWithCapacity:1000];
int charsRead;
do {
if(fscanf(file, "%4095[^\r\n]%n%*[\n\r]", buffer, &charsRead) == 1) {
[result appendFormat:#"%s", buffer];
}
else {
break;
}
} while(charsRead == 4095);
return result.length ? result : nil;
}
Credit goes to #Adam Rosenfield and #sooop
I see a lot of these answers rely on reading the whole text file into memory instead of taking it one chunk at a time. Here's my solution in nice modern Swift, using FileHandle to keep memory impact low:
enum MyError {
case invalidTextFormat
}
extension FileHandle {
func readLine(maxLength: Int) throws -> String {
// Read in a string of up to the maximum length
let offset = offsetInFile
let data = readData(ofLength: maxLength)
guard let string = String(data: data, encoding: .utf8) else {
throw MyError.invalidTextFormat
}
// Check for carriage returns; if none, this is the whole string
let substring: String
if let subindex = string.firstIndex(of: "\n") {
substring = String(string[string.startIndex ... subindex])
} else {
substring = string
}
// Wind back to the correct offset so that we don't miss any lines
guard let dataCount = substring.data(using: .utf8, allowLossyConversion: false)?.count else {
throw MyError.invalidTextFormat
}
try seek(toOffset: offset + UInt64(dataCount))
return substring
}
}
Note that this preserves the carriage return at the end of the line, so depending on your needs you may want to adjust the code to remove it.
Usage: simply open a file handle to your target text file and call readLine with a suitable maximum length - 1024 is standard for plain text, but I left it open in case you know it will be shorter. Note that the command will not overflow the end of the file, so you may have to check manually that you've not reached it if you intend to parse the entire thing. Here's some sample code that shows how to open a file at myFileURL and read it line-by-line until the end.
do {
let handle = try FileHandle(forReadingFrom: myFileURL)
try handle.seekToEndOfFile()
let eof = handle.offsetInFile
try handle.seek(toFileOffset: 0)
while handle.offsetInFile < eof {
let line = try handle.readLine(maxLength: 1024)
// Do something with the string here
}
try handle.close()
catch let error {
print("Error reading file: \(error.localizedDescription)"
}
Here's a nice simple solution i use for smaller files:
NSString *path = [[NSBundle mainBundle] pathForResource:#"Terrain1" ofType:#"txt"];
NSString *contents = [NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:nil];
NSArray *lines = [contents componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"\r\n"]];
for (NSString* line in lines) {
if (line.length) {
NSLog(#"line: %#", line);
}
}
Use this script, it works great:
NSString *path = #"/Users/xxx/Desktop/names.txt";
NSError *error;
NSString *stringFromFileAtPath = [NSString stringWithContentsOfFile: path
encoding: NSUTF8StringEncoding
error: &error];
if (stringFromFileAtPath == nil) {
NSLog(#"Error reading file at %#\n%#", path, [error localizedFailureReason]);
}
NSLog(#"Contents:%#", stringFromFileAtPath);

Creating URL query parameters from NSDictionary objects in ObjectiveC

With all the URL-handling objects lying around in the standard Cocoa libraries (NSURL, NSMutableURL, NSMutableURLRequest, etc), I know I must be overlooking an easy way to programmatically compose a GET request.
Currently I'm manually appending "?" followed by name value pairs joined by "&", but all of my name and value pairs need to be manually encoded so NSMutableURLRequest doesn't fail entirely when it tries to connect to the URL.
This feels like something I should be able to use a pre-baked API for.... is there anything out of the box to append an NSDictionary of query parameters to an NSURL? Is there another way I should approach this?
Introduced in iOS8 and OS X 10.10 is NSURLQueryItem, which can be used to build queries. From the docs on NSURLQueryItem:
An NSURLQueryItem object represents a single name/value pair for an item in the query portion of a URL. You use query items with the queryItems property of an NSURLComponents object.
To create one use the designated initializer queryItemWithName:value: and then add them to NSURLComponents to generate an NSURL. For example:
NSURLComponents *components = [NSURLComponents componentsWithString:#"http://stackoverflow.com"];
NSURLQueryItem *search = [NSURLQueryItem queryItemWithName:#"q" value:#"ios"];
NSURLQueryItem *count = [NSURLQueryItem queryItemWithName:#"count" value:#"10"];
components.queryItems = #[ search, count ];
NSURL *url = components.URL; // http://stackoverflow.com?q=ios&count=10
Notice that the question mark and ampersand are automatically handled. Creating an NSURL from a dictionary of parameters is as simple as:
NSDictionary *queryDictionary = #{ #"q": #"ios", #"count": #"10" };
NSMutableArray *queryItems = [NSMutableArray array];
for (NSString *key in queryDictionary) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:key value:queryDictionary[key]]];
}
components.queryItems = queryItems;
I've also written a blog post on how to build URLs with NSURLComponents and NSURLQueryItems.
You can create a category for NSDictionary to do this -- there isn't a standard way in the Cocoa library that I could find either. The code that I use looks like this:
// file "NSDictionary+UrlEncoding.h"
#import <cocoa/cocoa.h>
#interface NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString;
#end
with this implementation:
// file "NSDictionary+UrlEncoding.m"
#import "NSDictionary+UrlEncoding.h"
// helper function: get the string form of any object
static NSString *toString(id object) {
return [NSString stringWithFormat: #"%#", object];
}
// helper function: get the url encoded string form of any object
static NSString *urlEncode(id object) {
NSString *string = toString(object);
return [string stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
}
#implementation NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString {
NSMutableArray *parts = [NSMutableArray array];
for (id key in self) {
id value = [self objectForKey: key];
NSString *part = [NSString stringWithFormat: #"%#=%#", urlEncode(key), urlEncode(value)];
[parts addObject: part];
}
return [parts componentsJoinedByString: #"&"];
}
#end
I think the code's pretty straightforward, but I discuss it in some more detail at http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html.
I wanted to use Chris's answer, but it wasn't written for Automatic Reference Counting (ARC) so I updated it. I thought I'd paste my solution in case anyone else has this same issue. Note: replace self with the instance or class name where appropriate.
+(NSString*)urlEscapeString:(NSString *)unencodedString
{
CFStringRef originalStringRef = (__bridge_retained CFStringRef)unencodedString;
NSString *s = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,originalStringRef, NULL, (CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ", kCFStringEncodingUTF8);
CFRelease(originalStringRef);
return s;
}
+(NSString*)addQueryStringToUrlString:(NSString *)urlString withDictionary:(NSDictionary *)dictionary
{
NSMutableString *urlWithQuerystring = [[NSMutableString alloc] initWithString:urlString];
for (id key in dictionary) {
NSString *keyString = [key description];
NSString *valueString = [[dictionary objectForKey:key] description];
if ([urlWithQuerystring rangeOfString:#"?"].location == NSNotFound) {
[urlWithQuerystring appendFormat:#"?%#=%#", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
} else {
[urlWithQuerystring appendFormat:#"&%#=%#", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
}
}
return urlWithQuerystring;
}
The other answers work great if the values are strings, however if the values are dictionaries or arrays then this code will handle that.
Its important to note that there is no standard way of passing an array/dictionary via the query string but PHP handles this output just fine
-(NSString *)serializeParams:(NSDictionary *)params {
/*
Convert an NSDictionary to a query string
*/
NSMutableArray* pairs = [NSMutableArray array];
for (NSString* key in [params keyEnumerator]) {
id value = [params objectForKey:key];
if ([value isKindOfClass:[NSDictionary class]]) {
for (NSString *subKey in value) {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)[value objectForKey:subKey],
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#[%#]=%#", key, subKey, escaped_value]];
}
} else if ([value isKindOfClass:[NSArray class]]) {
for (NSString *subValue in value) {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)subValue,
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#[]=%#", key, escaped_value]];
}
} else {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)[params objectForKey:key],
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, escaped_value]];
[escaped_value release];
}
}
return [pairs componentsJoinedByString:#"&"];
}
Examples
[foo] => bar
[translations] =>
{
[one] => uno
[two] => dos
[three] => tres
}
foo=bar&translations[one]=uno&translations[two]=dos&translations[three]=tres
[foo] => bar
[translations] =>
{
uno
dos
tres
}
foo=bar&translations[]=uno&translations[]=dos&translations[]=tres
I refactored and converted to ARC answer by AlBeebe
- (NSString *)serializeParams:(NSDictionary *)params {
NSMutableArray *pairs = NSMutableArray.array;
for (NSString *key in params.keyEnumerator) {
id value = params[key];
if ([value isKindOfClass:[NSDictionary class]])
for (NSString *subKey in value)
[pairs addObject:[NSString stringWithFormat:#"%#[%#]=%#", key, subKey, [self escapeValueForURLParameter:[value objectForKey:subKey]]]];
else if ([value isKindOfClass:[NSArray class]])
for (NSString *subValue in value)
[pairs addObject:[NSString stringWithFormat:#"%#[]=%#", key, [self escapeValueForURLParameter:subValue]]];
else
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, [self escapeValueForURLParameter:value]]];
}
return [pairs componentsJoinedByString:#"&"];
}
- (NSString *)escapeValueForURLParameter:(NSString *)valueToEscape {
return (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) valueToEscape,
NULL, (CFStringRef) #"!*'();:#&=+$,/?%#[]", kCFStringEncodingUTF8);
}
If you are already using AFNetworking (as was the case with me), you can use it's class AFHTTPRequestSerializer to create the required NSURLRequest.
[[AFHTTPRequestSerializer serializer] requestWithMethod:#"GET" URLString:#"YOUR_URL" parameters:#{PARAMS} error:nil];
In case you only require the URL for your work, use NSURLRequest.URL.
Here is a simple example in Swift (iOS8+):
private let kSNStockInfoFetchRequestPath: String = "http://dev.markitondemand.com/Api/v2/Quote/json"
private func SNStockInfoFetchRequestURL(symbol:String) -> NSURL? {
if let components = NSURLComponents(string:kSNStockInfoFetchRequestPath) {
components.queryItems = [NSURLQueryItem(name:"symbol", value:symbol)]
return components.URL
}
return nil
}
I took Joel's recommendation of using URLQueryItems and turned into a Swift Extension (Swift 3)
extension URL
{
/// Creates an NSURL with url-encoded parameters.
init?(string : String, parameters : [String : String])
{
guard var components = URLComponents(string: string) else { return nil }
components.queryItems = parameters.map { return URLQueryItem(name: $0, value: $1) }
guard let url = components.url else { return nil }
// Kinda redundant, but we need to call init.
self.init(string: url.absoluteString)
}
}
(The self.init method is kinda cheesy, but there was no NSURL init with components)
Can be used as
URL(string: "http://www.google.com/", parameters: ["q" : "search me"])
I've got another solution:
http://splinter.com.au/build-a-url-query-string-in-obj-c-from-a-dict
+(NSString*)urlEscape:(NSString *)unencodedString {
NSString *s = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)unencodedString,
NULL,
(CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ",
kCFStringEncodingUTF8);
return [s autorelease]; // Due to the 'create rule' we own the above and must autorelease it
}
// Put a query string onto the end of a url
+(NSString*)addQueryStringToUrl:(NSString *)url params:(NSDictionary *)params {
NSMutableString *urlWithQuerystring = [[[NSMutableString alloc] initWithString:url] autorelease];
// Convert the params into a query string
if (params) {
for(id key in params) {
NSString *sKey = [key description];
NSString *sVal = [[params objectForKey:key] description];
// Do we need to add ?k=v or &k=v ?
if ([urlWithQuerystring rangeOfString:#"?"].location==NSNotFound) {
[urlWithQuerystring appendFormat:#"?%#=%#", [Http urlEscape:sKey], [Http urlEscape:sVal]];
} else {
[urlWithQuerystring appendFormat:#"&%#=%#", [Http urlEscape:sKey], [Http urlEscape:sVal]];
}
}
}
return urlWithQuerystring;
}
You can then use it like so:
NSDictionary *params = #{#"username":#"jim", #"password":#"abc123"};
NSString *urlWithQuerystring = [self addQueryStringToUrl:#"https://myapp.com/login" params:params];
-(NSString*)encodeDictionary:(NSDictionary*)dictionary{
NSMutableString *bodyData = [[NSMutableString alloc]init];
int i = 0;
for (NSString *key in dictionary.allKeys) {
i++;
[bodyData appendFormat:#"%#=",key];
NSString *value = [dictionary valueForKey:key];
NSString *newString = [value stringByReplacingOccurrencesOfString:#" " withString:#"+"];
[bodyData appendString:newString];
if (i < dictionary.allKeys.count) {
[bodyData appendString:#"&"];
}
}
return bodyData;
}
Yet another solution, if you use RestKit there's a function in RKURLEncodedSerialization called RKURLEncodedStringFromDictionaryWithEncoding that does exactly what you want.
Simple way of converting NSDictionary to url query string in Objective-c
Ex: first_name=Steve&middle_name=Gates&last_name=Jobs&address=Palo Alto, California
NSDictionary *sampleDictionary = #{#"first_name" : #"Steve",
#"middle_name" : #"Gates",
#"last_name" : #"Jobs",
#"address" : #"Palo Alto, California"};
NSMutableString *resultString = [NSMutableString string];
for (NSString* key in [sampleDictionary allKeys]){
if ([resultString length]>0)
[resultString appendString:#"&"];
[resultString appendFormat:#"%#=%#", key, [sampleDictionary objectForKey:key]];
}
NSLog(#"QueryString: %#", resultString);
Hope will help :)
If you are already using AFNetwork, you can use their built in serializer to to produce an encoded URL;
NSString *baseURL = #"https://api.app.com/parse";
NSDictionary *mutableParameters = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"true",#"option1", data, #"option2", token, #"token", #"3.0", #"app", nil];
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"GET" URLString:baseURL parameters:mutableParameters error:nil];
NSString *urlPath = request.URL.absoluteString;
NSLog(#"%#", urlPath); // https://api.app.com/parse?option1=true&option2=datavalue&token=200%3ATEST%3AENCODE ....
Note; this is an extension to an above answer. The edit queue is full so cannot be added to the existing answer.