Skip to content

Add visionOS support to UtilityNetworkTrace #1011

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 9 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 8 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: 1 addition & 1 deletion Examples/Examples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
1CC376D42ABA0B3700A83300 /* TableTopExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC376D32ABA0B3700A83300 /* TableTopExampleView.swift */; platformFilter = ios; };
4D19FCB52881C8F3002601E8 /* PopupExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D19FCB42881C8F3002601E8 /* PopupExampleView.swift */; };
4D995CD72B5B01DB00AD45FE /* FeatureFormExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B615012AD76158009D19B6 /* FeatureFormExampleView.swift */; platformFilters = (ios, maccatalyst, ); };
75230DAE28614369009AF501 /* UtilityNetworkTraceExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75230DAD28614369009AF501 /* UtilityNetworkTraceExampleView.swift */; platformFilters = (ios, maccatalyst, ); };
75230DAE28614369009AF501 /* UtilityNetworkTraceExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75230DAD28614369009AF501 /* UtilityNetworkTraceExampleView.swift */; };
75657E4827ABAC8400EE865B /* CompassExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75657E4727ABAC8400EE865B /* CompassExampleView.swift */; };
75C37C9227BEDBD800FC9DCE /* BookmarksExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75C37C9127BEDBD800FC9DCE /* BookmarksExampleView.swift */; };
75D41B2B27C6F21400624D7C /* ScalebarExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D41B2A27C6F21400624D7C /* ScalebarExampleView.swift */; };
Expand Down
61 changes: 42 additions & 19 deletions Examples/Examples/UtilityNetworkTraceExampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct UtilityNetworkTraceExampleView: View {
var body: some View {
GeometryReader { geometryProxy in
MapViewReader { mapViewProxy in
MapView(
let mapView = MapView(
map: map,
graphicsOverlays: [resultGraphicsOverlay]
)
Expand All @@ -54,24 +54,47 @@ struct UtilityNetworkTraceExampleView: View {
let publicSample = try? await ArcGISCredential.publicSample
ArcGISEnvironment.authenticationManager.arcGISCredentialStore.add(publicSample!)
}
.floatingPanel(
attributionBarHeight: attributionBarHeight,
backgroundColor: Color(uiColor: .systemGroupedBackground),
selectedDetent: $activeDetent,
horizontalAlignment: .trailing,
isPresented: .constant(true)
) {
UtilityNetworkTrace(
graphicsOverlay: $resultGraphicsOverlay,
map: map,
mapPoint: $mapPoint,
mapViewProxy: mapViewProxy
)
.floatingPanelDetent($activeDetent)
// Manually account for a device's bottom safe area when using a Floating Panel.
// See also #518.
.padding(.bottom, isPortraitOrientation ? geometryProxy.safeAreaInsets.bottom : nil)
}
#if os(visionOS)
mapView
.floatingPanel(
attributionBarHeight: attributionBarHeight,
selectedDetent: $activeDetent,
horizontalAlignment: .trailing,
isPresented: .constant(true)
) {
UtilityNetworkTrace(
graphicsOverlay: $resultGraphicsOverlay,
map: map,
mapPoint: $mapPoint,
mapViewProxy: mapViewProxy
)
.floatingPanelDetent($activeDetent)
// Manually account for a device's bottom safe area when using a Floating Panel.
// See also #518.
.padding(.bottom, isPortraitOrientation ? geometryProxy.safeAreaInsets.bottom : nil)
.padding(.top)
}
#else
mapView
.floatingPanel(
attributionBarHeight: attributionBarHeight,
backgroundColor: Color(uiColor: .systemGroupedBackground),
selectedDetent: $activeDetent,
horizontalAlignment: .trailing,
isPresented: .constant(true)
) {
UtilityNetworkTrace(
graphicsOverlay: $resultGraphicsOverlay,
map: map,
mapPoint: $mapPoint,
mapViewProxy: mapViewProxy
)
.floatingPanelDetent($activeDetent)
// Manually account for a device's bottom safe area when using a Floating Panel.
// See also #518.
.padding(.bottom, isPortraitOrientation ? geometryProxy.safeAreaInsets.bottom : nil)
}
#endif
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions Examples/ExamplesApp/Examples.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ extension ExampleList {
AnyExample("Floor Filter", content: FloorFilterExampleView()),
AnyExample("Overview Map", content: OverviewMapExampleView()),
AnyExample("Popup", content: PopupExampleView()),
AnyExample("Scalebar", content: ScalebarExampleView())
AnyExample("Scalebar", content: ScalebarExampleView()),
AnyExample("Utility Network Trace", content: UtilityNetworkTraceExampleView())
]
#if !os(visionOS)
examples.append(
contentsOf: [
AnyExample("Feature Form", content: FeatureFormExampleView()),
AnyExample("Search", content: SearchExampleView()),
AnyExample("Utility Network Trace", content: UtilityNetworkTraceExampleView())
AnyExample("Search", content: SearchExampleView())
] as [any Example]
)
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import SwiftUI
/// To see the `UtilityNetworkTrace` in action, check out the [Examples](https://github.com/Esri/arcgis-maps-sdk-swift-toolkit/tree/main/Examples/Examples)
/// and refer to [UtilityNetworkTraceExampleView.swift](https://github.com/Esri/arcgis-maps-sdk-swift-toolkit/blob/main/Examples/Examples/UtilityNetworkTraceExampleView.swift)
/// in the project. To learn more about using the `UtilityNetworkTrace` see the <doc:UtilityNetworkTraceTutorial>.
@available(visionOS, unavailable)
public struct UtilityNetworkTrace: View {
/// The proxy to provide access to map view operations.
private var mapViewProxy: MapViewProxy?
Expand Down Expand Up @@ -180,38 +179,38 @@ public struct UtilityNetworkTrace: View {
currentActivity = .viewingTraces(.viewingFeatureResults)
}
makeDetailSectionHeader(title: assetGroupName)
List {
ForEach(
assetTypeGroups.sorted(using: KeyPathComparator(\.key)),
id: \.key
) { (name, elements) in
Section(name) {
DisclosureGroup {
ForEach(elements, id: \.globalID) { element in
Button {
Task {
if let feature = await viewModel.feature(for: element),
let geometry = feature.geometry {
updateViewpoint(to: geometry.extent)
}
}
} label: {
Label {
Text(
"Object ID: \(element.objectID, format: .number.grouping(.never))",
bundle: .toolkitModule,
comment: "A string identifying a utility network object."
)
} icon: {
Image(systemName: "scope")
List(
assetTypeGroups.sorted(using: KeyPathComparator(\.key)),
id: \.key
) { (name, elements) in
Section(name) {
DisclosureGroup {
ForEach(elements, id: \.globalID) { element in
Button {
Task {
if let feature = await viewModel.feature(for: element),
let geometry = feature.geometry {
updateViewpoint(to: geometry.extent)
}
}
} label: {
Label(
String(
localized: "Object ID: \(element.objectID, format: .number.grouping(.never))",
bundle: .toolkitModule,
comment: "A string identifying a utility network object."
),
systemImage: "scope"
)
}
} label: {
Text(elements.count, format: .number)
.catalystPadding(4)
}
} label: {
Text(elements.count, format: .number)
.catalystPadding(4)
}
#if os(visionOS)
.listItemTint(.monochrome)
#endif
}
}
}
Expand All @@ -223,11 +222,9 @@ public struct UtilityNetworkTrace: View {
Text(String.noConfigurationsAvailable)
} else {
ForEach(viewModel.configurations.sorted { $0.name < $1.name }, id: \.name) { configuration in
Button {
Button(configuration.name) {
viewModel.setPendingTrace(configuration: configuration)
currentActivity = .creatingTrace(nil)
} label: {
Text(configuration.name)
}
.listRowBackground(configuration.name == viewModel.pendingTrace.configuration?.name ? Color.secondary.opacity(0.5) : nil)
}
Expand All @@ -237,13 +234,14 @@ public struct UtilityNetworkTrace: View {
/// Displays the list of available networks.
@ViewBuilder private var networksList: some View {
ForEach(viewModel.networks, id: \.name) { network in
Text(network.name)
.lineLimit(1)
.listRowBackground(network.name == viewModel.network?.name ? Color.secondary.opacity(0.5) : nil)
.onTapGesture {
viewModel.setNetwork(network)
currentActivity = .creatingTrace(nil)
}
Button {
viewModel.setNetwork(network)
currentActivity = .creatingTrace(nil)
} label: {
Text(network.name)
.lineLimit(1)
}
.listRowBackground(network.name == viewModel.network?.name ? Color.secondary.opacity(0.5) : nil)
}
}

Expand Down Expand Up @@ -311,20 +309,21 @@ public struct UtilityNetworkTrace: View {
Button(String.deleteAllStartingPoints, systemImage: "trash") {
deleteAllStartingPointsConfirmationIsPresented = true
}
#if !os(visionOS)
.buttonStyle(.plain)
#endif
.labelStyle(.iconOnly)
.confirmationDialog(
String.deleteAllStartingPoints,
isPresented: $deleteAllStartingPointsConfirmationIsPresented
String.deleteAllStartingPointsQuestion,
isPresented: $deleteAllStartingPointsConfirmationIsPresented,
titleVisibility: .visible
) {
Button(String.deleteAllStartingPoints, role: .destructive) {
viewModel.pendingTrace.startingPoints.forEach { startingPoint in
Button(String.deleteButtonLabel, role: .destructive) {
for startingPoint in viewModel.pendingTrace.startingPoints {
viewModel.deleteStartingPoint(startingPoint)
externalStartingPoints.removeAll()
}
}
} message: {
Text(String.deleteAllStartingPointsMessage)
}
// Override default uppercase capitalization for list
// section headers on iOS and iPadOS.
Expand All @@ -348,7 +347,12 @@ public struct UtilityNetworkTrace: View {
viewModel.pendingTrace.userDidSpecifyName = true
}
.multilineTextAlignment(.trailing)
#if os(visionOS)
.contentShape(.hoverEffect, .rect(cornerRadius: 12))
.hoverEffect()
#else
.foregroundStyle(.blue)
#endif
}
ColorPicker(String.colorLabel, selection: $viewModel.pendingTrace.color)
Toggle(String.zoomToResult, isOn: $shouldZoomOnTraceCompletion)
Expand Down Expand Up @@ -436,15 +440,18 @@ public struct UtilityNetworkTrace: View {
) {
if let selectedTrace = viewModel.selectedTrace {
ForEach(selectedTrace.assetGroupNames.sorted(), id: \.self) { assetGroupName in
HStack {
Text(assetGroupName)
Spacer()
Text(selectedTrace.elements(inAssetGroupNamed: assetGroupName).count, format: .number)
}
.foregroundStyle(.blue)
.contentShape(.rect)
.onTapGesture {
Button {
currentActivity = .viewingTraces(.viewingElementGroup(named: assetGroupName))
} label: {
HStack {
Text(assetGroupName)
Spacer()
Text(selectedTrace.elements(inAssetGroupNamed: assetGroupName).count, format: .number)
}
#if !os(visionOS)
.foregroundStyle(.blue)
.contentShape(.rect)
#endif
}
}
}
Expand Down Expand Up @@ -584,18 +591,23 @@ public struct UtilityNetworkTrace: View {
viewModel.setTerminalConfigurationFor(startingPoint: selectedStartingPoint!, to: newValue)
}
) {
ForEach(viewModel.pendingTrace.startingPoints.first {
$0 == selectedStartingPoint
}?.utilityElement?.assetType.terminalConfiguration?.terminals ?? [], id: \.self) {
ForEach(
viewModel.pendingTrace.startingPoints
.first(where: { $0 == selectedStartingPoint })?
.utilityElement?.assetType.terminalConfiguration?.terminals ?? [],
id: \.self
) {
Text($0.name)
}
}
#if !os(visionOS)
.foregroundStyle(.blue)
#endif
}
}
Section(String.attributesSectionTitle) {
ForEach(Array(selectedStartingPoint!.geoElement.attributes.sorted(by: { $0.key < $1.key})), id: \.key) { item in
HStack{
HStack {
Text(item.key)
Spacer()
Text(item.value as? String ?? "")
Expand Down Expand Up @@ -802,16 +814,10 @@ public struct UtilityNetworkTrace: View {
/// - Parameter title: The button's title.
/// - Parameter action: The action to be performed.
/// - Returns: The configured button.
private func makeBackButton(title: String, _ action: @escaping () -> Void) -> some View {
Button { action() } label: {
Label {
Text(title)
} icon: {
Image(systemName: "chevron.backward")
}
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
private func makeBackButton(title: String, action: @escaping () -> Void) -> some View {
Button(title, systemImage: "chevron.backward", action: action)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
}

/// Returns a section header.
Expand Down Expand Up @@ -890,11 +896,11 @@ private extension String {
)
}

static var deleteAllStartingPointsMessage: Self {
static var deleteAllStartingPointsQuestion: Self {
.init(
localized: "All starting points will be deleted.",
localized: "Delete all starting points?",
bundle: .toolkitModule,
comment: "A message describing the outcome of clearing all starting points."
comment: "The title of the dialogue confirming deletion of all points."
)
}

Expand Down Expand Up @@ -1064,8 +1070,6 @@ private extension String {
}
}


@available(visionOS, unavailable)
public extension UtilityNetworkTrace /* Deprecated */ {
/// A graphical interface to run pre-configured traces on a map's utility networks.
/// - Parameters:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import ArcGIS
import UIKit

/// A starting point of a utility network trace.
@available(visionOS, unavailable)
public struct UtilityNetworkTraceStartingPoint: Sendable {
/// The geo element to be used as a starting point.
var geoElement: GeoElement
Expand Down Expand Up @@ -50,7 +49,6 @@ public struct UtilityNetworkTraceStartingPoint: Sendable {
}
}

@available(visionOS, unavailable)
extension UtilityNetworkTraceStartingPoint: Hashable {
public static func == (lhs: UtilityNetworkTraceStartingPoint, rhs: UtilityNetworkTraceStartingPoint) -> Bool {
lhs.id == rhs.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import ArcGIS
import SwiftUI

@available(visionOS, unavailable)
extension UtilityNetworkTraceViewModel {
/// A trace performed on a utility network.
struct Trace {
Expand Down Expand Up @@ -74,7 +73,6 @@ extension UtilityNetworkTraceViewModel {
}
}

@available(visionOS, unavailable)
extension UtilityNetworkTraceViewModel.Trace {
/// Finds the set of utility elements returned by the trace that belong to the provided
/// asset group, grouped by type.
Expand Down Expand Up @@ -143,7 +141,6 @@ extension UtilityNetworkTraceViewModel.Trace {
}
}

@available(visionOS, unavailable)
extension UtilityNetworkTraceViewModel.Trace: Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
Expand Down
Loading