I'm using the iOS Google Maps SDK to show maps in my app.
On my maps, I also show some map markers.
This has been working great, however I want the map to ignore the safe areas of the device so that it stretches to the full height (behind any notches and bottom tab bars).
The problem I'm running into is that when I apply .ignoresSafeArea() to the map, all of my map markers get cut off, like so:
This only happens when I add .ignoresSafeArea(). Without it, the markers show normally.
I'm not sure what I'm doing wrong.
Here is my SwiftUI code that shows the map:
import GoogleMaps
struct MapView: View {
@EnvironmentObject var apiManager: ApiManager
private var mapOptions: GMSMapViewOptions {
var options = GMSMapViewOptions()
return options
}
var body: some View {
ZStack {
GoogleMapView(options: mapOptions)
.mapMarkers(MapHelper.convertToGoogleMarkers(markers: apiManager.markers))
.ignoresSafeArea()
}
.task {
getMapMarkers()
}
.navigationBarHidden(true)
}
func getMapMarkers() {
Task {
await apiManager.fetchAllMapMarkers()
}
}
}
And here is the UIViewRepresentable for GoogleMapView:
import SwiftUI
import GoogleMaps
/// A SwiftUI wrapper for GMSMapView that displays a map with optional markers and configurable map type
struct GoogleMapView: UIViewRepresentable {
// Configuration properties - set at initialization
private let options: GMSMapViewOptions
/// Array of markers to display on the map
private var 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: 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 {
// Initialize map with current options
let mapView = GMSMapView(options: options)
mapView.overrideUserInterfaceStyle = .unspecified
mapView.mapType = mapType
// Set shared delegate to handle interactions
mapView.delegate = Self.mapDelegate
return mapView
}
/// Updates the map view when SwiftUI state changes
func updateUIView(_ uiView: GMSMapView, context: Context) {
// Update runtime properties if set
if let camera = camera {
uiView.camera = camera
}
if let backgroundColor = backgroundColor {
uiView.backgroundColor = backgroundColor
}
//clears all markers and polylines
uiView.clear()
// Refresh markers on the map
markers.forEach { marker in
marker.map = uiView
}
uiView.mapType = mapType // Update map type if changed
}
}
class GoogleMapViewDelegate: NSObject, GMSMapViewDelegate {
var tapHandler: ((CLLocationCoordinate2D) -> Void)?
var markerTapHandler: ((GMSMarker) -> Bool)?
/// Called by GMSMapView when user taps the map at a specific coordinate
/// - Parameters:
/// - mapView: The GMSMapView that detected the tap
/// - coordinate: The geographic coordinate where the tap occurred
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
tapHandler?(coordinate) // Forward tap to handler if one is set
}
/// Called by GMSMapView when user taps a marker on the map
/// - Parameters:
/// - mapView: The GMSMapView that detected the tap
/// - marker: The GMSMarker that was tapped
/// - Returns: true if tap was handled by the app, false to allow default marker behavior
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
return markerTapHandler?(marker) ?? false // Forward to handler or use default behavior
}
}
// MARK: - viewModifiers and Markers
extension GoogleMapView {
/// 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
}
/// 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 {
var view = self
view.markers = markers
return view
}
}
// 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
func onMapTapped(_ handler: @escaping (CLLocationCoordinate2D) -> Void) -> GoogleMapView {
Self.mapDelegate.tapHandler = handler
return self
}
/// Adds handler for marker tap events
/// - Parameter handler: Closure called when marker is tapped
/// - Returns: Same GoogleMapView instance with updated marker handler
/// Return true from handler to indicate tap was handled
func onMarkerTapped(_ handler: @escaping (GMSMarker) -> Bool) -> GoogleMapView {
Self.mapDelegate.markerTapHandler = handler
return self
}
}
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
/// - Returns: Modified view that extends to screen edges except top
func ignoresSafeAreaExceptTop() -> some View {
ignoresSafeArea(.container, edges: [.bottom, .horizontal])
}
}
Lastly, here is my convertToGoogleMarkers() function:
static func convertToGoogleMarkers(markers: Array<MapMarker>, showOrder: Bool = false) -> Array<GMSMarker> {
return markers.enumerated().map { (index, mapMarker) in
let marker = GMSMarker(position: CLLocationCoordinate2D(
latitude: Double(mapMarker.latitude)!,
longitude: Double(mapMarker.longitude)!
))
// Set the Place as the userData
marker.userData = mapMarker
let markerView = MarkerUIView(marker: mapMarker, showOrder: showOrder)
let size = markerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
markerView.frame = CGRect(origin: .zero, size: size)
marker.iconView = markerView
if let order = mapMarker.order {
if (mapMarker.date != nil) {
marker.zIndex = Int32(10000 - order)
} else {
marker.zIndex = Int32(1000 - order)
}
}
return marker
}
}
Edit:
It seems that if I comment out this code from my convertToGoogleMarkers() function, the default google maps markers show fine. What could be the issue with my custom markers?
// let markerView = MarkerUIView(marker: mapMarker, showOrder: showOrder)
//
// let size = markerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
// markerView.frame = CGRect(origin: .zero, size: size)
//
// marker.iconView = markerView
Here is my MapMarkerView:
import SwiftUI
import MapKit
struct MapMarkerView: View {
var marker: MapMarker2
var showOrder: Bool = false
let circleBorderWidth: CGFloat = 4
var body: some View {
VStack(spacing: 0) {
ZStack {
let color = marker.hexColor != nil ? Color(hex: marker.hexColor!) : Color.wireframe
if (showOrder && marker.date != nil) {
Circle()
.fill(color)
.frame(width: 40, height: 40)
.shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 4)
.overlay(
Circle()
.inset(by: circleBorderWidth / 2)
.stroke(Color.mapMarkerBackground, lineWidth: circleBorderWidth)
)
if let order = marker.order {
Text("\(order)")
.font(.custom("Inter-SemiBold", size: 18))
.foregroundColor(Color.white)
}
} else {
Circle()
.fill(Color.mapMarkerBackground)
.frame(width: 40, height: 40)
.shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 4)
.overlay(
ZStack {
Circle()
.fill(color)
.opacity(0.33)
Circle()
.inset(by: circleBorderWidth / 2)
.stroke(Color.mapMarkerBackground, lineWidth: circleBorderWidth)
}
)
AsyncImage(url: URL(string: marker.iconUrl ?? "")) { image in
image.resizable()
.frame(width: 24, height: 24)
} placeholder: {
Circle()
.fill(Color.mapMarkerBackground)
.frame(width: 40, height: 40)
}
}
}
Triangle()
.rotationEffect(.degrees(-180))
.foregroundColor(Color.mapMarkerBackground)
.frame(width: 12, height: 10)
.offset(y: -4)
}
}
}
class MarkerUIView: UIView {
private var hostingController: UIHostingController<AnyView>?
private var onTapAction: (() -> Void)?
init(marker: MapMarker2, showOrder: Bool = false) {
super.init(frame: .zero)
// Use AnyView so we can capture the binding
self.hostingController = UIHostingController(
rootView: AnyView(MapMarkerView(marker: marker, showOrder: showOrder))
)
guard let hostingView = hostingController?.view else { return }
// Make background transparent
hostingView.backgroundColor = .clear
// Add the SwiftUI view to the UIView hierarchy
addSubview(hostingView)
// Size the hosting controller's view
hostingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingView.leadingAnchor.constraint(equalTo: leadingAnchor),
hostingView.trailingAnchor.constraint(equalTo: trailingAnchor),
hostingView.topAnchor.constraint(equalTo: topAnchor),
hostingView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
sizeToFit()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Forward tap to the SwiftUI view
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
onTapAction?()
super.touchesEnded(touches, with: event)
}
}

.task { await apiManager.fetchAllMapMarkers() }paddingAdjustmentBehaviorparameter on themakeUIViewand setting the value to.never?ignoresSafeAreaExceptTop()that you have...