Swift 4 'substring(from:)' is deprecated: Please use String slicing subscript with a 'partial range from' operator - swift

i've just converted my little app but i've found this error:
'substring(from:)' is deprecated: Please use String slicing subscript with a 'partial range from' operator
my code is:
let dateObj = dateFormatterFrom.date(from: dateStringa)
if dateObj != nil {
cell.detailTextLabel?.text = dateFormatterTo.string(from:(dateObj!))
} else {
let index = thisRecord.pubDate.index(thisRecord.pubDate.startIndex, offsetBy: 5)
cell.detailTextLabel?.text = thisRecord.pubDate.substring(from: index)
}

Follow the below example to fix this warning:
Supporting examples for Swift 3, 4 and 5.
let testStr = “Test Teja”
let finalStr = testStr.substring(to: index) // Swift 3
let finalStr = String(testStr[..<index]) // Swift 4
let finalStr = testStr.substring(from: index) // Swift 3
let finalStr = String(testStr[index...]) // Swift 4
//Swift 3
let finalStr = testStr.substring(from: index(startIndex, offsetBy: 3))
//Swift 4 and 5
let reqIndex = testStr.index(testStr.startIndex, offsetBy: 3)
let finalStr = String(testStr[..<reqIndex])
//**Swift 5.1.3 - usage of index**
let myStr = "Test Teja == iOS"
let startBound1 = String.Index(utf16Offset: 13, in: myStr)
let finalStr1 = String(myStr[startBound1...])// "iOS"
let startBound2 = String.Index(utf16Offset: 5, in: myStr)
let finalStr2 = String(myStr[startBound2..<myStr.endIndex]) //"Teja == iOS"

In place of substring use suffix. Use like below :
cell.detailTextLabel?.text = String(thisRecord.pubDate.suffix(from: index))

It means you should use the new partial range operator as your upperBound:
let str = "Hello World !!!"
if let index = str.range(of: "Hello ")?.upperBound {
let string = String(str[index...]) // "World !!!"
}
In your case
cell.detailTextLabel?.text = String(thisRecord.pubDate[index...]))

In Swift 5, it is:
extension String {
func index(from: Int) -> Index {
return self.index(startIndex, offsetBy: from)
}
func substring(from: Int) -> String {
let fromIndex = index(from: from)
return String(self[fromIndex...])
}
func substring(to: Int) -> String {
let toIndex = index(from: to)
return String(self[..<toIndex])
}
func substring(with r: Range<Int>) -> String {
let startIndex = index(from: r.lowerBound)
let endIndex = index(from: r.upperBound)
return String(self[startIndex..<endIndex])
}
}

Most of my strings have A-Za-z and 0-9 content. No need for difficult
Index handling. This extension of String is based on the familiar LEFT / MID and RIGHT functions.
extension String {
// LEFT
// Returns the specified number of chars from the left of the string
// let str = "Hello"
// print(str.left(3)) // Hel
func left(_ to: Int) -> String {
return "\(self[..<self.index(startIndex, offsetBy: to)])"
}
// RIGHT
// Returns the specified number of chars from the right of the string
// let str = "Hello"
// print(str.left(3)) // llo
func right(_ from: Int) -> String {
return "\(self[self.index(startIndex, offsetBy: self.length-from)...])"
}
// MID
// Returns the specified number of chars from the startpoint of the string
// let str = "Hello"
// print(str.left(2,amount: 2)) // ll
func mid(_ from: Int, amount: Int) -> String {
let x = "\(self[self.index(startIndex, offsetBy: from)...])"
return x.left(amount)
}
}

