Skip to content

Commit

Permalink
visionOS user guide and design update (#2010)
Browse files Browse the repository at this point in the history
  • Loading branch information
persidskiy committed Feb 1, 2024
1 parent 13eb522 commit 3feb203
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ struct StandardStyleImportExample: View {
}
.padding(10)
.floating(RoundedRectangle(cornerRadius: 10))
.limitPaneWidth()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,96 @@ import SwiftUI

@available(iOS 14.0, *)
struct StandardStyleLocationsExample: View {
@State private var lightPreset: StandardLightPreset = .day
@State private var poi = true
@State private var transitLabels = true
@State private var placeLabels = true
@State private var roadLabels = true
@State private var selectedBookmark = Location.all.first!
@State private var viewport: Viewport = Location.all.first!.viewport
/// This model is created in root application views for each platform:
///
/// - ``VisionOSMain`` for visionOS
/// - ``SwiftUIWrapper`` view for iOS
@EnvironmentObject var model: StandardStyleLocationsModel
@State private var settingsHeight: CGFloat = 0

#if swift(>=5.9) && os(visionOS)
@Environment(\.openWindow) var openWindow
@Environment(\.dismissWindow) var dismissWindow
#endif

var body: some View {
Map(viewport: $viewport)
Map(viewport: $model.viewport)
.mapStyle(.standard(
lightPreset: lightPreset,
showPointOfInterestLabels: poi,
showTransitLabels: transitLabels,
showPlaceLabels: placeLabels,
showRoadLabels: roadLabels))
lightPreset: model.lightPreset,
showPointOfInterestLabels: model.poi,
showTransitLabels: model.transitLabels,
showPlaceLabels: model.placeLabels,
showRoadLabels: model.roadLabels))
// Center of the map will be translated in order to accommodate settings panel
.additionalSafeAreaInsets(.bottom, settingsHeight)
.ignoresSafeArea()
#if swift(>=5.9) && os(visionOS)
.onAppear {
openWindow(id: "standard-style-locations-settings")
}
.onDisappear {
dismissWindow(id: "standard-style-locations-settings")
}
#else
// On iOS the settings pane will be placed in an overlay view.
.safeOverlay(alignment: .bottom) {
settingsPanel
StandardStyleLocationsSettings()
.floating(RoundedRectangle(cornerRadius: 10))
.limitPaneWidth()
.background(GeometryReader() { proxy in
Color.clear.onAppear() { settingsHeight = proxy.size.height }
})
}
.onChange(of: selectedBookmark) { newValue in
viewport = newValue.viewport
#endif
.onChange(of: model.selectedBookmark) { newValue in
model.viewport = newValue.viewport
}
}
}

