Search code examples
swiftuiios17swiftui-view

In SwiftUI how to refer to a "subclass -like" View struct as a variable, in View setup code?


I have a number of cute premade structs,

///Cute green flag icon
struct YahIcon: View {
    var body: some View {
        Image(systemName: "flag.checkered")
            .symbolRenderingMode(.palette).foregroundStyle(.green)
            .font(.system(size: 32, weight: .light))
            .transformEffect(CGAffineTransform(rotationAngle: -0.3).translatedBy(x: -8, y: 7))
    }
}

///Cute dotty box icon
struct NahIcon: View {
    var body: some View {
        Image(systemName: "square.dotted")
            .symbolRenderingMode(.palette).foregroundStyle(.tertiary)
            .font(.system(size: 32, weight: .medium))
    }
}

which you can use freely in a View

///Cancelled sportsman
struct Dossier: View {
    var data: MediaPersonality
    var body: some View {
        HStack {
            Blah
            NahIcon
            Blah
            Blah
        }
    }
}

Great.

Often for say text, I'll do some long calculation in a property up the top, rather than repetitively pasting it inside builder items.

struct Dossier: View {
    var data: MediaPersonality
    var descrip: String {
        return(data.blah.lowercased() + " blah\(data.blah.blah) \(blah.count)")
    }
    var body: some View {
        HStack {
            Text(descrip)
            NahIcon
            Text(descrip)
            Blah
        }
    }
}

All good.

However, when doing the same sort of decision making with images ...

struct Dossier: View {
    var data: MediaPersonality
    var body: some View {
        HStack {
            Blah
            if (data.toggle == "Go") {
                 YahIcon.init()
                     .frame( etc
                     .frame( etc
            }
            else {
                 NahIcon.init()
                     .frame( etc
                     .frame( etc
            }
            Blah
            Blah
        }
    }
}

it would be better to do something conceptually like THIS

struct Dossier: View {
    var data: MediaPersonality

    var CorrectIcon: View {
       if (data.toggle == "Go") {
         return (YahIcon.init()
            .frame( etc
            .frame( etc
         )
       }
       else {
         return (NahIcon.init()
            .frame( etc
            .frame( etc
         )
       }
    }

    var body: some View {
        HStack {
            Blah
            CorrectIcon
            Blah
            Blah
            VStack {
               CorrectIcon
               Blah
            }
        }
    }
}

(Note - or perhaps better with the "frame etc" on the CorrectIcon inside the View rather than included in the property; either/both would be ideal.)

But of course that is meaningless, you can't do it.

The "View subclasses" YahIcon and NahIcon can and will be changed frequently and indeed conceptually by the design krew and they are used as-is repo wide, so adding a simple toggle inside them is not a plan.

So,

  • using a property in a View, you can conveniently return (say) a string or numeric calculation for use inside one of the view items, no problem.

  • how can one "return" and use a View as such, from such a calculation?

  • should the globally available example icons (YahIcon etc) perhaps be encapsulated in a different way to help with this issue?

  • superficially Xcode will tall you "use any View rather than View" however I can't get that to work by hacking around - perhaps I've just made a simple mistake?

How to?

I've seen similar questions but unfortunately nothing exactly answering this (seemingly basic?) issue. Perhaps I'm just missing something simple.


Solution

  • WTT @Sweeper the formula is:

    struct PGATour: View {
        
        var data: TasksItem
        
        @ViewBuilder var KawaiiIcon: some View {
            if (d.Status == .bail) {
                YahIcon.init()
                    .frame(maxWidth: 44, alignment: .trailing)
            }
            else {
                NahIcon.init()
                    .frame(maxWidth: 44, alignment: .trailing)
            }
        }
        
        var body: some View {
            HStack {
                Text("example")
                KawaiiIcon
                Text("example")
        }
    }
    

    where the "subclass -like" View (see question) are

    struct YahIcon: View {
        var body: some View {
            Image(systemName: "flag.checkered")
                .etc
        }
    }
    struct NahIcon: View {
        var body: some View {
            Image(systemName: "square.dotted")
                .etc
        }
    }
    

    And the other approach:

    ///Also courtesy @Sweeper, a view with an initializer
    struct ABIcon: View {
        var good: Bool
        var body: some View {
            if good {
                YahIcon.init()
            }
            else {
                NahIcon.init()
            }
        }
    }