Why does String.subscript(_:) require the types `String.Index` and `Int` to be equal when there is no `Int` involved? - swift

I fail to understand the problem Xcode is confronting me with in this line:
iteration.template = template[iterationSubstring.endIndex...substring.startIndex]
template is a String and iterationSubstring and substring are Substrings of template. Xcode highlights the opening square bracket with the following message:
Subscript 'subscript(_:)' requires the types 'Substring.Index' and 'Int' be equivalent
The error message does not make any sense to me. I try to obtain a Substring by creating a Range<String.Index> with the [template.startIndex...template.endIndex] subscript. How is this related to Int? And why does the same pattern work elsewhere?
Xcode playground code reproducing the problem:
import Foundation
let template = "This is an ordinary string literal."
let firstSubstringStart = template.index(template.startIndex, offsetBy: 5)
let firstSubstringEnd = template.index(template.startIndex, offsetBy: 7)
let firstSubstring = template[firstSubstringStart...firstSubstringEnd]
let secondSubstringStart = template.index(template.startIndex, offsetBy: 10)
let secondSubstringEnd = template.index(template.startIndex, offsetBy: 12)
let secondSubstring = template[secondSubstringStart...secondSubstringEnd]
let part: String = template[firstSubstring.endIndex...secondSubstring.startIndex]
After all I have a template string and two substrings of it. I want to get a String ranging from the end of the first Substring to the start of the second Substring.

The current version of Swift works with the Substring struct which is a sliced String.
The error seems to be misleading and occurs if you are going to assign a (range-subscripted) Substring to a String variable.
To fix the error create a String from the Substring
iteration.template = String(template[iterationSubstring.endIndex...substring.startIndex])
Nevertheless you are strongly discouraged from creating ranges with indices from different strings (iterationSubstring and substring). Slice the main string, the indices are preserved.
The crash in the second (meanwhile deleted) example occurred because the last character of a string is at index before endIndex, it's
template[template.startIndex..<template.endIndex]
or shorter
template[template.startIndex...]

Related

Swift 5 split string at integer index

It used to be you could use substring to get a portion of a string. That has been deprecated in favor on string index. But I can't seem to make a string index out of integers.
var str = "hellooo"
let newindex = str.index(after: 3)
str = str[newindex...str.endIndex]
No matter what the string is, I want the second 3 characters. So and str would contain "loo". How can I do this?
Drop the first three characters and the get the remaining first three characters
let str = "helloo"
let secondThreeCharacters = String(str.dropFirst(3).prefix(3))
You might add some code to handle the case if there are less than 6 characters in the string

How do I format a string from a string with %# in Swift

I am using Swift 4.2. I am getting extraneous characters when formatting one string (s1) from another string(s0) using the %# format code.
I have searched extensively for details of string formatting but have come up with only partial answers including the code in the second line below. I need to be able to format s1 so that I can customize output from a Swift process. I ask this because I have not found an answer while searching for ways to format a string from a string.
I tried the following three statements:
let s0:[String] = ["abcdef"]
let s1:[String] = [String(format:"%#",s0)]
print(s1)
...
The output is shown below. It may not be clear, here, but there are four leading spaces to the left of the abcdef string.
["(\n abcdef\n)"]
How can I format s1 so it does not include the brackets, the \n escape characters, and the leading spaces?
The issue here is you are using an array but a string in s0.
so the following index will help you.
let s0:[String] = ["abcdef"]
let s1:[String] = [String(format:" %#",s0[0])]
I am getting extraneous characters when formatting one string (s1) from another string (s0) ...
The s0 is not a string. It is an array of strings (i.e. the square brackets of [String] indicate an array and is the same as saying Array<String>). And your s1 is also array, but one that that has one element, whose value is the string representation of the entire s0 array of strings. That’s obviously not what you intended.
How can I format s1 so it does not include the brackets, the \n escape characters, and the leading spaces?
You’re getting those brackets because s1 is an array. You’re getting the string with the \n and spaces because its first value is the string representation of yet another array, s0.
So, if you’re just trying to format a string, s0, you can do:
let s0: String = "abcdef"
let s1: String = String(format: "It is ‘%#’", s0)
Or, if you really want an array of strings, you can call String(format:) for each using the map function:
let s0: [String] = ["abcdef", "ghijkl"]
let s1: [String] = s0.map { String(format: "It is ‘%#’", $0) }
By the way, in the examples above, I didn’t use a string format of just %#, because that doesn’t accomplish anything at all, so I assumed you were formatting the string for a reason.
FWIW, we generally don’t use String(format:) very often. Usually we do “string interpolation”, with \( and ):
let s0: String = "abcdef"
let s1: String = "It is ‘\(s0)’"
Get rid of all the unneccessary arrays and let the compiler figure out the types:
let s0 = "abcdef" // a string
let s1 = String(format:"- %# -",s0) // another string
print(s1) // prints "- abcdef -"