@ViewBuilder
var settingsPanel: some View {
@available(iOS 14.0, *)
class StandardStyleLocationsModel: ObservableObject {
@Published var lightPreset: StandardLightPreset = .day
@Published var poi = true
@Published var transitLabels = true
@Published var placeLabels = true
@Published var roadLabels = true
@Published var selectedBookmark = Location.all.first!
@Published var viewport: Viewport = Location.all.first!.viewport

struct Location: Equatable, Identifiable {
var id: String { title }
var title: String
var viewport: Viewport

static let all = [
Location(title: "Globe", viewport: .camera(center: .init(latitude: 27.2, longitude: -26.9), zoom: 1.53, bearing: 0, pitch: 0)),
Location(title: "Europe", viewport: .camera(center: .init(latitude: 47.29, longitude: 0), zoom: 3.28, bearing: 0, pitch: 0)),
Location(title: "Paris", viewport: .camera(center: .init(latitude: 48.8603, longitude: 2.2932), zoom: 15.58, bearing: 337.89, pitch: 59.67)),
Location(title: "Japan", viewport: .camera(center: .init(latitude: 36.11, longitude: 138.239), zoom: 6.15, bearing: -85.9, pitch: 61)),
Location(title: "Washington DC", viewport: .camera(center: .init(latitude: 38.915, longitude: -76.972), zoom: 7.16, bearing: 0, pitch: 0)),
Location(title: "Amsterdam", viewport: .camera(center: .init(latitude: 52.344, longitude: 4.89), zoom: 10.33, bearing: 0, pitch: 66)),
Location(title: "Brasília", viewport: .camera(center: .init(latitude: -15.792, longitude: -47.888), zoom: 12.21, bearing: -25.8, pitch: 28)),
Location(title: "Chicago", viewport: .camera(center: .init(latitude: 41.8812, longitude: -87.62855), zoom: 14.12, bearing: 0, pitch: 0)),
Location(title: "Brussels", viewport: .camera(center: .init(latitude: 50.8443, longitude: 4.364), zoom: 15.75, bearing: -113.6, pitch: 38)),
Location(title: "New York", viewport: .camera(center: .init(latitude: 40.7488, longitude: -73.9682), zoom: 16.41, bearing: 96.8, pitch: 38)),
Location(title: "San Diego", viewport: .camera(center: .init(latitude: 32.7062, longitude: -117.1595), zoom: 18.77, bearing: -53.1, pitch: 72))
]
}
}

@available(iOS 14.0, *)
struct StandardStyleLocationsSettings: View {
@EnvironmentObject var model: StandardStyleLocationsModel
var body: some View {
VStack(alignment: .leading) {
SelectorView(data: Location.all, selection: $selectedBookmark) { b in
SelectorView(data: StandardStyleLocationsModel.Location.all,
selection: $model.selectedBookmark) { b in
Text(b.title)
}
HStack {
Text("Light")
Picker("Light", selection: $lightPreset) {
Picker("Light", selection: $model.lightPreset) {
Text("Dawn").tag(StandardLightPreset.dawn)
Text("Day").tag(StandardLightPreset.day)
Text("Dusk").tag(StandardLightPreset.dusk)
Expand All @@ -50,41 +103,18 @@ struct StandardStyleLocationsExample: View {
HStack {
Text("Labels")
Group {
Toggle("Poi", isOn: $poi)
Toggle("Transit", isOn: $transitLabels)
Toggle("Places", isOn: $placeLabels)
Toggle("Roads", isOn: $roadLabels)
Toggle("Poi", isOn: $model.poi)
Toggle("Transit", isOn: $model.transitLabels)
Toggle("Places", isOn: $model.placeLabels)
Toggle("Roads", isOn: $model.roadLabels)
}
.fixedSize()
.font(.footnote)
}.toggleStyleButton()
}
.padding(10)
.floating(RoundedRectangle(cornerRadius: 10))
.background(GeometryReader() { proxy in
Color.clear.onAppear() { settingsHeight = proxy.size.height }
})
}
}

@available(iOS 13.0, *)
private struct Location: Equatable, Identifiable {
var id: String { title }
var title: String
var viewport: Viewport

static let all = [
Location(title: "Globe", viewport: .camera(center: .init(latitude: 27.2, longitude: -26.9), zoom: 1.53, bearing: 0, pitch: 0)),
Location(title: "Europe", viewport: .camera(center: .init(latitude: 47.29, longitude: 0), zoom: 3.28, bearing: 0, pitch: 0)),
Location(title: "Paris", viewport: .camera(center: .init(latitude: 48.8603, longitude: 2.2932), zoom: 15.58, bearing: 337.89, pitch: 59.67)),
Location(title: "Japan", viewport: .camera(center: .init(latitude: 36.11, longitude: 138.239), zoom: 6.15, bearing: -85.9, pitch: 61)),
Location(title: "Washington DC", viewport: .camera(center: .init(latitude: 38.915, longitude: -76.972), zoom: 7.16, bearing: 0, pitch: 0)),
Location(title: "Amsterdam", viewport: .camera(center: .init(latitude: 52.344, longitude: 4.89), zoom: 10.33, bearing: 0, pitch: 66)),
Location(title: "Brasília", viewport: .camera(center: .init(latitude: -15.792, longitude: -47.888), zoom: 12.21, bearing: -25.8, pitch: 28)),
Location(title: "Chicago", viewport: .camera(center: .init(latitude: 41.8812, longitude: -87.62855), zoom: 14.12, bearing: 0, pitch: 0)),
Location(title: "Brussels", viewport: .camera(center: .init(latitude: 50.8443, longitude: 4.364), zoom: 15.75, bearing: -113.6, pitch: 38)),
Location(title: "New York", viewport: .camera(center: .init(latitude: 40.7488, longitude: -73.9682), zoom: 16.41, bearing: 96.8, pitch: 38)),
Location(title: "San Diego", viewport: .camera(center: .init(latitude: 32.7062, longitude: -117.1595), zoom: 18.77, bearing: -53.1, pitch: 72))
]
}

@available(iOS 13.0, *)
Expand Down
16 changes: 14 additions & 2 deletions Apps/Examples/Examples/SwiftUI Examples/SwiftUIRoot.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import SwiftUI
import UIKit
@_spi(Experimental) import MapboxMaps

@available(iOS 14.0, *)
struct SwiftUIRoot: View {
var body: some View {
Expand All @@ -21,7 +20,9 @@ struct SwiftUIRoot: View {
} header: { Text("Annotations") }

Section {
#if !swift(>=5.9) || !os(visionOS)
ExampleLink("Query Rendered Features on tap", note: "Use MapReader and MapboxMap to query rendered features.", destination: FeaturesQueryExample())
#endif
ExampleLink("Clustering data", note: "Display GeoJSON data with clustering using custom layers and handle interactions with them.", destination: ClusteringExample())
} header: { Text("Use cases") }

Expand All @@ -35,9 +36,11 @@ struct SwiftUIRoot: View {
if #available(iOS 15.0, *) {
ExampleLink("Attribution url open via environment", note: "Works on iOS 15+", destination: AttributionEnvironmentURLOpen())
}
#if !swift(>=5.9) || !os(visionOS)
if #available(iOS 16.5, *) {
ExampleLink("Attribution dialog with presented sheet", destination: AttributionDialogueWithSheet())
}
#endif

} header: { Text("Testing Examples") }
}
Expand Down Expand Up @@ -95,10 +98,19 @@ private struct ToolbarContentWhenPresented<T: ToolbarContent>: ViewModifier {
}


@available(iOS 14.0, *)
struct SwiftUIWrapper: View {
// A model for StandardStyleLocationsExample.
@StateObject var locationsModel = StandardStyleLocationsModel()
var body: some View {
SwiftUIRoot()
.environmentObject(locationsModel)
}
}

@available(iOS 14.0, *)
func createSwiftUIExamplesController() -> UIViewController {
let controller = UIHostingController(rootView: SwiftUIRoot())
let controller = UIHostingController(rootView: SwiftUIWrapper())
controller.title = title
controller.modalPresentationStyle = .fullScreen
return controller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ struct PuckPlayground: View {
}
.padding(10)
.floating(RoundedRectangle(cornerRadius: 10))
.limitPaneWidth()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ extension View {
}
return EmptyView()
}

func limitPaneWidth() -> some View {
self.frame(maxWidth: 500)
}
}

@available(iOS 14.0, *)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct ViewAnnotationsExample: View {
.background(
Circle().fill(selected ? .red : .blue))
.animation(.spring(), value: selected)
.hoverEffect()
.onTapGesture {
selected.toggle()
}
Expand Down Expand Up @@ -89,6 +90,7 @@ struct ViewAnnotationsExample: View {
.padding(.horizontal, 10)
.padding(.vertical, 6)
.floating(RoundedRectangle(cornerRadius: 10))
.limitPaneWidth()
.onChangeOfSize { size in
overlayHeight = size.height
}
Expand Down
11 changes: 11 additions & 0 deletions Apps/Examples/Examples/VisionOSMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@ import SwiftUI

@main
struct VisionOSMain: App {
// A model for StandardStyleLocationsExample.
@StateObject var locationsModel = StandardStyleLocationsModel()
var body: some Scene {
WindowGroup {
SwiftUIRoot()
.environmentObject(locationsModel)
}

WindowGroup(id: "standard-style-locations-settings") {
StandardStyleLocationsSettings()
.fixedSize()
.padding(10)
.environmentObject(locationsModel)
}
.windowResizability(.contentSize)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Work with VisionOS

Use Mapbox Maps in native applications for Apple Vision Pro.

## Overview

Starting from version `11.2.0-beta.1` of Mapbox Maps, you can use the Mapbox Maps SDK in your native application for Apple Vision Pro. Check out the code samples in our [Examples](https://github.com/mapbox/mapbox-maps-ios/tree/main/Apps/Examples) application on visionOS.

![Standard Style on visionOS](https://static-assets.mapbox.com/maps/ios/documentation/maps_vision_os_locations.png)

- Note: Currently, visionOS is not supported in CocoaPods. Please use SPM or the binary [distribution](https://docs.mapbox.com/ios/maps/guides/install/) of MapboxMaps.

### Make use of Mapbox Maps on VisionOS

Working with Mapbox Maps on VisionOS is very similar to iOS. As the entry point to the map, you use ``Map`` if your application uses SwiftUI, and ``MapView`` if the application uses UIKit. You can find more information about SwiftUI support in <doc:SwiftUI-User-Guide>.

Most of the Maps SDK's features from iOS are supported on VisionOS out-of-the-box. However, there are some platform limitations discussed below.

### Limitations

#### Eye tracking feedback

All the interactive UI elements on Vision OS are expected to have visual feedback when the user looks at them. Currently, this effect is only available for native views and can be applied with [hoverEffect](https://developer.apple.com/documentation/swiftui/view/hovereffect(_:)). Mapbox Map renders most of the map content in Metal, which means the hover won't be available for map symbols, lines, polygons, and others. However, you can use [view annotations](https://docs.mapbox.com/ios/maps/guides/annotations/view-annotations/) to place interactive elements onto the map.

```swift
Map {
// With point annotations you can handle gestures, but you won't receive visual eye-tracking feedback.
PointAnnotation(coordinate: coordinate1)
.onTapGesture {
print("point annotation tapped")
}

// With view annotations, you can handle gestures and receive visual feedback.
MapViewAnnotation(coordinate: coordinate2) {
Circle()
.fill(.blue)
.hoverEffect()
.onTapGesture {
print("view annotation tapped")
}
}
}
```

#### Location services

The compass data is not available on the platform, which means if you use ``PuckBearing/heading`` as a puck bearing source, the user location puck will point to the north.

To fix that you can disable puck heading.

```swift
Map {
Puck2D() // By default, puck doesn't use heading and doesn't draw direction pointer.
}
```

Alternatively, you can use you own implementation of heading provider.

```swift
struct PuckDemo: View {
class LocationModel: ObservableObject {
var locationProvider = AppleLocationProvider()

// A custom heading data
@Published var heading: Heading = .init(direction: 15, accuracy: 1)
}

@StateObject var model = LocationModel()

var body: some View {
MapReader { proxy in
Map(initialViewport: .followPuck(zoom: 16, pitch: 0)) {
Puck2D(bearing: .heading)
}
.onAppear {
proxy.location?.override(
locationProvider: model.locationProvider.onLocationUpdate,
headingProvider: model.$heading.eraseToSignal())
}

}
}
}
```
1 change: 1 addition & 0 deletions Sources/MapboxMaps/Documentation.docc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The Mapbox Maps SDK for iOS is a public library for displaying interactive, thor

### Essentials
- <doc:Migrate-to-v11>
- <doc:Work-with-visionOS>
- <doc:SwiftUI>
- <doc:Map-View>
- <doc:Snapshotter-APIs>
Expand Down

0 comments on commit 3feb203

Please sign in to comment.