Skip to content
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

[ios] non-notched layout #258

Merged
merged 13 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions apple/DemoApp/Demo/DemoNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ struct DemoNavigationView: View {
}
} label: {
Text("Start Nav")
.lineLimit(1)
.minimumScaleFactor(0.5)
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
.font(.body.bold())
}
.disabled(routes?.isEmpty == true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public extension Route {
/// - Parameters:
/// - route: The encoded JSON data for the OSRM route.
/// - waypoints: The encoded JSON data for the OSRM waypoints.
/// - precision: The polyline precision.
/// - polylinePrecision: The polyline precision.
static func initFromOsrm(route: Data, waypoints: Data, polylinePrecision: UInt32) throws -> Route {
try createRouteFromOsrm(routeData: route, waypointData: waypoints, polylinePrecision: polylinePrecision)
}
Expand Down
2 changes: 1 addition & 1 deletion apple/Sources/FerrostarCore/FerrostarCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public protocol FerrostarCoreDelegate: AnyObject {
///
/// The usual flow is for callers to configure an instance of the core, set a ``delegate``,
/// and reuse the instance for as long as it makes sense (necessarily somewhat app-specific).
/// You can first call ``getRoutes(waypoints:userLocation:)``
/// You can first call ``getRoutes(initialLocation:waypoints:)``
/// to fetch a list of possible routes asynchronously. After selecting a suitable route (either interactively by the
/// user, or programmatically), call ``startNavigation(route:config:)`` to start a session.
///
Expand Down
2 changes: 1 addition & 1 deletion apple/Sources/FerrostarCore/URLRequestLoading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum MockURLSessionError: Error {
/// Mocks network responses by URL. Super quick-and-dirty for testing with mocks.
///
/// By default, it will return an error for all requests. Register a mock by URL with
/// ``registerMock(forURL:withData:andResponse:)``
/// ``registerMock(forMethod:andURL:withData:andResponse:)``.
public class MockURLSession: URLRequestLoading {
private var urlResponseMap = [String: [URL: (Data, URLResponse)]]()

Expand Down
73 changes: 73 additions & 0 deletions apple/Sources/FerrostarMapLibreUI/Extensions/View.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import SwiftUI

extension View {
/// Given the view's `geometry`, synchronizes `childInsets`, such that
/// accumulating the child's insets with the parents insets, will be at least `minimumInset`.
///
/// ```
/// Given a minimumInset of 16:
/// +-------------------------------------------------------------+
/// | `parentGeometry` |
/// | +-----------------------------------------------------+ |
/// | | `parentGeometry.safeAreaInsets` (Top: 16) | |
/// | | +---------------------------------------------+ | |
/// | | | insets added by this method (Top: 0) | | |
/// | | | +------------------------------------+ | | |
/// | | | | | | | |
/// | | 8 | 8 | child view (self) | 16 | 0 | |
/// | | | | | | | |
/// | | | +------------------------------------+ | | |
/// | | | insets added by this method (Bottom: 0) | | |
/// | | +---------------------------------------------+ | |
/// | | `parentGeometry.safeAreaInsets` (Bottom: 20) | |
/// | +-----------------------------------------------------+ |
/// | |
/// +-------------------------------------------------------------+
/// ```
michaelkirk marked this conversation as resolved.
Show resolved Hide resolved
func complementSafeAreaInsets(
parentGeometry: GeometryProxy,
minimumInset: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)
) -> some View {
ComplementingSafeAreaView(content: self, parentGeometry: parentGeometry, minimumInset: minimumInset)
}

/// Do something reasonable-ish for clients that don't yet support
/// safeAreaPadding - in this case, fall back to regular padding.
func safeAreaPaddingPolyfill(_ insets: EdgeInsets) -> AnyView {
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
if #available(iOS 17.0, *) {
AnyView(self.safeAreaPadding(insets))
} else {
AnyView(padding(insets))
}
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
}
}

struct ComplementingSafeAreaView<V: View>: View {
var content: V

var parentGeometry: GeometryProxy
var minimumInset: EdgeInsets

@State
var childInsets: EdgeInsets = .init()

static func complement(parentInset: EdgeInsets, minimumInset: EdgeInsets) -> EdgeInsets {
var innerInsets = parentInset
innerInsets.top = max(0, minimumInset.top - parentInset.top)
innerInsets.bottom = max(0, minimumInset.bottom - parentInset.bottom)
innerInsets.leading = max(0, minimumInset.leading - parentInset.leading)
innerInsets.trailing = max(0, minimumInset.trailing - parentInset.trailing)
return innerInsets
}

var body: some View {
content.onAppear {
childInsets = ComplementingSafeAreaView.complement(
parentInset: parentGeometry.safeAreaInsets,
minimumInset: minimumInset
)
}.onChange(of: parentGeometry.safeAreaInsets) { newValue in
childInsets = ComplementingSafeAreaView.complement(parentInset: newValue, minimumInset: minimumInset)
}.safeAreaPaddingPolyfill(childInsets)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,32 @@ import SwiftUI
import UIKit

public enum NavigationMapViewContentInsetMode {
/// A predefined mode for landscape navigation map views
/// where the user location should appear toward the bottom of the map.
/// Dynamically determined insets suitable for landscape orientation,
/// where the user location indicator should appear toward the bottom right of the screen.
///
/// This is used to accommodate a left InstructionView
/// This mode is used to accommodate an InstructionView in a separate column, left of the content area.
///
/// - Parameter within : The `MapView`'s geometry
/// - Parameter verticalPct : How far "down" to inset the MapView overlay content. A higher number positions content
/// lower.
/// - Parameter horizontalPct : How far "right" to inset the MapView overlay content. A higher number positions
/// content farther right.
case landscape(within: GeometryProxy, verticalPct: CGFloat = 0.75, horizontalPct: CGFloat = 0.5)

/// A predefined mode for landscape navigation map views
/// where the user location should appear toward the bottom of the map.
/// Dynamically determined insets suitable for portrait orientation,
/// where the user location indicator should appear toward the bottom of the screen.
///
/// This mode is used to accommodate an InstructionView at the top of the MapView, in a single column with the
/// content area.
///
/// This is used to accommodate a top InstructionView
case portrait(within: GeometryProxy, verticalPct: CGFloat = 0.75)
/// - Parameter within : The `MapView`'s geometry
/// - Parameter verticalPct : How far "down" to inset the MapView overlay content. A higher number positions content
/// lower.
/// - Parameter minHeight : The minimum height (in points) of the content area. The content area could be larger
/// than this on sufficiently tall screens depending on `verticalPct`.
case portrait(within: GeometryProxy, verticalPct: CGFloat = 0.75, minHeight: CGFloat = 210)

/// Custom edge insets to manually control where the center of the map is.
/// Static edge insets to manually control where the center of the map is.
case edgeInset(UIEdgeInsets)

public init(orientation: UIDeviceOrientation, geometry: GeometryProxy) {
Expand All @@ -33,8 +46,10 @@ public enum NavigationMapViewContentInsetMode {
let leading = geometry.size.width * horizontalPct

return UIEdgeInsets(top: top, left: leading, bottom: 0, right: 0)
case let .portrait(geometry, verticalPct):
let top = geometry.size.height * verticalPct
case let .portrait(geometry, verticalPct, minVertical):
let ideal = geometry.size.height * verticalPct
let max = geometry.size.height - minVertical
let top = min(max, ideal)

return UIEdgeInsets(top: top, left: 0, bottom: 0, right: 0)
case let .edgeInset(uIEdgeInsets):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public struct DynamicallyOrientingNavigationView: View, CustomizableNavigatingIn

var onTapExit: (() -> Void)?

public var minimumSafeAreaInsets: EdgeInsets

/// Create a dynamically orienting navigation view. This view automatically arranges child views for both portait
/// and landscape orientations.
///
Expand All @@ -34,7 +36,8 @@ public struct DynamicallyOrientingNavigationView: View, CustomizableNavigatingIn
/// - camera: The camera binding that represents the current camera on the map.
/// - navigationCamera: The default navigation camera. This sets the initial camera & is also used when the center
/// on user button it tapped.
/// - navigationState: The current ferrostar navigation state provided by ferrostar core.
/// - navigationState: The current ferrostar navigation state provided by the Ferrostar core.
/// - minimumSafeAreaInsets: The minimum padding to apply from safe edges. See `complementSafeAreaInsets`.
/// - onTapExit: An optional behavior to run when the ArrivalView exit button is tapped. When nil (default) the
/// exit button is hidden.
/// - makeMapContent: Custom maplibre symbols to display on the map view.
Expand All @@ -43,11 +46,13 @@ public struct DynamicallyOrientingNavigationView: View, CustomizableNavigatingIn
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
onTapExit: (() -> Void)? = nil,
@MapViewContentBuilder makeMapContent: () -> [StyleLayerDefinition] = { [] }
) {
self.styleURL = styleURL
self.navigationState = navigationState
self.minimumSafeAreaInsets = minimumSafeAreaInsets
self.onTapExit = onTapExit

userLayers = makeMapContent()
Expand Down Expand Up @@ -94,7 +99,7 @@ public struct DynamicallyOrientingNavigationView: View, CustomizableNavigatingIn
midLeading?()
} bottomTrailing: {
bottomTrailing?()
}
}.complementSafeAreaInsets(parentGeometry: geometry, minimumInset: minimumSafeAreaInsets)
default:
PortraitNavigationOverlayView(
navigationState: navigationState,
Expand All @@ -114,7 +119,7 @@ public struct DynamicallyOrientingNavigationView: View, CustomizableNavigatingIn
midLeading?()
} bottomTrailing: {
bottomTrailing?()
}
}.complementSafeAreaInsets(parentGeometry: geometry, minimumInset: minimumSafeAreaInsets)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import SwiftUI

/// A landscape orientation navigation view that includes the InstructionsView and ArrivalView on the
/// leading half of the screen.
public struct LandscapeNavigationView: View {
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
public struct LandscapeNavigationView: View, CustomizableNavigatingInnerGridView {
@Environment(\.navigationFormatterCollection) var formatterCollection: any FormatterCollection

let styleURL: URL
Expand All @@ -25,6 +25,8 @@ public struct LandscapeNavigationView: View {

var onTapExit: (() -> Void)?

public var minimumSafeAreaInsets: EdgeInsets

/// Create a landscape navigation view. This view is optimized for display on a landscape screen where the
/// instructions are on the leading half of the screen
/// and the user puck and route are on the trailing half of the screen.
Expand All @@ -34,7 +36,8 @@ public struct LandscapeNavigationView: View {
/// - camera: The camera binding that represents the current camera on the map.
/// - navigationCamera: The default navigation camera. This sets the initial camera & is also used when the center
/// on user button it tapped.
/// - navigationState: The current ferrostar navigation state provided by ferrostar core.
/// - navigationState: The current ferrostar navigation state provided by the Ferrostar core.
/// - minimumSafeAreaInsets: The minimum padding to apply from safe edges. See `complementSafeAreaInsets`.
/// - onTapExit: An optional behavior to run when the ArrivalView exit button is tapped. When nil (default) the
/// exit button is hidden.
/// - makeMapContent: Custom maplibre symbols to display on the map view.
Expand All @@ -43,11 +46,13 @@ public struct LandscapeNavigationView: View {
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
onTapExit: (() -> Void)? = nil,
@MapViewContentBuilder makeMapContent: () -> [StyleLayerDefinition] = { [] }
) {
self.styleURL = styleURL
self.navigationState = navigationState
self.minimumSafeAreaInsets = minimumSafeAreaInsets
self.onTapExit = onTapExit

userLayers = makeMapContent()
Expand Down Expand Up @@ -88,7 +93,7 @@ public struct LandscapeNavigationView: View {
midLeading?()
} bottomTrailing: {
bottomTrailing?()
}
}.complementSafeAreaInsets(parentGeometry: geometry, minimumInset: minimumSafeAreaInsets)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import Foundation
import SwiftUI

public extension NavigationMapView {
/// Set the MapView's content inset to a dynamically controlled navigation setting.
/// Set the MapView's content inset. See ``NavigationMapViewContentInsetMode`` for static and dynamic options.
///
/// This functionality is used to appropriate space the navigation puck/user location in the map view.
/// This functionality is used to position the navigation puck/user location in the map view
///
/// - Parameter inset: The inset mode for the navigation map view
/// - Returns: The modified NavigationMapView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ struct LandscapeNavigationOverlayView: View, CustomizableNavigatingInnerGridView
distanceFormatter: formatterCollection.distanceFormatter,
distanceToNextManeuver: progress.distanceToNextManeuver
)
.padding(.top, 16)
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved
}

Spacer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ struct PortraitNavigationOverlayView: View, CustomizableNavigatingInnerGridView
distanceFormatter: formatterCollection.distanceFormatter,
distanceToNextManeuver: progress.distanceToNextManeuver
)
.padding(.horizontal, 16)
}

// The inner content is displayed vertically full screen
Expand All @@ -79,14 +78,12 @@ struct PortraitNavigationOverlayView: View, CustomizableNavigatingInnerGridView
} bottomTrailing: {
bottomTrailing?()
}
.padding(.horizontal, 16)

if case let .navigating(_, _, _, _, progress: progress, _, _, _) = navigationState?.tripState {
ArrivalView(
progress: progress,
onTapExit: onTapExit
)
.padding(.horizontal, 16)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public struct PortraitNavigationView: View, CustomizableNavigatingInnerGridView
public var midLeading: (() -> AnyView)?
public var bottomTrailing: (() -> AnyView)?

public var minimumSafeAreaInsets: EdgeInsets

@Binding var camera: MapViewCamera
let navigationCamera: MapViewCamera

Expand All @@ -35,7 +37,8 @@ public struct PortraitNavigationView: View, CustomizableNavigatingInnerGridView
/// - camera: The camera binding that represents the current camera on the map.
/// - navigationCamera: The default navigation camera. This sets the initial camera & is also used when the center
/// on user button it tapped.
/// - navigationState: The current ferrostar navigation state provided by ferrostar core.
/// - navigationState: The current ferrostar navigation state provided by the Ferrostar core.
/// - minimumSafeAreaInsets: The minimum padding to apply from safe edges. See `complementSafeAreaInsets`.
/// - onTapExit: An optional behavior to run when the ArrivalView exit button is tapped. When nil (default) the
/// exit button is hidden.
/// - makeMapContent: Custom maplibre symbols to display on the map view.
Expand All @@ -44,11 +47,13 @@ public struct PortraitNavigationView: View, CustomizableNavigatingInnerGridView
camera: Binding<MapViewCamera>,
navigationCamera: MapViewCamera = .automotiveNavigation(),
navigationState: NavigationState?,
minimumSafeAreaInsets: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
onTapExit: (() -> Void)? = nil,
@MapViewContentBuilder makeMapContent: () -> [StyleLayerDefinition] = { [] }
) {
self.styleURL = styleURL
self.navigationState = navigationState
self.minimumSafeAreaInsets = minimumSafeAreaInsets
self.onTapExit = onTapExit

userLayers = makeMapContent()
Expand Down Expand Up @@ -90,7 +95,7 @@ public struct PortraitNavigationView: View, CustomizableNavigatingInnerGridView
midLeading?()
} bottomTrailing: {
bottomTrailing?()
}
}.complementSafeAreaInsets(parentGeometry: geometry, minimumInset: minimumSafeAreaInsets)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion apple/Sources/FerrostarSwiftUI/Views/ArrivalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public struct ArrivalView: View {
}
.padding(.leading, 32)
.padding(.trailing, 12)
.padding(.vertical, 16)
.padding(.vertical, 8)
.background(theme.backgroundColor)
.clipShape(.rect(cornerRadius: 48))
.shadow(radius: 12)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public struct NavigationUIBanner<Label: View>: View {
/// The basic Ferrostar SwiftUI button style.
///
/// - Parameters:
/// - action: The action the button performs on tap.
/// - severity: The severity of the banner.
/// - backgroundColor: The capsule's background color.
/// - label: The label subview.
public init(
Expand Down
Loading
Loading