Skip to content

Commit 9ed84d9

Browse files
committed
Add FloatingPanelDetent.Preference
1 parent 1afa49b commit 9ed84d9

File tree

3 files changed

+67
-6
lines changed

3 files changed

+67
-6
lines changed

Sources/ArcGISToolkit/Components/FloatingPanel/FloatingPanel.swift

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ struct FloatingPanel<Content>: View where Content: View {
2525
let attributionBarHeight: CGFloat
2626
/// The background color of the floating panel.
2727
let backgroundColor: Color?
28-
/// A binding to the currently selected detent.
29-
@Binding var selectedDetent: FloatingPanelDetent
28+
/// A binding to the current active detent.
29+
@Binding var activeDetent: FloatingPanelDetent
3030
/// A binding to a Boolean value that determines whether the view is presented.
3131
@Binding var isPresented: Bool
3232
/// The content shown in the floating panel.
@@ -52,6 +52,11 @@ struct FloatingPanel<Content>: View where Content: View {
5252
/// The maximum allowed height of the content.
5353
@State private var maximumHeight: CGFloat = .zero
5454

55+
/// The detent that was the active detent until a FloatingPanelDetentPreference was set.
56+
///
57+
/// When the FloatingPanelDetentPreference is unset, this detent should be restored to the active detent..
58+
@State private var overriddenDetent: FloatingPanelDetent?
59+
5560
var body: some View {
5661
GeometryReader { geometryProxy in
5762
VStack(spacing: 0) {
@@ -64,6 +69,24 @@ struct FloatingPanel<Content>: View where Content: View {
6469
.padding(.bottom, isPortraitOrientation ? keyboardHeight - geometryProxy.safeAreaInsets.bottom : .zero)
6570
.frame(height: height)
6671
.clipped()
72+
.onPreferenceChange(FloatingPanelDetent.Preference.self) { preference in
73+
if let preference {
74+
// Only update the overridden detent if one
75+
// wasn't already saved. This prevents a
76+
// FloatingPanelDetentPreference from being
77+
// saved as the overridden detent.
78+
if overriddenDetent == nil {
79+
overriddenDetent = activeDetent
80+
}
81+
activeDetent = preference
82+
} else if let overriddenDetent {
83+
// When the FloatingPanelDetentPreference is
84+
// unset, restore the overridden detent as the
85+
// active detent.
86+
activeDetent = overriddenDetent
87+
self.overriddenDetent = nil
88+
}
89+
}
6790
if !isPortraitOrientation {
6891
Divider()
6992
makeHandleView()
@@ -98,7 +121,7 @@ struct FloatingPanel<Content>: View where Content: View {
98121
.onChange(of: isPresented) {
99122
updateHeight()
100123
}
101-
.onChange(of: selectedDetent) {
124+
.onChange(of: activeDetent) {
102125
updateHeight()
103126
}
104127
.onKeyboardStateChanged { state, height in
@@ -138,11 +161,18 @@ struct FloatingPanel<Content>: View where Content: View {
138161
let predictedEndLocation = $0.predictedEndLocation.y
139162
let inferredHeight = isPortraitOrientation ? maximumHeight - predictedEndLocation : predictedEndLocation
140163

141-
selectedDetent = [.summary, .half, .full]
164+
activeDetent = [.summary, .half, .full]
142165
.map { (detent: $0, height: heightFor(detent: $0)) }
143166
.min { abs(inferredHeight - $0.height) < abs(inferredHeight - $1.height) }!
144167
.detent
145168

169+
if overriddenDetent != nil {
170+
// Update the overridden detent with the user's choice to
171+
// prevent the user's choice from being unset when the
172+
// FloatingPanelDetentPreference is unset.
173+
overriddenDetent = activeDetent
174+
}
175+
146176
if $0.translation.height.magnitude > 100 {
147177
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
148178
}
@@ -180,7 +210,7 @@ struct FloatingPanel<Content>: View where Content: View {
180210
} else if keyboardState == .opening || keyboardState == .open {
181211
return heightFor(detent: .full)
182212
} else {
183-
return heightFor(detent: selectedDetent)
213+
return heightFor(detent: activeDetent)
184214
}
185215
}()
186216
withAnimation { height = max(0, (newHeight - .handleFrameHeight)) }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2025 Esri
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import SwiftUI
16+
17+
extension FloatingPanelDetent {
18+
/// Use this preference to override the active FloatingPanelDetent.
19+
///
20+
/// This can be used when a view shown in a Floating Panel needs to communicate that the view behind
21+
/// the Floating Panel should be revealed (e.g. to reveal a map for user interaction).
22+
///
23+
/// When the Floating Panel can be re-expanded, set the preference to `nil`.
24+
struct Preference: PreferenceKey {
25+
static let defaultValue: FloatingPanelDetent? = nil
26+
27+
static func reduce(value: inout FloatingPanelDetent?, nextValue: () -> FloatingPanelDetent?) {
28+
value = nextValue()
29+
}
30+
}
31+
}

Sources/ArcGISToolkit/Components/FloatingPanel/FloatingPanelModifier.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private struct FloatingPanelModifier<PanelContent>: ViewModifier where PanelCont
125125
FloatingPanel(
126126
attributionBarHeight: attributionBarHeight,
127127
backgroundColor: backgroundColor,
128-
selectedDetent: boundDetent ?? $managedDetent,
128+
activeDetent: boundDetent ?? $managedDetent,
129129
isPresented: isPresented,
130130
content: panelContent
131131
)

0 commit comments

Comments
 (0)