Статьи

Составные компоненты в Swift: набор RGB / CMYK на основе набора

Продолжая рассказ о моем последнем посте в блоге «  Создание элемента управления цифровым набором в Swift для iOS» , мы рассмотрим создание составного компонента с использованием более одного из моих элементов управления набором. Пример, который я создал, представляет собой палитру цветов, которая позволяет пользователю использовать циферблаты для установки красного, зеленого и синего или голубого, пурпурного, желтого и черного компонентов.

Мой   класс RGBpicker — это расширенный  UIControl,  который содержит три или четыре набора, в зависимости от того, имеет ли значение cmykMode значение  false или true. Подобно самому элементу управления dial, его компоненты добавляются в метод  init ()  . Поскольку черный циферблат используется только в режиме CMYK, он является дополнительным, а остальные три циферблата — нет.

Цветные шкалы отвечают за разные цвета в зависимости от режима и имеют соответствующие названия. Например, циферблат, который меняет красный или голубой компонент, называется  redCyanDial .

RGBpicker  имеет  currentColor  свойство и внутри его  didSet  наблюдателя код для обновления отдельных циферблатов на основе нового цвета. Этот код в основном разделен на два с одним набором вычислений для CMYK и одним набором для RBG:

    var currentColor : UIColor = UIColor.blackColor()
    {
        didSet
        {
            if updateDialsOnColorChange
            {
                let colorRef = CGColorGetComponents(currentColor.CGColor);
                
                removeDispatchers()
          
                if cmykMode
                {
                    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)
        }

    }

Функции  addDispatchers ()  и  removeDispatchers () не позволяют набираемым номерам  отправлять события изменения во время их обновления. Без них код входит в бесконечный цикл.

Все четыре набора вызывают одно и то же действие, когда они изменяются пользователем. Опять же, этот метод делится на два в зависимости от режима и обновляет текущий цвет элемента управления на основе значений набора:

    func numericDialValueChanged(numericDial : NumericDial)
    {
        updateDialsOnColorChange = false
        
        if cmykMode
        {
            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

    }

Вы заметите, что каждый циферблат имеет уникальный ярлык. Я обновил   класс NumericDial со   свойством labelFunction типа (Double) -> (String) , по умолчанию это:

    class func defaultLabelFunction(value : Double) -> String
    {
        return NSString(format: "%.4f", value)

    }

Но я маркирую цвета RGB их шестнадцатеричным значением и цветами CMYK в процентах. Я мог бы вручную создать отдельную функцию метки для каждого из семи циферблатов, но создание функции, которая создает их на основе режима CMYK, является гораздо лучшим решением. Таким образом, внутри  RGBpicker перегруженной «сек  didMoveToWindow ()  Я поставил функцию меток с этим кодом:

        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): ")
        
        if cmykMode
        {
            blackDial?.labelFunction = createLabelFunction("Black: ")

        }

… с моей фанкой  createLabelFunction (),  динамически создающей и возвращающей функцию для каждого из семи случаев:

    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))
            }
        }
        
        return rgbLabelFunction

    }

Резервное копирование в  ViewController , два экземпляра  RGBpicker  создаются и добавляются на дисплей — один с его cmykMode  установлен в значение true. Оба они обновляют  свойство currentColor контроллера представления,  а с помощью   наблюдателя didSet обновляют  currentColor  большого образца цвета и обоих палитр цветов. 
Конечный результат заключается в том, что пользователь выбирает один палитр цветов, оба образца и палитры обновляются.

Весь  исходный код доступен в моем репозитории GitHub .