Why doesn't XCode auto-prompt/complete swift ENum cases? [duplicate] - ios

I'm using an enum variable called BuildingType in a class initializer (of a class called Building).
This enum is defined outside the class because I want to use it also in other places.
The autocomplete for this enum is not working properly when initializing the variable typeOfBuilding.
Example code:
enum BuildingType {
case Flat, House, Villa
}
class Building {
var type : BuildingType = BuildingType.House
var floors : Int = 1
init(typeOfBuilding : BuildingType, numFloors : Int) {
self.type = typeOfBuilding
self.floors = numFloors
}
}
var myBuilding : Building = Building(typeOfBuilding: BuildingType.Flat , numFloors: 3)
So if I type "... typeOfBuilding: BuildingType." (when initializing myBuilding) 'floors' and 'type' are shown, and not the enum values.
I must be doing something wrong here but what?

This is a pretty weird bug
It occurs when you attempt to pass an enum into an argument of an initialiser, autocomplete will fail and instead of suggesting the enum cases after typing Enum., it will list the instance members of the class you’re calling the initialiser on. If you try and use the single dot syntax (.Case), autocomplete will also fail, but instead of displaying the list of instance members, it simply won't display anything.
I initially thought it may have had something to do with the naming of your enum and class (BuildingType & Building), but this is not the case.
This bug only appears to be present in initialisers with multiple arguments (one of which being an enum). I couldn’t reproduce this issue with single argument initialisers.
The reproducibility appears to depend on whether the initialiser is 'complete'. I'm considering an initialiser to be 'complete' if it has all argument names and values (except the enum) defined. For example:
// Incomplete (foo is one argument of many)
let baz = Baz(foo: Foo.
// Semi-Complete (before you assign the second parameter a value)
let baz = Baz(foo: Foo., string: <String Placeholder>)
// Complete
let baz = Baz(foo: Foo., string: "")
// Complete (note the lack of the last bracket)
let baz = Baz(param: 0, foo: Foo.
Here is my test setup (Xcode 7.3, Swift 2.2):
enum Foo {
case Bar
}
class Baz {
var iReallyShouldntBeDisplayedHere = 0
init(foo:Foo, string:String) {}
init(foo: Foo) {}
}
And here is a list of cases where I've found the bug does & doesn't occur:
// Enum is the only argument
// CORRECT: accepting the initialiser's autocomplete (so it's 'complete'), then typing "Foo." brings up autocomplete options for enum cases
let baz = Baz(foo: Foo.)
// CORRECT: typing the initialiser yourself (so it's 'incomplete'), then typing "Foo." in the first parameter brings up autocomplete options for enum cases
let baz2 = Baz(foo: Foo.
// Enum is one argument of many
// INCORRECT: accepting the initialiser's autocomplete (so it's 'semi-complete'), then typing "Foo." in the first parameter brings up Baz's instance members ("iReallyShouldntBeDisplayedHere")
let baz3 = Baz(foo: Foo., string: <String Placeholder>)
// CORRECT: typing the initialiser yourself (so it's 'incomplete'), and typing "Foo." in the first parameter brings up enum cases
let baz4 = Baz(foo: Foo.
// Single dot syntax (where enum is one argument of many)
// CORRECT: typing the initialiser yourself (so it's 'incomplete'), and typing "." in the first parameter brings up enum cases
let baz5 = Baz(foo:.
// CORRECT: accepting the initialiser's autocomplete (so it's 'semi-complete'), then typing "." in the first parameter brings up enum cases
let baz6 = Baz(foo:., string: <String Placeholder>)
// INCORRECT: modifying the foo: argument once the initialiser is 'complete' by typing "." in the first parameter doesn't generate the autocomplete list
let baz7 = Baz(foo:., string: "")
I also tried this where foo: is the last argument, but the initialiser always has to be complete in that case, so it always fails. I did try with initialisers that take 3 arguments, but it appears to have the same behaviour as an initialiser with 2 arguments.
If anyone knows of any more cases where this bug can be reproduced, I'd love to know!

Related

Error "nil requires a contextual type" using Swift

I'd like to achieve the following in code:
class MyService {
let mySubject = BehaviorSubject<MyData>(value: nil)
//....
}
Unfortunately, I get the "nil requires a contextual type" error. I want the subject to be "empty" till I actually put something in there. How can I pass nil as the argument then? Can I cast it to my own type to make it work?
Based on the reference for RxSwift BehaviorSubject, the init(value:) initializer is declared as
public init(value: Element)
Where the value parameter is described as:
value
Initial value sent to observers when no other value has been received
by the subject yet.
And where Element is the placeholder type of BehaviorSubject:
public final class BehaviorSubject<Element> ...
This means you need to specify the placeholder type Element as an Optional type if you are to be able to set the initial value (used when no other value has been received) to nil. E.g.:
class MyService {
let mySubject = BehaviorSubject<MyData?>(value: nil)
//....
}
Or, letting the compiler infer the placeholder as MyData? by using the non-sugared .none form for the nil argument:
class MyService {
let mySubject = BehaviorSubject(value: Optional<MyData>.none)
//....
}
As for understanding the actual error message better, consider the following self-contained example:
struct Foo<T> {
init(value: T) {}
}
struct Bar {}
let bar = Bar()
_ = Foo<Bar>(value: bar) // OK
_ = Foo(value: bar) // OK, T inferred as Bar
_ = Foo<Bar>(value: nil) // Error: error: 'nil' requires a contextual type
_ = Foo<Bar?>(value: nil) // OK
_ = Foo(value: Optional<Bar>.none) // OK, T inferred as Bar?
While dfri's answer is technically correct, you might want to consider a different type when working with RxSwift. Since you want your subject to be empty only at the beginning, I'd suggest to use ReplaySubject or PublishSubject.
A similar question has also been asked on RxSwift's GitHub issue page. Allow BehaviorSubject without initial value. There, kzaher suggests the ReplaySubject.
Your subject would then look like this, without any initial value and without MyData being Optional.
let subject = ReplaySubject<MyData>().create(bufferSize: 1)

Swift compile time errors when using generics and accessing dictionary's values

Below I have a class, B, with a generic type and the generic type has a subclass type constraint. In a separate class, A, I create a dictionary property, values, with the key as String and value as B. Class A then has methods to return or set values in the dictionary such that the values are not constrained to a single type (they maintain their generic SomeType type which is a subclass of NSObject). However, this produces the following two errors noted inline below:
class A: NSObject {
var values = [String : B]()
func get<SomeType: NSObject>(key: String) -> B<SomeType>? {
// #1 Error on line below: cannot convert return expression of type 'B<NSObject>?' to return type 'B<SomeType>?'
return values[key]
}
func set<SomeType: NSObject>(key: String, value: B<SomeType>) {
// #2 Error on line below: cannot assign value of type 'B<SomeType>' to type 'B<NSObject>?'
values[key] = value
}
}
class B<SomeType: NSObject>: NSObject {
}
I've attempted various forms of declaring the values dictionary to tell the compiler that SomeType is a subclass of NSObject and everything is going to be ok, but have been unsuccessful. Similarly to this question, I'm a bit stumped because the methods define SomeType as a subclass of NSObject and therefore things appear to be type safe when setting and getting from values.
I could remove the generic types from the methods and instead force the type to be <NSObject>, but then I'd run into the same problem as noted here.
This may not be doing what you think it's doing:
var values = [String : B]()
This isn't really [String : B], it's [String : B<NSObject>]. (I'm actually kind of surprised that this is legal syntax; I'd be tempted to open a bugreport about that; B isn't a proper type in Swift.) You may realize this already, but it's the first important note.
The second important note is that generic types are not covariant in Swift. A Thing<Cat> is not a subtype of Thing<Animal>. There are some type-theory reasons for this, and there are some technical implementation reasons for this, but the important fact is that it's not possible in Swift.
If you need to hold a variety of B types, then you'll need to build a type eraser. In your case, the type eraser could possibly be B<NSObject> with something like this:
class B<SomeType: NSObject>: NSObject {
let value: SomeType
init(value: SomeType) {
self.value = value
}
func liftGeneric() -> B<NSObject> {
return B<NSObject>(value: value)
}
}
If you need to hold just one kind of B type, then make A generic as well.

Trying to work with an enum that has duplicate values in Swift

I have an enum of type String which needs to have multiple variables that have the same value. My enum looks something like this:
class MyClass {
enum MyEnum: String {
case blahA = "blaha"
case blahB = "blahb"
...
static var blahD = "blah"
static var blahE = "blah"
}
}
The reason why I'm using static var's in the above construction is because both "blahD" and "blahE" need to reference the same String value, used in different places (don't ask me why, it just has to be this way). However, I have a method where I need to pass in the value of the enum as follows:
if let testString = myString(foo: MyEnum.blahD) {...}
I unfortunately am getting the following compilation error:
Cannot convert value of type "String" to expected argument type "MyClass.MyEnum".
How do I get around passing the above variable which has duplicate values in my enum in the method, but cast it to the type of "MyClass.MyEnum"?
You can do this if you make the extra case reference the other enum case directly instead of just assigning them the same string value:
class MyClass {
enum MyEnum: String {
case blahA = "blaha"
case blahB = "blahb"
...
case blahD = "blah"
static var blahE = MyEnum.blahD
}
}
Then you can pass MyEnum.blahE the same way you would pass MyEnum.blahD
If the function takes a value of type MyEnum, you cannot do this. The type properties blahD and blahE are simply not of that type. Only the cases of the enum are of type MyEnum.
The function parameter's type must be changed to String.
The only other way around it would be to add a case to the enum that has a raw value matching the value of those two properties: case blahDOrE = "blah". Then you could construct that case: MyEnum(rawValue: MyEnum.blahD), but I can't see that being very useful.

Swift syntax discrepancy between parameters in initializer and functions

It seems to me that there is a discrepancy in Swift's syntax between calling an initializer and a function with at least one paremeter.
Let's consider these two examples:
class SimpleClass {
var desc: String
init(desc: String) {
self.desc = desc
}
}
let aClass = SimpleClass(desc: "description")
and
func simpleFunc(a: Int, b:Int) -> Int {
return a + b;
}
let aVal = simpleFunc(5, b: 6)
It seems odd to me that the compiler forces you to omit the first label in a function call, otherwise you will get an error "Extraneous argument label 'a:' in call". Whereas if we want to omit the first label during initilization, you get the error "Missing argument label 'desc:' in call".
The language guide says:
When calling a function with more than one parameter, any argument after the first is labeled according to its corresponding parameter name.
Source: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html
The arguments to the initializer are passed like a function call when
you create an instance of the class.
Source: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html
I'm new to Swift so I hope I didn't miss something, but this seems like a syntax discrepancy, because initializers/ constructors are just kind of functions and forcing to omit the first label in a function call seems inconsistent to me.
That's because Swift focuses on readability; function calls to be able to be read like a sentence. See this, specifically the section on "Local and External Parameter Names for Methods". Your function, to comply with this style, should be more like:
func add(a: Int, to b: Int) -> Int {
return a + b
}
let c = add(1, to: 2)

Extra argument in call error mystery

I had some older Swift code that used to compile and work where I was using the .append to build out a data structure dynamically. After upgrading to a few compiler versions newer I am getting the dreaded "Extra Argument ' ' in call" error. I reduced the code down to this:
struct EHSearch {
let EHcategory : String = ""
let EHname : String = ""
}
var myEHSearch = [EHSearch]()
// Call to dynamically append the results
// Extra argument: 'EHcategory' in call
myEHSearch.append(EHSearch(EHcategory: "Food", EHname: "Joes Crab Shack"))
I can't see anything so far in searching on what has changed to cause this one so seeking guidance here.
Because you have let in your struct.
Define your structure like this:
struct EHSearch {
var EHcategory : String = ""
var EHname : String = ""
}
If you have constants in your struct, you can not provide them initial value while creating new structure instances. The automatically-generated member-wise initializer doesn't accept let members as parameters of the initializer of struct.
It depends on your intentions with the struct's properties. Do you want them to be mutable or not?
If yes, then #sasquatch's answer will do.
If not, then you need to ensure a value is assigned to them only once. As you already do that in the struct declaration (the default values), you can't assign new values to them. But being a struct, they don't need to have default values - moreover, struct automatically receive a memberwise initializer. https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html
So here is the variant for immutable properties:
struct EHSearch {
let EHcategory : String
let EHname : String
}
var myEHSearch = [EHSearch]()
// Call to dynamically append the results
// Extra argument: 'EHcategory' in call
myEHSearch.append(EHSearch(EHcategory: "Food", EHname: "Joes Crab Shack"))
The "Extra Argument" error you're seeing is because the compiler already has values for the properties so it doesn't expect any new ones. Here is the "middle" way - one property has a default value whilst the other doesn't - which should make it clearer:
struct EHSearch {
let EHcategory : String = ""
let EHname : String
}
var myEHSearch = [EHSearch]()
// Call to dynamically append the results
// Extra argument: 'EHcategory' in call
myEHSearch.append(EHSearch(EHname: "Joes Crab Shack"))

Resources