Cannot convert value of type 'String?' to expected element type 'Array<String>.ArrayLiteralElement' (aka 'String') - swift

A field in my firebase document is an array of strings which contains user ids.
When I try to initialise it in one of my views, I get the error
Cannot convert value of type 'String?' to expected element type 'Array<String>.ArrayLiteralElement' (aka 'String')
Here's what I am doing
#State var playerIds: [String] = [Auth.auth().currentUser?.uid, "SomeRandomHardcodedUserID"]
The line below works but the one mentioned above does not.
#State var playerIds: [String] = ["SomeHardcodedUID", "SomeOtherHardcodedUserID"]
My intention is to use playerIds to construct a codeable objet to send to Firestore.
The below line works with hardcoded userIds.
let newGame = Game(gameTime: gameTime, playerIds: playerIds, inProgress: gameInProgress, winnerId: winnerId)
What's the right way to go about putting current user's userId in my array?

So I figured out that the fix is to define the type of array as [String?] rather than [String], which fixed my issue.
Passing playerIds as an argument was fixed by changing my Game structs model for playerIds to also use [String?] rather than [String]

Related

How can I initialize an optional array?

I want to initialize my Empty Array with some data, however the array does not accept new data, It must be easy thing, but I do not know what I am missing!
var arrayOfString: [String]?
arrayOfString?.append( "New Data"); print( "\(arrayOfString?.count)" )
Initializing means to assign a default value
var arrayOfString: [String]? = []
But in almost all cases collection types can be declared (and initialized) non-optional
var arrayOfString = [String]()

Swift generic collection of Element cannot convert to collection of Any

I'm facing a problem I don't understand.
In my project, I want to make a collection of elements with some customised methods (like an update from a server). But when I try to group all these collections in an array, I get an error: "Cannot convert value of type MyCollection<someElement> to expected argument type MyCollection<Any>"
What I don't understand is that the same code with Array is working... Array isn't a collection?
// My collection which would contain an update method
class MyCollection<Element> {
var object:Element? = nil
}
let x = MyCollection<Int>()
var list = [MyCollection<Any>]()
list.append(x) //Cannot convert value of type 'MyCollection<In>' to expected argument type 'MyCollection<Any>'
let a = Array<Int>()
var lista = [Array<Any>]()
lista.append(a) //Doesn't get error at all...
I know I can do this with an array of the specific type but by grouping all of MyCollection in an array, I wish to use a code like :
func update() {
for e in list { // array of MyCollection<Any>
e.update()
}
}
Thank you in advance for your help ;)
Being able to convert from SomeType<Subtype> to SomeType<Supertype> is called covariance. In Swift, Array<T> is covariant on T by "compiler magic", and you can't do the same for your own types.
The type checker hardcodes conversions from Array to Array if there is a conversion from T to U. Similar rules exist for Optional and Dictionary. There's no mechanism for doing this with your own types.
Your own generic types are always invariant, meaning that there is never a conversion between SomeType<T> to SomeType<U>, as long as T and U are different types.
Let's imagine what would happen if the conversion on MyCollection were allowed. You could do:
let myCollectionInt = MyCollection<Int>()
let myCollectionAny: MyCollection<Any> = myCollectionInt // suppose you can do this
myCollectionAny.object = "string" // myCollectionAny.object is of type Any?, so this should be ok
We've set myCollectionAny.object to "string", but MyCollection is a reference type, so myCollectionInt.object should also be "string". But myCollectionInt.object is an Int?!
Of course this type-unsafety is also a problem with arrays, but the language designers have decided that casting arrays is a common enough thing to do, that disallowing it would do more hard than good.

Arrays & type-safety using optionals, best practices?

I am attempting to understand the difference between declaring the following optional arrays.
Question 1) In the below example imagine the arrays were appended with a nil value, what would occur & which variable declarations would not crash?
Question 2)similarly imagine we swapped out the type String with type someObject. If the object failed to initialize correctly and returned nil, what would occur & which variable declarations would not crash?
I believe the answer for both 1,2) is var three & var four.
I also think var one & var two can work if they are initialized () but I am not sure.
I am fairly certain var five & var six will crash when attempting to append the array.
Question 3) Assuming that I went with option var one & var two & they were initialized as a empty array, can I add nil/empty objects/strings?
Question 4) Assuming that I went with option var three & var four, would it be advisable to use compactmap to filter out nil values?
Question 5) Assuming I went with option var five and var six, would it be advisable to check for nil/empty object/strings using nil coalescing or a guard statement to check for a value?
var one : Array<String?> //array containing optional strings
//is the same type as
var two : [String?] //array containing optional strings
var three : Array<String?>? //optional array containing optional strings
//is the same type as
var four : [String?]? //optional array containing optional strings
var five : Array<String>? //optional array containing strings
//is the same type as
var six : [String]? //optional array containing strings
I inherited a codebase where I am receiving a json response and using the coddle protocol to create a object based on a struct model. I noticed that the variables were declared.
var seven : [someModel]?
The model is
struct someModel {
var a : String?
}
Question 6) Am I correct to assume that because all the properties/attributes of the struct are optionals, a object representing the struct can safely be appended to var seven even if someModel.a = optional(nil)?
First, none of these will crash. nil is not anything like Java's NULL (or C's NULL, and almost nothing like ObjC's nil). Accessing nil is completely safe. It's just a value, like every other value. nil is just a piece of syntactic sugar around Optional.none. They are exactly the same. Optionals are just enums:
enum Optional<Wrapped> {
case some(Wrapped)
case none
}
There is also syntactic sugar so that you can write Array<T> as [T]. These are exactly the same thing.
Similarly, you can write Optional<T> as T?. These, again, are exactly the same thing.
var one : Array<String?> //array containing optional strings
var two : [String?] //array containing optional strings
These are arrays of Optional<String>. Each element can be .some(string) or it can be .none.
var three : Array<String?>? //optional array containing optional strings
var four : [String?]? //optional array containing optional strings
As you say, these are optional arrays of optional strings. They can be .none or .some(array). In that array is identical to one and two above.
var five : Array<String>? //optional array containing strings
var six : [String]? //optional array containing strings
As you say, this is an optional array of strings. So it can be .none or it can be .some(array) where array is an array of strings (not optional strings).
Question 6) Am I correct to assume that because all the properties/attributes of the struct are optionals, a object representing the struct can safely be appended to var seven even if someModel.a = optional(nil)?
Yes, though it's not optional(nil); that would be an String?? rather than a String?. It's just nil.
The only place that optionals will crash due to being nil is if you evaluate them with a ! at the end. The only reason this crashes is because it literally means "please crash the program if this is nil," so don't use ! unless you mean that.
This isn't really a best-practices question, but since you titled it that way, a few points:
Type names should always have a leading capital letter.
As a rule, I discourage optional collections. That includes Array? and String?. You need to ask: how is "no value" different than "empty value." If they're the same (and they usually are), then you should just use empty values. So instead of nil, just use "" and []. Then all the optionals go away, and the code becomes much simpler.

