13
votes

Let say we have an enum with associated value types. In the example below the two value types are simple object that hold an image and a url to share.

enum Content {
  case Image(ShareableImage)
  case Video(ShareableVideo)
}

Now let's have an array of video and image cases.

let media: [Content] = [*a lot of enum cases inside here*]  

All the code above so far cannot be changed in any way in the codebase, I need to work with it.


Here starts my problem:

Let's filter the array with media to only image cases

    let imageOnlyCases: [Content] = media.filter { item -> Bool in

        switch item {
        case .Image: return true
        default: return false
        }
    }

Next step, I want to get from array of enum to an array of their associated values

[Content] -> [ShareableImage] by using map.

so I do this

    let shareablemages = imageOnlyCases.map { imageCase -> ShareableImage in

        switch imageCase {

        case .Image(let image): return image
        default: return  WHAT TO DO HERE?
        }
    }

You see, I have a problem with return type..I know that the enum cases are all .Image..and I want a simple map. But the swift syntax is not helping me.

Any ideas?

2
I would return nil so you have an array of optionals associated values after that you can filter it to ged rid of nil. - Greg

2 Answers

17
votes

You could return image for case .Image, and nil otherwise, within a .flatMap operation (to "filter" out nil entries):

/* Example */
enum Foo {
    case Bar(Int)
    case Baz(Int)
}

let foo: [Foo] = [.Bar(1), .Bar(9),. Baz(3), .Bar(39), .Baz(5)]

/* 1. using 'switch' */
let barOnlyValues: [Int] = foo.flatMap {
    switch $0 {
    case .Bar(let val): return val
    case _: return nil
    }}

/* 2. alternatively, as pointed out in MartinR:s answer; 
      as you're only looking for a single case, the alternative
      'if case let' clause could be preferred over 'switch':     */
let barOnlyValuesAlt: [Int] = foo.flatMap {
    if case let .Bar(val) = $0 { return val }
    else { return nil }}                               

print(barOnlyValues) // [1, 9, 39]

Applied to your use case: note that you needn't perform the filtering to create the imageOnlyCases array, as you can apply the above directly on the media array:

/* 1. using switch */
let shareableImages : [ShareableImage] = media.flatMap {
    switch $0 {
    case .Image(let image): return image
    case _: return nil
    }}

/* 2. 'if case let' alternative, as per MartinR:s suggestion */
let shareableImagesAlt : [ShareableImage] = media.flatMap {
    if case let .Image(image) = $0 { return image }
    else { return nil }}

Disclaimer: I cannot verify your specific use case in practice as I don't have access to the ShareableImage class/struct.

(Thanks @MartinR for advice that .map{ ... }.flatMap{ ... } can be simplified to just .flatMap{ ... }).

2
votes

If it is guaranteed that only the .Image case can occur then you can call fatalError() in all other cases:

let shareableImages = imageOnlyCases.map { imageCase -> ShareableImage in

    if case let .Image(image) = imageCase {
        return image
    } else {
        fatalError("Unexpected content")
    }
}

fatalError() causes the program to terminate immediately. It is only meant for situations that "cannot occur", i.e. to find programming errors.

It satisfies the compiler because the function is marked as @noreturn.

If you cannot make that guarantee then use flatMap() as suggested in the other answer.

Note also that you can use if case here with a pattern instead of switch/case.