Skip to content

Commit

Permalink
refactor: Separation of model initialization properties from runtime …
Browse files Browse the repository at this point in the history
…updates
  • Loading branch information
waynewbishop committed Dec 8, 2024
1 parent 7655772 commit 93cb73a
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 144 deletions.
13 changes: 7 additions & 6 deletions GoogleMaps-SwiftUI/GoogleMaps-SwiftUI/Dialog/Dialog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@
import SwiftUI
import GoogleMaps


struct Dialog: View {

@StateObject private var viewModel = MapExamplesViewModel()
@State private var mapOptions: GMSMapViewOptions

init() {
_mapOptions = State(initialValue: MapExamplesViewModel().mapOptions)
}
private let mapOptions: GMSMapViewOptions = {
var options = GMSMapViewOptions()
options.camera = .camera(.googleplex)
return options
}()

var body: some View {
NavigationStack {
VStack(spacing: 0) {
// Top half - Map View centered on Seattle
GoogleMapView(options: $mapOptions)
GoogleMapView(options: mapOptions)
.frame(maxWidth: .infinity)
.frame(height: UIScreen.main.bounds.height * 0.5)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class MapExamplesViewModel: ObservableObject {
@Published var examples: [MapExample] = [
MapExample(
title: "Basic map",
description: "A simple map. Shows how to configure GMSMapViewOptions as a view modifier.",
description: "A simple map. Shows how to initalize and upddate map options.",
destination: AnyView(BasicMap())
),
MapExample(
Expand Down Expand Up @@ -54,11 +54,4 @@ class MapExamplesViewModel: ObservableObject {
)
]

//set intial dialog map options - override default zoom level
let mapOptions: GMSMapViewOptions = {
var options = GMSMapViewOptions()
options.camera = .camera(.googleplex, zoom: 13) //location predefined in a helper class
return options
}()

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,31 @@ import GoogleMaps

/// A SwiftUI wrapper for GMSMapView that displays a map with optional markers and configurable map type
struct GoogleMapView: UIViewRepresentable {
/// Binding to map options that can be updated from parent view
@Binding var options: GMSMapViewOptions

// Configuration properties - set at initialization
private let options: GMSMapViewOptions

/// Array of markers to display on the map
private let markers: [GMSMarker]

/// Type of map to display (normal, satellite, hybrid, terrain)
private let mapType: GMSMapViewType

// Runtime updatable properties
private var camera: GMSCameraPosition?
private var backgroundColor: UIColor?

/// Shared delegate instance to handle map interactions across all instances
/// Using static ensures callbacks work together when chaining modifiers
private static let mapDelegate = GoogleMapViewDelegate()


init(options: Binding<GMSMapViewOptions> = .constant(GMSMapViewOptions()),
markers: [GMSMarker] = [],
mapType: GMSMapViewType = .normal) {
self._options = options
self.markers = markers
self.mapType = mapType
}
init(options: GMSMapViewOptions,
markers: [GMSMarker] = [],
mapType: GMSMapViewType = .normal) {
self.options = options
self.markers = markers
self.mapType = mapType
}

/// Creates the underlying UIKit map view
func makeUIView(context: Context) -> GMSMapView {
Expand All @@ -52,52 +56,66 @@ struct GoogleMapView: UIViewRepresentable {

/// Updates the map view when SwiftUI state changes
func updateUIView(_ uiView: GMSMapView, context: Context) {

// Update camera if it exists and has changed
if let newCamera = options.camera, uiView.camera != newCamera {
uiView.camera = newCamera
}

// Update background color if it exists and has changed
if let newBackgroundColor = options.backgroundColor, uiView.backgroundColor != newBackgroundColor {
uiView.backgroundColor = newBackgroundColor
}
// Update runtime properties if set
if let camera = camera {
uiView.camera = camera
}

if let backgroundColor = backgroundColor {
uiView.backgroundColor = backgroundColor
}

// refresh markers to the map
// Refresh markers on the map
markers.forEach { marker in
marker.map = uiView
}

uiView.mapType = mapType // Update map type if changed
uiView.mapType = mapType // Update map type if changed
}
}

// MARK: - viewModifiers and callbacks
// MARK: - viewModifiers and Markers

extension GoogleMapView {
/// Updates map options
/// - Parameter options: New GMSMapViewOptions to apply
/// - Returns: New GoogleMapView instance with updated options
func mapOptions(_ options: GMSMapViewOptions) -> GoogleMapView {
GoogleMapView(options: .constant(options), markers: markers, mapType: mapType)
/// Updates the camera position of the map view during runtime
/// - Parameter position: New camera position to apply
/// - Returns: Updated GoogleMapView instance
func camera(_ position: GMSCameraPosition?) -> GoogleMapView {
var view = self
if let position = position {
view.camera = position
}
return view
}

/// Updates the background color of the map view during runtime
/// - Parameter color: New background color to apply
/// - Returns: Updated GoogleMapView instance
func backgroundColor(_ color: UIColor) -> GoogleMapView {
var view = self
view.backgroundColor = color
return view
}

/// Adds markers to the map
/// - Parameter markers: Array of GMSMarker objects to display
/// - Returns: New GoogleMapView instance with updated markers
func mapMarkers(_ markers: [GMSMarker]) -> GoogleMapView {
GoogleMapView(options: _options, markers: markers, mapType: mapType)
}
/// Changes the map display type
/// - Parameter type: GMSMapViewType to use (.normal, .satellite, etc)
/// - Returns: New GoogleMapView instance with updated map type
func mapType(_ type: GMSMapViewType) -> GoogleMapView {
GoogleMapView(options: options, markers: markers, mapType: type)
}


/// Changes the map display type
/// - Parameter type: GMSMapViewType to use (.normal, .satellite, etc)
/// - Returns: New GoogleMapView instance with updated map type
func mapType(_ type: GMSMapViewType) -> GoogleMapView {
GoogleMapView(options: _options, markers: markers, mapType: type)
}

/// Adds markers to the map
/// - Parameter markers: Array of GMSMarker objects to display
/// - Returns: New GoogleMapView instance with updated markers
func mapMarkers(_ markers: [GMSMarker]) -> GoogleMapView {
GoogleMapView(options: options, markers: markers, mapType: mapType)
}

}

// MARK: - View Callbacks

extension GoogleMapView {
/// Adds handler for map tap events
/// - Parameter handler: Closure called when map is tapped, providing tap coordinates
/// - Returns: Same GoogleMapView instance with updated tap handler
Expand All @@ -116,7 +134,6 @@ extension GoogleMapView {
}
}


extension View {
/// Configures the view to ignore safe areas except for the top
/// Useful for map views that should fill the screen below status bar
Expand Down
29 changes: 14 additions & 15 deletions GoogleMaps-SwiftUI/GoogleMaps-SwiftUI/Samples/BasicMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,28 @@ import SwiftUI
import GoogleMaps

struct BasicMap: View {
@State private var defaultOptions: GMSMapViewOptions = {

// Initial options - set once at creation
private let mapOptions: GMSMapViewOptions = {
var options = GMSMapViewOptions()
options.camera = .camera(.googleplex)
options.camera = .camera(.googleplex) // Initial camera position
return options
}()

// Runtime updatable property
@State private var newCamera: GMSCameraPosition?

var body: some View {
/*
GoogleMapView can be configured using view modifier:
GoogleMapView()
.mapOptions(newOptions)
- Preferred method for updating map options at initialization or during the view lifecycle
- Can be chained with other modifiers like mapMarkers() and mapType()
*/
VStack {
GoogleMapView()
.mapOptions(defaultOptions)
GoogleMapView(options: mapOptions)
.camera(newCamera) // Runtime camera updates
.ignoresSafeAreaExceptTop()

Button("Fly to New York") {
let newOptions = GMSMapViewOptions()
newOptions.camera = .camera(.newYork)
defaultOptions = newOptions
VStack(spacing: 12) {
Button("Fly to New York") {
newCamera = .camera(.newYork)
}

}
.padding()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,28 @@ import GoogleMaps

struct MapWithContainer: View {

@State private var defaultOptions: GMSMapViewOptions = {
private let mapOptions: GMSMapViewOptions = {
var options = GMSMapViewOptions()
// Initialize map centered on San Francisco
options.camera = .camera(.seattle)

// Or with custom zoom level for closer view
// options.camera = .camera(.sanFrancisco, zoom: 15)
options.camera = .camera(.seattle) // Initial camera centered on Seattle
return options
}()

var body: some View {
VStack(spacing: 16) {
GoogleMapView()
.mapOptions(defaultOptions)
.ignoresSafeAreaExceptTop() //optional property for samples display
.frame(maxWidth: .infinity, minHeight: 325)

VStack(alignment: .leading) {
Text("Working with Container Views")
.font(.headline)

Text("The GoogleMapView seamlessly integrates with SwiftUI layouts, allowing for standard modifiers like frame and padding.")
.font(.body)
.foregroundColor(.secondary)
}
.padding(.horizontal)
}
}
var body: some View {
VStack(spacing: 16) {
GoogleMapView(options: mapOptions)
.ignoresSafeAreaExceptTop() // Optional property for samples display
.frame(maxWidth: .infinity, minHeight: 325)
VStack(alignment: .leading) {
Text("Working with Container Views")
.font(.headline)
Text("The GoogleMapView seamlessly integrates with SwiftUI layouts, allowing for standard modifiers like frame and padding.")
.font(.body)
.foregroundColor(.secondary)
}
.padding(.horizontal)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import GoogleMaps
struct MapWithDelegate: View {

@State var response: String = ""
@State private var defaultOptions: GMSMapViewOptions = {

private var mapOptions: GMSMapViewOptions = {
var options = GMSMapViewOptions()
// Initialize map centered on San Francisco
options.camera = .camera(.sanFrancisco)
Expand All @@ -38,8 +39,7 @@ struct MapWithDelegate: View {
var body: some View {

VStack(spacing: 16) {
GoogleMapView()
.mapOptions(defaultOptions)
GoogleMapView(options: mapOptions)
.mapMarkers(multipleMarkers)
.onMarkerTapped { marker in
response = "Marker tapped at: \(marker.position)"
Expand Down
43 changes: 20 additions & 23 deletions GoogleMaps-SwiftUI/GoogleMaps-SwiftUI/Samples/MapWithMarker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,24 @@ import SwiftUI
import GoogleMaps

struct MapWithMarker: View {

@State private var defaultOptions: GMSMapViewOptions = {
var options = GMSMapViewOptions()
// Initialize map centered on San Francisco
options.camera = .camera(.sanFrancisco)

// Or with custom zoom level for closer view
// options.camera = .camera(.sanFrancisco, zoom: 15)
return options
}()

// Single marker example
let singleMarker = [
GMSMarker(position: .sanFrancisco)
]

var body: some View {
GoogleMapView()
.mapOptions(defaultOptions)
//Adds one or more markers to be displayed on the map
.mapMarkers(singleMarker)
}

private let mapOptions: GMSMapViewOptions = {

var options = GMSMapViewOptions()
// Initialize map centered on San Francisco
options.camera = .camera(.sanFrancisco)

// Or with custom zoom level for closer view
// options.camera = .camera(.sanFrancisco, zoom: 15)
return options
}()

// Single marker example - no @State needed since markers won't change during runtime
let markers = [
GMSMarker(position: .sanFrancisco)
]

var body: some View {
GoogleMapView(options: mapOptions)
.mapMarkers(markers)
}
}
Loading

0 comments on commit 93cb73a

Please sign in to comment.