Pull to refresh

Swift Generics: cтили для UIView и не только #1

Reading time4 min
Views10K

Часть #2


Вступление


Идея для публикации возникла после прочтения перевода CSS для Swift: использование стилей для любых подклассов UIView. Подход достаточно интересный, но он оказался не очень гибким, т.к. не позволяет объединять стили разных типов. Подробнее можно прочитать в комментарии.


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


Декорации


Введем понятие декорации, которое будет олицетворять придание неких свойств объекту:


typealias Decoration<T> = (T) -> Void

Декорация

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


Пример использования декорации для придания свойств объекту
let decoration: Decoration<UIView> = { (view: UIView) -> Void in
    view.backgroundColor = UIColor.orange
    view.alpha = 0.5
    view.isOpaque = true
}
let view = UIView()     // класс
decoration(view)
let label = UILabel()   // подкласс
decoration(label)

Преимущества применения декораций над обычным приданием свойств объекту:


  • Можно одновременно придавать сразу несколько свойств объекту
  • Свойство описывается один раз и не требует изменений во всех местах применения декорации при рефакторинге (DRY)
  • Меньше кода и больше наглядности в местах применения декораций
  • Объединение декораций путем создания декорации, содержащей несколько других декораций
  • Стильно, модно, молодежно

Декоратор и методы экзмепляра


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


Методы экземпляра

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


Для решения данной задачи можно использовать промежуточное звено — декоратор. Декоратор является обобщенной структурой, которая имеет указатель на экземпляр класса, к которому будут применяться декорации.


struct Decorator<T> {
    let object: T
}

С помощью обобщенного протокола для декорируемого экземпляра можно получить декоратор. Для целей публикации декоратор можно будет получить для экземпляра любого класса, наследуемого от UILabel.


protocol DecoratorCompatible {
    associatedtype DecoratorCompatibleType
    var decorator: Decorator<DecoratorCompatibleType> { get }
}

extension DecoratorCompatible {
    var decorator: Decorator<Self> {
        return Decorator(object: self)
    }
}

extension UILabel: DecoratorCompatible {}

Простые и обобщенные протоколы

Простой протокол строго задаёт все типы — параметры своих требований. Протокол сам определяет тип, подходящий для объявления параметра функции или переменной.


Обобщённый протокол — содержащий в своём определении подстановочное имя типа. Точный тип вычисляется только во время задания соответствия протоколу. Обобщённый протокол определяет некоторую концепцию, задавая ряд подстановочных имён для независимых типов и связывая их воедино с функциями и переменными — требованиями протокола.


Дополним структуру декоратора методом экземпляра, который будет принимать декорации. Стоит обратить внимание, что декорации будут применяться в той последовательности, в которой будут переданы декоратору. Это касается случаев, когда несколько декораций меняют одно и то же свойство объекта.


struct Decorator<T> {
    let object: T
    func apply(_ decorations: Decoration<T>...) -> Void {
        decorations.forEach({ $0(object) })
    }
}

Пример


Для целей публикации был создан репозиторий на github, который содержит пример использования. Также доступна установка через cocoapods: pod 'Decorator'.


Во-первых, следует создать набор нужных декораций любым удобным способом. Например, вот так:


struct Style {
    static var fontNormal: Decoration<UILabel> {
        return { (view: UILabel) -> Void in
            view.font = UIFont.systemFont(ofSize: 14.0)
        }
    }
    static var fontTitle: Decoration<UILabel> {
        return { (view: UILabel) -> Void in
            if #available(iOS 8.2, *) {
                view.font = UIFont.systemFont(ofSize: 17.0, weight: UIFontWeightBold)
            } else {
                view.font = UIFont.boldSystemFont(ofSize: 17.0)
            }
        }
    }
    static func corners(rounded: Bool) -> Decoration<UIView> {
        return { [rounded] (view: UIView) -> Void in
            switch rounded {
            case true:
                let mask = CAShapeLayer()
                let size = CGSize(width: 10, height: 10)
                let rect = view.bounds
                let path = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: size)
                mask.path = path.cgPath
                view.layer.mask = mask
            default:
                view.layer.mask = nil
            }
        }
    }
}

Стоит обратить внимание на тот факт, что декорации представлены двумя видами:


Decoration<UIView>
Decoration<UILabel>

Оба вида можно применять одновременно несмотря на то, что применяться они будут к объекту класса UILabel. Применение декораций через декоратора происходит следующим образом:


let labelNormal = UILabel()
labelNormal.decorator.apply(Style.fontNormal, Style.corners(rounded: false))
let labelTitle = UILabel()
labelNormal.decorator.apply(Style.fontTitle, Style.corners(rounded: true))

Заключение


Подход получился более гибким, чем в переводе статьи, т.к. удалось добиться применения разных стилей одновременно. Если есть идеи по улучшения подхода — комментарии приветствуются. Спасибо за внимание.


Часть #2

Tags:
Hubs:
+4
Comments12

Articles