Store UIColor in CoreData

3k Views Asked by At

How can i store UIColor in CoreData without loss on 64 bit? On 32 bit the correct UIColor is returned.

CoreData setup

  • attribute type: Transformable
  • NSManagedObject subclass property: @NSManaged var color: UIColor?

Before the color value is stored

color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

output e.g. red on 64bit:

0.20000000000000018  

output red on 32 bit

0.199999928

After the color is retrieved from CoreData

color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

output e.g. red on 64bit:

0.20000000298023224 

output red on 32 bit:

0.199999928

Resulting Problem

Color comparison, using == , fails on 64 bit, because the values slightly differ. On 32 bit everything is fine and color comparison succeeds.

3

There are 3 best solutions below

0
On

Swift 5.7, minimum deployment target iOS 11

import UIKit

final class ColorAttributeTransformer: ValueTransformer {
    override class func transformedValueClass() -> AnyClass {
        NSData.self
    }
    
    override func transformedValue(_ value: Any?) -> Any? {
        let color = value as! UIColor
        
        return NSKeyedArchiver.archivedData(withRootObject: color)
    }
    
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        let data = value as! Data

        return NSKeyedUnarchiver.unarchiveObject(with: data)
    }
}

Be aware, that Xcode complines in console: 'NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release.

4
On

Assign NSKeyedArchiver.archivedDataWithRootObject(color) to a data variable and save that to your Core Data store instead.

To read the data, just assign NSKeyedUnarchiver.unarchiveObjectWithData(colorData) to a color variable.


In case you're wondering how to compare floats anyways, you can always refer to this.

6
On

This solved my problem:

  1. set CoreData attribute name to my custom NSValueTransformer subclass: MQColorTransformer

  2. implemented the following NSValueTransformer subclass (which is also converting UIColor to NSData, like Schemetrical suggested, but it has the advantage that i can still assign UIColor to my color property and NSValueTransformer takes care of the transformation behind the scence):

NSValueTransformer subclass

import Foundation
import UIKit

@objc(MQColorTransformer) class MQColorTransformer: NSValueTransformer {

  override class func transformedValueClass() -> AnyClass{
      return NSData.classForCoder()
  }

  override func transformedValue(value: AnyObject!) -> AnyObject {

      // Transform UIColor to NSData
      let color = value as! UIColor

      var red: CGFloat = 0
      var green: CGFloat = 0
      var blue: CGFloat = 0
      var alpha: CGFloat = 0
      color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

      var components:[CGFloat] = [red, green, blue, alpha]
      let dataFromColors = NSData(bytes: components,
          length: sizeofValue(components)*components.count)

      return dataFromColors

  }

  override func reverseTransformedValue(value: AnyObject!) -> AnyObject {

      // Transform NSData to UIColor
      let data = value  as! NSData
      var components = [CGFloat](count: 4, repeatedValue:0)
      var length=sizeofValue(components)

      data.getBytes(&components, length: length * components.count)
      let color = UIColor(red: components[0],
          green: components[1],
          blue: components[2],
          alpha: components[3])

      return color
  }
}