-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b87a0c6
commit b63868f
Showing
10 changed files
with
542 additions
and
33 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,108 @@ | ||
# Invalidating | ||
|
||
A description of this package. | ||
A property wrapper that backports the new `@Invalidating` property wrapper to older versions. For more information on this new property wrapper, see the WWDC 2021 talk ["What's new in AppKit"](https://developer.apple.com/wwdc21/10054) for a brief introduction. | ||
|
||
When it's time to finally update your project to iOS 15/tvOS 15/macOS 12, you can remove this package without having to make any changes to your code! ✨ | ||
|
||
## Usage | ||
|
||
Annotate your `Equatable` properties with `@Invalidating` and provide configuration options that will be used to trigger updates on the view whenever the property changes: | ||
|
||
```swift | ||
final class MyView: UIView { | ||
// Calls setNeedsLayout() | ||
@Invalidating(.layout) var cornerRadius: CGFloat = 0.5 | ||
|
||
// Calls setNeedsLayout() then setNeedsUpdateConstraints() | ||
@Invalidating(.layout, .constraints) var heightConstraintValue: CFloat = 200 | ||
|
||
// Calls setNeedsLayout() then setNeedsUpdateConstraints() then invalidateIntrinsicContentSize() | ||
@Invalidating(.layout, .constraints, .intrinsicContentSize) var magicProperty: CGFloat = 1234 | ||
``` | ||
|
||
### Adding custom invalidators | ||
|
||
You can add custom invalidators by creating a type that conforms to `UIViewInvalidating` or `NSViewInvalidating` protocol (depending on the target platform) and implementing the `invalidate` method requirement: | ||
|
||
```swift | ||
extension UIView.Invalidations { | ||
struct State: UIViewInvalidating { | ||
static let state: Self = .init() | ||
|
||
func invalidate(view: UIView) { | ||
// Your custom logic to invalidate some state on the view | ||
} | ||
} | ||
} | ||
``` | ||
|
||
You can then expose it to the property wrapper by extensing the `InvalidatingStaticMember` type: | ||
|
||
```swift | ||
extension InvalidatingStaticMember where Base: InvalidatingViewProtocol { | ||
static var state: InvalidatingStaticMember<UIView.Invalidations.State> { .init(.state) } | ||
} | ||
``` | ||
|
||
Then you can use it on `@Invalidating`: | ||
|
||
```swift | ||
final class MyView: UIView { | ||
|
||
// Calls setNeedsLayout() and State.invalidate(self) | ||
@Invalidating(.layout, .state) var customProperty: CGFloat = 1.0 | ||
} | ||
``` | ||
|
||
## Extending `@Invalidating` to accept more values | ||
|
||
By default, you can provide up to 3 options (say `.layout, .display, .custom`) to the property wrapper, however you can provide custom initializers so the property wrapper can be initialized with a larger set of customization options. This can be done by utilizing the built-in `Tuple` type. For example, here's how you can provide an intializer so `@Invalidating` can take up to 4 values: | ||
|
||
```swift | ||
extension UIView.Invalidating { | ||
convenience init<InvalidationType2: UIViewInvalidating, InvalidationType3: UIViewInvalidating, InvalidationType4: UIViewInvalidating>(wrappedValue: Value, _ invalidation1: InvalidationType.Member, _ invalidation2: InvalidationType2.Member, _ invalidation3: InvalidationType3.Member, _ invalidation4: InvalidationType4.Member) { | ||
self.init(wrappedValue: wrappedValue, invalidation1, .init(Tuple(invalidation1: invalidation2.base, invalidation2: Tuple(invalidation1: invalidation3.base, invalidation2: invalidation4.base)))) | ||
} | ||
} | ||
``` | ||
|
||
## Requirements | ||
|
||
- iOS 13+, tvOS 13+ or macOS 10.15+ | ||
- Swift 5.3 or above | ||
|
||
## Note | ||
|
||
Add the following to your project's `Package.swift` file: | ||
|
||
```swift | ||
.package(url: "https://github.com/theblixguy/Invalidating", from: "0.0.1") | ||
``` | ||
|
||
or add this package via the Xcode UI by going to File > Swift Packages > Add Package Dependency. | ||
|
||
## License | ||
|
||
``` | ||
MIT License | ||
|
||
Copyright (c) 2021 Suyash Srijan | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. | ||
``` |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// | ||
// AnyInvalidation.swift | ||
// | ||
// | ||
// Created by Suyash Srijan on 28/06/2021. | ||
// | ||
|
||
import Foundation | ||
|
||
internal struct AnyInvalidation<T>: InvalidatingViewProtocol { | ||
let base: T | ||
|
||
init(base: T) { | ||
precondition(base is InvalidatingViewProtocol) | ||
self.base = base | ||
} | ||
|
||
func invalidate(view: InvalidatingViewType) { | ||
precondition(base is InvalidatingViewProtocol) | ||
(base as! InvalidatingViewProtocol).invalidate(view: view) | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// Imports.swift | ||
// | ||
// | ||
// Created by Suyash Srijan on 28/06/2021. | ||
// | ||
|
||
#if os(iOS) || os(tvOS) | ||
import UIKit | ||
|
||
public typealias InvalidatingViewType = UIView | ||
public typealias InvalidatingViewProtocol = UIViewInvalidating | ||
|
||
public protocol UIViewInvalidating { | ||
typealias Member = InvalidatingStaticMember<Self> | ||
func invalidate(view: InvalidatingViewType) | ||
} | ||
#elseif os(macOS) | ||
import AppKit | ||
|
||
public typealias InvalidatingViewType = NSView | ||
public typealias InvalidatingViewProtocol = NSViewInvalidating | ||
|
||
public protocol NSViewInvalidating { | ||
typealias Member = InvalidatingStaticMember<Self> | ||
func invalidate(view: InvalidatingViewType) | ||
} | ||
#else | ||
#error("Unsupported platform") | ||
#endif |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,50 @@ | ||
struct Invalidating { | ||
var text = "Hello, World!" | ||
extension InvalidatingViewType { | ||
@propertyWrapper | ||
public class Invalidating<Value: Equatable, InvalidationType: InvalidatingViewProtocol> { | ||
|
||
@available(*, unavailable) | ||
public var wrappedValue: Value { | ||
get { fatalError() } | ||
set { fatalError() } | ||
} | ||
|
||
private var _wrappedValue: Value | ||
private let invalidationTuple: Invalidations.Tuple<AnyInvalidation<InvalidatingViewProtocol>, AnyInvalidation<InvalidatingViewProtocol>> | ||
|
||
public init(wrappedValue: Value, _ invalidation: InvalidationType.Member) { | ||
self._wrappedValue = wrappedValue | ||
self.invalidationTuple = .init(invalidation1: .init(base: invalidation.base)) | ||
} | ||
|
||
public init<InvalidationType2: InvalidatingViewProtocol>(wrappedValue: Value, _ invalidation1: InvalidationType.Member, _ invalidation2: InvalidationType2.Member) { | ||
self._wrappedValue = wrappedValue | ||
self.invalidationTuple = .init(invalidation1: .init(base: invalidation1.base), invalidation2: .init(base: invalidation2.base)) | ||
} | ||
|
||
public static subscript<EnclosingSelf>( | ||
_enclosingInstance observed: EnclosingSelf, | ||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>, | ||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Invalidating> | ||
) -> Value where EnclosingSelf: InvalidatingViewType { | ||
|
||
get { | ||
return observed[keyPath: storageKeyPath]._wrappedValue | ||
} | ||
|
||
set { | ||
guard observed[keyPath: storageKeyPath]._wrappedValue != newValue else { return } | ||
observed[keyPath: storageKeyPath]._wrappedValue = newValue | ||
let invalidationTuple = observed[keyPath: storageKeyPath].invalidationTuple | ||
invalidationTuple.invalidate(view: observed) | ||
} | ||
} | ||
} | ||
} | ||
|
||
public extension InvalidatingViewType.Invalidating { | ||
typealias Tuple = InvalidatingViewType.Invalidations.Tuple | ||
|
||
convenience init<InvalidationType2: InvalidatingViewProtocol, InvalidationType3: InvalidatingViewProtocol>(wrappedValue: Value, _ invalidation1: InvalidationType.Member, _ invalidation2: InvalidationType2.Member, _ invalidation3: InvalidationType3.Member) { | ||
self.init(wrappedValue: wrappedValue, invalidation1, .init(Tuple(invalidation1: invalidation2.base, invalidation2: invalidation3.base))) | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
Sources/Invalidating/InvalidatingStaticMember+Extensions.swift
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// InvalidatingStaticMember+Extensions.swift | ||
// | ||
// | ||
// Created by Suyash Srijan on 28/06/2021. | ||
// | ||
|
||
import Foundation | ||
|
||
extension InvalidatingStaticMember where Base: InvalidatingViewProtocol { | ||
public static var layout: InvalidatingStaticMember<InvalidatingViewType.Invalidations.Layout> { .init(.layout) } | ||
public static var display: InvalidatingStaticMember<InvalidatingViewType.Invalidations.Display> { .init(.display) } | ||
public static var constraints: InvalidatingStaticMember<InvalidatingViewType.Invalidations.Constraints> { .init(.constraints) } | ||
public static var intrinsicContentSize: InvalidatingStaticMember<InvalidatingViewType.Invalidations.IntrinsicContentSize> {.init(.intrinsicContentSize) } | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// InvalidatingStaticMember.swift | ||
// | ||
// | ||
// Created by Suyash Srijan on 28/06/2021. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct InvalidatingStaticMember<Base> { | ||
public let base: Base | ||
internal init(_ base: Base) { | ||
self.base = base | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// | ||
// Invalidations.swift | ||
// | ||
// | ||
// Created by Suyash Srijan on 28/06/2021. | ||
// | ||
|
||
import Foundation | ||
|
||
public extension InvalidatingViewType { | ||
enum Invalidations { | ||
public struct Layout: InvalidatingViewProtocol { | ||
public static let layout: Self = .init() | ||
|
||
public func invalidate(view: InvalidatingViewType) { | ||
#if os(iOS) || os(tvOS) | ||
view.setNeedsLayout() | ||
#elseif os(macOS) | ||
view.needsLayout = true | ||
#endif | ||
} | ||
} | ||
|
||
public struct Display: InvalidatingViewProtocol { | ||
public static let display: Self = .init() | ||
|
||
public func invalidate(view: InvalidatingViewType) { | ||
#if os(iOS) || os(tvOS) | ||
view.setNeedsDisplay() | ||
#elseif os(macOS) | ||
view.setNeedsDisplay(view.bounds) | ||
#endif | ||
} | ||
} | ||
|
||
public struct Constraints: InvalidatingViewProtocol { | ||
public static let constraints: Self = .init() | ||
|
||
public func invalidate(view: InvalidatingViewType) { | ||
#if os(iOS) || os(tvOS) | ||
view.setNeedsUpdateConstraints() | ||
#elseif os(macOS) | ||
view.needsUpdateConstraints = true | ||
#endif | ||
} | ||
} | ||
|
||
public struct IntrinsicContentSize: InvalidatingViewProtocol { | ||
public static let intrinsicContentSize: Self = .init() | ||
|
||
public func invalidate(view: InvalidatingViewType) { | ||
view.invalidateIntrinsicContentSize() | ||
} | ||
} | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// | ||
// Tuple.swift | ||
// | ||
// | ||
// Created by Suyash Srijan on 28/06/2021. | ||
// | ||
|
||
import Foundation | ||
|
||
public extension InvalidatingViewType.Invalidations { | ||
struct Tuple<Invalidation1: InvalidatingViewProtocol, Invalidation2: InvalidatingViewProtocol>: InvalidatingViewProtocol { | ||
private let tuple: (Invalidation1, Invalidation2?) | ||
|
||
public init(invalidation1: Invalidation1, invalidation2: Invalidation2? = nil) { | ||
self.tuple = (invalidation1, invalidation2) | ||
} | ||
|
||
public func invalidate(view: InvalidatingViewType) { | ||
tuple.0.invalidate(view: view) | ||
tuple.1?.invalidate(view: view) | ||
} | ||
} | ||
} |
Oops, something went wrong.