Skip to content

Commit 4099499

Browse files
committed
Clean Up Animation Code
1 parent ed43078 commit 4099499

File tree

2 files changed

+50
-28
lines changed

2 files changed

+50
-28
lines changed

Sources/CodeEditSourceEditor/Find/FindViewController+Toggle.swift

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ import AppKit
99

1010
extension FindViewController {
1111
/// Show the find panel
12+
///
13+
/// Performs the following:
14+
/// - Makes the find panel the first responder.
15+
/// - Sets the find panel to be just outside the visible area (`resolvedTopPadding - FindPanel.height`).
16+
/// - Animates the find panel into position (resolvedTopPadding).
17+
/// - Makes the find panel the first responder.
1218
func showFindPanel(animated: Bool = true) {
1319
if isShowingFindPanel {
1420
// If panel is already showing, just focus the text field
@@ -18,46 +24,40 @@ extension FindViewController {
1824

1925
isShowingFindPanel = true
2026

21-
let updates: () -> Void = { [self] in
27+
// Smooth out the animation by placing the find panel just outside the correct position before animating.
28+
findPanel.isHidden = false
29+
findPanelVerticalConstraint.constant = resolvedTopPadding - FindPanel.height
30+
view.layoutSubtreeIfNeeded()
31+
32+
// Perform the animation
33+
conditionalAnimated(animated) {
2234
// SwiftUI breaks things here, and refuses to return the correct `findPanel.fittingSize` so we
2335
// are forced to use a constant number.
2436
target?.findPanelWillShow(panelHeight: FindPanel.height)
2537
setFindPanelConstraintShow()
26-
}
27-
28-
// Smooth this animation
29-
findPanel.isHidden = false
30-
findPanelVerticalConstraint.constant = topPadding - FindPanel.height
31-
view.layoutSubtreeIfNeeded()
32-
33-
if animated {
34-
withAnimation(updates) { }
35-
} else {
36-
updates()
37-
}
38+
} onComplete: { }
3839

3940
_ = findPanel?.becomeFirstResponder()
4041
findPanel?.addEventMonitor()
4142
}
4243

4344
/// Hide the find panel
45+
///
46+
/// Performs the following:
47+
/// - Resigns the find panel from first responder.
48+
/// - Animates the find panel just outside the visible area (`resolvedTopPadding - FindPanel.height`).
49+
/// - Hides the find panel.
50+
/// - Sets the text view to be the first responder.
4451
func hideFindPanel(animated: Bool = true) {
4552
isShowingFindPanel = false
4653
_ = findPanel?.resignFirstResponder()
4754
findPanel?.removeEventMonitor()
4855

49-
let updates: () -> Void = { [self] in
56+
conditionalAnimated(animated) {
5057
target?.findPanelWillHide(panelHeight: FindPanel.height)
5158
setFindPanelConstraintHide()
52-
}
53-
54-
if animated {
55-
withAnimation(updates) { [weak self] in
56-
self?.findPanel.isHidden = true
57-
}
58-
} else {
59-
updates()
60-
findPanel.isHidden = true
59+
} onComplete: { [weak self] in
60+
self?.findPanel.isHidden = true
6161
}
6262

6363
// Set first responder back to text view
@@ -66,12 +66,26 @@ extension FindViewController {
6666
}
6767
}
6868

69+
/// Performs an animation with a completion handler, conditionally animating the changes.
70+
/// - Parameters:
71+
/// - animated: Determines if the changes are performed in an animation context.
72+
/// - animatable: Perform the changes to be animated in this callback. Implicit animation will be enabled.
73+
/// - onComplete: Called when the changes are complete, animated or not.
74+
private func conditionalAnimated(_ animated: Bool, animatable: () -> Void, onComplete: @escaping () -> Void) {
75+
if animated {
76+
withAnimation(animatable, onComplete: onComplete)
77+
} else {
78+
animatable()
79+
onComplete()
80+
}
81+
}
82+
6983
/// Runs the `animatable` callback in an animation context with implicit animation enabled.
7084
/// - Parameter animatable: The callback run in the animation context. Perform layout or view updates in this
7185
/// callback to have them animated.
7286
private func withAnimation(_ animatable: () -> Void, onComplete: @escaping () -> Void) {
7387
NSAnimationContext.runAnimationGroup { animator in
74-
animator.duration = 0.2
88+
animator.duration = 0.15
7589
animator.allowsImplicitAnimation = true
7690

7791
animatable()
@@ -87,7 +101,7 @@ extension FindViewController {
87101
/// Can be animated using implicit animation.
88102
func setFindPanelConstraintShow() {
89103
// Update the find panel's top to be equal to the view's top.
90-
findPanelVerticalConstraint.constant = topPadding
104+
findPanelVerticalConstraint.constant = resolvedTopPadding
91105
findPanelVerticalConstraint.isActive = true
92106
}
93107

@@ -99,7 +113,7 @@ extension FindViewController {
99113
// SwiftUI hates us. It refuses to move views outside of the safe are if they don't have the `.ignoresSafeArea`
100114
// modifier, but with that modifier on it refuses to allow it to be animated outside the safe area.
101115
// The only way I found to fix it was to multiply the height by 3 here.
102-
findPanelVerticalConstraint.constant = topPadding - FindPanel.height
116+
findPanelVerticalConstraint.constant = resolvedTopPadding - (FindPanel.height * 3)
103117
findPanelVerticalConstraint.isActive = true
104118
}
105119
}

Sources/CodeEditSourceEditor/Find/FindViewController.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import CodeEditTextView
1212
final class FindViewController: NSViewController {
1313
weak var target: FindPanelTarget?
1414

15-
var topPadding: CGFloat = 0.0
15+
/// The amount of padding from the top of the view to inset the find panel by.
16+
/// When set, the safe area is ignored, and the top padding is measured from the top of the view's frame.
17+
var topPadding: CGFloat?
1618

1719
var childView: NSView
1820
var findPanel: FindPanel!
@@ -24,6 +26,12 @@ final class FindViewController: NSViewController {
2426

2527
var isShowingFindPanel: Bool = false
2628

29+
/// The 'real' top padding amount.
30+
/// Is equal to ``topPadding`` if set, or the view's top safe area inset if not.
31+
var resolvedTopPadding: CGFloat {
32+
(topPadding ?? view.safeAreaInsets.top)
33+
}
34+
2735
init(target: FindPanelTarget, childView: NSView) {
2836
self.target = target
2937
self.childView = childView
@@ -48,7 +56,7 @@ final class FindViewController: NSViewController {
4856
@objc private func textDidChange() {
4957
// Only update if we have find text
5058
if !findText.isEmpty {
51-
performFind(query: findText)
59+
performFind()
5260
}
5361
}
5462

0 commit comments

Comments
 (0)