Swift Generics:#2だけでなくUIViewのスタイル

この出版物は、オブジェクトの装飾のトピックに触れた問題の続きです。 最初の出版物に精通すると、現在の状況をよりよく理解するのに役立ちます。 前述の用語とソリューションについては、簡略化して説明します。


このアプローチは非常に成功し、実際のプロジェクトで繰り返しテストされてきました。 さらに、アプローチへの追加が登場し、使いやすさが大幅に向上しました。


提示されたスタイル設定メソッドの主な要素は一般化されたクロージャーであることを思い出させてください。


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

このクロージャーを使用して、次のようにUIViewプロパティを指定できます。


 let decoration: Decoration<UIView> = { (view: UIView) -> Void in view.backgroundColor = .white } let view = UIView() decoration(view) 

景観構成


加算演算子を使用し、シーナリーアプリケーションの順序を観察すると、シーナリー構成メカニズムを取得できます。


 func +<T>(lhs: @escaping Decoration<T>, rhs: @escaping Decoration<T>) -> Decoration<T> { return { (value: T) -> Void in lhs(value) rhs(value) } } 

同じクラスのオブジェクトを受け入れるクロージャーだけでなく、追加することができます。 ただし、クロージャーの1つに渡されるオブジェクトのクラスは、他のクロージャーに渡されるオブジェクトのサブクラスでなければならないことに注意してください。


 Decoration<UISwitch> + Decoration<UISwitch> = Decoration<UISwitch> Decoration<UISwitch> + Decoration<UIView> = Decoration<UISwitch> Decoration<UISwitch> + Decoration<UILabel> =  

風景を作成する


シーナリーを作成する際の主な不便は、シーナリーデザイン自体のコードを書くことでした。 クロージャー内に装飾、クロージャー、クラスタイプのタイプを記述する必要がありました...ほとんどの場合、CTRL + C、CTRL + Vで終了しました。


状況から抜け出し、オートコンプリートを通じて回路を生成するために、オブジェクトのタイプをとるユニバーサル関数が作成されました。


 func decor<T>(_ type: T.Type, closure: @escaping Decoration<T>) -> Decoration<T> { return closure } 

次のように使用されました。


 let decoration = decor(UIView.self) { (view) in view.backgroundColor = .white } 

しかし、 selfはオートコンプリートされず、関数をdecorationと呼ぶことができません。 ほとんどの場合、名前のdecorationを持つクロージャーを作成し、エラーが発生しました。


