-1

I have a view controller that will show users a "question drill down", where answering one question shows the next assigned question based on the previous answer. I'm trying to figure out the best way to structure this "question drill down".

I'm assuming nested enums is the way to go. Here's an example of the setup I currently have:

enum SubmissionSteps {
    case techQuestion(TechQuestionStep)
    
    enum TechQuestionStep {
        case website(WebsiteStep), app, password, other
        
        enum WebsiteStep {
            case yesStreaming(YesStreamingStep), noStreaming
            
            enum YesStreamingStep {
                case startTime(StartTimeStep)
                
                enum StartTimeStep {
                    case endTime
                }
            }
        }
    }
}

func showFirstStep() {
    currentStep = SubmissionSteps.techQuestion // error here 'ViewController' has no member 'techQuestion'
}

var currentStep:SubmissionSteps? {
    didSet {
        
    }
}

var currentlyShowing:[SubmissionSteps] = [] {
    didSet {
        
    }
}

func getStepTitle(step:SubmissionSteps) -> String {
    switch step {
        
    case .techQuestion(_):
        return "Tech Question"
    case .techQuestion(.app):
        return "App" // Case is already handled by previous patterns;
    case .techQuestion(.other):
        return "Other" // Case is already handled by previous patterns;
    case .techQuestion(.password):
        return "Password" // Case is already handled by previous patterns;
    case .techQuestion(.website(_)):
        return "Website" // Case is already handled by previous patterns;
    case .techQuestion(.website(.noStreaming)):
        return "No Streaming" // Case is already handled by previous patterns;
    case .techQuestion(.website(.yesStreaming(_))):
        return "Yes Streaming" // Case is already handled by previous patterns;
    case .techQuestion(.website(.yesStreaming(.startTime(_)))):
        return "Start Time" // Case is already handled by previous patterns;
    case .techQuestion(.website(.yesStreaming(.startTime(.endTime)))):
        return "End Time" // Case is already handled by previous patterns;
    }
}

The issue with the setup above exists in the getStepTitle() func. I can't return the title for a parent option, only the titles for the children. The switch statement in the getStepTitle() func that I currently have shows "Case is already handled by previous patters; consider removing it".

I also can't set the currentStep to be a parent option in the enum, only the children.

Either using nested enums in not the proper way to handle a data setup like this, or I have a basic misunderstanding of how to access parent values within the nested enum to get the current title for any level inside the enum. Suggestions or thoughts?

3
  • I tried to slim it down for brevity's sake. Ignore the error message if you wish or I can remove it from the example. SubmissionSteps.techQuestion has child values, so i can not set currentStep = SubmissionSteps.techQuestion() . Swift is making me provide a child value for techQuestion, even though I want to set the parent value Commented Apr 12, 2023 at 16:58
  • Wouldn't it be better to use some kind of tree structure instead? Commented Apr 12, 2023 at 17:23
  • Haven't even heard of a tree structure until now. Going over the docs. Commented Apr 12, 2023 at 17:24

2 Answers 2

1

Your current use of enum associated values doesn't allow you to create "parent" values. For example, currentStep = SubmissionSteps.techQuestion doesn't work because techQuestion requires an associated value.

I see two solutions.

  1. Make the associated value optional.
  2. Create a new case to represent a "top" level.

For solution 1 your nested enum becomes:

enum SubmissionSteps {
    case techQuestion(TechQuestionStep?)

    enum TechQuestionStep {
        case website(WebsiteStep?), app, password, other

        enum WebsiteStep {
            case yesStreaming(YesStreamingStep?), noStreaming

            enum YesStreamingStep {
                case startTime(StartTimeStep?)

                enum StartTimeStep {
                    case endTime
                }
            }
        }
    }
}

And you can create a "parent" techQuestion with:

currentStep = SubmissionSteps.techQuestion(nil)

You resolve the issues with getStepTitle by putting the most specific cases first and the most general last:

func getStepTitle(step:SubmissionSteps) -> String {
    switch step {

    case .techQuestion(.website(.yesStreaming(.startTime(.endTime)))):
        return "End Time"
    case .techQuestion(.website(.yesStreaming(.startTime(_)))):
        return "Start Time"
    case .techQuestion(.website(.yesStreaming(_))):
        return "Yes Streaming"
    case .techQuestion(.website(.noStreaming)):
        return "No Streaming"
    case .techQuestion(.website(_)):
        return "Website"
    case .techQuestion(.password):
        return "Password"
    case .techQuestion(.other):
        return "Other"
    case .techQuestion(.app):
        return "App"
    case .techQuestion(_):
        return "Tech Question"
    }
}

For solution 2 your nested enum becomes something like:

enum SubmissionSteps {
    case techQuestion(TechQuestionStep)

    enum TechQuestionStep {
        case top
        case website(WebsiteStep), app, password, other

        enum WebsiteStep {
            case top
            case yesStreaming(YesStreamingStep), noStreaming

            enum YesStreamingStep {
                case top
                case startTime(StartTimeStep)

                enum StartTimeStep {
                    case endTime
                }
            }
        }
    }
}

And you can create a "parent" techQuestion with:

currentStep = SubmissionSteps.techQuestion(.top)

The fix for getStepTitle is the same.


As for your statement: "I'm assuming nested enums is the way to go.", that's a whole other discussion well beyond the scope here.

Sign up to request clarification or add additional context in comments.

2 Comments

Nice! I can see how that would work. Would you suggest a different way to organize this drilldown other than a nested enum?
@AlexRitter See the very last statement of my answer. That's just too broad of a question. But I'll throw out one idea - use a tree structure.
0

Your first case, case .techQuestion(_):, will match any techQuestion, regardless of sub-type. Once you have that case, you can't then have more specific cases. Get rid of that case and handle each subtype individulally, or move your case .techQuestion(_): to below the other case .techQuestion() cases so it only catches .techQuestion types that don't have one of the specific subtypes.

Consider this sample project:

import Foundation

enum Bar {
    case bar1
    case bar2
    case bar3
}

enum Foo {
    case foo1(Bar?)
    case foo2
}


let aFooBar1: Foo = .foo1(.bar1)
let aFooBar2: Foo = .foo1(.bar2)
let aFoo: Foo = .foo2



func talkboutAFoo(_ aFoo: Foo) {
    switch aFoo {
    case .foo1(nil):
        print("Foo(no bar)")
    case .foo1(.bar1):
        print("foo1(.bar1)")
    case .foo1(.bar2):
        print("foo1(.bar2)")
    case .foo1(_): // Catch-all that matches other foo1 subtypes
        print("foo1(_)")
    default:
        print("some other case")
    }

}

talkboutAFoo(Foo.foo1(.bar1))
talkboutAFoo(Foo.foo1(.bar2))
talkboutAFoo(Foo.foo1(.bar3))
talkboutAFoo(Foo.foo2)
talkboutAFoo(Foo.foo1(nil))

That outputs

foo1(.bar1)
foo1(.bar2)
foo1(_)
some other case
Foo(no bar)

2 Comments

I'm following you here, and I'm regretting adding that "getStepTitle()" function in my example. I really need to know how to set "currentStep" to be a parent value, even though it has nested options, so I can drill down through them. From your example, how could I set aFoo:Foo = .foo1() ?
I don't think you can have a foo(.bar1) case and a foo() case. You can make your foo1 enum take an optional Bar: case foo1(Bar?) and then pass in nil for the bar associated value. I'll edit my answer illustrating that approach.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.