Remove "(null)" from string if no value available - ios

In have an app where I show the user's current position in address. The problem is that if example the Postal Code or Administrative Area isn't available, the string prints (null) where that value should be staying - all the other data is there.
Example:
(null) Road No 19
(null) Mumbai
Maharashtra
What I was wondering was if it was possible to just have a blank space instead of (null)?
My current code:
_addressLabel.text = [NSString stringWithFormat: #"%# %#\n%# %#\n%#",
placemark.subThoroughfare, placemark.thoroughfare,
placemark.postalCode, placemark.locality,
placemark.administrativeArea];

This is very easily accomplished with the NSString method
- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement
For example, after you have populated your _addressLabel.text string with all of the (possibly nil) values, just replace the occurrences of the undesired string with the desired string. For example, the following will solve your problem.
_addressLabel.text = [NSString stringWithFormat: #"%# %#\n%# %#\n%#",
placemark.subThoroughfare, placemark.thoroughfare,
placemark.postalCode, placemark.locality,
placemark.administrativeArea];
// that string may contain nil values, so remove them.
NSString *undesired = #"(null)";
NSString *desired = #"\n";
_addressLabel.text = [_addressLabel.text stringByReplacingOccurrencesOfString:undesired
withString:desired];

Use an NSMutableString, and have some if statements which append a string to it if the value isn't [NSNull null] or nil.
CLPlacemark *placemark = ...;
NSMutableString *address = [NSMutableString string];
if (placemark.subThoroughfare) {
[address appendString:placemark.subThoroughfare];
}
if (...) {
[address appendFormat:#"%#\n", ...];
}
// etc...
_addressLabel.text = address;

Use this to replace null with empty string
addressLabel.text = [NSString stringWithFormat: #"%# %#\n%# %#\n%#",
placemark.subThoroughfare?:#"", placemark.thoroughfare?:#"",
placemark.postalCode?:#"", placemark.locality?:#"",
placemark.administrativeArea?:#""];

I believe a cleaner(*) solution is to use the "?" operator here, like this, for each of the string values:
Instead of placemark.subThoroughfare write:
(placemark.subThoroughfare ? placemark.subThoroughfare : #"")
or even shorter:
(placemark.subThoroughfare ?: #"")
This will check if the value is NULL (or nil) - if non-zero, it'll use the string's value, otherwise it'll use a string containing a space.
It's cleaner because my solution does not depend on how NULL strings are printed, i.e. if they get printed in a future OS version as (nil) instead of (null), then my solution will still work, while Brian Tracy's won't. Not that I expect that to be a problem, ever, just pointing out what's a more proper solution to the issue out hand, for those who care what's going on behind the scenes.

Related

Create NSString with different memory

I created three string, that bind it data with method argument. However, i face issue that all of three string share same memory, therefore, it show the same text. Here is how i create it:
-(void)buildViewsWIithTitle:(NSString*)eventTitle{
NSString *firstStr = #"";
NSString *secondStr = #"";
NSString *thirdStr = #"";
Next i set label text to all of this three string. I set it value like:
thirdStr = [NSString stringWithFormat:#"%#", eventTitle];
secondStr = [NSString stringWithFormat:#"%#", eventTitle]
firstStr = [NSString stringWithFormat:#"%#", eventTitle];
In console i output its memory just after creation:
NSLog(#"memory %p , %p , %p", firstStr, secondStr, thirdStr);
memory 0x109af82d8 , 0x109af82d8 , 0x109af82d8
Any idea how make memory address different for them?
As you are using NSString for all 3 objects, which can't be mutated, The compiler will check the value of the string, which is the same empty to all strings. The compiler will optimise the memory usage by pointing the same memory.
The iOS compiler optimizes references to string objects that have the same value (i.e., it reuses them rather than allocating identical string objects redundantly), so all three pointers are in fact pointing to same address.
If you used the NSMutableString, still it may point to the same object, but when you try to mutate the string, it will be copied to the new memory(Lazy memory allocation).
if you want the different memory for each string then you can allocate the memory then initialize the string, like
NSMutableString *str1 = [[NSMutableString alloc]initWithString:#""]
NSMutableString *str2 = [[NSMutableString alloc]initWithString:#""]
NSMutableString *str3 = [[NSMutableString alloc]initWithString:#""]
But note that NSMutableString is mutable.
That's because your all string have address of [NSString stringWithFormat:#"%#", eventTitle]; and this is same for all strings.
For example if you will write below code in your viewdidload then you will get different memory address,
NSString *firstStr = #"";
NSString *secondStr = #"";
NSString *thirdStr = #"";
thirdStr = [NSString stringWithFormat:#"%#", #"eventTitle"];
secondStr = [NSString stringWithFormat:#"%#", #"eventTitle"];
firstStr = [NSString stringWithFormat:#"%#", #"eventTitle"];
NSLog(#"memory %p , %p , %p", firstStr, secondStr, thirdStr);
Because everystring has own memory and not pointing to any single string.
Maybe there's some kind of compiler optimisation going on. It determines (correctly) that the strings are the same so it optimises while compiling. As soon as you change your code so the strings will not be the same and recompile then the compiler won't optimise those particular strings.
thirdStr = [NSString stringWithFormat:#"a %#", eventTitle];
secondStr = [NSString stringWithFormat:#"b %#", eventTitle];
firstStr = [NSString stringWithFormat:#"c %#", eventTitle];

Make address lines blank if they don't exist (Objective-C)

My app finds the user's location, and shows the address in a label. The problem is that if something doesn't exist at a certain place, a postal code for example, then the line says (null). How do I make that line blank? I suppose it has to be set to nil somehow, somewhere...
Please help!
Here's my code:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSLog(#"Location: %#", newLocation);
CLLocation *currentLocation = newLocation;
[geoCoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (error == nil && [placemarks count] > 0) {
placeMark = [placemarks lastObject];
NSString *locationString = [NSString stringWithFormat:#"%# %#\n%# %#\n%#\n%#",
placeMark.subThoroughfare,
placeMark.thoroughfare,
placeMark.postalCode,
placeMark.locality,
placeMark.administrativeArea,
placeMark.country];
locationLabel.text = locationString;
}
else {
NSLog(#"%#", error.debugDescription);
}
}];
}
This code checks all fields for nil (which causes the <null> output) and replaces nil values with an empty string.
NSString *locationString = [NSString stringWithFormat:#"%# %#\n%# %#\n%#\n%#",
placeMark.subThoroughfare ?: #"",
placeMark.thoroughfare ?: #"",
placeMark.postalCode ?: #"",
placeMark.locality ?: #"",
placeMark.administrativeArea ?: #"",
placeMark.country ?: #""];
Quick and (very) dirty solution, but ones may find it readable:
substitute placeMark.postalCode,
with
placeMark.postalCode ? placeMark.postalCode : #"",
for each element you want nothing (or whatever other string) to appear in case of nil.
Or just write custom code to check for each element in local variables.
--
Edit:
In the remote case that you actually have a string containing "(null)" the you may want to consider checking for this value substituting again each line with:
[placeMark.postalCode isEqualToString:#"(null)"]? #"" : placeMark.postalCode,
consider anyway that you should really take care of this earlier in your logic, probably some parsing went wrong when creating the placeMark object string members.
You really need to write code to check for missing fields and handle them appropriately.
Consider that if some of the items are missing there may be blank lines in the resulting string.

present NSString as a line of elements

I have an NSString that hold data (actually that could be presented an NSArray). and i want to output that on a label.
In NSLog my NSString output is:
(
"cristian_camino",
"daddu_02",
"_ukendt_babe_",
"imurtaza.zoeb"
)
What i want is, to present it like :"cristian_camino","daddu_02","_ukendt_babe_","imurtaza.zoeb"
In a single line.
I could accomplish that turning string to an array and do following: arrayObjectAtIndex.0, arrayObjectAtIndex.1, arrayObjectAtIndex.2, arrayObjectAtIndex.3.
But thats look not good, and that objects may be nil, so i prefer NSString to hold data.
So, how could i write it in a single lane?
UPDATE:
There is the method i want to use to set text for UILabel:
-(void)setLikeLabelText:(UILabel*)label{
//Likes
NSString* likersCount = [self.photosDictionary valueForKeyPath:#"likes.count"];
NSString* likersRecent = [self.photosDictionary valueForKeyPath:#"likes.data.username"];
NSString *textString = [NSString stringWithFormat:#"%# - amount of people like it, recent "likes": %#", likersCount, likersRecent];
label.text = textString;
NSLog(#"text String is %#", textString);
}
valueForKeyPath: returns an NSArray, not an NSString. Whilst you've declared likersCount and likersRecent as instances of NSString, they're actually both arrays of values. You should be able to do something like the following to construct a string:
NSArray* likersRecent = [self.photosDictionary valueForKeyPath:#"likes.data.username"];
NSString *joined = [likersRecent componentsJoinedByString:#"\", \""];
NSString *result = [NSString stringWithFormat:#"\"%#\"", joined];
NSLog(#"Result: %#", result);
componentsJoinedByString: will join the elements of the array with ", ", and then the stringWithFormat call will add a " at the beginning and end.
The statement is incorrect, the internal quote marks (" that you want to display) need to be escaped:
NSString *textString = [NSString stringWithFormat:#"%# - amount of people like it, recent \"likes\": %#", likersCount, likersRecent];
If somebody curious how i fix it, there it is:
for (int i =0; i < [likersRecent count]; i++){
stringOfLikers = [stringOfLikers stringByAppendingString:[NSString stringWithFormat:#" %#", [likersRecent objectAtIndex:i]]];
}
Not using commas or dots though.

simple calculator app, presents SIGABRT error when I add new button

I'm new to Objective-C programminga nd X-Code so please bear with me. I copied code from a book about objective C to make a calculator class and it's working wonders. Then it asked me as part of the exercises to add a convert button to convert a fraction to a double and print it. The thing is that when I press the convert button, the app stops and I get a message saying that the program received a SIGABRT signal.
Here is the button code:
-(IBAction)clickConvert //convert method
{
if ( [myCalculator accumulator] != 0 ){
displayString = [NSString stringWithFormat: #"%e", [myCalculator convertResult]];
display.text = displayString;
[displayString setString:#""];
}}
The convertResult code:
-(double) convertResult
{
double convertedFraction;
convertedFraction = [accumulator convertToNum];
return convertedFraction;
}
(accumulator is a Fraction object that contains the value of the mathematical operation done on the 2 operands)
and the ConverToNum function:
-(double) convertToNum
{
if (denominator != 0)
return (double) numerator/denominator;
else
return NAN;
}
everything else in teh app works fine, so it must be a problem with what I've done......can anyone help? I've been searching online all day, but I read completely different stuff about this SIGABRT error. Thank you for your help
If displayString is an NSString, then there is no setString setter. Just assign the string to displayString.
Instead of
[displayString setString:#""]
do:
displayString = #"";
Edit:
You say that displayString is an NSMutableString. In that case, your first assignment to displayString:
displayString = [NSString stringWithFormat: #"%e", [myCalculator convertResult]];
assigns an immutable object to the displayString pointer. I don't know why the compiler isn't complaining about that. Then when you try to call setString on displayString, the actual type of the object is NSString even though you have declared the pointer to be an NSMutableString. To fix this, try:
displayString = [[NSString stringWithFormat: #"%e", [myCalculator convertResult]] mutableCopy];
or:
[displayString setString:[NSString stringWithFormat: #"%e", [myCalculator convertResult]]];
or:
displayString = [NSMutableString stringWithFormat: #"%e", [myCalculator convertResult]];

NSString : String extraction from a comma Separated NSString

Below are two examples of strings separated by comma that I get back as results:
NSString *placeResult = #"111 Main Street, Cupertino, CA"
or sometimes the result contains the name of a place:
NSString *placeResult = #"Starbucks, 222 Main Street, Cupertino, CA"
What's the best way to check the strings above to see if it starts with a name or or a street address?
If it does start with a name (i.e. Starbucks in the 2nd example above"), I'd like to extract the name and store it into another variable. Thus after extraction the string will be:
NSLog (#"%s", placeResult);
The log will print:
"222 Main Street, Cupertino, CA"
Another string will now store the #"Starbucks" in it:
NSLog (#"%s", placeName);
The log will print:
"Starbucks"
Important: I can't lose the comma separations after extraction.
Thank you!
Make use of NSDataDetector and NSTextCheckingResult:
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeAddress error:nil];
NSString *str = #"Starbucks, 222 Main Street, Cupertino, CA";
NSArray *matches = [detector matchesInString:str options:0 range:NSMakeRange(0, str.length)];
for (NSTextCheckingResult *match in matches) {
if (match.resultType == NSTextCheckingTypeAddress) {
NSDictionary *data = [match addressComponents];
NSLog(#"address = %#, range: %#", data, NSStringFromRange(match.range));
NSString *name = data[NSTextCheckingNameKey];
if (!name && match.range.location > 0) {
name = [str substringToIndex:match.range.location - 1];
// "name" may now include a trailing comma and space - strip these as needed
}
}
}
This outputs:
address = {
City = Cupertino;
State = CA;
Street = "222 Main Street";
}, range: {11, 30}
The odd thing is that the resulting dictionary of results does not contain a reference to the "Starbucks" portion. What you can do is check to see of the addressComponents contains a value for the NSTextCheckingNameKey. If not, check the range of the match. If the match's range isn't the start of the string, then you can use that value to extract the name from the beginning of the string.
To get an array of the things between commas, you could use:
NSArray *components = [placeResult componentsSeparatedByString:#","];
Possibly with a follow-up of:
NSMutableArray *trimmedComponents =
[NSMutableArray arrayWithCapacity:[components count]];
NSCharacterSet *whitespaceCharacterSet = [NSCharacterSet whitespaceCharacterSet];
for(NSString *component in components)
[trimmedComponents addObject:
[component stringByTrimmingCharactersInSet:whitespaceCharacterSet]];
To remove any leading or trailing spaces from each individual component. You would reverse the transformation using e.g.
NSString *fullAddress = [trimmedComponents componentsJoinedByString:#", "];
So then the question is, given NSString *firstComponent = [trimmedComponents objectAtIndex:0];, how do you guess whether it is a name or a street address? If it's as simple as checking whether there's a number at the front that isn't zero then you can just do:
if([firstComponent integerValue])
{
/* ... started with a non-zero number ... */
NSString *trimmedAddress = [[trimmedComponents subArrayWithRange:
NSMakeRange(1, [trimmedComponents count]-1)] componentsJoinedByString:", "];
NSLog(#"trimmed address is: %#");
}
Though that conditional test would also have worked with placeResult, and you'll probably want to add validity checks to make sure you have at least two components before you start assuming you can make an array from the 2nd one onwards.

Resources