Skip to content

Instantly share code, notes, and snippets.

@bok-
Created June 28, 2021 04:29
Show Gist options
  • Save bok-/1d44b5757f893eb6db57195e72b38e0a to your computer and use it in GitHub Desktop.
Save bok-/1d44b5757f893eb6db57195e72b38e0a to your computer and use it in GitHub Desktop.
// An exercise in implementing `@Invalidating` for UIView before Xcode 13.0 beta 2
// was released, rendering it unnecessary
import UIKit
public extension UIView {
@propertyWrapper
class Invalidating<Value> where Value: Equatable {
// MARK: - Properties
/// The original wrapper value. This is not used. Do not call it directly.
@available(*, unavailable, message: "Do not access this property directly")
public var wrappedValue: Value {
get { fatalError("@Invalidating can only be used in a UIView subclass.") }
set { fatalError("@Invalidating can only be used in a UIView subclass.") }
}
/// The actual wrapped value
private var value: Value
@Atomic private var invalidations: [ViewInvalidation] = []
// MARK: - Initialisation
public init(wrappedValue: Value, _ invalidations: ViewInvalidation...) {
self.value = wrappedValue
self.invalidations = invalidations
}
// MARK: - Property Wrapper
/// This is the actual method called from `EnclosingSelf` when accessing the property wrapper.
///
/// This is in the the Swift Evolution proposal:
/// https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type
///
/// And was implemented to support `@Published` in Combine. You can see the Property Wrapper source for it here:
/// https://github.com/apple/swift/blob/master/lib/Sema/TypeCheckPropertyWrapper.cpp
///
/// So while it is not a publicly supported implementation, its pretty safe to use for two reasons:
///
/// 1. ABI stability means any changes before it becomes public will be not break backwards compatibility
/// 2. Any changes will probably be minimal and just require a small patch in Xcode when upgrading Swift versions
///
/// As such, it is felt to be pretty safe to use right now
///
public static subscript<EnclosingSelf> (
_enclosingInstance observed: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Invalidating>
) -> Value where EnclosingSelf: UIView {
get {
observed[keyPath: storageKeyPath].value
}
set {
let wrapper = observed[keyPath: storageKeyPath]
guard wrapper.value != newValue else {
return
}
wrapper.value = newValue
for invalidation in wrapper.invalidations {
invalidation.invalidate(view: observed)
}
}
}
}
}
// MARK: - View Invalidation
public enum ViewInvalidation {
case display
case displayRect(CGRect)
case layout
case constraints
case intrinsicContentSize
case focusUpdate
func invalidate(view: UIView) {
switch self {
case .display: view.setNeedsDisplay()
case .displayRect(let rect): view.setNeedsDisplay(rect)
case .layout: view.setNeedsLayout()
case .constraints: view.setNeedsUpdateConstraints()
case .intrinsicContentSize: view.invalidateIntrinsicContentSize()
case .focusUpdate: view.setNeedsFocusUpdate()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment