This question already has answers here:
Type 'String.Index' does not conform protocol 'IntegerLiteralConvertible'
(3 answers)
Closed 8 years ago.
I get compiler error at this line:
UIDevice.currentDevice().identifierForVendor.UUIDString.substringToIndex(8)
Type 'String.Index' does not conform to protocol 'IntegerLiteralConvertible'
My intention is to get the substring, but how?
In Swift, String indexing respects grapheme clusters, and an IndexType is not an Int. You have two choices - cast the string (your UUID) to an NSString, and use it as "before", or create an index to the nth character.
Both are illustrated below :
However, the method has changed radically between versions of Swift. Read down for later versions...
Swift 1
let s: String = "Stack Overflow"
let ss1: String = (s as NSString).substringToIndex(5) // "Stack"
//let ss2: String = s.substringToIndex(5)// 5 is not a String.Index
let index: String.Index = advance(s.startIndex, 5)
let ss2:String = s.substringToIndex(index) // "Stack"
CMD-Click on substringToIndex confusingly takes you to the NSString definition, however CMD-Click on String and you will find the following:
extension String : Collection {
struct Index : BidirectionalIndex, Reflectable {
func successor() -> String.Index
func predecessor() -> String.Index
func getMirror() -> Mirror
}
var startIndex: String.Index { get }
var endIndex: String.Index { get }
subscript (i: String.Index) -> Character { get }
func generate() -> IndexingGenerator<String>
}
Swift 2
As commentator #DanielGalasko points out advance has now changed...
let s: String = "Stack Overflow"
let ss1: String = (s as NSString).substringToIndex(5) // "Stack"
//let ss2: String = s.substringToIndex(5)// 5 is not a String.Index
let index: String.Index = s.startIndex.advancedBy(5) // Swift 2
let ss2:String = s.substringToIndex(index) // "Stack"
Swift 3
In Swift 3, it's changed again:
let s: String = "Stack Overflow"
let ss1: String = (s as NSString).substring(to: 5) // "Stack"
let index: String.Index = s.index(s.startIndex, offsetBy: 5)
var ss2: String = s.substring(to: index) // "Stack"
Swift 4
In Swift 4, yet another change:
let s: String = "Stack Overflow"
let ss1: String = (s as NSString).substring(to: 5) // "Stack"
let index: String.Index = s.index(s.startIndex, offsetBy: 5)
var ss3: Substring = s[..<index] // "Stack"
var ss4: String = String(s[..<index]) // "Stack"
Related
This question already has answers here:
How can I concatenate multiple optional strings in swift 3.0?
(5 answers)
Closed 6 years ago.
I'm using Swift 3.0.
I've got the following code:
var double1 : Double! = 1.0
var double2 : Double! = 2.0
var double3 : Double! = 3.0
When I tried to do the calculation like this:
let result = double1-double2-double3
The compiler gave me the error:
binary operator '-' cannot be applied to operands of type 'Double' and 'Double!'
I've found out that double1-double2 will result a Double value, thus cannot do math with Double!
Then, I've tried:
let result = double1! - double2! - double3!
The compiler gave me this error:
ambiguous reference to member '-'
So what's the correct grammar for this?
Add these custom operator overloads to your project:
public func + <T: FloatingPoint>(lhs: T!, rhs: T!) -> T!
{
if let lhs = lhs, let rhs = rhs
{
return lhs + rhs
}
return nil
}
public func - <T: FloatingPoint>(lhs: T!, rhs: T!) -> T!
{
if let lhs = lhs, let rhs = rhs
{
return lhs - rhs
}
return nil
}
public func * <T: FloatingPoint>(lhs: T!, rhs: T!) -> T!
{
if let lhs = lhs, let rhs = rhs
{
return lhs * rhs
}
return nil
}
public func / <T: FloatingPoint>(lhs: T!, rhs: T!) -> T!
{
if let lhs = lhs, let rhs = rhs
{
return lhs / rhs
}
return nil
}
It will allow for what you were attempting:
var double1 : Double! = 1.0
var double2 : Double! = 2.0
var double3 : Double! = 3.0
let result = double1 - double2 - double3 // -4
And, as a bonus, this will work with any arbitrary FloatingPoint type (including Float and Float80).
One possible workaround is:
let result = (double1 - double2) as Double - double3
Or just cast your doubles to non-optionals. You know that you cannot really perform operations on nil values, right?
Ok,I've found one solution. I can use:
let result = Double(double1) - Double(double2) - Double(double3)
But the code looks anti-humanity. Is there a more elegant way to do this?
Define your Doubles as Double:
var double1 : Double = 1.0
var double2 : Double = 2.0
var double3 : Double = 3.0
The type can also be inferred:
var double1 = 1.0
var double2 = 2.0
var double3 = 3.0
This question already has answers here:
Swift Tuple index using a variable as the index?
(5 answers)
Closed 6 years ago.
Here's the [generic version of the] situation:
let tuple: (first: Int, second: Int, third: Int) // tuple.0 is equivalent to tuple.first
enum TupleIndex: Int {
case first = 0, second, third
}
func selectTupleElement (index: TupleIndex) {
let indexNum = index.rawValue
let tupleElement = tuple.indexNum // Oh noooooo!!!
}
The compiler reads the problem spot, indicated in the last line above, as "the indexNum property or element of tuple" (which of course doesn't exist) rather than "the element of tuple at the index equal to the value of indexNum"
Is there a way to do what I'm trying to do, using tuples?
You could make use of runtime introspection to conditionally extract the n:th member of a tuple of fixed size and same-type members (e.g. below: implemented for tuple of arity 3 with uniform typed members) and (conditionally) convert it to the member's type. E.g.:
func selectTupleElementFor<T>(memberNumber: Int, inTuple tuple: (T, T, T)) -> T? {
return Mirror(reflecting: tuple).children.enumerated()
.first(where: { $0.0 == memberNumber - 1 }).flatMap { $1.1 as? T }
}
// example usage
let tuple: (first: Int, second: Int, third: Int) = (10, 20, 30)
if let tupleMember = selectTupleElementFor(memberNumber: 2, inTuple: tuple) {
print(tupleMember) // 20
}
Or, using your TupleIndex enum:
enum TupleIndex: Int {
case first = 0, second, third
}
func selectTupleElementFor<T>(tupleIndex: TupleIndex, inTuple tuple: (T, T, T)) -> T? {
return Mirror(reflecting: tuple).children.enumerated()
.first(where: { $0.0 == tupleIndex.rawValue }).flatMap { $1.1 as? T }
}
// example usage
let tuple: (first: Int, second: Int, third: Int) = (10, 20, 30)
if let tupleMember = selectTupleElementFor(tupleIndex: .second, inTuple: tuple) {
print(tupleMember) // 20
}
Perhaps this is an Xcode 8 beta issue, however, prior to 2.2 the var keyword is allowed to prepend parameters in function signatures:
func (var stringName: String) { ... }
This is has since been deprecated in lieu of there being little benefit over inout
func (stringName: inout String) { ... }
I've attempted the following in a map closure, and though I don't receive a deprecation warning as mildly expected I should, the error was rather a segmentation fault: 11
let demoString = ["hi", "there", "world"].map { (var word) -> String in
let firstChar = word.remove(at: word.startIndex)
}
The error kicks in as soon as I attempt to mutate the (assumedly mutable) word variable.
I've attempted other variation e.g. using inout
let demoString = ["hi", "there", "world"].map { (word: inout String) -> String in
let firstChar = word.remove(at: word.startIndex)
}
But the compiler complains that this erroneously changes the signature of the closure altogether and won't compile.
Obviously, the workaround is simply to copy the variable to a local one within the closure:
let demoString = ["hi", "there", "world"].map { (word) -> String in
let tempWord = word
let firstChar = tempWord.remove(at: tempWord.startIndex)
}
However, I am interested in knowing if this is expected functionality & whether or not there is a way of mutating a parameter passed into a closure directly?
Closures can mutate inout arguments just fine:
var str1 = "Pine"
var str2 = "Juniper"
let closure = { (s1: inout String, s2: inout String) -> Void in
s1 = "Oak"
s2 = "Chestnut"
}
closure(&str1, &str2)
print(str1, str2)
The problem you are facing is Array.map doesn't have a method signature that includes an inout parameter.
The only way around this that I can think of is to extend Array and add the map method yourself:
extension Array {
func map<T>(_ closure: (inout T) -> Void) -> Array<T> {
var arr = [T]()
for i in 0..<self.count {
var temp : T = self[i] as! T
closure(&temp)
arr.append(temp)
}
return arr
}
}
var hi = "hi", there = "there", world = "world"
var demoStrings = [hi, there, world]
var result = demoStrings.map { (word: inout String) in
word.remove(at: word.startIndex)
}
print(result) // ["i", "here", "orld"]
As per SE-0003 var parameters no longer exist in Swift 3.
Fundamentally, you shouldn't be mutating the parameters given from map, anyway. Instead: use non-mutating functions, or make a local mutable copy.
In this case, there's no reason to be using remove(_:) just to get the first character. This would require copying a String's memory (omitting the first character), solely to get the character that was removed. It's quite clunky.
In this case, you can just get the first property (from the Collection protocol) on the characters property of String.
Try this:
let demoStrings = ["hi", "there", "world"]
let firstLetters = demoStrings.map {(word: String) -> String in
return word.characters.first
}
or for short:
let firstLetters = demoStrings.map{ $0.characters.first }
First, I try mapping a [String?], to get a [String]:
$ xcrun swift
Welcome to Apple Swift version 2.2 (swiftlang-703.0.18.8 clang-703.0.30). Type :help for assistance.
1> import Foundation
2> let j: [String?] = ["a", nil]
j: [String?] = 2 values {
[0] = "a"
[1] = nil
}
3> j.map {$0 ?? ""}
$R0: [String] = 2 values {
[0] = "a"
[1] = ""
}
This makes perfect sense to me. I nil-coalesce a String?, and I get a String. But with [AnyObject?], something strange occurs:
4> let k: [AnyObject?] = ["a", nil]
k: [AnyObject?] = 2 values {
[0] = "a"
[1] = nil
}
5> k.map {$0 ?? ""}
$R1: [AnyObject?] = 2 values {
[0] = "a"
[1] = (instance_type = 0x00007fff7bc2c140 #"")
}
I'm nil-coalescing optionals, but this time I get out an optional. Why?
The Swift Programming Language says a ?? b is shorthand for a != nil ? a! : b, but when I try that, I get out an array of non-optionals:
6> k.map {$0 != nil ? $0! : ""}
$R2: [AnyObject] = 2 values {
[0] = "a"
[1] = ""
}
Am I misunderstanding how ?? is supposed to work? What is going on here?
The detailed behaviour is not well-documented, so, would change in the future Swifts.
But you should know coalescing operator has two overloads:
#warn_unused_result
public func ??<T>(optional: T?, #autoclosure defaultValue: () throws -> T) rethrows -> T
#warn_unused_result
public func ??<T>(optional: T?, #autoclosure defaultValue: () throws -> T?) rethrows -> T?
In your case, Swift has chosen the latter for your code.
You can test with a simplified codes like:
let x: AnyObject? = "a"
x ?? ""
The inferred type (in Swift 2.2.1) becomes AnyObject?.
But this code is also valid.
let y: AnyObject = x ?? ""
String literals like "" can be treated as variety of types. All of these are valid in Swift.
"" as String
"" as String?
"" as NSString
"" as NSString?
"" as AnyObject
"" as AnyObject?
So, with some unspecified reason Swift has chosen AnyObject?.
And, in case type inference can be ambiguous, you should use explicit type annotation, as suggested in appzYourLife's comment.
It has come to my attention that Apple considered this a bug in Swift 2.
In Swift 3, the 1st example above still works, while the 2nd and 3rd examples are invalid syntax (with or without Foundation bridging).
Replacing the AnyObject declaration with Any works: a ?? b then behaves identically to a != nil ? a! : b, as the documentation says.
I realize this is a unnecessary question, but... why can I NOT use reduce to convert a character array into a string?
e.g.,
let str = "this is a string"
let clist = Array(str)
let slist = clist.reduce("", +)
gives me: 'Character' is not a subtype of 'Uint8'
when
list dlist = [0, 1, 2, 3, 4, 5, 6]
let sum = dlist.reduce(0, +)
works
I know I can simply do slist = String(clist), but I just wanna know, ya know?
Swift 1.1 in playground of xcode 6.2
Thanks!
Inside the combine: closure of
let slist = clist.reduce("", +)
$0 is the so-far accumulated result – a String,
$1 is the current element from clist – a Character.
There is no + operator which takes (String, Character) as arguments.
This would work:
let slist = clist.reduce("") { $0 + String($1) }
In Swift 1.2:
let str = "this is a string"
let clist = Array(str)
let slist = clist.map { String($0) }.reduce("", combine: { $0 + $1 })
println(slist) // "this is a string"