Saranjith
Saranjith

Reputation: 11577

Xcode showing unwanted syntax errors

I have an enumeration with values:

enum Types {
  case A
  case B
  case C
  case D
}

var tableViewDataSource: [Types] = [.A, .B, .C, .D]

I want to implement the following conditions:

let pickerSelectingFields: [Types] = [.A, .B, .C, .D]
    let indexes = pickerSelectingFields.map { tableViewDataSource.firstIndex(of: $0) }

    if indexes.contains(textField.tag) {
    // Working
    }

When I try to make the whole things in a single line, as below it shows error:

Anonymous closure argument not contained in a closure

Code is below:

if ([.A, .B, .C, .D] as? [Types]).map { tableViewDataSource.firstIndex(of: $0) }
                                 .contains(textField.tag)

What am I doing wrong here?

Upvotes: 0

Views: 105

Answers (4)

vacawama
vacawama

Reputation: 154691

if ([.A, .B, .C, .D] as? [Types]).map { tableViewDataSource.firstIndex(of: $0) }
                             .contains(textField.tag)

What am I doing wrong here?

There are two issues.

First, you used as? instead of as. When you use the conditional cast as?, the result is [Types]?: an optional [Types]. Then Swift uses the optional version of map and you're majorly headed off in the wrong direction.

You needed to use as [Types] because you are simply telling Swift to interpret [.A, .B, .C, .D] as [Types].

The second issue is that since you're doing this in a single line, you need some extra parentheses (, ) around the closure for map because Swift doesn't like multiple { after an if. Without clarifying parens, it will interpret the first { which belongs to the closure for map as the start of then block for the if.

So:

if ([.A, .B, .C, .D] as [Types]).map({ tableViewDataSource.firstIndex(of: $0) }).contains(textField.tag) {
    // do something
}

will work.

You can also just explicitly type one of the entries of the array, and Swift will interpret the entire array to be [Types] like so:

if [Types.A, .B, .C, .D].map({ tableViewDataSource.firstIndex(of: $0) }).contains(textField.tag) {
    // do something
}

Note:

It is common Swift convention to start class, struct, and enum type names with uppercase letters, and to start variables, methods, and enum values with lowercase letters.

So your enum could be written as:

enum Types {
    case a, b, c, d
}

Upvotes: 2

JeremyP
JeremyP

Reputation: 86671

Anonymous closure argument not contained in a closure

This error occurs because you cannot use trailing block syntax in the condition of an if statement. When the compiler sees the opening brace {, it assumes it has found the block to be executed when the if condition is true. $0 makes no sense in that context.

To fix it, you must use the non trailing block syntax i.e. put the parentheses for the map function call in.

if ([.A, .B, .C, .D] as? [Types]).map ({ tableViewDataSource.firstIndex(of: $0)}).contains(textField.tag)
//                                    ^- here                                   ^-- and here                            
{
    // Do stuff
}

That will leave you with this error:

type of expression is ambiguous without more context

That's because of the as?. By putting the question mark there, you tell the compiler you are not sure that the array can be converted to an array of Types and it therefore assumes it does not have enough information to infer the type. Take out the question mark and the compiler knows it has to be an array of Types and can therefore infer the type of the expression correctly.

In fact, if you only have one enum with those members, you might find you don't need the cast:

if [.A, .B, .C, .D].map ({ tableViewDataSource.firstIndex(of: $0) }).contains(1)
{
    // do something
}

compiles and runs fine in a playground.

Upvotes: 0

Matic Oblak
Matic Oblak

Reputation: 16784

The problem in second line is implicit type which Swift complier can not determine. In short doing ([.A, .B, .C, .D] as? [Types]) does nothing good, you need to be explicit in every type: ([Types.A, Types.B, Types.C, Types.D]). Now Swift compiler can interpret this as [Types].

You don't need it in the first example because you used explicit type:

let pickerSelectingFields: [Types] = [.A, .B, .C, .D]

But you would get the same issue by doing it implicitly:

let pickerSelectingFields: = [.A, .B, .C, .D]

And you could again fix it by giving a hint to compiler:

let pickerSelectingFields: = [Types.A, Types.B, Types.C, Types.D]

It might be a bit hard to explain/understand but I hope this clears a few things.

Also a bracket is missing so the final result should be:

if (([Types.A, Types.B, Types.C, Types.D]).map { tableViewDataSource.firstIndex(of: $0) }).contains(textField.tag) {

}

Upvotes: 1

Joakim Danielson
Joakim Danielson

Reputation: 52033

One solution is to make the enum implement CaseIterable

enum Types: CaseIterable {
  case A 
  case B 
  case C 
  case D 
}

Then you can check for the presence of the tag in the array like this

if textField.tag >= 0 && textField.tag < Types.allCases.count { //maybe not needed
    if tableViewDataSource.contains(Types.allCases[textField.tag]) {
         //do stuff
    }
}

Another option is to make the enum of type Int

enum Types: Int{
  case A = 1
  case B 
  case C 
  case D 
}

And then check directly using the tag

if let type = Types(rawValue: textField.tag) {
    if tableViewDataSource.contains(type) {
        //do stuff
    }
}

Upvotes: 1

Related Questions