📜 ⬆️ ⬇️

Swift Generics: Styles for UIView and more. # 1

Part 2


Introduction


The idea for publishing came after reading the CSS translation for Swift: using styles for any subclass of UIView . The approach is quite interesting, but it was not very flexible, because does not allow combining styles of different types. Read more in the comments .


In this publication, an attempt will be made to obtain a more flexible way of defining styles, and examples will be given of using the resulting mechanism.


Scenery


We introduce the concept of scenery, which will personify imparting certain properties to an object:


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

Decoration

Decoration is a generalized closure that can be applied to an object of the corresponding class or to an object whose class is a subclass of the class used to create the decoration.


An example of using decorations to give properties to an object.
 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) 

The advantages of using decorations over the usual imparting properties to an object:



Decorator and exmepleration methods


To apply the decoration, you must pass the instance to the decoration closure. However, a more natural process is passing the scenery to the instance method.


Instance methods

Instance methods are functions that belong to instances of a particular class, structure, or enumeration. They provide the functionality of these instances, either by allowing the access and modification of the properties of the instance, or by providing the functionality of the instance in accordance with its purpose. An instance method can only be invoked for a specific instance of the type to which it belongs. It cannot be called in isolation, without an existing instance.


To solve this problem, you can use an intermediate - decorator. A decorator is a generic structure that has a pointer to an instance of a class to which decorations will be applied.


 struct Decorator<T> { let object: T } 

Using the generalized protocol for the decorated instance you can get a decorator. For publication purposes, the decorator will be available for an instance of any class inherited from UILabel.


 protocol DecoratorCompatible { associatedtype DecoratorCompatibleType var decorator: Decorator<DecoratorCompatibleType> { get } } extension DecoratorCompatible { var decorator: Decorator<Self> { return Decorator(object: self) } } extension UILabel: DecoratorCompatible {} 

Simple and generic protocols

A simple protocol strictly specifies all types - the parameters of its requirements. The protocol itself determines the type suitable for declaring a parameter of a function or variable.


Generic protocol - containing in its definition a wildcard type name. The exact type is calculated only during the task of compliance with the protocol. The generic protocol defines a concept by specifying a series of wildcard names for independent types and associating them with functions and variables — the requirements of the protocol.


Let us complement the structure of the decorator with the instance method that will receive the decorations. It is worth paying attention that the scenery will be applied in the sequence in which they will be transferred to the decorator. This applies to cases where several decorations change the same property of an object.


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

Example


For publishing purposes, a repository has been created on github , which contains an example of usage. Installation via cocoapods : pod 'Decorator' is also available.


First, you should create a set of necessary decorations in any convenient way. For example, like this:


 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 } } } } 

It is worth paying attention to the fact that the scenery is represented by two types:


 Decoration<UIView> Decoration<UILabel> 

Both types can be applied simultaneously, although they will be applied to an object of the UILabel class. The use of decorations through the decorator is as follows:


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

Conclusion


The approach turned out to be more flexible than in the translation of the article , since managed to achieve the use of different styles at the same time. If you have ideas for improving the approach - comments are welcome. Thanks for attention.


Part 2


')

Source: https://habr.com/ru/post/327662/


All Articles