UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: How to manage use of Color when storing a textual color value in SwiftData?

Forums > SwiftUI

I have a SwiftData based project where I am trying to convert from using a custom Color picker to the SwiftUI ColorPicker.

I already store colors in my model as Text.

I am trying to migrate to using ColorPicker and am able to convert to/from hex values to enable continuing to store the colors as text in my SwiftData database.

I'm using the techniques documented in the following article. This works fine using the following patterns. No problems with this. blog.eidinger.info/from-hex-to-color-and-back-in-swiftui

Color(hex: colorValueStoredasHex)
colorValueStoredasUIColor.toHex()

The problem I'm having is in my update view. I'm using bound properties to support the updates. I don't know how to handle color. The property needs to be bindable to the color property in my model (Category.color) but in the update logic I need to convert to/from hex.

I feel I need to initialize CategoryUpdateView with a property of type Color equivalent to the current value of Category.color so that I can use ColorPicker, but that obviously will not work with my SwiftData model.

@Model
class Category {
   var name: String = "Unknown"
   var creationDate: Date = Date.now
   var color: String = "000000"
   var icon: String = "pencil"
   var itemSort: String = "Name"
   var showMostRecentEventDate: Bool = true

   @Relationship(deleteRule: .cascade, inverse: \Item.category) var items: [Item]? = [Item]()

   init(name: String, creationDate: Date, color: String, icon: String, itemSort: String,  showMostRecentEventDate: Bool) {
      self.name = name
      self.creationDate = creationDate
      self.color = color
      self.icon = icon
      self.itemSort = itemSort
      self.showMostRecentEventDate = showMostRecentEventDate
   }

   var unwrappedItems: [Item] {
      get {
         return items ?? []
      }
      set {
         items = newValue
      }
   }

}

The following code doesn't compile, of course. ColorPicker expects a type of UIColor. This is where I'm stuck. I don't know how to handle both binding requirement and the need to move between the hex/text type and the UIColor.

import SwiftUI

struct CategoryUpdateView: View {

   @Binding var name: String
   @Binding var iconColor: String
   @Binding var icon: String
   @Binding var itemSort: String
   @Binding var showMostRecentEventDate: Bool

   var body: some View {
      iconAndTitleHeader
      sortOrderAndRecentEvent
      colorPalette
      iconPalette
   }
}

extension CategoryUpdateView {

   // MARK: iconAndTitleHeader
   var iconAndTitleHeader: some View {
...
   }

   // MARK: sortOrderAndRecentEvent
   var sortOrderAndRecentEvent: some View {
  ...
   }

   // MARK: colorPalette
   var colorPalette: some View {
      VStack {
         ColorPicker("Select Icon Color", selection: $iconColor, supportsOpacity: false)
      }
      .frame(maxWidth: .infinity, maxHeight: .infinity)
   }

   // MARK: iconPalette
   var iconPalette: some View {
 ...
   }

}

edit... I realize it may be useful to see the 2 views where I invoke CategoryUpdateView

struct AddCategoryView: View {

   @Environment(\.dismiss) var dismiss
   @Environment(\.modelContext) private var modelContext

   @State private var name = ""
   @State private var color = Color(.black)
   @State private var icon = Category.defaultIcon
   @State private var itemSort = Category.defaultItemSort
   @State private var includeMultipleEvents = true
   @State private var showMostRecentEventDate = true

   var body: some View {
      NavigationStack {
         Form {
            CategoryUpdateView (
               name: $name,
               iconColor: $color,
               icon: $icon,
               itemSort: $itemSort,
               showMostRecentEventDate: $showMostRecentEventDate
            )
         }
         .toolbar {
            Button("Add") {
               save()
            }
            .disabled(name.isEmpty)
         }
      }
   }
}

extension AddCategoryView {
   private func save() {
      let category = Category (
         name: name,
         creationDate: .now,
         color: color.toHex() ?? "000000",
         icon: icon,
         itemSort: itemSort,
         showMostRecentEventDate: showMostRecentEventDate
      )
      modelContext.insert(category)
      dismiss()
   }
}
struct EditCategoryView: View {

   @Environment(\.dismiss) var dismiss
   @Bindable var category: Category

   var body: some View {
      NavigationStack {
         Form {
            CategoryUpdateView (
               name: $category.name,
               iconColor: $category.color,
               icon: $category.icon,
               itemSort: $category.itemSort,
               showMostRecentEventDate: $category.showMostRecentEventDate
            )
         }
         .toolbar {
            Button("Update") {
               dismiss()
            }
            .disabled(category.name.isEmpty)
         }
      }
      .interactiveDismissDisabled(category.name.isEmpty)
   }
}

2      

hi Jay,

you might check out this recent Stewart Lynch video, where he builds out a SwiftData model having a color: String property (a hex string, same as you) and adds a View that uses the SwiftUI color picker to edit the model.

hope that's of interest,

DMG

2      

thanks for the quick response. I may need to review his content in more detail, but I don't think his project addresses my problem. He does utilize the hex color conversion for his genre property. However, he does not have any logic relating to editing the genre color once set.

I have no problem saving an initial Category.color with the correct hex value and then coverting it back for subsequent use in other views.

I can't figure out how to create my edit view to allow edits to that property. He doesn't seem to handle that use case.

2      

hi Jay,

if you keep watching, you'll see the NewGenresView in which you define a new Genre. this view uses a SwiftUI color picker linked to the view's @State private var color: Color property.

although this view only adds a new Genre, you can use the same code to accept an incoming Genre and, either using an init() or an .onAppear modifier, set up the state variable to have a starting value of the incoming genre's color property.

when the view is dismissed, just copy the value of the state variable (a Color) over to the Genre.

if you'd like something of a similar flavor, then take a look at my shopping list project, where each Location has a color (internally stored as red, green, blue, and opacity values) that is edited using a SwiftUI color picker. look specifically at LocationsView.swift (shows a list of locations), which navigates to an edit screen defined in ModifyExistingLocationView.swift where you can change the locations assigned color.

hope that helps,

DMG

2      

Thanks again for this help. Between review of this code and some online assistance with Phind.com I was able to fix the views.

In brief, I refactored both the Add and CategoryUpdate views to move to a single state variable for category.

The code I needed within CategoryUpdateView to enable the ColorPicker and my hex stored values is the following:

 private var colorBinding: Binding<Color> {
      Binding<Color>(
         get: { category.unwrappedCategoryColor },
         set: { newColor in
            category.color = Color(newColor).toHex() ?? "000000"
         }
      )
   }

      var unwrappedCategoryColor: Color {
         Color(hex: color) ?? Color(.black)
   }

Everything is working as expected now. Thanks again for the tips. I always enjoy learning from other's solutions! cheers -- jay

2      

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 your entire paywall view without any code changes or app updates.

Learn more here

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.