2
votes

OK. This is really weird. I'm trying to transmit colors from a phone app to its companion Watch app.

The standard (UIColor.red, etc.) colors work, but ones defined by the IB file don't. I get weird colors, which seems as if it's a color space issue.

First, here's a VERY simple (and absolutely HIDEOUS) app project that demonstrates this.

What I am doing, is sending a message from the Watch to the Phone, asking the phone for a set of colors.

The phone obliges by compiling a list of UIColor instances, and sending them over to the watch. It does this by first instantiating three standard colors (UIColor.red, UIColor.green, UIColor.blue), and then appending some colors that are read from a list of labels defined in the app storyboard (It reads the text color from the label).

The resulting array of seven UIColor instances is then serialized and sent over to the watch, which unserializes them, and creates a simple table of labels; each with the corresponding color.

The first three colors look good:

The first three (Standard) colors are golden

However, when I read the colors from the IB, they get skewed.

Here's what's supposed to happen:

These colors should be exactly matched on the Watch

Here's what actually happens:

The colors are wrong

Only the last (Odd) color is correct. The colors are defined in the storyboard as RGB (sliders).

Here's the code in the iOS app that gathers and sends the color:

func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
    print("iOS App: session(_:,didReceiveMessage:)\n\(message)")
    if let value = message["HI"] as? String {
        if "HOWAYA" == value {
            var colorArray: [UIColor] = [UIColor.red, UIColor.green, UIColor.blue]
            if let primaryViewController = self.window?.rootViewController {
                if let view = primaryViewController.view {
                    for subview in view.subviews {
                        if let label = subview as? UILabel {
                            if let color = label.textColor {
                                colorArray.append(color)
                            }
                        }
                    }
                }
            }
            let colorData = NSKeyedArchiver.archivedData(withRootObject: colorArray)
            let responseMessage = [value:colorData]

            session.sendMessage(responseMessage, replyHandler: nil, errorHandler: nil)
        }
    }
}

Here's the code in the Watch that gets the colors, and instantiates the labels:

func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
    if let value = message["HOWAYA"] as? Data {
        if let colorArray = NSKeyedUnarchiver.unarchiveObject(with:value) as? [UIColor] {
            self.labelTable.setNumberOfRows(colorArray.count, withRowType: "TestColorTableRow")
            for row in 0..<colorArray.count {
                if let controller = self.labelTable.rowController(at: row) as? TestTableRowController {
                    controller.setLabelColor(colorArray[row])
                }
            }
        }
    }
}

Other data that I send (in another app) gets through fine, but the colors are skewed.

Any ideas?

UPDATE: It was suggested that maybe the serialization was a problem, so I branched the demo, and unserialized while still in iOS. It turns out OK.

I created this branch, where I unserialize before sending the message:

func colorCrosscheck(_ value: Data) {
    if let colorArray = NSKeyedUnarchiver.unarchiveObject(with:value) as? [UIColor] {
        if let primaryViewController = self.window?.rootViewController {
            if let view = primaryViewController.view {
                for subview in view.subviews {
                    if let containerView = subview as? ViewList {
                        for row in 3..<colorArray.count {
                            if let label = containerView.subviews[row - 3] as? UILabel {
                                label.textColor = colorArray[row]
                            }
                        }
                        break
                    }
                }
            }
        }
    }
}

What I did, was add four labels with clear text color, that are colorized by the same process I use on the Watch.

A few seconds after the Watch gets its message, four labels appear below the top ones in iOS.

The results show the serialization isn't the issue:

Looks OK on iOS

UPDATE (2): It was suggested I try programmatically adding colors. These work fine, which means this is starting to look like an Apple bug. I'll report back when I get a response to my DTS incident.

First, here's a new branch that demonstrates adding the colors dynamically.

Here's the relevant section of code (I also messed with the IB file):

colorArray.append(UIColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 1.0))
colorArray.append(UIColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 1.0))
colorArray.append(UIColor(red: 0.0, green: 0.0, blue: 0.5, alpha: 1.0))
colorArray.append(UIColor(hue: 1.2, saturation: 1.0, brightness: 0.5, alpha: 1.0))
colorArray.append(UIColor(hue: 1.2, saturation: 0.6, brightness: 0.7, alpha: 1.0))

I added 5 new dynamic colors, in both RGB and HSL.

They show up fine in the iOS app:

Looks Good on iOS (If you're a deranged magpie)

And they also transmit properly to the Watch:

The first Three The Last Two

1
Are you sure your data encoding and decoding is correct? I would try unarchiving the data on the phone itself and see if the colours are still correct. I couldn't see any obvious issues with your code, so this is the only source of bugs I can think of at the moment.Dávid Pásztor
I did submit a request. I have a feeling that some kind of "context" is established in iOS, with the colors, and this context is lost when crossing the boundary from Phone to Watch.Chris Marshall
Yeah it's worth getting some help from Apple's engineers, since it looks like it might be a bug. Please update this thread if you get an answer, I am curious what's the solution to this problem.Dávid Pásztor
In the meantime, I would give it a try if custom UIColors instantiated in code show the same behaviour as well or if it only happens with colours set in InterfaceBuilder.Dávid Pásztor
I'll update. Programatically-added colors do fine. I suspect it's a bug.Chris Marshall

1 Answers

0
votes

OK. I have a workaround. It's definitely a bug, but I can't wait until iOS 11/WatchOS 4 comes out. This workaround is effective (so far -lots more testing to go).

Here's the branch with the workaround.

if let color = label.textColor {
    if let destColorSpace: CGColorSpace = CGColorSpace(name: CGColorSpace.sRGB) {
        let newColor = color.cgColor.converted(to: destColorSpace, intent: CGColorRenderingIntent.perceptual, options: nil)
        colorArray.append(UIColor(cgColor: newColor!))
    }
}

It looks like the core color of the IB-based color is bad. Not sure how it gets the color right.

What I do, is create a new CGColor from the original one, casting it to sRGB. The new CGColor is valid.

It's a yucky and kludgy fix, but it should work fine.

I need to test for memory leaks, though. CG tends to leak like a sieve.