GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

SOLVED: Using Classes, Enums, or Structs to Simplify Settings

Forums > SwiftUI

One of the features of my app is the ability to "favorite" a Core Data item.

A favorite item gets a Image(systemName: "heart.fill") and one that hasn't been favorited gets Image(systemName: "heart"). I'm doing this with a simple comparison operator, like so:

Image(systemName: item.favorited ? "heart.fill" : "heart" )
  .foregroundColor( item.favorited ? .red : .black)

I'd like to provide the option to switch from hearts to stars. (I know this isn't a dramatic or integral feature; it's more of a teaching exercise for myself to improve my skills.)

So I'd have in my app's settings the ability to choose between hearts or stars for favorites.

Obviously I'm not going to do this:

let x = hearts_or_stars_preference
Image(systemName: item.favorited ? ( x ? "heart.fill" : "star.fill" ) : ( x ? "heart" : "star" ) )
  .foregroundColor( item.favorited ? .red : .black)

I could, but that's ugly. And what happens if I decide to also give users the ability to choose between hearts, stars, or checkmarks? Checkmarks aren't going to be "checkmark" and "checkmark.fill"; they'd be "checkmark.circle" and "xmark.circle".

Surely there's a good way to make this scalable. I'd like to be able to save an integer to UserDefaults and then determine which image to display based on that. But here's where it gets a little tricky: Hearts are red, but stars are yellow. Checkmarks could be any color (although probably green). So I need to have a single integer that allows me to determine not only the image, but also the positive/negative color associated with it.

My gut is telling me that I'd use a class for this, but I fear that's because I've spent 20 years writing PHP.

Can anyone give me some advice here? Or point me to one of Paul's articles which covers this that I might have missed?

Thanks!

3      

Use an enum for something like this.

enum FavoriteIcon: Int, RawRepresentable {
    case star
    case heart
    case check

    var iconOn: String {
        switch self {
        case .star: return "star.fill"
        case .heart: return "heart.fill"
        case .check: return "checkmark.circle"
        }
    }

    var iconOff: String {
        switch self {
        case .star: return "star"
        case .heart: return "heart"
        case .check: return "xmark.circle"
        }
    }

    var iconColorOn: UIColor {
        switch self {
        case .star: return .yellow
        case .heart: return .red
        case .check: return .green
        }
    }

    var iconColorOff: UIColor {
        return .black
    }
}

And then you would do:

let x = hearts_or_stars_preference
Image(systemName: item.favorited ? x.iconOn : x.iconOff)
  .foregroundColor( item.favorited ? Color(uiColor: x.iconColorOn) : Color(uiColor: x.iconColorOff))

Since the enum conforms to RawRepresentable, you can initialize it from your UserDefaults value like so:

let intValueFromUserDefaults = //get Int from UserDefaults
let fave = FavoriteIcon(rawValue: intValueFromUserDefaults)

3      

Thanks, @roosterboy! That appears to be exactly what I need.

But how do I force it to not be optional?

Before even attempting to associate it with a UserDefaults value, I just attempted to hard-code it, like so:

    let iconstyle = FavoriteIcon(rawValue: 1)

But when I use it -- e.g. Image(systemName: self.favorited ? iconstyle.iconTrue : iconstyle.iconFalse ) -- I'm getting this:

  • Value of optional type 'FavoriteIcon?' must be unwrapped to refer to member 'iconFalse' of wrapped base type 'FavoriteIcon'
  • Chain the optional using '?' to access member 'iconFalse' only for non-'nil' base values
  • Force-unwrap using '!' to abort execution if the optional value contains 'nil'

I don't want to have to sprinkle a bunch of question marks and exclamation points all over the place. What am I doing wrong?

3      

You aren't doing anything wrong. It's a consequence of FavoriteIcon being representable as an Int. There are (in that example) only 3 cases that are valid for FavoriteIcon (corresponding to 0, 1, and 2) but Int has millions of valid values. So, if you were to do, say, let iconstyle = FavoriteIcon(rawValue: 1000), that's not a valid value and so you would get nil back as a result.

One way you could handle that would be to supply a default value for FavoriteIcon, like so:

enum FavoriteIcon: Int, RawRepresentable {
    //...

    static var `default`: FavoriteIcon { return .heart } // or whatever you want the default to be
}

Then when you assign a FavoriteIcon, you would use nil-coalescing to assign the default value if you got nil out:

let fave = FavoriteIcon(rawValue: 1000) ?? .default

And now fave is no longer an Optional!

3      

Brilliant! Thank you so much, @roosterboy. And double extra thank you for explaining the optionals bit. I never understood that about Int and now it makes sense.

3      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.

Learn more here

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.