エントリー
パブリッシュのアイデアは、SwiftのCSS翻訳を読んだ後に浮上しました: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リポジトリが作成されています。 ココアポッドを介したインストール:ポッド「デコレーター」も利用できます。
まず、必要な風景のセットを便利な方法で作成する必要があります。 たとえば、次のように:
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 } } } }
風景が次の2つの方法で表示されることに注意する価値があります。
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))
おわりに
このアプローチは、記事の翻訳よりも柔軟性が高いことが判明しました。 さまざまなスタイルのアプリケーションを同時に実現することができました。 アプローチを改善するためのアイデアがあれば、コメントを歓迎します。 ご清聴ありがとうございました。