If you wish to get substring with specific offset without upper bound do the following:
let index = thisRecord.pubDate.index(thisRecord.pubDate.startIndex, offsetBy: 5)
cell.detailTextLabel?.text = String(thisRecord.pubDate[index...]
This way you create a new String object from your existing String thisRecord.pubDate taking anything from specified index to the end index of original String.

str[..<index]
str[index...]
The code above is "partial range from"
Look at this How can I use String slicing subscripts in Swift 4?

Related

How to figure out the range of a substring in one string and then use it in another

Basicly I want something like this,
NSString* foobar(NSString *input) {
// say input is "1"
NSString *string = #"0123456789";
NSString *anotherString = #"零一二三四五六七八九";
NSRange range = [string rangeOfString:input];
// return "一" here
return [anotherString substringWithRange:range];
}
I tried the same stuff in Swift,
func foobar(input: String) -> String {
// say input is "1"
let string = "0123456789"
let range = string.range(of: input, options: .anchored)
let result = anotherString[range!]
// return "012" here
return String(result)
}
why?
And how can I achieve this?
String (or generally, collection) indices must only be used with the collection that they were created with. In order to find the same positions in another string, the indices must be converted to (integer) offsets and back to indices of the target string:
func foobar(input: String) -> String? {
let s1 = "0123456789"
let s2 = "😀一二三四五六七八九";
guard let range = s1.range(of: input) else {
return nil
}
let pos = s1.distance(from: s1.startIndex, to: range.lowerBound)
let len = s1.distance(from: range.lowerBound, to: range.upperBound)
guard
let lo = s2.index(s2.startIndex, offsetBy: pos, limitedBy: s2.endIndex),
let hi = s2.index(lo, offsetBy: len, limitedBy: s2.endIndex)
else {
return nil
}
return String(s2[lo..<hi])
}
print(foobar(input: "1") as Any) // Optional("一")
print(foobar(input: "123") as Any) // Optional("一二三")
print(foobar(input: "124") as Any) // nil
Your Objective-C code works as long as all characters in the string consume a single UTF-16 code unit (because that is what NSRange counts). It will not work correctly emojis, flags, and other characters which are represented as UTF-16 surrogate pairs, e.g. with
NSString *anotherString = #"😀一二三四五六七八九";
Another approach is converting strings to array of characters
func find(_ str: Character) {
let firstArr = Array("0123456789")
let secondArr = Array("零一二三四五六七八九")
guard let index = firstArr.firstIndex(of: str) else {
print("Not found")
return
}
print(firstArr[index]) // 2
print(secondArr[index]) // 二
}
find("2")

distance(from:to:)' is unavailable: Any String view index conversion can fail in Swift 4; please unwrap the optional indices

I was trying to migrate my app to Swift 4, Xcode 9. I get this error. Its coming from a 3rd party framework.
distance(from:to:)' is unavailable: Any String view index conversion can fail in Swift 4; please unwrap the optional indices
func nsRange(from range: Range<String.Index>) -> NSRange {
let utf16view = self.utf16
let from = range.lowerBound.samePosition(in: utf16view)
let to = range.upperBound.samePosition(in: utf16view)
return NSMakeRange(utf16view.distance(from: utf16view.startIndex, to: from), // Error: distance(from:to:)' is unavailable: Any String view index conversion can fail in Swift 4; please unwrap the optional indices
utf16view.distance(from: from, to: to))// Error: distance(from:to:)' is unavailable: Any String view index conversion can fail in Swift 4; please unwrap the optional indices
}
You can simply unwrap the optional indices like this:
func nsRange(from range: Range<String.Index>) -> NSRange? {
let utf16view = self.utf16
if let from = range.lowerBound.samePosition(in: utf16view), let to = range.upperBound.samePosition(in: utf16view) {
return NSMakeRange(utf16view.distance(from: utf16view.startIndex, to: from), utf16view.distance(from: from, to: to))
}
return nil
}
The error says that the distances you are generating are optionals and need to be unwrapped. Try this:
func nsRange(from range: Range<String.Index>) -> NSRange {
let utf16view = self.utf16
guard let lowerBound = utf16view.distance(from: utf16view.startIndex, to: from), let upperBound = utf16view.distance(from: from, to: to) else { return NSMakeRange(0, 0) }
return NSMakeRange(lowerBound, upperBound)
}
However the return could be handled better in the guard statement. I'd recommend making the return type of the function NSRange? and checking for nil wherever you call the function to avoid inaccurate values being returned.
Please check :
let dogString = "Dog‼🐶"
let range = dogString.range(of: "🐶")!
// This is using Range
let strRange = dogString.range(range: range)
print((dogString as NSString).substring(with: strRange!)) // 🐶
extension String {
func range(range : Range<String.Index>) -> NSRange? {
let utf16view = self.utf16
guard
let from = String.UTF16View.Index(range.lowerBound, within: utf16view),
let to = String.UTF16View.Index(range.upperBound, within: utf16view)
else { return nil }
let utf16Offset = utf16view.startIndex.encodedOffset
let toOffset = to.encodedOffset
let fromOffset = from.encodedOffset
return NSMakeRange(fromOffset - utf16Offset, toOffset - fromOffset)
}
}
// This is using NSRange
let strNSRange = dogString.range(nsRange: NSRange(range, in: dogString))
print((dogString as NSString).substring(with: strNSRange!)) // 🐶
extension String {
func range(nsRange: NSRange) -> NSRange? {
guard
let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
let to16 = utf16.index(utf16.startIndex, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
let from = from16.samePosition(in: self),
let to = to16.samePosition(in: self)
else { return nil }
return NSMakeRange(from.encodedOffset, to.encodedOffset)
}
}

Hex string to text conversion - swift 3

I'm trying to convert an hex string to text.
This is what i have:
// Str to Hex
func strToHex(text: String) -> String {
let hexString = text.data(using: .utf8)!.map{ String(format:"%02x", $0) }.joined()
return "0x" + hexString
}
and I'm trying to reverse the hex string that I've just created back to the original one.
So, for example:
let foo: String = strToHex(text: "K8") //output: "0x4b38"
and i would like to do something like
let bar: String = hexToStr(hex: "0x4b38") //output: "K8"
can someone help me?
Thank you
You probably can use something like this:
func hexToStr(text: String) -> String {
let regex = try! NSRegularExpression(pattern: "(0x)?([0-9A-Fa-f]{2})", options: .caseInsensitive)
let textNS = text as NSString
let matchesArray = regex.matches(in: textNS as String, options: [], range: NSMakeRange(0, textNS.length))
let characters = matchesArray.map {
Character(UnicodeScalar(UInt32(textNS.substring(with: $0.rangeAt(2)), radix: 16)!)!)
}
return String(characters)
}
NSRegularExpression is overkill for the job. You can convert the string to byte array by grabbing two characters at a time:
func hexToString(hex: String) -> String? {
guard hex.characters.count % 2 == 0 else {
return nil
}
var bytes = [CChar]()
var startIndex = hex.index(hex.startIndex, offsetBy: 2)
while startIndex < hex.endIndex {
let endIndex = hex.index(startIndex, offsetBy: 2)
let substr = hex[startIndex..<endIndex]
if let byte = Int8(substr, radix: 16) {
bytes.append(byte)
} else {
return nil
}
startIndex = endIndex
}
bytes.append(0)
return String(cString: bytes)
}
Solution that supports also special chars like emojis etc.
static func hexToPlain(input: String) -> String {
let pairs = toPairsOfChars(pairs: [], string: input)
let bytes = pairs.map { UInt8($0, radix: 16)! }
let data = Data(bytes)
return String(bytes: data, encoding: .utf8)!
}

Swift3 & Xcode8: ’subscript' is unavailable: cannot subscript String with a CountableClosedRange<Int>,

Error 1:
When I am trying get the stringValue from Metadata shows above error in Swift3:
let myMetadata: AVMetadataMachineReadableCodeObject = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
// take out the system and check-digits
let myBarcode = myMetadata.stringValue[1...11] //error
Error 2:
In extensions of String I write these to get right(x) and left(x) function to get substring:
extension String {
// length of string
var length: Int {
return self.characters.count
}
// right(x) and left(x) function to get substring
func right(_ i: Int) -> String?
{
return self[self.length-i ... self.length-1 ] //error
}
func left(_ i: Int) -> String?
{
return self[0 ... i-1] //error
}
}
Use this extension for the countable closed range [0...4] subscripting
extension String {
subscript (r: CountableClosedRange<Int>) -> String {
get {
let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound)
return self[startIndex...endIndex]
}
}
}
or a safer version which checks the bounds and returns nil rather than an out-of-range exception:
extension String {
subscript (r: CountableClosedRange<Int>) -> String? {
get {
guard r.lowerBound >= 0, let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound, limitedBy: self.endIndex),
let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound, limitedBy: self.endIndex) else { return nil }
return self[startIndex...endIndex]
}
}
}
Swift 4 change: You need to create a new string from the result
return String(self[startIndex...endIndex])
I took inspiration from #vadian's answer and created a set of (Swift 4) extensions that make pulling substrings trivially easy. These do not bounds check, which is generally my preference since I shouldn't be deferring sanity checking to lower level utilities like these.
extension String {
subscript (_ index: Int) -> String {
return String(self[self.index(startIndex, offsetBy: index)])
}
subscript (_ range: CountableRange<Int>) -> String {
let lowerBound = index(startIndex, offsetBy: range.lowerBound)
let upperBound = index(startIndex, offsetBy: range.upperBound)
return String(self[lowerBound..<upperBound])
}
subscript (_ range: CountableClosedRange<Int>) -> String {
let lowerBound = index(startIndex, offsetBy: range.lowerBound)
let upperBound = index(startIndex, offsetBy: range.upperBound)
return String(self[lowerBound...upperBound])
}
subscript (_ range: CountablePartialRangeFrom<Int>) -> String {
return String(self[index(startIndex, offsetBy: range.lowerBound)...])
}
subscript (_ range: PartialRangeUpTo<Int>) -> String {
return String(self[..<index(startIndex, offsetBy: range.upperBound)])
}
subscript (_ range: PartialRangeThrough<Int>) -> String {
return String(self[...index(startIndex, offsetBy: range.upperBound)])
}
}

