Skip to content
Merged
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ Add to `ios/Runner/Info.plist` (on macOS, `macos/Runner/Info.plist` plus the Loc
<string>Used to show your current location on the map.</string>
```

Minimum: iOS 17, macOS 14, Dart 3.10, Flutter 3.41.
### Minimum requirements

| | |
|---|---|
| iOS | 17.0 |
| macOS | 14.0 |
| Flutter / Dart | 3.41 / 3.10 |
| Xcode | 16+ (the native code is Swift 6 language mode — data-race safety is compiler-enforced) |

## Quick start

Expand Down
9 changes: 6 additions & 3 deletions darwin/mapkit_flutter.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ and modern MapKit configurations.

s.ios.deployment_target = '17.0'
s.osx.deployment_target = '14.0'
s.swift_version = '5.0'
# Swift 6 language mode: data-race safety enforced by the compiler, not a
# flag stack (subsumes the old -strict-concurrency=complete). Needs
# Xcode 16+, already below Flutter's own minimum.
s.swift_version = '6.0'

s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'SWIFT_VERSION' => '5.0',
'OTHER_SWIFT_FLAGS' => '-warnings-as-errors -strict-concurrency=complete'
'SWIFT_VERSION' => '6.0',
'OTHER_SWIFT_FLAGS' => '-warnings-as-errors'
}
end
4 changes: 3 additions & 1 deletion darwin/mapkit_flutter/Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.0
import PackageDescription

let package = Package(
Expand All @@ -12,6 +12,8 @@ let package = Package(
.library(name: "mapkit-flutter", targets: ["mapkit_flutter"])
],
targets: [
// swiftLanguageMode defaults to v6 under tools 6.0 — data-race safety is
// compiler-enforced, matching the podspec's swift_version = '6.0'.
.target(name: "mapkit_flutter")
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,8 @@ public class MapKitViewHost: NSObject, @preconcurrency MapKitHostApi {
#if os(iOS)
let coord = coordinate.clCoordinate
Task { @MainActor in
let request = MKLookAroundSceneRequest(coordinate: coord)
do {
guard let scene = try await request.scene else {
guard let scene = try await Self.requestLookAroundScene(at: coord) else {
completion(.success(false))
return
}
Expand All @@ -193,6 +192,18 @@ public class MapKitViewHost: NSObject, @preconcurrency MapKitHostApi {
#endif
}

#if os(iOS)
/// Runs the scene request off the main actor and hands the result back
/// with `sending`: older SDKs don't mark `MKLookAroundScene` Sendable, so
/// awaiting `request.scene` directly from `@MainActor` fails to compile
/// there. Region isolation proves the fresh scene is safe to move.
private nonisolated static func requestLookAroundScene(
at coordinate: CLLocationCoordinate2D
) async throws -> sending MKLookAroundScene? {
try await MKLookAroundSceneRequest(coordinate: coordinate).scene
}
#endif

func addTileOverlay(overlay overlayData: PlatformTileOverlay) throws {
let overlay = FlutterTileOverlay(fromPlatform: overlayData)
if !overlay.id.isEmpty, tileOverlays[overlay.id] != nil {
Expand Down Expand Up @@ -293,22 +304,25 @@ extension MapKitViewHost: MKMapViewDelegate {

extension MapKitViewHost {
private func takeSnapshot(options: PlatformSnapshotOptions) async throws -> FlutterStandardTypedData? {
// Caller-side options exist only for `size`/`mapRect` in the
// compositing pass below; the snapshotter gets its own copy off-actor.
let snapshotOptions = MKMapSnapshotter.Options()
snapshotOptions.region = self.mapView.region
snapshotOptions.size = self.mapView.frame.size

#if os(iOS)
// Unspecified traits report displayScale == 0; leaving `scale` unset
// falls back to MapKit's main-screen default.
// Unspecified traits report displayScale == 0; 0 leaves `scale` unset
// so MapKit falls back to its main-screen default.
let displayScale = self.mapView.traitCollection.displayScale
if displayScale > 0 {
snapshotOptions.scale = displayScale
}
#else
let displayScale: CGFloat = 0
#endif
snapshotOptions.showsBuildings = options.showsBuildings
snapshotOptions.pointOfInterestFilter = options.showsPointsOfInterest ? .includingAll : .excludingAll

let snapshotter = MKMapSnapshotter(options: snapshotOptions)
let snapshot = try await snapshotter.start()
let snapshot = try await Self.takeSnapshot(
region: snapshotOptions.region,
size: snapshotOptions.size,
scale: displayScale,
showsBuildings: options.showsBuildings,
showsPointsOfInterest: options.showsPointsOfInterest)

#if os(iOS)
let image = UIGraphicsImageRenderer(size: snapshotOptions.size).image { context in
Expand Down Expand Up @@ -340,6 +354,32 @@ extension MapKitViewHost {
return FlutterStandardTypedData(bytes: imageData)
}

/// Runs the snapshotter off the main actor and hands the result back with
/// `sending`: older SDKs don't mark `MKMapSnapshotter.Snapshot` Sendable,
/// so awaiting `start()` directly from `@MainActor` fails to compile
/// there. Only Sendable value types cross in; the options and snapshotter
/// live entirely in this disconnected region, so the fresh snapshot is
/// provably safe to move out.
private nonisolated static func takeSnapshot(
region: MKCoordinateRegion,
size: CGSize,
scale: CGFloat,
showsBuildings: Bool,
showsPointsOfInterest: Bool
) async throws -> sending MKMapSnapshotter.Snapshot {
let options = MKMapSnapshotter.Options()
options.region = region
options.size = size
#if os(iOS)
if scale > 0 {
options.scale = scale
}
#endif
options.showsBuildings = showsBuildings
options.pointOfInterestFilter = showsPointsOfInterest ? .includingAll : .excludingAll
return try await MKMapSnapshotter(options: options).start()
}

#if os(iOS)
private func drawAnnotations(annotation: FlutterAnnotation, point: CGPoint) {
let annotationView = self.getAnnotationView(annotation: annotation)
Expand Down
Loading