エラー:独自の初期値内で使用されている変数
let decoration = decoration(UIView.self){(view)in

より成功したソリューションは、ユニバーサルstatic関数を作成することでした:


 protocol Decorable: class {} extension NSObject: Decorable {} extension Decorable { static func decoration(closure: @escaping Decoration<Self>) -> Decoration<Self> { return closure } } 

その結果、次のように装飾クロージャーを作成できます。


 let decoration = UIView.decoration { (view) in view.backgroundColor = .white } 

状態


 class MyView: UIView { var isDisabled: Bool = false var isFavorite: Bool = false var isSelected: Bool = false } 

ほとんどの場合、このような変数の組み合わせは、特定のUIViewスタイルを変更するためにのみ使用されます。


単一の変数のUIViewスタイルの状態を記述しようとする場合、列挙を使用できます。 ただし、組み合わせを可能にするOptionSetはさらに優れています。


 struct MyViewState: OptionSet, Hashable { let rawValue: Int init(rawValue: Int) { self.rawValue = rawValue } static let normal = MyViewState(rawValue: 1 << 0) static let disabled = MyViewState(rawValue: 1 << 1) static let favorite = MyViewState(rawValue: 1 << 2) static let selected = MyViewState(rawValue: 1 << 3) var hashValue: Int { return rawValue } } 

次のように使用できます。


 class MyView: UIView { var state: MyViewState = .normal } let view = MyView() view.state = [.disabled, .favorite] view.state = .selected 

最後の公開では、シーナリーが適用されるクラスのインスタンスへのポインターを持つ一般化された構造が導入されました。


 struct Decorator<T> { let object: T } 

一般化されたDecorator構造では、スタイルの状態を担当する追加の変数を導入します。


 extension Decorator where T: Decorable { var state: AnyHashable? { get { // } set { // } } } 

オブジェクトの関連付けのランタイム機能を使用すると、一般化された構造を介してオブジェクトの状態を保持することが可能になりました。 デコレーションオブジェクトに関連付けられ、必要な変数を含むクラスを導入します。


 class Holder<T:Decorable> { var state = Optional<AnyHashable>.none } var KEY: UInt8 = 0 extension Decorable { var holder: Holder<Self> { get { if let holder = objc_getAssociatedObject(self, &KEY) as? Holder<Self> { return holder } else { let holder = Holder<Self>() let policy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC objc_setAssociatedObject(self, &KEY, holder, policy) return holder } } } } 

現在、一般化されたDecorator構造は、 Holderオブジェクトに関連付けられたクラスを通じて状態を保存できます。


 extension Decorator where T: Decorable { var state: AnyHashable? { get { return object.holder.state } set(value) { object.holder.state = value } } } 

デコレーション収納


スタイルの状態を保存できる場合、同じように異なる状態の装飾を保存できます。 これは、装飾オブジェクトに関連付けられているHolderクラスのインスタンスに装飾辞書[AnyHashable: Decoration<T>]を作成することで実現されます。


 class Holder<T:Decorable> { var state = Optional<AnyHashable>.none var states = [AnyHashable: Decoration<T>]() } 

辞書に装飾を追加するには、次の関数を導入します。


 extension Decorator where T: Decorable { func prepare(state: AnyHashable, decoration: @escaping Decoration<T>) { object.holder.states[state] = decoration } } 

次のように使用できます。


 let view = MyView() view.decorator.prepare(state: MyViewState.disabled) { (view) in view.backgroundColor = .gray } view.decorator.prepare(state: MyViewState.favorite) { (view) in view.backgroundColor = .yellow } 

風景の利用


風景ディクショナリを埋めた後、スタイルの状態が変わったら、ディクショナリから適切な装飾を適用する必要があります。 これは、スタイルステートセッターの実装を変更することで実現できます。


 extension Decorator where T: Decorable { var state: AnyHashable? { get { return object.holder.state } set(value) { let holder = object.holder if let key = value, let decoration = holder.states[key] { object.decorator.apply(decoration) } holder.state = value } } } 

装飾は次のように適用されます。


 let view = MyView() //   view.decorator.state = .selected 

また、対応する装飾が風景辞書に入る前にオブジェクトがスタイルの状態に設定された場合にも言及する価値があります。 このような状況では、州のシーナリーを準備する機能を最終決定する価値があります。


 extension Decorator where T: Decorable { func prepare(state: AnyHashable, decoration: @escaping Decoration<T>) { let holder = object.holder holder.states[state] = decoration if state == holder.state { object.decorator.apply(decoration) } } } 

アニメーション?


適用された装飾の中にアニメーション化できるものが含まれている場合...


正の場合、レイヤーの背景は次のように描画されます
丸い角。 によって生成されたマスクにも影響します
'masksToBounds'プロパティ。 デフォルトはゼロです。 アニメート可能

オープンvar cornerRadius:CGFloat

...次に、アニメーションブロック内のオブジェクトのスタイルを変更すると、対応するアニメーションが生成されます。


 UIView.animate(withDuration: 0.5) { view.decorator.state = .selected } 

おわりに


組成物を作成、保存、使用、再利用、装飾するための便利なツールが得られました。 完全なツールコードはこちらにあります 。 いつものように、 CocoaPodsからインストールして試すことができます:


ポッド「スタイル」


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


All Articles