Swift 3; Range 'Out of bounds'

I've just updated Xcode to 8.0 beta 2 and swift 3.0. After updating from swift 2.3 i'm getting a lot of errors.
I have a String-extension that's converting a Range in the "self"-string to a NSRange:
extension String {
func NSRangeFromRange(_ range : Range<String.Index>) -> NSRange {
let utf16view = self.utf16
let from = String.UTF16View.Index(range.lowerBound, within: utf16view)
let to = String.UTF16View.Index(range.upperBound, within: utf16view)
print("to: \(to) from: \(from)")
print(self.characters.count)
return NSMakeRange(utf16view.startIndex.distance(to: from), from.distance(to: to))
// return NSMakeRange(0, 0) // <-- removes exception
}
}
When NSMakeRange is executed I'm getting a error:
Terminating app due to uncaught exception 'NSRangeException', reason:
'NSMutableRLEArray objectAtIndex:effectiveRange:: Out of bounds'
When I'm printing the to- and from-index's, I'm getting:
to: Index(_offset: 194) from: Index(_offset: 181)
The character-count of the String is 210, which seems about right.
So, I don't get why it's telling me that the index's are out of bounds, when they are less that the total count.
This line was working perfectly before I updated to swift 3. Back then it was looking like this:
return NSMakeRange(utf16view.startIndex.distanceTo(from), from.distanceTo(to))
The auto-converter didn't update the syntax from swift 2.3 to 3.0, I did that myselves..
Any clues?
In Swift 3, "Collections move their index", see
A New Model for Collections and Indices on Swift evolution.
In Swift 2.2, the advancedBy() and distanceTo() methods are
called on the index:
let u16 = "12345".utf16
let i1 = u16.startIndex.advancedBy(1)
let i2 = u16.startIndex.advancedBy(3)
let d = i1.distanceTo(i2)
print(d) // 2
This methods still exist in Swift 3 but give unexpected results, at least on character collections:
let u16 = "12345".utf16
let i1 = u16.startIndex.advanced(by: 1)
let i2 = u16.startIndex.advanced(by: 3)
let d = i1.distance(to: i2)
print(d) // -2
The correct way is to use the index() and distance() methods
of the collection itself:
let u16 = "12345".utf16
let i1 = u16.index(u16.startIndex, offsetBy: 1)
let i2 = u16.index(u16.startIndex, offsetBy: 3)
let d = u16.distance(from: i1, to: i2)
print(d) // 2
Applied to your problem, this is how you can
convert a Range<String.Index> to the corresponding NSRange in Swift 3
(copied from https://stackoverflow.com/a/30404532/1187415):
extension String {
func nsRange(from range: Range<String.Index>) -> NSRange {
let utf16view = self.utf16
let from = range.lowerBound.samePosition(in: utf16view)
let to = range.upperBound.samePosition(in: utf16view)
return NSMakeRange(utf16view.distance(from: utf16view.startIndex, to: from),
utf16view.distance(from: from, to: to))
}
}
And for the sake of completeness, this is the opposite conversion
extension String {
func range(from nsRange: NSRange) -> Range<String.Index>? {
guard
let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
let from = String.Index(from16, within: self),
let to = String.Index(to16, within: self)
else { return nil }
return from ..< to
}
}