Created
June 28, 2021 04:29
-
-
Save bok-/1d44b5757f893eb6db57195e72b38e0a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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