Swift 4: Trim last character of string based on character

I'm trying to remove the last numbers of an IP address string in Swift so I can loop through IP addresses. For instance if my variable = 192.168.1.123, I would like to trim the string to equal 192.169.1.
I'm not sure how to do this since some IP addresses will end in 1, 2 or 3 digits. I couldn't figure out how to trim back to a certain character.
I have a solution (In your case only). You can try it
let str = "192.168.1.123"
var arr = str.components(separatedBy: ".")
arr.removeLast()
let newstr = arr.joined(separator: ".") + "."
You can find the range of the last .:
let ip = "192.168.1.123"
let lastdot = ip.range(of: ".", options: .backwards)!
let base = ip[...lastdot.lowerBound]
This code assumes there is at least one . in the string. If not it will crash. That is easily fixed with proper use of if let.
base will be a Substring so depending on what you do next, you may need to wrap that as:
let base = String(ip[...lastdot.lowerBound])
Whether explicitly converting to String depends on whether subsequent methods require String or StringProtocol. Converting to String copies over the storage again, which is costly and unnecessary for many operations, but may be required in some cases.

Remove substring from a string knowing first and last characters in Swift

Having a string like this:
let str = "In 1273, however, they lost their son in an accident;[2] the young Theobald was dropped by his nurse over the castle battlements.[3]"
I'm looking for a solution of removing all appearances of square brackets and anything that between it.
I was trying using a String's method: replacingOccurrences(of:with:), but it requires the exact substring it needs to be removed, so it doesn't work for me.
You can use:
let updated = str.replacingOccurrences(of: "\\[[^\\]]+\\]", with: "", options: .regularExpression)
The regular expression (without the required escapes needed in a Swift string is:
\[[^\]+]\]
The \[ and \] look for the characters [ and ]. They have a backslash to remove the normal special meaning of those characters in a regular expression.
The [^]] means to match any character except the ] character. The + means match 1 or more.
You can create a while loop to get the lowerBound of the range of the first string and the upperBound of the range of the second string and create a range from that. Next just remove the subrange of your string and set the new startIndex for the search.
var str = "In 1273, however, they lost their son in an accident;[2] the young Theobald was dropped by his nurse over the castle battlements.[3]"
var start = str.startIndex
while let from = str.range(of: "[", range: start..<str.endIndex)?.lowerBound,
let to = str.range(of: "]", range: from..<str.endIndex)?.upperBound,
from != to {
str.removeSubrange(from..<to)
start = from
}
print(str) // "In 1273, however, they lost their son in an accident; the young Theobald was dropped by his nurse over the castle battlements."

Convert Character to Integer in Swift

I am creating an iPhone app and I need to convert a single digit number into an integer.
My code has a variable called char that has a type Character, but I need to be able to do math with it, therefore I think I need to convert it to a string, however I cannot find a way to do that.
In the latest Swift versions (at least in Swift 5) there is a more straighforward way of converting Character instances. Character has property wholeNumberValue which tries to convert a character to Int and returns nil if the character does not represent and integer.
let char: Character = "5"
if let intValue = char.wholeNumberValue {
print("Value is \(intValue)")
} else {
print("Not an integer")
}
With a Character you can create a String. And with a String you can create an Int.
let char: Character = "1"
if let number = Int(String(char)) {
// use number
}
The String middleman type conversion isn’t necessary if you use the unicodeScalars property of Swift 4.0’s Character type.
let myChar: Character = "3"
myChar.unicodeScalars.first!.value - Unicode.Scalar("0")!.value // 3: UInt32
This uses a trick commonly seen in C code of subtracting the value of the char ’0’ literal to convert from ascii values to decimal values. See this site for the conversions: https://www.asciitable.com
Also there are some implicit unwraps in my answer. To avoid those, you can validate that you have a decimal digit with CharacterSet.decimalDigits, and/or use guard lets around the first property. You can also subtract 48 directly rather than converting ”0” through Unicode.Scalar.