Вступление.
Идея для публикации возникла после прочтения перевода 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))
Заключение.
Подход получился более гибким, чем в переводе статьи, т.к. удалось добиться применения разных стилей одновременно. Если есть идеи по улучшения подхода — комментарии приветствуются. Спасибо за внимание.
ссылка на оригинал статьи https://habrahabr.ru/post/327662/
Добавить комментарий