1
votes

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.

2
Neither 32-bit nor 64-bit floats can store the number 0.2 exactly. Comparing floating point numbers with == is bad in most cases. - Martin R
Have tried saving the colour values in Hex instead to avoid the precision issue altogether. - Edwin Abraham
How are you archiving the UIColor? doing == with float / double is never a clever idea! Look into stackoverflow.com/questions/1275662/… - Volker

2 Answers

2
votes

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.

1
votes

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
  }
}