変化するとUIView.setNeedsLayoutを呼び出すPropertyWrapper

時々必要になる、「特定のプロパティが変わったら再レイアウトが必要なことをUIKitに伝える」という仕組み

一般的な方法として、次のような実装が挙げられます。

var myProperty: Int = 0 {
  didSet { 
    setNeedsLayout()
  }
}

また、レイアウトに限らず、setNeedsDisplayを呼ぶ必要があるケースも多いと思います。

これをPropertyWrapperを使って短くかけないか、というのが今回のテーマ

作ってみたのが次のコード

class MyView: UIView {
  
  @ViewState var count: Int = 0
  
  override func setNeedsLayout() {
    print("SetNeedsLayout")
    super.setNeedsLayout()
  }
  
}

@ViewStateで定義したプロパティは変更されるとsetNeedsLayoutが呼び出されます。

実現方法として、メインはPropertyWrapperの利用ですが、その中でも

Referencing the enclosing 'self' in a wrapper type という機能を利用したものになります。

@propertyWrapper
public struct ViewState<T> {
  
  public static subscript<EnclosingSelf: UIView, TargetValue>(
    _enclosingInstance instance: EnclosingSelf,
    wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, TargetValue>,
    storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
  ) -> T {
    get {
      
      let value = instance[keyPath: storageKeyPath].wrappedValue
      return value
    }
    set {
      instance[keyPath: storageKeyPath].wrappedValue = newValue
      instance.setNeedsLayout()
    }
  }
  
  public var wrappedValue: T
  
  public var projectedValue: ViewState<T> {
    self
  }
  
  public init(wrappedValue: T) {
    self.wrappedValue = wrappedValue
  }
  
}