NEW: Learn to build the incredible iOS 15 Weather app today! >>

Dynamic creation of View to avoid long switch statement?

Forums > SwiftUI

Hi,

I'm very new to Swift and SwiftUI, so I'm probably missing something obvious. I have a list of files and for each file's type I have a different View object. How do I set up my NavigationView so that I don't have a massive long switch statement?

This is what I currently have:

    var body: some View {
        let nv = NavigationView {
            List {
                ForEach(files) { file in
                    switch(file.type) {
                    case .text:
                        NavigationLink(destination: FileTextView(file: file)) {
                            FileRow(file.title)
                        }
                    case .markdown:
                        NavigationLink(destination: FileMarkdownView(file: file)) {
                            FileRow(file.title)
                        }
                    case .pdf:
                        NavigationLink(destination: FilePDFView(file: file)) {
                            FileRow(file.title)
                        }
                    case .image:
                        NavigationLink(destination: FileImageView(file: file)) {
                            FileRow(file.title)
                        }
                    default:
                        EmptyView()
                    }
                }
            }
            .navigationTitle("Files")
        }
    }

As I add more and more types, I can see that this is going to get very tedious. Is there a better way?

   

I see what you mean, and I'll be very interested in seeing what anyone else may suggest. My first thought is that you might stick the appropriate icon into the file type enumeration, and then make a more generic FileView:

enum FileType: String {
    case text = "name_of_text_icon"
    case pdf = "name_of_PDF_icon"
    case unknown = "name_of_unknown_icon"
}

struct File {
    let path: URL
    let type: FileType
    let title: String
}

struct FileView: View {
    let file: File

    var body: some View {
        Image(file.type.rawValue)
    }
}

Then, your body looks like:

var body: some View {
        let nv = NavigationView {
            List {
                ForEach(files) { file in
                  NavigationLink(destination: FileView(file: file)) {
                    FileRow(file.title)
                  }
                }
              }
          }
        return nv
}

   

Thanks. However, the FileXyzView views are the detail view which is a render of the file contents. The icon next to each file name in the list can be handled in FileRow in pretty much the way you suggest.

   

I tend to keep my functions, methods, and bodies in SwiftUI fairly small. After 10-20 lines I look to refactor.

The exception is a switch statement, which you can't really break up that easily. I think that is ok as it is, but as you said it might grow.

However you could get away with some type erasing to AnyView . This wont compile but this is an idea:

 func getViewForFile(file:File) -> AnyView {
       switch file.type {
          case  .pdf: return AnyView( FilePDFView(file))
          case  .image: return AnyView( FileImageView(file) )
          default: return AnyView(EmptyView())
       }
   }

Actually you could probably even use assoicated values in enums there, but I never got comfortable with that.

  ForEach(file) { file in
                NavigationLink(destination:   getViewForFile(file) {
                   FileRow(file.title)
                }
              }

This is moving the swtich, not eliminating it, but it's neater.

   

Without seeing the code for the various different Views, it's impossible to say if they could be coded in such a way that they could be generically applicable to each type of file. That's a possibility that only you can determine at this point. One way might be if everything but the file rendering is the same, you could extract the rendering code into a protocol and a set of conforming objects and pass the appropriate one to a general FileView or something. So you'd have, e.g. FileView(file: file, renderer: PDFRenderer()), FileView(file: file, renderer: MarkdownRenderer()), etc. This could be further generalized with a liberal use of enums and such.

But the approach described by @eoinnorris is on the right track. Since enums can conform to the View protocol, you can do something like this:

import SwiftUI

enum EnumView: View {

    case textView
    case imageView
    //using associated values to supply additional information
    case labelView(String, String)

    //use @ViewBuilder so we won't have to wrap in AnyView
    @ViewBuilder
    var body: some View {
        switch self {
        case .textView:
            Text("Hello world")
        case .imageView:
            Image(systemName: "person")
        case let .labelView(text, image):
            Label(text, systemImage: image)
        }
    }
}

struct MultiView: View {

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                NavigationLink(destination: EnumView.textView) {
                    Label("Text View", systemImage: "arrow.forward")
                }
                NavigationLink(destination: EnumView.imageView) {
                    Label("Image View", systemImage: "arrow.forward")
                }
                NavigationLink(destination: EnumView.labelView("Square", "square")) {
                    Label("Label View", systemImage: "arrow.forward")
                }
            }
        }
    }
}

   

Thanks for all the advice. Definitely useful!

   

Hacking with Swift is sponsored by Sentry

SPONSORED With Sentry’s error and performance monitoring for iOS, you see mobile vitals that actually matter, can solve any latency issues quickly, and learn how each release is performing over time.

Learn More

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.