Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 1.5.0

* Added `MapElevationStyle` to support realistic 3D elevation on iOS 16+.
* Allow zoom level 0 so maps can zoom farther out toward globe-style views.

## 1.4.0

* Flutter 3.27.1 compatibility, replace `ui.hash*` with `Object.hash*
Expand Down
53 changes: 47 additions & 6 deletions ios/Classes/MapView/FlutterMapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class FlutterMapView: MKMapView, UIGestureRecognizerDelegate {
var oldBounds: CGRect?
var options: Dictionary<String, Any>?
var isMyLocationButtonShowing: Bool? = false
var currentMapType: Int = 0
var currentMapElevationStyle: Int = 0
var currentTrafficEnabled: Bool = false

fileprivate let locationManager: CLLocationManager = CLLocationManager()

Expand Down Expand Up @@ -141,16 +144,24 @@ class FlutterMapView: MKMapView, UIGestureRecognizerDelegate {
self.layoutMargins = margins
}

var shouldUpdateMapConfiguration = false
if let mapType: Int = options["mapType"] as? Int {
self.mapType = self.mapTypes[mapType]
self.currentMapType = mapType
shouldUpdateMapConfiguration = true
}

if let mapElevationStyle: Int = options["mapElevationStyle"] as? Int {
self.currentMapElevationStyle = mapElevationStyle
shouldUpdateMapConfiguration = true
}

if let trafficEnabled: Bool = options["trafficEnabled"] as? Bool {
if #available(iOS 9.0, *) {
self.showsTraffic = trafficEnabled
} else {
// do nothing
}
self.currentTrafficEnabled = trafficEnabled
shouldUpdateMapConfiguration = true
}

if shouldUpdateMapConfiguration {
self.applyMapConfiguration()
}

if let rotateGesturesEnabled: Bool = options["rotateGesturesEnabled"] as? Bool {
Expand Down Expand Up @@ -203,6 +214,36 @@ class FlutterMapView: MKMapView, UIGestureRecognizerDelegate {

}

private func applyMapConfiguration() {
guard self.currentMapType >= 0 && self.currentMapType < self.mapTypes.count else {
return
}

if #available(iOS 16.0, *) {
let elevationStyle: MKMapConfiguration.ElevationStyle = self.currentMapElevationStyle == 1 ? .realistic : .flat

switch self.currentMapType {
case 0:
let configuration = MKStandardMapConfiguration(elevationStyle: elevationStyle)
configuration.showsTraffic = self.currentTrafficEnabled
self.preferredConfiguration = configuration
case 1:
self.preferredConfiguration = MKImageryMapConfiguration(elevationStyle: elevationStyle)
case 2:
let configuration = MKHybridMapConfiguration(elevationStyle: elevationStyle)
configuration.showsTraffic = self.currentTrafficEnabled
self.preferredConfiguration = configuration
default:
self.mapType = self.mapTypes[self.currentMapType]
}
} else {
self.mapType = self.mapTypes[self.currentMapType]
if #available(iOS 9.0, *) {
self.showsTraffic = self.currentTrafficEnabled
}
}
}

func setUserLocation() {
let authorizationStatus = CLLocationManager.authorizationStatus()

Expand Down
10 changes: 3 additions & 7 deletions ios/Classes/MapView/MapViewExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public extension MKMapView {
static var _pitch: CGFloat = CGFloat(0)
static var _heading: CLLocationDirection = CLLocationDirection(0)
static var _maxZoomLevel: Double = Double(21)
static var _minZoomLevel: Double = Double(2)
static var _minZoomLevel: Double = Double(0)
}

var maxZoomLevel: Double {
Expand Down Expand Up @@ -239,12 +239,8 @@ public extension MKMapView {
}

func zoomOut(animated: Bool) {
if Holder._zoomLevel - 1 >= Holder._minZoomLevel {
Holder._zoomLevel -= 1
if round(Holder._zoomLevel) <= 2 {
Holder._zoomLevel = 0
}

if Holder._zoomLevel > Holder._minZoomLevel {
Holder._zoomLevel = Swift.max(Holder._minZoomLevel, Holder._zoomLevel - 1)
if #available(iOS 9.0, *) {
self.setCenterCoordinateWithAltitude(centerCoordinate: centerCoordinate, zoomLevel: Holder._zoomLevel, animated: animated)
} else {
Expand Down
41 changes: 29 additions & 12 deletions lib/src/apple_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class AppleMap extends StatefulWidget {
this.compassEnabled = true,
this.trafficEnabled = false,
this.mapType = MapType.standard,
this.mapElevationStyle = MapElevationStyle.flat,
this.minMaxZoomPreference = MinMaxZoomPreference.unbounded,
this.trackingMode = TrackingMode.none,
this.rotateGesturesEnabled = true,
Expand Down Expand Up @@ -59,6 +60,9 @@ class AppleMap extends StatefulWidget {
/// Type of map tiles to be rendered.
final MapType mapType;

/// The elevation style used by the native Apple map.
final MapElevationStyle mapElevationStyle;

/// The mode used to track the user location.
final TrackingMode trackingMode;

Expand Down Expand Up @@ -204,7 +208,8 @@ class _AppleMapState extends State<AppleMap> {
);
}
return Text(
'$defaultTargetPlatform is not yet supported by the apple maps plugin');
'$defaultTargetPlatform is not yet supported by the apple maps plugin',
);
}

@override
Expand All @@ -229,8 +234,9 @@ class _AppleMapState extends State<AppleMap> {

void _updateOptions() async {
final _AppleMapOptions newOptions = _AppleMapOptions.fromWidget(widget);
final Map<String, dynamic> updates =
_appleMapOptions.updatesMap(newOptions);
final Map<String, dynamic> updates = _appleMapOptions.updatesMap(
newOptions,
);
if (updates.isEmpty) {
return;
}
Expand All @@ -241,31 +247,35 @@ class _AppleMapState extends State<AppleMap> {

void _updateAnnotations() async {
final AppleMapController controller = await _controller.future;
controller._updateAnnotations(_AnnotationUpdates.from(
_annotations.values.toSet(), widget.annotations));
controller._updateAnnotations(
_AnnotationUpdates.from(_annotations.values.toSet(), widget.annotations),
);
_annotations = _keyByAnnotationId(widget.annotations);
}

void _updatePolylines() async {
final AppleMapController controller = await _controller.future;
controller._updatePolylines(
_PolylineUpdates.from(_polylines.values.toSet(), widget.polylines));
_PolylineUpdates.from(_polylines.values.toSet(), widget.polylines),
);
_polylines = _keyByPolylineId(widget.polylines);
}

void _updatePolygons() async {
final AppleMapController controller = await _controller.future;
// ignore: unawaited_futures
controller._updatePolygons(
_PolygonUpdates.from(_polygons.values.toSet(), widget.polygons));
_PolygonUpdates.from(_polygons.values.toSet(), widget.polygons),
);
_polygons = _keyByPolygonId(widget.polygons);
}

void _updateCircles() async {
final AppleMapController controller = await _controller.future;
// ignore: unawaited_futures
controller._updateCircles(
_CircleUpdates.from(_circles.values.toSet(), widget.circles));
_CircleUpdates.from(_circles.values.toSet(), widget.circles),
);
_circles = _keyByCircleId(widget.circles);
}

Expand Down Expand Up @@ -332,6 +342,7 @@ class _AppleMapOptions {
this.compassEnabled,
this.trafficEnabled,
this.mapType,
this.mapElevationStyle,
this.minMaxZoomPreference,
this.rotateGesturesEnabled,
this.scrollGesturesEnabled,
Expand All @@ -349,6 +360,7 @@ class _AppleMapOptions {
compassEnabled: map.compassEnabled,
trafficEnabled: map.trafficEnabled,
mapType: map.mapType,
mapElevationStyle: map.mapElevationStyle,
minMaxZoomPreference: map.minMaxZoomPreference,
rotateGesturesEnabled: map.rotateGesturesEnabled,
scrollGesturesEnabled: map.scrollGesturesEnabled,
Expand All @@ -368,6 +380,8 @@ class _AppleMapOptions {

final MapType? mapType;

final MapElevationStyle? mapElevationStyle;

final MinMaxZoomPreference? minMaxZoomPreference;

final bool? rotateGesturesEnabled;
Expand Down Expand Up @@ -400,6 +414,7 @@ class _AppleMapOptions {
addIfNonNull('compassEnabled', compassEnabled);
addIfNonNull('trafficEnabled', trafficEnabled);
addIfNonNull('mapType', mapType?.index);
addIfNonNull('mapElevationStyle', mapElevationStyle?.index);
addIfNonNull('minMaxZoomPreference', minMaxZoomPreference?._toJson());
addIfNonNull('rotateGesturesEnabled', rotateGesturesEnabled);
addIfNonNull('scrollGesturesEnabled', scrollGesturesEnabled);
Expand All @@ -410,16 +425,18 @@ class _AppleMapOptions {
addIfNonNull('myLocationButtonEnabled', myLocationButtonEnabled);
addIfNonNull('padding', _serializePadding(padding));
addIfNonNull(
'insetsLayoutMarginsFromSafeArea', insetsLayoutMarginsFromSafeArea);
'insetsLayoutMarginsFromSafeArea',
insetsLayoutMarginsFromSafeArea,
);
return optionsMap;
}

Map<String, dynamic> updatesMap(_AppleMapOptions newOptions) {
final Map<String, dynamic> prevOptionsMap = toMap();

return newOptions.toMap()
..removeWhere(
(String key, dynamic value) => prevOptionsMap[key] == value);
return newOptions.toMap()..removeWhere(
(String key, dynamic value) => prevOptionsMap[key] == value,
);
}

List<double>? _serializePadding(EdgeInsets? insets) {
Expand Down
21 changes: 18 additions & 3 deletions lib/src/ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ enum MapType {
hybrid,
}

/// Controls whether MapKit renders the map on flat terrain or with realistic
/// 3D elevation where the device and map data support it.
enum MapElevationStyle {
/// Flat, 2D terrain.
flat,

/// Realistic, 3D terrain.
///
/// This maps to MapKit's iOS 16+ realistic elevation configuration on iOS.
/// Unsupported devices or areas may fall back to flat terrain.
realistic,
}

enum TrackingMode {
// the user's location is not followed
none,
Expand Down Expand Up @@ -63,7 +76,7 @@ class CameraTargetBounds {

class MinMaxZoomPreference {
const MinMaxZoomPreference(this.minZoom, this.maxZoom)
: assert(minZoom == null || maxZoom == null || minZoom <= maxZoom);
: assert(minZoom == null || maxZoom == null || minZoom <= maxZoom);

/// The preferred minimum zoom level or null, if unbounded from below.
final double? minZoom;
Expand All @@ -72,8 +85,10 @@ class MinMaxZoomPreference {
final double? maxZoom;

/// Unbounded zooming.
static const MinMaxZoomPreference unbounded =
MinMaxZoomPreference(null, null);
static const MinMaxZoomPreference unbounded = MinMaxZoomPreference(
null,
null,
);

dynamic _toJson() => <dynamic>[minZoom, maxZoom];

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: apple_maps_flutter
description: This plugin uses the Flutter platform view to display an Apple Maps widget.
version: 1.4.0
version: 1.5.0
homepage: https://luisthein.de
repository: https://github.com/LuisThein/apple_maps_flutter
issue_tracker: https://github.com/LuisThein/apple_maps_flutter/issues
Expand Down
Loading