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

от автора

Вступление.

Идея для публикации возникла после прочтения перевода 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *