11import UIKit
2+ import SwiftUI
23import MapboxMaps
34
45final class TrackingModeExample : UIViewController , ExampleProtocol {
5- private var cancelables = Set < AnyCancelable > ( )
66 private var locationTrackingCancellation : AnyCancelable ?
77
88 private var mapView : MapView !
9- private lazy var toggleBearingImageButton = UIButton ( frame: . zero)
10- private lazy var trackingButton = UIButton ( frame: . zero)
11- private lazy var styleToggle = UISegmentedControl ( items: Style . allCases. map ( \. name) )
129 private var style : Style = . standard {
1310 didSet {
1411 mapView. mapboxMap. styleURI = style. uri
1512 }
1613 }
1714 private var showsBearingImage : Bool = false {
1815 didSet {
19- syncPuckAndButton ( )
16+ let configuration = Puck2DConfiguration . makeDefault ( showBearing: showsBearingImage)
17+ mapView. location. options. puckType = . puck2D( configuration)
2018 }
2119 }
2220
@@ -31,11 +29,7 @@ final class TrackingModeExample: UIViewController, ExampleProtocol {
3129 mapView. autoresizingMask = [ . flexibleWidth, . flexibleHeight]
3230 view. addSubview ( mapView)
3331
34- addStyleToggle ( )
35-
36- // Setup and create button for toggling show bearing image
37- setupToggleShowBearingImageButton ( )
38- setupLocationButton ( )
32+ setupSettingsButton ( )
3933
4034 // Add user position icon to the map with location indicator layer
4135 mapView. location. options. puckType = . puck2D( )
@@ -53,74 +47,72 @@ final class TrackingModeExample: UIViewController, ExampleProtocol {
5347 finish ( )
5448 }
5549
56- @objc func showHideBearingImage( ) {
57- showsBearingImage. toggle ( )
58- }
59-
60- func syncPuckAndButton( ) {
61- // Update puck config
62- let configuration = Puck2DConfiguration . makeDefault ( showBearing: showsBearingImage)
50+ private func setupSettingsButton( ) {
51+ let buttonView = SettingsButtonView ( onTap: openSettings)
52+ let hostingController = UIHostingController ( rootView: buttonView)
53+ hostingController. view. backgroundColor = . clear
54+ hostingController. view. translatesAutoresizingMaskIntoConstraints = false
6355
64- mapView. location. options. puckType = . puck2D( configuration)
65-
66- // Update button title
67- let title : String = showsBearingImage ? " Hide bearing image " : " Show bearing image "
68- toggleBearingImageButton. setTitle ( title, for: . normal)
69- }
70-
71- private func setupToggleShowBearingImageButton( ) {
72- // Styling
73- toggleBearingImageButton. backgroundColor = . systemBlue
74- toggleBearingImageButton. addTarget ( self , action: #selector( showHideBearingImage) , for: . touchUpInside)
75- toggleBearingImageButton. setTitleColor ( . white, for: . normal)
76- toggleBearingImageButton. layer. cornerRadius = 4
77- toggleBearingImageButton. clipsToBounds = true
78- toggleBearingImageButton. contentEdgeInsets = UIEdgeInsets ( top: 8 , left: 16 , bottom: 8 , right: 16 )
79-
80- toggleBearingImageButton. translatesAutoresizingMaskIntoConstraints = false
81- view. addSubview ( toggleBearingImageButton)
82-
83- // Constraints
84- toggleBearingImageButton. leadingAnchor. constraint ( equalTo: view. leadingAnchor, constant: 20.0 ) . isActive = true
85- toggleBearingImageButton. trailingAnchor. constraint ( equalTo: view. trailingAnchor, constant: - 20.0 ) . isActive = true
86- toggleBearingImageButton. bottomAnchor. constraint ( equalTo: view. safeAreaLayoutGuide. bottomAnchor, constant: - 70.0 ) . isActive = true
87-
88- syncPuckAndButton ( )
89- }
90-
91- private func setupLocationButton( ) {
92- trackingButton. addTarget ( self , action: #selector( switchTracking) , for: . touchUpInside)
93-
94- trackingButton. setImage ( UIImage ( systemName: " location.fill " ) , for: . normal)
95-
96- let buttonWidth = 44.0
97- trackingButton. translatesAutoresizingMaskIntoConstraints = false
98- trackingButton. backgroundColor = UIColor ( white: 0.97 , alpha: 1 )
99- trackingButton. layer. cornerRadius = buttonWidth/ 2
100- trackingButton. layer. shadowOffset = CGSize ( width: - 1 , height: 1 )
101- trackingButton. layer. shadowColor = UIColor . black. cgColor
102- trackingButton. layer. shadowOpacity = 0.5
103- view. addSubview ( trackingButton)
56+ addChild ( hostingController)
57+ view. addSubview ( hostingController. view)
58+ hostingController. didMove ( toParent: self )
10459
10560 NSLayoutConstraint . activate ( [
106- trackingButton. trailingAnchor. constraint ( equalTo: toggleBearingImageButton. trailingAnchor) ,
107- trackingButton. bottomAnchor. constraint ( equalTo: toggleBearingImageButton. topAnchor, constant: - 20 ) ,
108- trackingButton. widthAnchor. constraint ( equalTo: trackingButton. heightAnchor) ,
109- trackingButton. widthAnchor. constraint ( equalToConstant: buttonWidth)
61+ hostingController. view. trailingAnchor. constraint ( equalTo: view. trailingAnchor, constant: - 20 ) ,
62+ hostingController. view. bottomAnchor. constraint ( equalTo: view. safeAreaLayoutGuide. bottomAnchor, constant: - 120 )
11063 ] )
11164 }
11265
113- @objc func switchStyle( sender: UISegmentedControl ) {
114- style = Style ( rawValue: sender. selectedSegmentIndex) ?? . satelliteStreets
115- }
116-
117- @objc func switchTracking( ) {
118- let isTrackingNow = locationTrackingCancellation != nil
119- if isTrackingNow {
120- stopTracking ( )
121- } else {
122- startTracking ( )
66+ private func openSettings( ) {
67+ let controlsSection = SettingsSection (
68+ title: " Controls " ,
69+ controls: [
70+ . toggle(
71+ title: " Track user location " ,
72+ isOn: Binding (
73+ get: { [ weak self] in self ? . locationTrackingCancellation != nil } ,
74+ set: { [ weak self] in $0 ? self ? . startTracking ( ) : self ? . stopTracking ( ) }
75+ )
76+ ) ,
77+ . toggle(
78+ title: " Show bearing image " ,
79+ isOn: Binding (
80+ get: { [ weak self] in self ? . showsBearingImage ?? false } ,
81+ set: { [ weak self] in self ? . showsBearingImage = $0 }
82+ )
83+ ) ,
84+ . segmentedPicker(
85+ title: " Map style " ,
86+ options: Style . allCases. map ( \. name) ,
87+ selection: Binding (
88+ get: { [ weak self] in self ? . style. rawValue ?? 0 } ,
89+ set: { [ weak self] newValue in
90+ self ? . style = Style ( rawValue: newValue) ?? . standard
91+ }
92+ )
93+ )
94+ ]
95+ )
96+
97+ let docsSection = SettingsSection (
98+ title: " Docs " ,
99+ controls: [
100+ . link(
101+ title: " Tracking mode example " ,
102+ url: URL ( string: " https://docs.mapbox.com/ios/maps/examples/tracking-mode/ " ) !
103+ )
104+ ]
105+ )
106+
107+ let settingsView = SettingsSheet ( sections: [ controlsSection, docsSection] )
108+ let hostingController = UIHostingController ( rootView: settingsView)
109+
110+ if let sheet = hostingController. sheetPresentationController {
111+ sheet. detents = [ . medium( ) ]
112+ sheet. prefersGrabberVisible = true
123113 }
114+
115+ present ( hostingController, animated: true )
124116 }
125117
126118 private func startTracking( ) {
@@ -130,24 +122,11 @@ final class TrackingModeExample: UIViewController, ExampleProtocol {
130122 to: CameraOptions ( center: location. coordinate, zoom: 15 ) ,
131123 duration: 1.3 )
132124 }
133-
134- trackingButton. setImage ( UIImage ( systemName: " location.fill " ) , for: . normal)
135125 }
136126
137- func stopTracking( ) {
138- trackingButton. setImage ( UIImage ( systemName: " location " ) , for: . normal)
127+ private func stopTracking( ) {
139128 locationTrackingCancellation = nil
140129 }
141-
142- func addStyleToggle( ) {
143- // Create a UISegmentedControl to toggle between map styles
144- styleToggle. selectedSegmentIndex = style. rawValue
145- styleToggle. addTarget ( self , action: #selector( switchStyle ( sender: ) ) , for: . valueChanged)
146- styleToggle. translatesAutoresizingMaskIntoConstraints = false
147-
148- // set the segmented control as the title view
149- navigationItem. titleView = styleToggle
150- }
151130}
152131
153132extension TrackingModeExample : GestureManagerDelegate {
@@ -162,6 +141,11 @@ extension TrackingModeExample: GestureManagerDelegate {
162141
163142extension TrackingModeExample {
164143 private enum Style : Int , CaseIterable {
144+ case standard
145+ case light
146+ case satelliteStreets
147+ case customUri
148+
165149 var name : String {
166150 switch self {
167151 case . standard:
@@ -188,10 +172,17 @@ extension TrackingModeExample {
188172 return . init( url: localStyleURL) !
189173 }
190174 }
175+ }
176+ }
191177
192- case standard
193- case light
194- case satelliteStreets
195- case customUri
178+ // MARK: - SwiftUI Settings Button
179+ private struct SettingsButtonView : View {
180+ let onTap : ( ) -> Void
181+
182+ var body : some View {
183+ Button ( action: onTap) {
184+ Image ( systemName: " slider.horizontal.3 " )
185+ }
186+ . buttonStyle ( MapFloatingButtonStyle ( ) )
196187 }
197188}
0 commit comments