Quantcast
Channel: FlexMonkey
Viewing all articles
Browse latest Browse all 257

Composite Components in Swift: Dial Based RGB / CMYK Picker

$
0
0

Continuing on from my last blog post, Creating a Numeric Dial Control in Swift for iOS, this post looks at creating a composite component using more that one of my dial controls. The example I've created is a colour picker which allows the user to use dials to set either the red, green and blue or cyan, magenta, yellow and black components.

My RGBpicker class is an extended UIControl which contains either three or four dials, depending on whether cmykMode is false or true. Much like the dial control itself, its components are added in the init() method. Because the dial for black is only used in CMYK mode, it's an optional while the other three dials aren't.

The colour dials are responsible for different colours based on the mode and are named accordingly. For example, the dial that changes either the red or cyan component is named redCyanDial.

The RGBpicker has a currentColor property and inside its didSet observer is the code to update the individual dials based on the new colour. That code is basically split into two with one set of calculations for CMYK and one set for RBG:

    var currentColor : UIColor = UIColor.blackColor()
    {
        didSet
        {
            ifupdateDialsOnColorChange
            {
                let colorRef = CGColorGetComponents(currentColor.CGColor);
                
                removeDispatchers()
          
                ifcmykMode
                {
                    let k = 1 - max(max(Double(colorRef[0]), Double(colorRef[1])), Double(colorRef[2]))
                    
                    blackDial!.currentValue = k
                    redCyanDial.currentValue = (1 - Double(colorRef[0]) - k) / (1 - k)
                    greenMagentaDial.currentValue = (1 - Double(colorRef[1]) - k) / (1 - k)
                    blueYellowDial.currentValue = (1 - Double(colorRef[2]) - k) / (1 - k)
                }
                else
                {
                    redCyanDial.currentValue = Double(colorRef[0])
                    greenMagentaDial.currentValue = Double(colorRef[1])
                    blueYellowDial.currentValue = Double(colorRef[2])
                }
        
                addDispatchers()
            }
            
            swatch.backgroundColor = currentColor
            
            sendActionsForControlEvents(.ValueChanged)
        }

    }

The addDispatchers() and removeDispatchers() functions prevent the dials from dispatching change events while I'm updating them. Without these, the code enters an infinite loop.

All four dials invoke the same action when they're change by the user. Again, this method is split into two depending on the mode and updates the control's current colour based on the dial values:

    func numericDialValueChanged(numericDial : NumericDial)
    {
        updateDialsOnColorChange = false
        
        ifcmykMode
        {
            let red = (1-CGFloat(redCyanDial.currentValue)) * (1-CGFloat(blackDial!.currentValue))
            let green = (1-CGFloat(greenMagentaDial.currentValue)) * (1-CGFloat(blackDial!.currentValue))
            let blue = (1-CGFloat(blueYellowDial.currentValue)) * (1-CGFloat(blackDial!.currentValue))
            
            currentColor = UIColor(red: red, green: green, blue: blue, alpha: 1)
        }
        else
        {
            let red = CGFloat(redCyanDial.currentValue)
            let green = CGFloat(greenMagentaDial.currentValue)
            let blue = CGFloat(blueYellowDial.currentValue)
            
            currentColor = UIColor(red: red, green: green, blue: blue, alpha: 1)
        }
        
        updateDialsOnColorChange = true

    }

You'll notice that each dial has a unique label. I've updated the NumericDial class with a labelFunction property of type (Double) -> (String), by default it is:

    classfunc defaultLabelFunction(value : Double) -> String
    {
        returnNSString(format: "%.4f", value)

    }

But I'm labelling RGB colours with their hex value and CMYK colours as a percentage. I could hand craft a separate label function for each of the seven dials, but creating a function that creates these based on the CMYK mode is a far better solution. So, inside the RGBpicker's overridden didMoveToWindow() I set the label functions with this code:

        let redCyanLabel = cmykMode ? "Cyan" : "Red"
        let greenMagentaLabel = cmykMode ? "Magenta" : "Green"
        let blueYellowLabel = cmykMode ? "Yellow" : "Blue"
        
        redCyanDial.labelFunction = createLabelFunction("\(redCyanLabel): ")
        greenMagentaDial.labelFunction = createLabelFunction("\(greenMagentaLabel): ")
        blueYellowDial.labelFunction = createLabelFunction("\(blueYellowLabel): ")
        
        ifcmykMode
        {
            blackDial?.labelFunction = createLabelFunction("Black: ")

        }

...with my funky createLabelFunction() dynamically creating and returning a function for each of the seven cases:

    func createLabelFunction(label : String) -> ((Double) -> String)
    {
        func rgbLabelFunction(value : Double) -> String
        {
            if (cmykMode)
            {
                return label + NSString(format: "%d", Int(value * 100)) + "%"
            }
            else
            {
                return label + NSString(format: "%2X", Int(value * 255))
            }
        }
        
        returnrgbLabelFunction

    }

Back up in the ViewController, two instances of RGBpicker are created and added to the display - one with its cmykMode set to true. Both of these update the view controller's currentColor property and using a didSet observer update the currentColor of a big colour swatch and both colour pickers. 

The end result is as one colour picker is changed by the user, the swatch and the other picker are both updated. 

All the source code is available in my GitHub repository.

Viewing all articles
Browse latest Browse all 257

Trending Articles