Cannot convert value of type 'Int' to expected argument type '_?'

Note: I'm a rookie in Swift
I'm using Former.
I'm fetching data from a realm model.
let industries = realm.objects(Industry)
Then I try to define a list of InlinePickerItem from it:
$0.pickerItems = industries.map({ industry in
return InlinePickerItem(title: industry.name, value: industry.id)
})
But XCode keeps saying: Cannot convert value of type 'Int' to expected argument type '_?', pointing to industry.id.
Am I missing something? I don't know if the issue comes from Former or from something that I don't understand in Swift. For example, what kind of type is _??
UPDATE:
After #dfri comment, attempt was unsuccessful. From my small understanding of Swift, I get that Swift gets lost. So I extracted the initialisation of the list of InlinePickerItem from the closure.
let industries = realm.objects(Industry)
let inlinePickerItems = industries.map({ industry in
return InlinePickerItem(title: industry.name, displayTitle: nil, value: industry.id)
})
let catRow = InlinePickerRowFormer<ProfileLabelCell, String>(instantiateType: .Nib(nibName: "ProfileLabelCell")) {
$0.titleLabel.text = "CATEGORY".localized
}.configure {
$0.pickerItems = inlinePickerItems
}
The error is disappeared when calling InlinePickerItem(title: industry.name, displayTitle: nil, value: industry.id) but I get something new when assigning it to $0.pickerItems which now is:
Cannot assign value of type '[InlinePickerItem<Int>]' to type '[InlinePickerItem<String>]'
Hope this will provide you with some helpful hints.
Type mismatch when assigning array to different type array
After the re-factoring of your code (after "update") its now apparent what is the source of error.
Immutable catRow is of type InlinePickerRowFormer<ProfileLabelCell, String>. From the source of [InlinePickerRowFormer] we see the that the class and its property pickerItems is declared as follows
public class InlinePickerRowFormer<T: UITableViewCell, S where T: InlinePickerFormableRow>
: ... {
// ...
public var pickerItems: [InlinePickerItem<S>] = []
// ...
}
The key here is that for an instance InlinePickerRowFormer<T,S> its property pickerItems will be an array with elements of type InlinePickerItem<S>. In your example above S is String
let catRow = InlinePickerRowFormer<ProfileLabelCell, String>
/* |
S = String */
Hence pickerItems is an array of InlinePickerItem<String> instances.
You try, however, to append the immutable inlinePickerItems to pickerItems, which means you're trying to assign an array of InlinePickerItem<Int> instances to an array with elements of type InlinePickerItem<String>; naturally leading to a type mismatch.
You can solve this type mismatch by:
Setting your catRow immutable to be of type InlinePickerRowFormer<ProfileLabelCell, Int>.

Why can you not initialize an empty Array in Swift?

This code works,
let people = ["Tom","Dick","Harry"]
but this code doesn't work, for no apparent reason
let people = []
and nor does this (mutability matters not):
var people = []
The error is "Cannot convert expression's type Array to type 'ArrayLiteralConvertible'", but that makes no sense to me, and none of the other questions that show up in a search address this question.
At first I thought it had to do with type inference, but that proves not to be the issue (at least not simply that!) since although this works (with type specified)
var people:Array = [""]
this does not (with type specified as above but no String given inside the Array):
var people:Array = []
Since the last of these two has the type specified explicitly, it shouldn't need a String passed inside the Array.
Some languages (weirdly!) consider the type of the variable to refer to the type of item inside the array, so I also tried specifying String instead of Array, but got the same results. This first one works and the second doesn't:
var people:String = [""]
var people:String = []
The syntax you are looking for is
let people : [String] = []
or
let people = [String]()
in both case, you can substitute [String] with Array<String>
For this code
let people = []
It is impossible for compiler to figure it out the type of people. What type do you expect it have?
Note Array is not a complete type because it require the generic part. A complete type is like Array<String>
You can do this
let blankArray = [String]()
or ny other type you need.