Swift 3: How to do string ranges? - swift

Last night I had to convert my Swift 2.3 code to Swift 3.0 and my code is a mess after the conversion.
In Swift 2.3 I had the following code:
let maxChar = 40;
let val = "some long string";
var startRange = val.startIndex;
var endRange = val.startIndex.advancedBy(maxChar, limit: val.endIndex);
let index = val.rangeOfString(" ", options: NSStringCompareOptions.BackwardsSearch , range: startRange...endRange , locale: nil)?.startIndex;
Xcode converted my code to this which doesn't work:
let maxChar = 40;
let val = "some long string";
var startRange = val.startIndex;
var endRange = val.characters.index(val.startIndex, offsetBy: maxChar, limitedBy: val.endIndex);
let index = val.range(of: " ", options: NSString.CompareOptions.backwards , range: startRange...endRange , locale: nil)?.lowerBound
The error is in the parameter range in val.rage, saying No '...' candidates produce the expected contextual result type 'Range?'.
I tried using Range(startRange...endRange) as suggestd in the docs but I'm getting en error saying: connot invoke initiliazer for type ClosedRange<_> with an arguement list of type (ClosedRange). Seems like I'm missing something fundametnal.
Any help is appreciated.
Thanks!

Simple answer: the fundamental thing you are missing is that a closed range is now different from a range. So, change startRange...endRange to startRange..<endRange.
In more detail, here's an abbreviated version of your code (without the maxChar part):
let val = "some long string";
var startRange = val.startIndex;
var endRange = val.endIndex;
let index = val.range(
of: " ", options: .backwards, range: startRange..<endRange)?.lowerBound
// 9
Now you can use that as a basis to restore your actual desired functionality.
However, if all you want to do is split the string, then reinventing the wheel is kind of silly:
let arr = "hey ho ha".characters.split(separator:" ").map{String($0)}
arr // ["hey", "ho", "ha"]

Related

How to replace Swift substring with an Un-Equal substring by using location?

Can’t use occurrence of pattern as text can change at any instance.
var originalString = "Hi there <un>"
var stringToPut = "Some Amazing Name"
// Change string between 10th index and 13th to the following.
var requiredString = "Hi there <Some Amazing Name>"
This is very easy for just 1 character or when the length of the replacing string is same. But breaks when the substrings are unequal in length as the length of parent string changes and exact location references cannot be made.
Hopefully this works.
let originalString = "Hi there <un>"
let subString = "Some Amazing Name"
let characters = Array(originalString)
let firstPart = characters[0..<9]
let lastPart = characters[13..<characters.count]
let finaString = ("\(String(firstPart))\(subString)\(String(lastPart))")
Or you can use replaceSubrange:
var originalString = "Hi there <un>"
var stringToPut = "Some Amazing Name"
// Change string between 10th index and 13th to the following.
var requiredString = "Hi there <Some Amazing Name>"
let startIndex = originalString.index(originalString.startIndex, offsetBy: 9)
let endIndex = originalString.index(originalString.startIndex, offsetBy: 12)
originalString.replaceSubrange(startIndex...endIndex, with: "Some Amazing Name") // "Hi there Some Amazing Name"
If you know the format of <un> the simplest method would be:
let newString = originalString.replacingOccurrences(of: "<un>", with: stringToPut, options: .literal, range: nil)

How to get substring with specific ranges in Swift 4?

