Автосброс оттенка и насыщенности цвета

В процессе разработки компонента выбора цвета пришлось столкнуться с парой неприятных моментов, касающихся работы с HSB-составляющими на нулевых значениях. Первым открытием был сброс оттенка при нулевой насыщенности, а вторым — обнуление и оттенка, и насыщенности при нулевой яркости.

import UIKit

var color: UIColor

var hue = CGFloat()
var saturation = CGFloat()
var brightness = CGFloat()

color = UIColor(hue: 1, saturation: 1, brightness: 1, alpha: 1)
color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
// Output: 1.0 1.0 1.0

// Saturation = 0 resets hue to 0
color = UIColor(hue: 1, saturation: 0, brightness: 1, alpha: 1)
color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
// Output: 0.0 0.0 1.0

// Brightness = 0 resets hue and saturation to 0
color = UIColor(hue: 1, saturation: 1, brightness: 0, alpha: 1)
color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
// Output: 0.0 0.0 0.0

NSLayoutAnchor — альтернатива NSLayoutConstraint

До выхода iOS 9 программное создание ограничений происходило через инициализацию объектов NSLayoutConstraint, включавшую длинный список передаваемых аргументов или код на Visual Format Language, однако с появлением нового класса NSLayoutAnchor процесс можно заметно упростить.

По сути, теперь ограничиваемые атрибуты добавлены в UIView как свойства с типом одного из подклассов NSLayoutAnchor: NSLayoutXAxisAnchor (для горизонтальных ограничений), NSLayoutYAxisAnchor (вертикальных) или NSLayoutDimension (высоты и ширины). И установка ограничений с нужными взаимосвязями доступна через их методы.

if #available(iOS 9.0, *) {
    view.widthAnchor.constraintEqualToAnchor(view.heightAnchor, multiplier: 0.5).active = true
    view.centerXAnchor.constraintEqualToAnchor(self.centerXAnchor, constant: 10).active = true
    view.centerYAnchor.constraintEqualToAnchor(self.centerYAnchor, constant: 10).active = true
    view.widthAnchor.constraintLessThanOrEqualToAnchor(self.widthAnchor).active = true
    view.heightAnchor.constraintLessThanOrEqualToAnchor(self.heightAnchor).active = true
} else {
    addConstraints([
        NSLayoutConstraint(item: view,
            attribute: .Width,
            relatedBy: .Equal,
            toItem: view,
            attribute: .Height,
            multiplier: 0.5,
            constant: 0),

        NSLayoutConstraint(item: view,
            attribute: .CenterX,
            relatedBy: .Equal,
            toItem: self,
            attribute: .CenterX,
            multiplier: 1,
            constant: 10),

        NSLayoutConstraint(item: view,
            attribute: .CenterY,
            relatedBy: .Equal,
            toItem: self,
            attribute: .CenterY,
            multiplier: 1,
            constant: 10),

        NSLayoutConstraint(item: view,
           attribute: .Width,
           relatedBy: .LessThanOrEqual,
           toItem: self,
           attribute: .Width,
           multiplier: 1,
           constant: 0),

        NSLayoutConstraint(item: view,
            attribute: .Height,
            relatedBy: .LessThanOrEqual,
            toItem: self,
            attribute: .Height,
            multiplier: 1,
            constant: 0)
    ])
}

Обновление if и for-in в Swift 2

Во второй версия языка операторам if и for-in добавили способности, позволяющие писать еще более компактный и читаемый код.

Теперь в условном операторе можно сравнивать с диапазоном значений:

import UIKit

let number = arc4random_uniform(50)

if case 0...25 = number {
    print("Number is between 0 and 25")
} else {
    print("Number is over 25")
}

И фильтровать прямо в конструкции for-in:

let numbers = [0, 1, 2, 3, -7, 12, 13, -8, -11]

for number in numbers where number >= 0 {
    print("Positive number: \(number)")
}

Разметка и документирование кода

В Xcode есть возможность добавлять в код специальные маркеры:

Первым чаще всего обозначают группы элементов, а двумя остальными — требующие доработки места.

import Foundation

class Cat {
    // MARK: - Properties
    // MARK: required
    let breed: String!
    var age: UInt!
    
    // MARK: optional
    let sex: String?
    
    // MARK: - Initialization
    init() {
        breed = "siamese"
        age = 1
        
        // FIXME: Sex setting
        sex = nil
    }
    
    // MARK: - Main
    func walk() {
        print("Walk")
    }
    
    // TODO: Write a logic
    func meow() {
        print("Meow...")
    }
}

В панели иерархии это выглядит так: Xcode markup sample

Кроме разметки, редактор также позволяет документировать код Markup-языком. Возможности его достаточно обширны и включают наряду с базовым описанием объектов, параметров и возвращаемых значений, различные заголовки, списки, ссылки, вставки кода, изображений и многое другое.

Для наглядности, пример с использованием основных команд:

import Foundation

/**
 The `Utility` class provides various useful functions and values.
*/
class Utility {
    /**
     Fixes the string representation of number.
     
     ```
     1.0 -> 1 (not 1.0)
     0.00003 -> 0.00003 (not 3e-05)
     ```
     
     - parameters:
        - doubleValue: The number to be represented as the string.
     
     - returns: The string representation of number.
    */
    class func doubleToString(doubleValue: Double) -> String {
        var stringValue: String
        
        if trunc(doubleValue) == doubleValue {
            stringValue = "\(Int(doubleValue))"
        } else if abs(doubleValue) < 0.0001 {
            stringValue = "\(doubleValue + (doubleValue.isSignMinus ? -1 : 1))"
            stringValue = stringValue.stringByReplacingOccurrencesOfString("1.", withString: "0.")
        } else {
            stringValue = "\(doubleValue)"
        }
        
        return stringValue
    }
}

Теперь эта информация будет отображаться во всплывающей подсказке и панели помощи: Xcode documentation sample

Особенности наблюдателей за свойствами

Иногда бывает необходимо выполнить дополнительные операции при изменении значений свойств. Чтобы отловить события, используются наблюдатели willSet/didSet, в работе с которыми есть нюансы.

Рассмотрим пример:

import UIKit

class Object {
    var color = UIColor.greenColor() {
        didSet {
            color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
        }
    }
    
    private(set) var red = CGFloat()
    private(set) var green = CGFloat()
    private(set) var blue = CGFloat()
    private(set) var alpha = CGFloat()
    
    var point = CGPoint() {
        didSet {
            print("Point changed to \(point)")
        }
    }
    
    var name = NSMutableString() {
        didSet {
            print("Name changed to \(name)")
        }
    }

    init() {
        color = UIColor.redColor()
    }
}

Первое — наблюдатели не вызываются при задании значений по умолчанию, а также в инициализаторе:

let object = Object()
print("Red: \(object.red), green: \(object.green), blue: \(object.blue), alpha \(object.alpha)")

// Output: Red: 0.0, green: 0.0, blue: 0.0, alpha 0.0

И второе — изменение (мутация) свойств значений не ссылочных типов (структур и перечислений) реализуется через перезапись экземпляра на новый, содержащий обновления, поэтому мутация провоцирует вызов наблюдателей. Для ссылочных типов (объектных классов) такого не происходит:

object.point = CGPoint(x: 10, y: 10)
object.point.x = 20

// Output: Point changed to (10.0, 10.0)
//         Point changed to (20.0, 10.0)

object.name = NSMutableString(string: "'The new name'")
object.name.setString("Changed name")

// Output: Name changed to 'The new name'