Skip to content

Commit fd7c646

Browse files
committed
Added View.withResponderChainForCurrentWindow() so you can use ResponderChain in SwiftUI only apps
1 parent 5a075a0 commit fd7c646

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ Cross-platform first responder handling without subclassing views or making cust
1010

1111
## Example
1212

13-
**SceneDelegate.swift**
13+
Attach the ResponderChain as environmentObject
14+
1415
```swift
1516
...
17+
// In the SceneDelegate or ApplicationDelegate where you have access to the window:
1618
let rootView = ResponderChainExample().environmentObject(ResponderChain(forWindow: window))
19+
20+
// SwiftUI only:
21+
ResponderChainExample().withResponderChainForCurrentWindow()
1722
...
1823
```
1924

Sources/ResponderChain/ResponderChain.swift

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import Combine
1010
import SwiftUI
1111
import AmzdIntrospect
1212

13+
// MARK: Platform specifics
14+
1315
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
1416
typealias ResponderPublisher = AnyPublisher<PlatformResponder?, Never>
1517

@@ -60,6 +62,27 @@ extension UIView {
6062
}
6163
#endif
6264

65+
// MARK: View Extension
66+
67+
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
68+
extension View {
69+
/// Tag the closest sibling view that can become first responder
70+
public func responderTag<Tag: Hashable>(_ tag: Tag) -> some View {
71+
inject(FindResponderSibling(tag: tag))
72+
}
73+
74+
/// This attaches the ResponderChain for the current window as environmentObject
75+
///
76+
/// Will not show anything for the first frame as it introspects the closest view to get the window
77+
///
78+
/// Use `.environmentObject(ResponderChain(forWindow: window))` if possible.
79+
public func withResponderChainForCurrentWindow() -> some View {
80+
self.modifier(ResponderChainWindowFinder())
81+
}
82+
}
83+
84+
// MARK: ResponderChain
85+
6386
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
6487
public class ResponderChain: ObservableObject {
6588
@Published public var firstResponder: AnyHashable? {
@@ -118,12 +141,24 @@ public class ResponderChain: ObservableObject {
118141
}
119142

120143
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
121-
extension View {
122-
public func responderTag<Tag: Hashable>(_ tag: Tag) -> some View {
123-
inject(FindResponderSibling(tag: tag))
144+
private struct ResponderChainWindowFinder: ViewModifier {
145+
@State private var window: PlatformWindow? = nil
146+
147+
func body(content: Content) -> some View {
148+
Group {
149+
if let window = window {
150+
content.environmentObject(ResponderChain(forWindow: window))
151+
} else {
152+
EmptyView()
153+
}
154+
}.introspect(selector: { $0.self }) {
155+
self.window = $0.window
156+
}
124157
}
125158
}
126159

160+
// MARK: - Tag
161+
127162
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
128163
private struct FindResponderSibling<Tag: Hashable>: View {
129164
@EnvironmentObject var responderChain: ResponderChain

0 commit comments

Comments
 (0)