Skip to content

Commit b920b81

Browse files
committed
Remove hardcoded dependency on RunLoop.main.
By making use of the Scheduler abstraction and the SwiftUI environment, we can make it possible to remove the asynchronous behaviour in e.g. snapshot tests by setting the responderScheduler environment key to .immediate (or any other test scheduler).
1 parent eb5ab42 commit b920b81

File tree

4 files changed

+46
-11
lines changed

4 files changed

+46
-11
lines changed

Package.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ let package = Package(
1212
targets: ["ResponsiveTextField"]),
1313
],
1414
dependencies: [
15-
.package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.8.1")
15+
.package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.8.1"),
16+
.package(name: "combine-schedulers", url: "https://github.com/pointfreeco/combine-schedulers.git", from: "0.5.3")
1617
],
1718
targets: [
1819
.target(
1920
name: "ResponsiveTextField",
20-
dependencies: []),
21+
dependencies: [
22+
.product(name: "CombineSchedulers", package: "combine-schedulers")
23+
]),
2124
.testTarget(
2225
name: "ResponsiveTextFieldTests",
2326
dependencies: ["ResponsiveTextField", "SnapshotTesting"],

ResponsiveTextField.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/ResponsiveTextField/ResponsiveTextField+EnvironmentValues.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created by Luke Redpath on 14/03/2021.
66
//
77

8+
import CombineSchedulers
89
import SwiftUI
910

1011
// MARK: - Environment Keys
@@ -29,6 +30,10 @@ extension ResponsiveTextField {
2930
fileprivate struct FirstResponderDemandKey: EnvironmentKey {
3031
static let defaultValue: FirstResponderDemand? = nil
3132
}
33+
34+
fileprivate struct ResponderSchedulerKey: EnvironmentKey {
35+
static let defaultValue: AnySchedulerOf<RunLoop> = .main
36+
}
3237
}
3338

3439
// MARK: - Environment Values
@@ -58,4 +63,9 @@ extension EnvironmentValues {
5863
get { self[ResponsiveTextField.FirstResponderDemandKey.self] }
5964
set { self[ResponsiveTextField.FirstResponderDemandKey.self] = newValue }
6065
}
66+
67+
public var responderScheduler: AnySchedulerOf<RunLoop> {
68+
get { self[ResponsiveTextField.ResponderSchedulerKey.self] }
69+
set { self[ResponsiveTextField.ResponderSchedulerKey.self] = newValue }
70+
}
6171
}

Sources/ResponsiveTextField/ResponsiveTextField.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import Combine
66
import UIKit
77
import SwiftUI
8+
import CombineSchedulers
89

910
// MARK: - Main Interface
1011

@@ -71,6 +72,9 @@ public struct ResponsiveTextField {
7172
@Environment(\.textFieldTextAlignment)
7273
var textAlignment: NSTextAlignment
7374

75+
@Environment(\.responderScheduler)
76+
private var responderScheduler: AnySchedulerOf<RunLoop>
77+
7478
/// A calllback function that will be called whenever the first responder state changes.
7579
var onFirstResponderStateChanged: FirstResponderStateChangeHandler?
7680

@@ -344,25 +348,25 @@ extension ResponsiveTextField: UIViewRepresentable {
344348

345349
switch (uiView.isFirstResponder, firstResponderDemand?.wrappedValue) {
346350
case (true, .shouldResignFirstResponder):
347-
RunLoop.main.schedule { uiView.resignFirstResponder() }
351+
responderScheduler.schedule { uiView.resignFirstResponder() }
348352
case (false, .shouldBecomeFirstResponder):
349-
RunLoop.main.schedule { uiView.becomeFirstResponder() }
353+
responderScheduler.schedule { uiView.becomeFirstResponder() }
350354
case (_, nil):
351355
// If there is no demand then there's nothing to do.
352356
break
353357
default:
354358
// If the current responder state matches the demand then
355359
// the demand is already fulfilled so we can just reset it.
356-
resetFirstResponderDemandAfterViewUpdate()
360+
resetFirstResponderDemand()
357361
}
358362
}
359363

360-
fileprivate func resetFirstResponderDemandAfterViewUpdate() {
364+
fileprivate func resetFirstResponderDemand() {
361365
// Because the first responder demand will trigger a view
362366
// update when it is set, we need to wait until the next
363367
// runloop tick to reset it back to nil to avoid runtime
364368
// warnings.
365-
RunLoop.main.schedule {
369+
responderScheduler.schedule {
366370
firstResponderDemand?.wrappedValue = nil
367371
}
368372
}
@@ -382,7 +386,7 @@ extension ResponsiveTextField: UIViewRepresentable {
382386
if let canBecomeFirstResponder = parent.onFirstResponderStateChanged?.canBecomeFirstResponder {
383387
let shouldBeginEditing = canBecomeFirstResponder()
384388
if !shouldBeginEditing {
385-
parent.resetFirstResponderDemandAfterViewUpdate()
389+
parent.resetFirstResponderDemand()
386390
}
387391
return shouldBeginEditing
388392
}
@@ -391,14 +395,14 @@ extension ResponsiveTextField: UIViewRepresentable {
391395

392396
public func textFieldDidBeginEditing(_ textField: UITextField) {
393397
parent.onFirstResponderStateChanged?(true)
394-
parent.resetFirstResponderDemandAfterViewUpdate()
398+
parent.resetFirstResponderDemand()
395399
}
396400

397401
public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
398402
if let canResignFirstResponder = parent.onFirstResponderStateChanged?.canResignFirstResponder {
399403
let shouldEndEditing = canResignFirstResponder()
400404
if !shouldEndEditing {
401-
parent.resetFirstResponderDemandAfterViewUpdate()
405+
parent.resetFirstResponderDemand()
402406
}
403407
return shouldEndEditing
404408
}
@@ -407,7 +411,7 @@ extension ResponsiveTextField: UIViewRepresentable {
407411

408412
public func textFieldDidEndEditing(_ textField: UITextField) {
409413
parent.onFirstResponderStateChanged?(false)
410-
parent.resetFirstResponderDemandAfterViewUpdate()
414+
parent.resetFirstResponderDemand()
411415
}
412416

413417
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {

0 commit comments

Comments
 (0)