I am starting to learn Swift and hope to find it an excellent replacement for Objective C.
I am attempting to convert my Objective C classes into Swift and I cannot find the best way to translate the following method into Swift.
#implementation VersionReader
- (NSString *)readVersionFromString:(NSString *)string {
if (string.length == 0) {
return nil;
}
unichar firstChar = [string characterAtIndex:0];
if (firstChar < '0' || firstChar > '9') {
return nil;
}
NSUInteger length = string.length;
for (NSUInteger i = 0; i < length; ++i) {
if ([string characterAtIndex:i] == ' ') {
return [string substringToIndex:i];
}
}
return string;
}
#end
So far my Swift code looks like this:
import Cocoa
class VersionReader {
func readVersionFromString(string: String) -> String? {
if (string.isEmpty) {
return nil
}
var firstChar = string.characterAtIndex[0]
if (firstChar < 48 || firstChar > 57) {
return nil
}
var length = string.utf16Count
for (var i = 0; i < length; ++i) {
if (string.characterAtIndex(i) == 32) {
return string.substringToIndex(i)
}
}
return string
}
}
Tom this, I get the same error on two lines:
'String' does not have a member named 'characterAtIndex'
What would be an alternative to make this work in Swift? Thanks in advance.
A possible Swift solution:
func readVersionFromString(string: String) -> String? {
if string.isEmpty {
return nil
}
let firstChar = string[string.startIndex]
if !find("0123456789", firstChar) {
return nil
} else if let pos = find(string, " ") {
return string.substringToIndex(pos)
} else {
return string
}
}
Swift's String type doesn't have a characterAtIndex method. You can cast it to an NSString and use it as below:
var firstChar = (string as NSString).characterAtIndex(0)
Or
var firstChar = string.bridgeToObjectiveC().characterAtIndex(0)
Note that the characterAtIndex method returns an unichar, which seems to be what you want. But the correct approach to get a Swift Character would be that suggested by FreeAsInBeer in his answer: Array(string)[0]
Swift doesn't have a characterAtIndex selector. Instead, you need to use Array(string)[0].
Related
I searched on google and on SO but didn't find any useful help for this issue.
I'm trying to translate this code from objective-c to swift:
- (void)metaTitleUpdated:(NSString *)title {
NSLog(#"delegate title updated to %#", title);
NSArray *chunks = [title componentsSeparatedByString:#";"];
if ([chunks count]) {
NSArray *streamTitle = [[chunks objectAtIndex:0] componentsSeparatedByString:#"="];
if ([streamTitle count] > 1) {
titleLabel.text = [streamTitle objectAtIndex:1];
}
}
}
so far i have translated it to this:
func metaTitleUpdated(input: String) {
println("delegate title updated to \(title)")
let chunks: NSArray = title!.componentsSeparatedByString(";")
if (chunks.count) {
let streamTitle = chunks .objectAtIndex(0) .componentsSeparatedByString(";")
if (streamTitle.count > 1) {
titleLabel.text = streamTitle.objectAtIndex(1)
}
}
}
but i always get the error "Type 'Int' does not conform to protocol 'BooleanType'" in the line: if (chunks.count) {
What does cause this error? Is the rest of the code in swift correct or are there any other errors?
chunks.count has the type Int, but the if statement requires a boolean expression.
This is different from (Objective-)C, where the controlling expression of an if statement can have any scalar type and is compared with zero.
So the corresponding Swift code for
if ([chunks count]) { ... }
is
if chunks.count != 0 { ... }
I solved the answer by myself.
func metaTitleUpdated(title: String) {
var StreamTitle = split(title) {$0 == "="}
var derRichtigeTitel: String = StreamTitle[1]
titleLabel.text = derRichtigeTitel
println("delegate title updated to \(derRichtigeTitel)")
}
I've been trying to find a better way to switch on each character of a string.
My existing code is:
NSUInteger len = [oldName length], i;
SEL xSelector = #selector(characterAtIndex:);
unichar (*charAtIdx)(id, SEL, NSUInteger) = (typeof (charAtIdx)) [oldName methodForSelector:xSelector];
NSMutableString *NewName = [NSMutableString new];
for (i=0 ; i<len ; i++){
unichar c = charAtIdx(oldName,xSelector,i);
if (c == "Ú" || c == "°"){
[NewName appendString:#"s"];
}
else if (c == "Û" || c == "”"){
[NewName appendString:#"s"];
}
else if (c == "◊" || c == "˜"){
[NewName appendString:#"x"];
}
else blablabla
}
return NewName;
Now, the above seems to be working, however i have about 50 if statements that "switch" mainly extended ASCII codes (character codes 128-255) to more meaningful ones.
I thought about using a switch statement with a typedef enum and switch on that, however, the below doesn't work:
typedef enum {·,¡,Ê,∆} ExtendedASCII;
The idea would be to replace "unichar c = charAtIdx(oldName,xSelector,i);" with the below:
ExtendedASCII c = charAtIdx(oldName,xSelector,i);
Switch c
case 0: //being ·
case 1: // being ¡
blablabla
Any ideas????
thanks,
alex
usualy you could do
switch(c)
{
case:'a':; // fall throught
case:'b':
{
[NewName appendString:#"x"];
break;
}
}
but if you use 'Ú' you will get a compiler error, because this "char" is no unichar.
you can try to set the int value for the char
switch(c)
{
case: 218:; // 'Ú' , fall throught
case: 186: // '°'
{
[NewName appendString:#"x"];
break;
}
...
}
I cannot test, because I cannot get a valid 'Ú'.
Hoping for comments, maybe I can extend and answer upcomming questions ;)
What would be the most efficient way to convert a string like "ThisStringIsJoined" to "This String Is Joined" in objective-c?
I receive strings like this from a web service thats out of my control and I would like to present the data to the user, so I would just like to tidy it up a bit by adding spaces infront of each uppercase word. The strings are always formatted with each word beginning in an uppercase letter.
I'm quite new to objective-c so cant really figure this one out.
Thanks
One way of achieving this is as follows:
NSString *string = #"ThisStringIsJoined";
NSRegularExpression *regexp = [NSRegularExpression
regularExpressionWithPattern:#"([a-z])([A-Z])"
options:0
error:NULL];
NSString *newString = [regexp
stringByReplacingMatchesInString:string
options:0
range:NSMakeRange(0, string.length)
withTemplate:#"$1 $2"];
NSLog(#"Changed '%#' -> '%#'", string, newString);
The output in this case would be:
'ThisStringIsJoined' -> 'This String Is Joined'
You might want to tweak the regular expression to you own needs. You might want to make this into a category on NSString.
NSRegularExpressions are the way to go, but as trivia, NSCharacterSet can also be useful:
- (NSString *)splitString:(NSString *)inputString {
int index = 1;
NSMutableString* mutableInputString = [NSMutableString stringWithString:inputString];
while (index < mutableInputString.length) {
if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:[mutableInputString characterAtIndex:index]]) {
[mutableInputString insertString:#" " atIndex:index];
index++;
}
index++;
}
return [NSString stringWithString:mutableInputString];
}
Here's a category on NSString that will do what you want. This will handle non-ASCII letters. It will also split "IDidAGoodThing" properly.
#implementation NSString (SeparateCapitalizedWords)
-(NSString*)stringBySeparatingCapitalizedWords
{
static NSRegularExpression * __regex ;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSError * error = nil ;
__regex = [ NSRegularExpression regularExpressionWithPattern:#"[\\p{Uppercase Letter}]" options:0 error:&error ] ;
if ( error ) { #throw error ; }
});
NSString * result = [ __regex stringByReplacingMatchesInString:self options:0 range:(NSRange){ 1, self.length - 1 } withTemplate:#" $0" ] ;
return result ;
}
#end
Here is Swift Code (objective c code by webstersx), Thanks !
var str: NSMutableString = "iLoveSwiftCode"
var str2: NSMutableString = NSMutableString()
for var i:NSInteger = 0 ; i < str.length ; i++ {
var ch:NSString = str.substringWithRange(NSMakeRange(i, 1))
if(ch .rangeOfCharacterFromSet(NSCharacterSet.uppercaseLetterCharacterSet()).location != NSNotFound) {
str2 .appendString(" ")
}
str2 .appendString(ch)
}
println("\(str2.capitalizedString)")
}
Output : I Love Swift Code
For anyone who came here looking for the similar question answered in Swift:
Perhaps a cleaner (adding to Sankalp's answer), and more 'Swifty' approach:
func addSpaces(to givenString: String) -> String{
var string = givenString
//indexOffset is needed because each time replaceSubrange is called, the resulting count is incremented by one (owing to the fact that a space is added to every capitalised letter)
var indexOffset = 0
for (index, character) in string.characters.enumerated(){
let stringCharacter = String(character)
//Evaluates to true if the character is a capital letter
if stringCharacter.lowercased() != stringCharacter{
guard index != 0 else { continue } //"ILoveSwift" should not turn into " I Love Swift"
let stringIndex = string.index(string.startIndex, offsetBy: index + indexOffset)
let endStringIndex = string.index(string.startIndex, offsetBy: index + 1 + indexOffset)
let range = stringIndex..<endStringIndex
indexOffset += 1
string.replaceSubrange(range, with: " \(stringCharacter)")
}
}
return string
}
You call the function like so:
var string = "iLoveSwiftCode"
addSpaces(to: string)
//Result: string = "i Love Swift Code"
Alternatively, if you prefer extensions:
extension String{
mutating func seperatedWithSpaces(){
//indexOffset is needed because each time replaceSubrange is called, the resulting count is incremented by one (owing to the fact that a space is added to every capitalised letter)
var indexOffset = 0
for (index, character) in characters.enumerated(){
let stringCharacter = String(character)
if stringCharacter.lowercased() != stringCharacter{
guard index != 0 else { continue } //"ILoveSwift" should not turn into " I Love Swift"
let stringIndex = self.index(self.startIndex, offsetBy: index + indexOffset)
let endStringIndex = self.index(self.startIndex, offsetBy: index + 1 + indexOffset)
let range = stringIndex..<endStringIndex
indexOffset += 1
self.replaceSubrange(range, with: " \(stringCharacter)")
}
}
}
}
Call the method from a string:
var string = "iLoveSwiftCode"
string.seperatedWithSpaces()
//Result: string = "i Love Swift Code"
You could try making a new string that is a lowercase copy of the original string. Then compare the two strings and insert spaces wherever the characters are different.
Use the NSString method to turn to lowercase.
- (NSString *)lowercaseString
How to check if the content of a NSString is an integer value? Is there any readily available way?
There got to be some better way then doing something like this:
- (BOOL)isInteger:(NSString *)toCheck {
if([toCheck intValue] != 0) {
return true;
} else if([toCheck isEqualToString:#"0"]) {
return true;
} else {
return false;
}
}
You could use the -intValue or -integerValue methods. Returns zero if the string doesn't start with an integer, which is a bit of a shame as zero is a valid value for an integer.
A better option might be to use [NSScanner scanInt:] which returns a BOOL indicating whether or not it found a suitable value.
Something like this:
NSScanner* scan = [NSScanner scannerWithString:toCheck];
int val;
return [scan scanInt:&val] && [scan isAtEnd];
Building on an answer from #kevbo, this will check for integers >= 0:
if (fooString.length <= 0 || [fooString rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound) {
NSLog(#"This is not a positive integer");
}
A swift version of the above:
func getPositive(incoming: String) -> String {
if (incoming.characters.count <= 0) || (incoming.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet) != nil) {
return "This is NOT a positive integer"
}
return "YES! +ve integer"
}
Do not forget numbers with decimal point!!!
NSMutableCharacterSet *carSet = [NSMutableCharacterSet characterSetWithCharactersInString:#"0123456789."];
BOOL isNumber = [[subBoldText stringByTrimmingCharactersInSet:carSet] isEqualToString:#""];
func getPositive(input: String) -> String {
if (input.count <= 0) || (input.rangeOfCharacter(from: NSCharacterSet.decimalDigits.inverted) != nil) {
return "This is NOT a positive integer"
}
return "YES! integer"
}
Update #coco's answer for Swift 5
How to check if the content of a NSString is an integer value? Is there any readily available way?
There got to be some better way then doing something like this:
- (BOOL)isInteger:(NSString *)toCheck {
if([toCheck intValue] != 0) {
return true;
} else if([toCheck isEqualToString:#"0"]) {
return true;
} else {
return false;
}
}
You could use the -intValue or -integerValue methods. Returns zero if the string doesn't start with an integer, which is a bit of a shame as zero is a valid value for an integer.
A better option might be to use [NSScanner scanInt:] which returns a BOOL indicating whether or not it found a suitable value.
Something like this:
NSScanner* scan = [NSScanner scannerWithString:toCheck];
int val;
return [scan scanInt:&val] && [scan isAtEnd];
Building on an answer from #kevbo, this will check for integers >= 0:
if (fooString.length <= 0 || [fooString rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound) {
NSLog(#"This is not a positive integer");
}
A swift version of the above:
func getPositive(incoming: String) -> String {
if (incoming.characters.count <= 0) || (incoming.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet) != nil) {
return "This is NOT a positive integer"
}
return "YES! +ve integer"
}
Do not forget numbers with decimal point!!!
NSMutableCharacterSet *carSet = [NSMutableCharacterSet characterSetWithCharactersInString:#"0123456789."];
BOOL isNumber = [[subBoldText stringByTrimmingCharactersInSet:carSet] isEqualToString:#""];
func getPositive(input: String) -> String {
if (input.count <= 0) || (input.rangeOfCharacter(from: NSCharacterSet.decimalDigits.inverted) != nil) {
return "This is NOT a positive integer"
}
return "YES! integer"
}
Update #coco's answer for Swift 5