Swift Combine: Alternatives too eraseToAnySubscriber? - swift

I am trying to use Combine to update a colour when my red, green or blue variables change. The examples I have looked at use sink() and that seems appropriate for me but eraseToAnySubscriber is MIA and I can't find an alternate.
What seems to work is to use an assign() to a computed variable but that seems like a bit of a hack.
init() {
redCancellable = red.hasChanged.receive(on: RunLoop.main).assign(to: \.rgbUpdated, on: self)
}
Is there any way to save the value returned by sink()?

This sounds like a job for CombineLatest. And yes, sink is a perfectly good way to dispose of the end of the pipeline in whatever way you like.
Here's a simple example. I'll start with an object that has r, g, and b variables:
class ColorObject {
#Published var r : CGFloat = 1
#Published var g : CGFloat = 1
#Published var b : CGFloat = 1
}
Now imagine that somewhere we have an instance of that object; call it colorObject. Then we can configure a publisher:
let rpub = colorObject.$r
let gpub = colorObject.$g
let bpub = colorObject.$b
let colorpub = Publishers.CombineLatest3(rpub,gpub,bpub)
.map { UIColor(red: $0.0, green: $0.1, blue: $0.2, alpha: 1) }
The result is that every time colorObject's r or g or b changes, a UIColor comes down the pipeline. Now we can receive a notification from colorpub by subscribing to it with sink and dispose of the result however we like. Let's set some interface object's color to that color:
let sink = colorpub.sink { self.view.backgroundColor = $0 }
Alternatively, I could write it using assign, which perhaps is cleaner, though backgroundColor is an Optional so I have to interpose a map operator because keyPaths are not covariant:
let assign = colorpub.map{Optional($0)}
.assign(to: \.backgroundColor, on: self.view)
Now whenever colorObject's r, g, or b changes, our view's color changes accordingly.
This is not the only way to accomplish this goal — far from it! But it's a simple example of getting things done with Combine. A possibly useful variant would be to move the colorpub publisher up into the ColorObject; that way, the ColorObject vends the color, directly, itself:
class ColorObject {
#Published var r : CGFloat = 1
#Published var g : CGFloat = 1
#Published var b : CGFloat = 1
lazy var colorpub = Publishers.CombineLatest3($r,$g,$b)
.map { UIColor(red: $0.0, green: $0.1, blue: $0.2, alpha: 1) }
}
This changes nothing about the sink or assign:
let sink = colorObject.colorpub.sink { // ... whatever
// or
let assign = colorObject.colorpub.map{Optional($0)}
.assign(to: \.backgroundColor, on: self.view)

Related

How to create a matrix of CAShapeLayers?

I have this code:
var triangles: [[[CAShapeLayer]]] = Array(repeating: Array(repeating: Array(repeating: 0, count: 2), count: 15), count: 15);
But it generates an "Cannot convert value of type..." compilation error.
How can I solve that? I want to access my CAShapeLayers like this:
triangles[1][2][1].fillColor = UIColor(red: 40/255, green: 73/255, blue: 80/255, alpha: 1).cgColor;
Use optionals.
var triangles: [[[CAShapeLayer?]]] = Array(repeating: Array(repeating: Array(repeating: nil, count: 2), count: 15), count: 15)
Now there's a nil instead of a 0, which is what I think you were hinting at. But every triangles[x][y][z] is now an optional type you'll have to safely unwrap.
So now you have to do something like triangles[x][y][z] = CAShapeLayer() before you do anything to that object.
Edit for correction. Thanks #OOPer
I thought about it some more, and realized I didn't really answer your question.
So you may use for loops to initialize everything (which would be a pain), or you could do something like this every time you access an index:
if triangles[x][y][z] == nil
{
triangles[x][y][z] = CAShapeLayer()
}
let bloop = triangles[x][y][z]!
bloop.fillColor = UIColor(...
Then you could pull it out into an outside method so it becomes a 1 liner. Like:
func tri(at x: Int, _ y: Int, _ z: Int) -> CAShapeLayer
{
if triangles[x][y][z] == nil
{
triangles[x][y][z] = CAShapeLayer()
}
return triangles[x][y][z]!
}
Then when using it:
tri(at: 1, 2, 1).fillColor = ...
Of course, you should pull triangles out and make it a property of the class you're in, or you can include it in the parameter list of that 1 liner method.
All that nesting makes your code hard to understand, and Array(repeating:count:) can't do what you want anyway.
func newGrid() -> [[[CAShapeLayer]]] {
func newStack() -> [CAShapeLayer] {
return (0 ..< 2).map({ _ in CAShapeLayer() })
}
func newRow() -> [[CAShapeLayer]] {
return (0 ..< 15).map({ _ in newStack() })
}
return (0 ..< 15).map({ _ in newRow() })
}
var triangles = newGrid()
You cannot use "0" as the repeating value, it will be inferred to be type [[[Int]]]. Just replace "0" with "CAShapeLayer()"

RGB Values doing strange things when colorizing? - Swift

func colorBall() {
let colorize1 = SKAction.colorizeWithColor(UIColor.redColor(), colorBlendFactor: 1.0, duration: 0.1)
let colorize2 = SKAction.colorizeWithColor(UIColor.greenColor(), colorBlendFactor: 1.0, duration: 0.1)
let colorize3 = SKAction.colorizeWithColor(UIColor.blueColor(), colorBlendFactor: 1.0, duration: 0.1)
let actions = [colorize1, colorize2, colorize3]
let randomIndex = Int(arc4random_uniform(3))
self.Ball.runAction(actions[randomIndex])
}
var colorBucket = [UIColor]()
func randomColor() -> UIColor {
if colorBucket.isEmpty {
fillBucket()
}
let randomIndex = Int(arc4random_uniform(UInt32(colorBucket.count)))
let randomColor = colorBucket[randomIndex]
colorBucket.removeAtIndex(randomIndex)
return randomColor
}
func fillBucket() {
colorBucket = [UIColor.redColor(), UIColor.greenColor(), UIColor.blueColor()]
}
When I run this code in my game, and print out the color value of my ball, it sometimes prints out numbers like this:
UIDeviceRGBColorSpace 1 2.98023e-08 2.98023e-08 1
Why does it do this? I just want it to say: UIDeviceRGBColorSpace 0 0 1 1 if it's blue, IDeviceRGBColorSpace 1 0 0 1 if it's red, etc.
How can I keep those numbers from going higher than one, or much lower than one? What makes them do that in my code?
Based partially on zneak's answer I've made this (no thrills or frills) extension to UIColor which could come in handy:
extension UIColor {
func isVisuallyEqual(color: UIColor) -> Bool {
let compareValues = CGColorGetComponents(color.CGColor)
let values = CGColorGetComponents(self.CGColor)
let count = CGColorGetNumberOfComponents(self.CGColor)
if count != CGColorGetNumberOfComponents(color.CGColor) {
debugPrint("color-parameter has mismatching colorSpace")
return false
}
for index in 0..<count {
if !fuzzyFloatCompares(values[index], float2: compareValues[index]) {
return false
}
}
return true
}
private func fuzzyFloatCompares(float1: CGFloat, float2: CGFloat) -> Bool {
let difference = float1 - float2
return difference >= -1/256 && difference <= 1/256
}
}
2.98023e-08 is 0.0000000298023. If you look up the value 2.98023e-08 on Google or another search engine, you can find several examples of people getting that value because of rounding errors. Rounding errors occur because of how computers treat floating-point numbers.
It's probably a rounding error from the interpolation code that colorizeWithColor uses, and you get it instead of zero. For practical purposes, when talking about color components about to be displayed to an end user, I'd say that anything smaller than 1/256 can be considered to be zero.
You can test if two floating point numbers are "about equal" like this (typed on my phone, not really guaranteed to work):
func areAboutTheSame(a: Double, b: Double) -> Bool {
let difference = a-b
return difference < 1/256 && difference > -1/256
}

How to sort a Dictionary in Swift

How can I sort a dictionary in Swift?
My Dictionary declaration:
var colorDictionary = Dictionary<Pixel, Int>() //pixel class stores RGB values of a pixel, Int stores the appearing times of the same color showing on the same image.
My target:
I need the elements in the dictionary sorted by value (appearing times of colors) from high to low.
What I tried:
I have done some research online, and I know the Dictionary in swift doesn't provide the sort function. So I wrote the following code:
var tempArray = Array(colorDictionary.keys)
var sortedKeys: () = sort(&tempArray){
var obj1 = self.colorDictionary[$0]
var obj2 = self.colorDictionary[$1]
return obj1>obj2
}
println("the most color: \(colorDictionary[tempArray[0])")
Output I got: "the most color: Optional(27)" //where 27 is the highest appearing time of the color, which is the value of the dictionary.
Question: How could I make it return the key as well?
My Pixel Class:
//for making the hashvalue stuff equatable
func ==(lhs: Pixel, rhs: Pixel) -> Bool {
return lhs.hashValue == rhs.hashValue
}
//customized class for storing pixel info of an image
class Pixel: Hashable {
//RGB components from one pixel
let r: CGFloat
let g: CGFloat
let b: CGFloat
var y: CGFloat = 0
var u: CGFloat = 0
var v: CGFloat = 0
var theUIColorOfThisPixel:UIColor
//adding for make the key hashable
var hashValue: Int {
return theUIColorOfThisPixel.hashValue
}
init(_thePixel: UIColor){
r = _thePixel.components.red
g = _thePixel.components.green
b = _thePixel.components.blue
theUIColorOfThisPixel=UIColor(red: r, green: g, blue: b, alpha: 1)
rgbToYuv(r, _g: g, _b: b)
}
}
[Problem solved]
My solution:
if I convert the result to Int (e.g. Int(colorDictionary[tempArray[0]]), it will just return the appearing time of the most common color on the image. For getting the UIColor of the pixel, I used:
var theMostUIColor: UIColor = tempArray[0].theUIColorOfThisPixel
I thought after storing my dictionary to the Array, it will just store the values. But now I found it actually stores the keys as well. Thanks for all the people who replied on this post. I appreciate!
You can return an array of tuples. Where each tuple has two values; the Pixel and the integer.
See: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html#//apple_ref/doc/uid/TP40014097-CH10-ID164

Typecast UnsafeMutablePointer<Void> to UnsafeMutablePointer<#Struct type#>

I created a struct in Swift called RGB, simple enough:
struct PixelRGB {
var r: CUnsignedChar = 0
var g: CUnsignedChar = 0
var b: CUnsignedChar = 0
init(red: CUnsignedChar, green: CUnsignedChar, blue: CUnsignedChar) {
r = red
g = green
b = blue
}
}
And I have a pointer var imageData: UnsafeMutablePointer<PixelRGB>!.
I wish to malloc some space for this pointer, but malloc returns UnsafeMutablePointer<Void> and I cannot cast it like below:
imageData = malloc(UInt(dataLength)) as UnsafeMutablePointer<PixelRGB> // 'Void' is not identical to `PixelRGB`
Anyway to solve this? Thank you for your help!
I think what you want to say is something like this:
imageData = UnsafeMutablePointer<PixelRGB>.alloc(dataLength)
Have you tried the following?
imageData = unsafeBitCast(malloc(UInt(dataLength)), UnsafeMutablePointer<PixelRGB>.self)
Ref: Using Legacy C APIs with Swift

Int in swift closure not incrementing

I have a closure in the code below that executes over an array of custom SKShapeNodes (blockNode). The problem I'm having is that completedActionsCount is always 1, no matter how many how many times the closure is executed. Is completedActionsCount just copied at the start of the closure?? Is there something I'm missing here?
for action in blockActionSets[index]
{
var blockActionSet = blockActionSets[index]
var completedActionsCount = 0
let blockNode = getBlockNodeForBlock(action.block)
if blockNode
{
blockNode!.updateWithAction(action, completion:
{ blockAction in
completedActionsCount++
println(completedActionsCount)
println(blockActionSet.count)
if (completedActionsCount == blockActionSet.count)
{
index = index + 1
self.executeBlockActionSetAtIndexRecursive(index, blockActionSets: blockActionSets, completion: completion)
}
})
}
else
{
completedActionsCount++
}
}
The following code is how the block is executed:
func updateWithAction(blockAction : BlockAction, completion : (BlockAction) -> Void)
{
if blockAction.blockActionType == BlockActionType.ChangeColor
{
var startColor = CIColor(CGColor: self.fillColor.CGColor)
var endColor = CIColor(CGColor: UI.getUIColorForBlockType(blockAction.endBlockType).CGColor)
var startRed = startColor.red()
var startGreen = startColor.green()
var startBlue = startColor.blue()
var endRed = endColor.red()
var endGreen = endColor.green()
var endBlue = endColor.blue()
var action = SKAction.customActionWithDuration(CHANGE_COLOR_ANIMATION_DURATION, actionBlock: {(node : SKNode!, elapsedTime : CGFloat)->Void in
var blockNode = node as? BlockNode
if blockNode
{
var ratio : CGFloat = min(1.0, elapsedTime / CGFloat(self.CHANGE_COLOR_ANIMATION_DURATION))
blockNode!.fillColor = UIColor(red: startRed + ratio * (endRed - startRed),
green: startGreen + ratio * (endGreen - startGreen),
blue: startBlue + ratio * (endBlue - startBlue),
alpha: 1.0)
}
if elapsedTime >= CGFloat(self.CHANGE_COLOR_ANIMATION_DURATION)
{
completion(blockAction)
}
})
self.runAction(action)
}
}
I believe the issue is that you are defining the completedActionsCount variable inside the loop, so each time through the loop a new Int value is defined and the value is reset to 0
does this work for you?
var completedActionsCount = 0
for action in blockActionSets[index] {
var blockActionSet = blockActionSets[index]
…
}
re-reading the question… I'm not sure if you intended for each blockActionSet to have it's own counter. But, to answer one of your sub-questions, I'm pretty sure that even though Int is a value type, it is not being copied into the closure, it's available via the outer scope, and you should be able to modify it.
Is updateWithAction:completion: run in the current thread? if it's asynchronous, that may be a factor.
response to update: Generally any API that has "withDuration" in the name will be async to avoid blocking the main thread.
I'm not sure exactly what is happening, but if the counter is being copied because it's a value type I don't know if switching to the main thread will actually fix your issue, but it's worth a try:
blockNode!.updateWithAction(action, completion:
{ blockAction in
dispatch_async(dispatch_get_main_queue()) {
completedActionsCount++
…
}
}
})
I have somewhat low hopes for this approach though since I suspect the value is being copied and copying it again is certainly not going to give you back the original reference. You may be able to wrap the reference to the counter in a reference type (like a variable array or dictionary):
var completedActionsCounts : [Int:Int] = [:]
for action in blockActionSets[index] {
var blockActionSet = blockActionSets[index]
completedActionsCounts[index] = 0
…
blockNode!.updateWithAction(action, completion:
{ blockAction in
dispatch_async(dispatch_get_main_queue()) {
completedActionsCounts[index] += 1
…
}
}
})
}