Skip to content

Floating Panel - Add Keyboard Avoidance Support #380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 96 additions & 34 deletions Examples/Examples/FloatingPanelExampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ struct FloatingPanelExampleView: View {
map: Map(basemapStyle: .arcGISImagery)
)

@State var isPresented = true
/// The Floating Panel's current content.
@State private var demoContent: FloatingPanelDemoContent?

@State var selectedDetent: FloatingPanelDetent = .half
/// The Floating Panel's current detent.
@State private var selectedDetent: FloatingPanelDetent = .half

/// The initial viewpoint shown in the map.
private let initialViewpoint = Viewpoint(
center: Point(x: -93.258133, y: 44.986656, spatialReference: .wgs84),
scale: 1_000_000
Expand All @@ -35,46 +38,105 @@ struct FloatingPanelExampleView: View {
map: dataModel.map,
viewpoint: initialViewpoint
)
.floatingPanel(selectedDetent: $selectedDetent, isPresented: $isPresented) {
List {
Section("Preset Heights") {
Button("Summary") {
selectedDetent = .summary
}
Button("Half") {
selectedDetent = .half
.floatingPanel(selectedDetent: $selectedDetent, isPresented: isPresented) {
switch demoContent {
case .list:
FloatingPanelListDemoContent(selectedDetent: $selectedDetent)
case .textField:
FloatingPanelTextFieldDemoContent()
case .none:
EmptyView()
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
Button("List") {
demoContent = .list
}
Button("Full") {
selectedDetent = .full
Button("Text Field") {
demoContent = .textField
}
} label: {
Text("Present")
}
Section("Fractional Heights") {
Button("1/4") {
selectedDetent = .fraction(1 / 4)
}
Button("1/2") {
selectedDetent = .fraction(1 / 2)
}
Button("3/4") {
selectedDetent = .fraction(3 / 4)
}
}
}
}

/// A Boolean value indicating whether the Floating Panel is displayed or not.
var isPresented: Binding<Bool> {
.init {
demoContent != nil
} set: { _ in
}
}
}

/// The types of content available for demo in the Floating Panel.
private enum FloatingPanelDemoContent {
case textField
case list
}

/// Demo content consisting of a list with inner sections each containing a set of buttons This
/// content also demonstrates the ability to control the Floating Panel's detent.
private struct FloatingPanelListDemoContent: View {
@Binding var selectedDetent: FloatingPanelDetent

var body: some View {
List {
Section("Preset Heights") {
Button("Summary") {
selectedDetent = .summary
}
Section("Value Heights") {
Button("200") {
selectedDetent = .height(200)
}
Button("600") {
selectedDetent = .height(600)
}
Button("Half") {
selectedDetent = .half
}
Button("Full") {
selectedDetent = .full
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(isPresented ? "Close" : "Open") {
isPresented.toggle()
Section("Fractional Heights") {
Button("1/4") {
selectedDetent = .fraction(1 / 4)
}
Button("1/2") {
selectedDetent = .fraction(1 / 2)
}
Button("3/4") {
selectedDetent = .fraction(3 / 4)
}
}
Section("Value Heights") {
Button("200") {
selectedDetent = .height(200)
}
Button("600") {
selectedDetent = .height(600)
}
}
}
}
}

/// Demo content consisting of a vertical stack of items, including a text field which demonstrates
/// the Floating Panel's keyboard avoidance capability.
private struct FloatingPanelTextFieldDemoContent: View {
@State private var sampleText = ""

var body: some View {
VStack(alignment: .leading) {
Text("Text Field")
.font(.title)
Text("The Floating Panel has built-in keyboard avoidance.")
.font(.caption)
TextField(
"Text Field",
text: $sampleText,
prompt: Text("Enter sample text.")
)
.textFieldStyle(.roundedBorder)
}
}
}
32 changes: 25 additions & 7 deletions Sources/ArcGISToolkit/Components/FloatingPanel/FloatingPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,17 @@ struct FloatingPanel<Content>: View where Content: View {
self.content = content()
}

/// A binding to the currently selected detent.
private var selectedDetent: Binding<FloatingPanelDetent>

/// The color of the handle.
@State private var handleColor: Color = .defaultHandleColor

/// The height of the content.
@State private var height: CGFloat = .minHeight

/// A binding to a Boolean value that determines whether the view is presented.
private var isPresented: Binding<Bool>
/// The height of the keyboard, if present, otherwise zero.
@State private var keyboardHeight: CGFloat = 0.0

/// The latest recorded drag gesture value.
@State var latestDragGesture: DragGesture.Value?
@State private var latestDragGesture: DragGesture.Value?

/// The maximum allowed height of the content.
@State private var maximumHeight: CGFloat = .infinity
Expand All @@ -74,6 +71,12 @@ struct FloatingPanel<Content>: View where Content: View {
horizontalSizeClass == .compact && verticalSizeClass == .regular
}

/// A binding to a Boolean value that determines whether the view is presented.
private var isPresented: Binding<Bool>

/// A binding to the currently selected detent.
private var selectedDetent: Binding<FloatingPanelDetent>

public var body: some View {
GeometryReader { geometryProxy in
VStack(spacing: 0) {
Expand All @@ -84,7 +87,10 @@ struct FloatingPanel<Content>: View where Content: View {
content
.frame(height: height)
.clipped()
.padding(.bottom, isPresented.wrappedValue ? (isCompact ? 25 : 10) : .zero)
.padding(
.bottom,
keyboardHeight.isZero ? (isPresented.wrappedValue ? (isCompact ? 25 : 10) : .zero) : keyboardHeight
)
if !isCompact && isPresented.wrappedValue {
Divider()
makeHandleView()
Expand Down Expand Up @@ -124,6 +130,18 @@ struct FloatingPanel<Content>: View where Content: View {
height = heightFor(detent: selectedDetent)
}
}
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { notification in
withAnimation {
keyboardHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? .zero
height = heightFor(detent: .full) - keyboardHeight
}
}
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification)) { _ in
withAnimation {
keyboardHeight = .zero
height = heightFor(detent: selectedDetent.wrappedValue) - keyboardHeight
}
}
}
.padding([.leading, .top, .trailing], isCompact ? 0 : 10)
.padding([.bottom], isCompact ? 0 : 50)
Expand Down