swiftenumsenumeration

Creating Question Drill Down with Nested Enums


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?


Solution

  • 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.