This is using the example code from the official Swift4 doc
let greeting = "Hi there! It's nice to meet you! πŸ‘‹"
let endOfSentence = greeting.index(of: "!")!
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"
But lets say let greeting = "Hello there world!"
and I want to retrieve only the second word (substring) in this sentence? So I only want the word "there".
I've tried using "world!" as an argument like
let endOfSentence = greeting.index(of: "world!")! but Swift 4 Playground doesn't like that. It's expecting 'Character' and my argument is a string.
So how can I get a substring of a very precise subrange? Or get nth word in a sentence for greater use in the future?
You can search for substrings using range(of:).
import Foundation
let greeting = "Hello there world!"
if let endIndex = greeting.range(of: "world!")?.lowerBound {
print(greeting[..<endIndex])
}
outputs:
Hello there
EDIT:
If you want to separate out the words, there's a quick-and-dirty way and a good way. The quick-and-dirty way:
import Foundation
let greeting = "Hello there world!"
let words = greeting.split(separator: " ")
print(words[1])
And here's the thorough way, which will enumerate all the words in the string no matter how they're separated:
import Foundation
let greeting = "Hello there world!"
var words: [String] = []
greeting.enumerateSubstrings(in: greeting.startIndex..<greeting.endIndex, options: .byWords) { substring, _, _, _ in
if let substring = substring {
words.append(substring)
}
}
print(words[1])
EDIT 2: And if you're just trying to get the 7th through the 11th character, you can do this:
import Foundation
let greeting = "Hello there world!"
let startIndex = greeting.index(greeting.startIndex, offsetBy: 6)
let endIndex = greeting.index(startIndex, offsetBy: 5)
print(greeting[startIndex..<endIndex])
For swift4,
let string = "substring test"
let start = String.Index(encodedOffset: 0)
let end = String.Index(encodedOffset: 10)
let substring = String(string[start..<end])
In Swift 5 encodedOffset (swift 4 func) is deprecated.
You will need to use utf160Offset
// Swift 5
let string = "Hi there! It's nice to meet you!"
let startIndex = 10 // random for this example
let endIndex = string.count
let start = String.Index(utf16Offset: startIndex, in: string)
let end = String.Index(utf16Offset: endIndex, in: string)
let substring = String(string[start..<end])
prints -> It's nice to meet you!
There is one mistake in the first answer.
Range<String.Index>.upperBound
The upperBound property should be the endIndex
For Example:
let text = "From Here Hello World"
if let result = text.range(of: "Hello World") {
let startIndex = result.upperBound
let endIndex = result.lowerBound
print(String(text[startIndex..<endIndex])) //"Hello World"
}
the simplest way I use is :
var str = "abcdefg"
String(Array(str)[2...4])
Old habits die hard. I did it the "Java" way and split the string up by spaces, then accessed the second word.
print(greeting.split(separator: " ")[1]) // "there /n"

Display of special characters \u{n}

Impossible to find the solution ; it does not work...
I've been on this for hours... A little help will give me the opportunity to sleep without a nightmare...
Where is the error ?
let num: Int = 128150 // smiley = "\u{1F496}" => 128150
var str: String = String(num, radix: 16)
str = str.uppercased()
var wkHex: String = "\\u{"+str+"}" // wkHex = "\u{"+str+"}" not match
wkHex.characters.removeFirst(0) // remove "\" unnecessary at startIndex
let cnt = wkHex.characters.count
let zzz: Array = Array(wkHex.characters)
var car: String = ""
for i in 0...cnt - 1 {
car.append(zzz[i])
}
outputChar.stringValue = car // outputChar is a Label (NSTextField)
// output : \u{1F496} ! instead of : πŸ’–
So the idea is to go from a code point to a character?
let iii = 128150
let char = Character(UnicodeScalar(iii)!)
print(char) // πŸ’–
Swift only allows you to use the \u{...} syntax at compile time. This means that the string won't be turned into the emoji at runtime, when the value of num is known.
To do this, you can use UnicodeScalar:
let unicode = UnicodeScalar(128150)
unicode?.description // πŸ’–

New substring method swift 3 [duplicate]

I'm using the following code:
var continousDigitsRange:Range<Int> = Range<Int>(start: 0, end: 0)
Since update to Xcode 7.3 (Swift 2.2) I got the following hint:
'init(start:end:)' is deprecated: it will be removed in Swift 3. Use
the '..<' operator.
For me is not clear how to "translate" it correctly with "using the '..<' operator.
You should simply write
var continousDigitsRange1:Range<Int> = 0..<0
or if you want to go even simpler
var continousDigitsRange = 0..<0
Also worth noting, to substringWithRange a String, you can now use
let theString = "Hello, how are you"
let range = theString.startIndex.advancedBy(start) ..< theString.startIndex.advancedBy(end)
theString.substringWithRange(range)
The closed range operator (a...b) defines a range that runs from a
to b, and includes the values a and b. The value of a must not be
greater than b.
The half-open range operator (a..<b) defines a range that runs from a
to b, but does not include b. It is said to be half-open because it
contains its first value, but not its final value. As with the closed
range operator, the value of a must not be greater than b. If the
value of a is equal to b, then the resulting range will be empty.
The Swift Programming Language (Swift 2.2) - Basic Operators
var continousDigitsRange:Range<Int> = Range<Int>(start: 0, end: 0)
--to--
var continousDigitsRange:Range<Int> = 0..<0
to show bmichotte's answer in full...
let theString = "Hello, how are you today my friend"
let start = 3
let end = 15
let range = theString.startIndex.advancedBy(start) ..< theString.startIndex.advancedBy(end)
let p = theString.substringWithRange(range)
print("this is the middle bit>\(p)<")
this produces this is the middle bit>lo, how are <
Adding some points with reference to swift 3.0
//Countable Range Example.
let range1 = 0..<5
Countable Closed Range Example
let range2 = 0...5
//Range from bounds
let range = Range(uncheckedBounds: (range1.lowerBound,range1.upperBound))
//To get the distance from substringRange.
let str = "Hello, how are you"
let substringRange = str.characters.indices
// Below Swift 3.0
let length = substringRange.distance(from: substringRange.startIndex, to: substringRange.endIndex)
//For Swift 3.0
let length2 = str.distance(from: substringRange.startIndex, to: substringRange.endIndex)
I have always had a function to get the substring range of a string. Here is my updated function for Swift 3:
func getSubStringRange(fullString: String, fromIndex: Int, subStringSize: Int) -> Range<String.Index> {
let startIndex = fullString.characters.index(fullString.startIndex, offsetBy: fromIndex)
let endIndex = fullString.characters.index(startIndex, offsetBy: subStringSize)
let subStringRange = startIndex..<endIndex
return subStringRange
}
The function is pretty self explanatory - You pass in a string(fullString), the index of that string where the substring starts(fromIndex) and how big the subString is(subStringSize).
Example:
let greeting = "Hi, my name is Nathaniel"
let getName = greeting[getSubStringRange(fullString: greeting, fromIndex: 15, subStringSize: 9)]
print("Name: \(getName)")
-> Prints: "Name: Nathaniel"

'init(start:end:)' is deprecated: it will be removed in Swift 3. Use the '..<' operator

I'm using the following code:
var continousDigitsRange:Range<Int> = Range<Int>(start: 0, end: 0)
Since update to Xcode 7.3 (Swift 2.2) I got the following hint:
'init(start:end:)' is deprecated: it will be removed in Swift 3. Use
the '..<' operator.
For me is not clear how to "translate" it correctly with "using the '..<' operator.
You should simply write
var continousDigitsRange1:Range<Int> = 0..<0
or if you want to go even simpler
var continousDigitsRange = 0..<0
Also worth noting, to substringWithRange a String, you can now use
let theString = "Hello, how are you"
let range = theString.startIndex.advancedBy(start) ..< theString.startIndex.advancedBy(end)
theString.substringWithRange(range)
The closed range operator (a...b) defines a range that runs from a
to b, and includes the values a and b. The value of a must not be
greater than b.
The half-open range operator (a..<b) defines a range that runs from a
to b, but does not include b. It is said to be half-open because it
contains its first value, but not its final value. As with the closed
range operator, the value of a must not be greater than b. If the
value of a is equal to b, then the resulting range will be empty.
The Swift Programming Language (Swift 2.2) - Basic Operators
var continousDigitsRange:Range<Int> = Range<Int>(start: 0, end: 0)
--to--
var continousDigitsRange:Range<Int> = 0..<0
to show bmichotte's answer in full...
let theString = "Hello, how are you today my friend"
let start = 3
let end = 15
let range = theString.startIndex.advancedBy(start) ..< theString.startIndex.advancedBy(end)
let p = theString.substringWithRange(range)
print("this is the middle bit>\(p)<")
this produces this is the middle bit>lo, how are <
Adding some points with reference to swift 3.0
//Countable Range Example.
let range1 = 0..<5
Countable Closed Range Example
let range2 = 0...5
//Range from bounds
let range = Range(uncheckedBounds: (range1.lowerBound,range1.upperBound))
//To get the distance from substringRange.
let str = "Hello, how are you"
let substringRange = str.characters.indices
// Below Swift 3.0
let length = substringRange.distance(from: substringRange.startIndex, to: substringRange.endIndex)
//For Swift 3.0
let length2 = str.distance(from: substringRange.startIndex, to: substringRange.endIndex)
I have always had a function to get the substring range of a string. Here is my updated function for Swift 3:
func getSubStringRange(fullString: String, fromIndex: Int, subStringSize: Int) -> Range<String.Index> {
let startIndex = fullString.characters.index(fullString.startIndex, offsetBy: fromIndex)
let endIndex = fullString.characters.index(startIndex, offsetBy: subStringSize)
let subStringRange = startIndex..<endIndex
return subStringRange
}
The function is pretty self explanatory - You pass in a string(fullString), the index of that string where the substring starts(fromIndex) and how big the subString is(subStringSize).
Example:
let greeting = "Hi, my name is Nathaniel"
let getName = greeting[getSubStringRange(fullString: greeting, fromIndex: 15, subStringSize: 9)]
print("Name: \(getName)")
-> Prints: "Name: Nathaniel"