diff --git a/darwin/mapkit_flutter/Sources/mapkit_flutter/Annotations/AnnotationIcon.swift b/darwin/mapkit_flutter/Sources/mapkit_flutter/Annotations/AnnotationIcon.swift index a3bdd27..afa64ee 100644 --- a/darwin/mapkit_flutter/Sources/mapkit_flutter/Annotations/AnnotationIcon.swift +++ b/darwin/mapkit_flutter/Sources/mapkit_flutter/Annotations/AnnotationIcon.swift @@ -44,7 +44,8 @@ class AnnotationIcon: Equatable { case .image: self.iconType = .image if let data = icon.imageBytes?.data { - self.image = PlatformImage.fromImageData(data) + self.image = PlatformImage.fromImageData( + data, pixelRatio: CGFloat(icon.imagePixelRatio ?? 1)) } } } diff --git a/darwin/mapkit_flutter/Sources/mapkit_flutter/MapKitPlugin.swift b/darwin/mapkit_flutter/Sources/mapkit_flutter/MapKitPlugin.swift index b339eea..2860037 100644 --- a/darwin/mapkit_flutter/Sources/mapkit_flutter/MapKitPlugin.swift +++ b/darwin/mapkit_flutter/Sources/mapkit_flutter/MapKitPlugin.swift @@ -15,6 +15,9 @@ public final class MapKitPlugin: NSObject, FlutterPlugin { // Plugin registration runs on the main thread; the factory and view // host are main-actor isolated. macOS's GeneratedPluginRegistrant // calls this from a nonisolated context, so assume isolation here. + // The registrar hand-off shares that guarantee: it never leaves the + // main thread, it just isn't Sendable-provable. + nonisolated(unsafe) let registrar = registrar MainActor.assumeIsolated { let factory = MapKitViewFactory(withRegistrar: registrar) #if os(iOS) diff --git a/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/FlutterMapView.swift b/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/FlutterMapView.swift index 4a4acc6..d3526c8 100644 --- a/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/FlutterMapView.swift +++ b/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/FlutterMapView.swift @@ -10,7 +10,10 @@ import AppKit import FlutterMacOS #endif -class FlutterMapView: MKMapView, PlatformGestureRecognizerDelegate, CLLocationManagerDelegate { +// `@preconcurrency`: CLLocationManagerDelegate is nonisolated, but callbacks +// arrive on the thread that created the manager — main, since the manager is +// an instance property of this MainActor-isolated view. +class FlutterMapView: MKMapView, PlatformGestureRecognizerDelegate, @preconcurrency CLLocationManagerDelegate { weak var flutterApi: MapKitFlutterApi? /// Camera handed over before the first layout pass; re-applied once the diff --git a/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/MapKitViewHost.swift b/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/MapKitViewHost.swift index cd07a43..95cb7ec 100644 --- a/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/MapKitViewHost.swift +++ b/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/MapKitViewHost.swift @@ -297,7 +297,12 @@ extension MapKitViewHost { snapshotOptions.region = self.mapView.region snapshotOptions.size = self.mapView.frame.size #if os(iOS) - snapshotOptions.scale = PlatformScreen.scale + // Unspecified traits report displayScale == 0; leaving `scale` unset + // falls back to MapKit's main-screen default. + let displayScale = self.mapView.traitCollection.displayScale + if displayScale > 0 { + snapshotOptions.scale = displayScale + } #endif snapshotOptions.showsBuildings = options.showsBuildings snapshotOptions.pointOfInterestFilter = options.showsPointsOfInterest ? .includingAll : .excludingAll @@ -367,7 +372,10 @@ extension MapKitViewHost { } #if os(iOS) -extension MapKitViewHost: FlutterPlatformView { +// `@preconcurrency`: FlutterPlatformView is nonisolated; the engine calls +// `view()` on the platform thread (main), same as the MapKitHostApi +// conformance above. +extension MapKitViewHost: @preconcurrency FlutterPlatformView { public func view() -> UIView { return contentView } diff --git a/darwin/mapkit_flutter/Sources/mapkit_flutter/Overlays/FlutterPolyline.swift b/darwin/mapkit_flutter/Sources/mapkit_flutter/Overlays/FlutterPolyline.swift index 8957390..389e4e9 100644 --- a/darwin/mapkit_flutter/Sources/mapkit_flutter/Overlays/FlutterPolyline.swift +++ b/darwin/mapkit_flutter/Sources/mapkit_flutter/Overlays/FlutterPolyline.swift @@ -161,6 +161,7 @@ final class FlutterGeodesicPolyline: MKGeodesicPolyline, StyledPolyline, @unchec public extension MKPolyline { // maxMeters is the preferred distance offset from the self to be acknowledged as a touch + @MainActor func contains(coordinate: CLLocationCoordinate2D, mapView: MKMapView, maxMeters: Int = 8) -> Bool { let distance: Double = distanceOf(pt: MKMapPoint.init(coordinate), toMultipPoint: self) return distance <= meters(fromPixel: maxMeters, at: coordinate, view: mapView) @@ -200,6 +201,7 @@ public extension MKPolyline { return distance } + @MainActor private func meters(fromPixel pixel: Int, at touchCoordinate: CLLocationCoordinate2D, view: MKMapView) -> Double { let touchPoint: CGPoint = view.convert(touchCoordinate, toPointTo: view) let maxOffsetPoint = CGPoint(x: touchPoint.x + CGFloat(pixel), y: touchPoint.y) diff --git a/darwin/mapkit_flutter/Sources/mapkit_flutter/Utils/PlatformAliases.swift b/darwin/mapkit_flutter/Sources/mapkit_flutter/Utils/PlatformAliases.swift index 2f94be2..4409e8e 100644 --- a/darwin/mapkit_flutter/Sources/mapkit_flutter/Utils/PlatformAliases.swift +++ b/darwin/mapkit_flutter/Sources/mapkit_flutter/Utils/PlatformAliases.swift @@ -42,18 +42,6 @@ extension PlatformView { } } -enum PlatformScreen { - /// Backing scale factor of the main screen (`UIScreen.scale` / - /// `NSScreen.backingScaleFactor`). Defaults to 2 when unavailable. - static var scale: CGFloat { - #if os(iOS) - return UIScreen.main.scale - #elseif os(macOS) - return NSScreen.main?.backingScaleFactor ?? 2 - #endif - } -} - extension PlatformImage { /// An SF Symbol image by name (`UIImage(systemName:)` / /// `NSImage(systemSymbolName:)`). @@ -65,19 +53,18 @@ extension PlatformImage { #endif } - /// A bitmap image from raw PNG/image bytes, sized in points to match the - /// screen scale (`UIImage(data:scale:)` has no AppKit equivalent, so the - /// macOS path divides the pixel size by the backing scale). - static func fromImageData(_ data: Data) -> PlatformImage? { + /// A bitmap image from raw PNG/image bytes, sized in points via the + /// Dart-supplied pixel ratio of the bytes (`UIImage(data:scale:)` has no + /// AppKit equivalent, so the macOS path divides the pixel size by it). + static func fromImageData(_ data: Data, pixelRatio: CGFloat) -> PlatformImage? { #if os(iOS) - return UIImage(data: data, scale: PlatformScreen.scale) + return UIImage(data: data, scale: pixelRatio) #elseif os(macOS) guard let image = NSImage(data: data) else { return nil } if let rep = image.representations.first { - let scale = PlatformScreen.scale image.size = NSSize( - width: CGFloat(rep.pixelsWide) / scale, - height: CGFloat(rep.pixelsHigh) / scale + width: CGFloat(rep.pixelsWide) / pixelRatio, + height: CGFloat(rep.pixelsHigh) / pixelRatio ) } return image diff --git a/darwin/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift b/darwin/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift index f4066df..8591ea0 100644 --- a/darwin/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift +++ b/darwin/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v27.1.0), do not edit directly. +// Autogenerated from Pigeon (v27.3.0), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -533,6 +533,8 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { var glyphTintArgb: Int64? = nil /// Raw PNG bytes for the `image` variant (`MKAnnotationView.image`). var imageBytes: FlutterStandardTypedData? = nil + /// Pixels per logical point in [imageBytes] (`UIImage(data:scale:)`). + var imagePixelRatio: Double? = nil // swift-format-ignore: AlwaysUseLowerCamelCase @@ -543,6 +545,7 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { let glyphSystemImage: String? = nilOrValue(pigeonVar_list[3]) let glyphTintArgb: Int64? = nilOrValue(pigeonVar_list[4]) let imageBytes: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[5]) + let imagePixelRatio: Double? = nilOrValue(pigeonVar_list[6]) return PlatformAnnotationIcon( type: type, @@ -550,7 +553,8 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { glyphText: glyphText, glyphSystemImage: glyphSystemImage, glyphTintArgb: glyphTintArgb, - imageBytes: imageBytes + imageBytes: imageBytes, + imagePixelRatio: imagePixelRatio ) } func toList() -> [Any?] { @@ -561,13 +565,14 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { glyphSystemImage, glyphTintArgb, imageBytes, + imagePixelRatio, ] } static func == (lhs: PlatformAnnotationIcon, rhs: PlatformAnnotationIcon) -> Bool { if Swift.type(of: lhs) != Swift.type(of: rhs) { return false } - return messagesPigeonInternal.deepEquals(lhs.type, rhs.type) && messagesPigeonInternal.deepEquals(lhs.markerTintArgb, rhs.markerTintArgb) && messagesPigeonInternal.deepEquals(lhs.glyphText, rhs.glyphText) && messagesPigeonInternal.deepEquals(lhs.glyphSystemImage, rhs.glyphSystemImage) && messagesPigeonInternal.deepEquals(lhs.glyphTintArgb, rhs.glyphTintArgb) && messagesPigeonInternal.deepEquals(lhs.imageBytes, rhs.imageBytes) + return messagesPigeonInternal.deepEquals(lhs.type, rhs.type) && messagesPigeonInternal.deepEquals(lhs.markerTintArgb, rhs.markerTintArgb) && messagesPigeonInternal.deepEquals(lhs.glyphText, rhs.glyphText) && messagesPigeonInternal.deepEquals(lhs.glyphSystemImage, rhs.glyphSystemImage) && messagesPigeonInternal.deepEquals(lhs.glyphTintArgb, rhs.glyphTintArgb) && messagesPigeonInternal.deepEquals(lhs.imageBytes, rhs.imageBytes) && messagesPigeonInternal.deepEquals(lhs.imagePixelRatio, rhs.imagePixelRatio) } func hash(into hasher: inout Hasher) { @@ -578,10 +583,11 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { messagesPigeonInternal.deepHash(value: glyphSystemImage, hasher: &hasher) messagesPigeonInternal.deepHash(value: glyphTintArgb, hasher: &hasher) messagesPigeonInternal.deepHash(value: imageBytes, hasher: &hasher) + messagesPigeonInternal.deepHash(value: imagePixelRatio, hasher: &hasher) } public var description: String { - return "PlatformAnnotationIcon(type: \(String(describing: type)), markerTintArgb: \(String(describing: markerTintArgb)), glyphText: \(String(describing: glyphText)), glyphSystemImage: \(String(describing: glyphSystemImage)), glyphTintArgb: \(String(describing: glyphTintArgb)), imageBytes: \(String(describing: imageBytes)))" + return "PlatformAnnotationIcon(type: \(String(describing: type)), markerTintArgb: \(String(describing: markerTintArgb)), glyphText: \(String(describing: glyphText)), glyphSystemImage: \(String(describing: glyphSystemImage)), glyphTintArgb: \(String(describing: glyphTintArgb)), imageBytes: \(String(describing: imageBytes)), imagePixelRatio: \(String(describing: imagePixelRatio)))" } } @@ -1907,23 +1913,23 @@ class MapKitHostApiSetup { /// /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. protocol MapKitFlutterApiProtocol { - func onCameraMoveStarted(completion: @escaping (Result) -> Void) - func onCameraMove(camera cameraArg: PlatformMapCamera, completion: @escaping (Result) -> Void) - func onCameraIdle(completion: @escaping (Result) -> Void) - func onAnnotationTap(annotationId annotationIdArg: String, completion: @escaping (Result) -> Void) - func onAnnotationDragStart(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) - func onAnnotationDrag(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) - func onAnnotationDragEnd(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) - func onCalloutTap(annotationId annotationIdArg: String, completion: @escaping (Result) -> Void) - func onPolylineTap(polylineId polylineIdArg: String, completion: @escaping (Result) -> Void) - func onPolygonTap(polygonId polygonIdArg: String, completion: @escaping (Result) -> Void) - func onCircleTap(circleId circleIdArg: String, completion: @escaping (Result) -> Void) - func onMapTap(coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) - func onMapLongPress(coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) + func onCameraMoveStarted(completion: @escaping @Sendable (Result) -> Void) + func onCameraMove(camera cameraArg: PlatformMapCamera, completion: @escaping @Sendable (Result) -> Void) + func onCameraIdle(completion: @escaping @Sendable (Result) -> Void) + func onAnnotationTap(annotationId annotationIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onAnnotationDragStart(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) + func onAnnotationDrag(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) + func onAnnotationDragEnd(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) + func onCalloutTap(annotationId annotationIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onPolylineTap(polylineId polylineIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onPolygonTap(polygonId polygonIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onCircleTap(circleId circleIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onMapTap(coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) + func onMapLongPress(coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) /// `MKMapViewDelegate.mapViewDidFailLoadingMap(_:withError:)`. - func onDidFailLoadingMap(error errorArg: String, completion: @escaping (Result) -> Void) + func onDidFailLoadingMap(error errorArg: String, completion: @escaping @Sendable (Result) -> Void) /// `MKMapViewDelegate.mapView(_:didFailToLocateUserWithError:)`. - func onDidFailToLocateUser(error errorArg: String, completion: @escaping (Result) -> Void) + func onDidFailToLocateUser(error errorArg: String, completion: @escaping @Sendable (Result) -> Void) } class MapKitFlutterApi: MapKitFlutterApiProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -1935,7 +1941,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { var codec: MessagesPigeonCodec { return MessagesPigeonCodec.shared } - func onCameraMoveStarted(completion: @escaping (Result) -> Void) { + func onCameraMoveStarted(completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCameraMoveStarted\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage(nil) { response in @@ -1953,7 +1959,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onCameraMove(camera cameraArg: PlatformMapCamera, completion: @escaping (Result) -> Void) { + func onCameraMove(camera cameraArg: PlatformMapCamera, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCameraMove\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([cameraArg] as [Any?]) { response in @@ -1971,7 +1977,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onCameraIdle(completion: @escaping (Result) -> Void) { + func onCameraIdle(completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCameraIdle\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage(nil) { response in @@ -1989,7 +1995,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onAnnotationTap(annotationId annotationIdArg: String, completion: @escaping (Result) -> Void) { + func onAnnotationTap(annotationId annotationIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onAnnotationTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg] as [Any?]) { response in @@ -2007,7 +2013,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onAnnotationDragStart(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onAnnotationDragStart(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onAnnotationDragStart\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg, coordinateArg] as [Any?]) { response in @@ -2025,7 +2031,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onAnnotationDrag(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onAnnotationDrag(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onAnnotationDrag\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg, coordinateArg] as [Any?]) { response in @@ -2043,7 +2049,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onAnnotationDragEnd(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onAnnotationDragEnd(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onAnnotationDragEnd\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg, coordinateArg] as [Any?]) { response in @@ -2061,7 +2067,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onCalloutTap(annotationId annotationIdArg: String, completion: @escaping (Result) -> Void) { + func onCalloutTap(annotationId annotationIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCalloutTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg] as [Any?]) { response in @@ -2079,7 +2085,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onPolylineTap(polylineId polylineIdArg: String, completion: @escaping (Result) -> Void) { + func onPolylineTap(polylineId polylineIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onPolylineTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([polylineIdArg] as [Any?]) { response in @@ -2097,7 +2103,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onPolygonTap(polygonId polygonIdArg: String, completion: @escaping (Result) -> Void) { + func onPolygonTap(polygonId polygonIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onPolygonTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([polygonIdArg] as [Any?]) { response in @@ -2115,7 +2121,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onCircleTap(circleId circleIdArg: String, completion: @escaping (Result) -> Void) { + func onCircleTap(circleId circleIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCircleTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([circleIdArg] as [Any?]) { response in @@ -2133,7 +2139,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onMapTap(coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onMapTap(coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onMapTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([coordinateArg] as [Any?]) { response in @@ -2151,7 +2157,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onMapLongPress(coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onMapLongPress(coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onMapLongPress\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([coordinateArg] as [Any?]) { response in @@ -2170,7 +2176,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } /// `MKMapViewDelegate.mapViewDidFailLoadingMap(_:withError:)`. - func onDidFailLoadingMap(error errorArg: String, completion: @escaping (Result) -> Void) { + func onDidFailLoadingMap(error errorArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onDidFailLoadingMap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([errorArg] as [Any?]) { response in @@ -2189,7 +2195,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } /// `MKMapViewDelegate.mapView(_:didFailToLocateUserWithError:)`. - func onDidFailToLocateUser(error errorArg: String, completion: @escaping (Result) -> Void) { + func onDidFailToLocateUser(error errorArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onDidFailToLocateUser\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([errorArg] as [Any?]) { response in diff --git a/ios/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift b/ios/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift index 7bbf549..8591ea0 100644 --- a/ios/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift +++ b/ios/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v27.1.0), do not edit directly. +// Autogenerated from Pigeon (v27.3.0), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -121,18 +121,13 @@ enum messagesPigeonInternal { case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): guard lhsDictionary.count == rhsDictionary.count else { return false } for (lhsKey, lhsValue) in lhsDictionary { - var found = false - for (rhsKey, rhsValue) in rhsDictionary { - if deepEquals(lhsKey, rhsKey) { - if deepEquals(lhsValue, rhsValue) { - found = true - break - } else { - return false - } + if let rhsValue = rhsDictionary[lhsKey] { + if !deepEquals(lhsValue, rhsValue) { + return false } + } else { + return false } - if !found { return false } } return true @@ -538,6 +533,8 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { var glyphTintArgb: Int64? = nil /// Raw PNG bytes for the `image` variant (`MKAnnotationView.image`). var imageBytes: FlutterStandardTypedData? = nil + /// Pixels per logical point in [imageBytes] (`UIImage(data:scale:)`). + var imagePixelRatio: Double? = nil // swift-format-ignore: AlwaysUseLowerCamelCase @@ -548,6 +545,7 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { let glyphSystemImage: String? = nilOrValue(pigeonVar_list[3]) let glyphTintArgb: Int64? = nilOrValue(pigeonVar_list[4]) let imageBytes: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[5]) + let imagePixelRatio: Double? = nilOrValue(pigeonVar_list[6]) return PlatformAnnotationIcon( type: type, @@ -555,7 +553,8 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { glyphText: glyphText, glyphSystemImage: glyphSystemImage, glyphTintArgb: glyphTintArgb, - imageBytes: imageBytes + imageBytes: imageBytes, + imagePixelRatio: imagePixelRatio ) } func toList() -> [Any?] { @@ -566,13 +565,14 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { glyphSystemImage, glyphTintArgb, imageBytes, + imagePixelRatio, ] } static func == (lhs: PlatformAnnotationIcon, rhs: PlatformAnnotationIcon) -> Bool { if Swift.type(of: lhs) != Swift.type(of: rhs) { return false } - return messagesPigeonInternal.deepEquals(lhs.type, rhs.type) && messagesPigeonInternal.deepEquals(lhs.markerTintArgb, rhs.markerTintArgb) && messagesPigeonInternal.deepEquals(lhs.glyphText, rhs.glyphText) && messagesPigeonInternal.deepEquals(lhs.glyphSystemImage, rhs.glyphSystemImage) && messagesPigeonInternal.deepEquals(lhs.glyphTintArgb, rhs.glyphTintArgb) && messagesPigeonInternal.deepEquals(lhs.imageBytes, rhs.imageBytes) + return messagesPigeonInternal.deepEquals(lhs.type, rhs.type) && messagesPigeonInternal.deepEquals(lhs.markerTintArgb, rhs.markerTintArgb) && messagesPigeonInternal.deepEquals(lhs.glyphText, rhs.glyphText) && messagesPigeonInternal.deepEquals(lhs.glyphSystemImage, rhs.glyphSystemImage) && messagesPigeonInternal.deepEquals(lhs.glyphTintArgb, rhs.glyphTintArgb) && messagesPigeonInternal.deepEquals(lhs.imageBytes, rhs.imageBytes) && messagesPigeonInternal.deepEquals(lhs.imagePixelRatio, rhs.imagePixelRatio) } func hash(into hasher: inout Hasher) { @@ -583,10 +583,11 @@ struct PlatformAnnotationIcon: Hashable, CustomStringConvertible { messagesPigeonInternal.deepHash(value: glyphSystemImage, hasher: &hasher) messagesPigeonInternal.deepHash(value: glyphTintArgb, hasher: &hasher) messagesPigeonInternal.deepHash(value: imageBytes, hasher: &hasher) + messagesPigeonInternal.deepHash(value: imagePixelRatio, hasher: &hasher) } public var description: String { - return "PlatformAnnotationIcon(type: \(String(describing: type)), markerTintArgb: \(String(describing: markerTintArgb)), glyphText: \(String(describing: glyphText)), glyphSystemImage: \(String(describing: glyphSystemImage)), glyphTintArgb: \(String(describing: glyphTintArgb)), imageBytes: \(String(describing: imageBytes)))" + return "PlatformAnnotationIcon(type: \(String(describing: type)), markerTintArgb: \(String(describing: markerTintArgb)), glyphText: \(String(describing: glyphText)), glyphSystemImage: \(String(describing: glyphSystemImage)), glyphTintArgb: \(String(describing: glyphTintArgb)), imageBytes: \(String(describing: imageBytes)), imagePixelRatio: \(String(describing: imagePixelRatio)))" } } @@ -1912,23 +1913,23 @@ class MapKitHostApiSetup { /// /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. protocol MapKitFlutterApiProtocol { - func onCameraMoveStarted(completion: @escaping (Result) -> Void) - func onCameraMove(camera cameraArg: PlatformMapCamera, completion: @escaping (Result) -> Void) - func onCameraIdle(completion: @escaping (Result) -> Void) - func onAnnotationTap(annotationId annotationIdArg: String, completion: @escaping (Result) -> Void) - func onAnnotationDragStart(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) - func onAnnotationDrag(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) - func onAnnotationDragEnd(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) - func onCalloutTap(annotationId annotationIdArg: String, completion: @escaping (Result) -> Void) - func onPolylineTap(polylineId polylineIdArg: String, completion: @escaping (Result) -> Void) - func onPolygonTap(polygonId polygonIdArg: String, completion: @escaping (Result) -> Void) - func onCircleTap(circleId circleIdArg: String, completion: @escaping (Result) -> Void) - func onMapTap(coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) - func onMapLongPress(coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) + func onCameraMoveStarted(completion: @escaping @Sendable (Result) -> Void) + func onCameraMove(camera cameraArg: PlatformMapCamera, completion: @escaping @Sendable (Result) -> Void) + func onCameraIdle(completion: @escaping @Sendable (Result) -> Void) + func onAnnotationTap(annotationId annotationIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onAnnotationDragStart(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) + func onAnnotationDrag(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) + func onAnnotationDragEnd(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) + func onCalloutTap(annotationId annotationIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onPolylineTap(polylineId polylineIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onPolygonTap(polygonId polygonIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onCircleTap(circleId circleIdArg: String, completion: @escaping @Sendable (Result) -> Void) + func onMapTap(coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) + func onMapLongPress(coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) /// `MKMapViewDelegate.mapViewDidFailLoadingMap(_:withError:)`. - func onDidFailLoadingMap(error errorArg: String, completion: @escaping (Result) -> Void) + func onDidFailLoadingMap(error errorArg: String, completion: @escaping @Sendable (Result) -> Void) /// `MKMapViewDelegate.mapView(_:didFailToLocateUserWithError:)`. - func onDidFailToLocateUser(error errorArg: String, completion: @escaping (Result) -> Void) + func onDidFailToLocateUser(error errorArg: String, completion: @escaping @Sendable (Result) -> Void) } class MapKitFlutterApi: MapKitFlutterApiProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -1940,7 +1941,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { var codec: MessagesPigeonCodec { return MessagesPigeonCodec.shared } - func onCameraMoveStarted(completion: @escaping (Result) -> Void) { + func onCameraMoveStarted(completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCameraMoveStarted\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage(nil) { response in @@ -1958,7 +1959,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onCameraMove(camera cameraArg: PlatformMapCamera, completion: @escaping (Result) -> Void) { + func onCameraMove(camera cameraArg: PlatformMapCamera, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCameraMove\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([cameraArg] as [Any?]) { response in @@ -1976,7 +1977,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onCameraIdle(completion: @escaping (Result) -> Void) { + func onCameraIdle(completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCameraIdle\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage(nil) { response in @@ -1994,7 +1995,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onAnnotationTap(annotationId annotationIdArg: String, completion: @escaping (Result) -> Void) { + func onAnnotationTap(annotationId annotationIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onAnnotationTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg] as [Any?]) { response in @@ -2012,7 +2013,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onAnnotationDragStart(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onAnnotationDragStart(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onAnnotationDragStart\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg, coordinateArg] as [Any?]) { response in @@ -2030,7 +2031,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onAnnotationDrag(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onAnnotationDrag(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onAnnotationDrag\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg, coordinateArg] as [Any?]) { response in @@ -2048,7 +2049,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onAnnotationDragEnd(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onAnnotationDragEnd(annotationId annotationIdArg: String, coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onAnnotationDragEnd\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg, coordinateArg] as [Any?]) { response in @@ -2066,7 +2067,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onCalloutTap(annotationId annotationIdArg: String, completion: @escaping (Result) -> Void) { + func onCalloutTap(annotationId annotationIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCalloutTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([annotationIdArg] as [Any?]) { response in @@ -2084,7 +2085,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onPolylineTap(polylineId polylineIdArg: String, completion: @escaping (Result) -> Void) { + func onPolylineTap(polylineId polylineIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onPolylineTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([polylineIdArg] as [Any?]) { response in @@ -2102,7 +2103,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onPolygonTap(polygonId polygonIdArg: String, completion: @escaping (Result) -> Void) { + func onPolygonTap(polygonId polygonIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onPolygonTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([polygonIdArg] as [Any?]) { response in @@ -2120,7 +2121,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onCircleTap(circleId circleIdArg: String, completion: @escaping (Result) -> Void) { + func onCircleTap(circleId circleIdArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onCircleTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([circleIdArg] as [Any?]) { response in @@ -2138,7 +2139,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onMapTap(coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onMapTap(coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onMapTap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([coordinateArg] as [Any?]) { response in @@ -2156,7 +2157,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } } - func onMapLongPress(coordinate coordinateArg: PlatformCoordinate, completion: @escaping (Result) -> Void) { + func onMapLongPress(coordinate coordinateArg: PlatformCoordinate, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onMapLongPress\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([coordinateArg] as [Any?]) { response in @@ -2175,7 +2176,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } /// `MKMapViewDelegate.mapViewDidFailLoadingMap(_:withError:)`. - func onDidFailLoadingMap(error errorArg: String, completion: @escaping (Result) -> Void) { + func onDidFailLoadingMap(error errorArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onDidFailLoadingMap\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([errorArg] as [Any?]) { response in @@ -2194,7 +2195,7 @@ class MapKitFlutterApi: MapKitFlutterApiProtocol { } } /// `MKMapViewDelegate.mapView(_:didFailToLocateUserWithError:)`. - func onDidFailToLocateUser(error errorArg: String, completion: @escaping (Result) -> Void) { + func onDidFailToLocateUser(error errorArg: String, completion: @escaping @Sendable (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.mapkit_flutter.MapKitFlutterApi.onDidFailToLocateUser\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([errorArg] as [Any?]) { response in diff --git a/lib/src/messages.g.dart b/lib/src/messages.g.dart index b6fbc99..b109d75 100644 --- a/lib/src/messages.g.dart +++ b/lib/src/messages.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v27.1.0), do not edit directly. +// Autogenerated from Pigeon (v27.3.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: unused_import, unused_shown_name // ignore_for_file: type=lint @@ -457,6 +457,7 @@ class PlatformAnnotationIcon { this.glyphSystemImage, this.glyphTintArgb, this.imageBytes, + this.imagePixelRatio, }); PlatformAnnotationIconType type; @@ -476,6 +477,9 @@ class PlatformAnnotationIcon { /// Raw PNG bytes for the `image` variant (`MKAnnotationView.image`). Uint8List? imageBytes; + /// Pixels per logical point in [imageBytes] (`UIImage(data:scale:)`). + double? imagePixelRatio; + List _toList() { return [ type, @@ -484,6 +488,7 @@ class PlatformAnnotationIcon { glyphSystemImage, glyphTintArgb, imageBytes, + imagePixelRatio, ]; } @@ -500,6 +505,7 @@ class PlatformAnnotationIcon { glyphSystemImage: result[3] as String?, glyphTintArgb: result[4] as int?, imageBytes: result[5] as Uint8List?, + imagePixelRatio: result[6] as double?, ); } @@ -517,7 +523,8 @@ class PlatformAnnotationIcon { _deepEquals(glyphText, other.glyphText) && _deepEquals(glyphSystemImage, other.glyphSystemImage) && _deepEquals(glyphTintArgb, other.glyphTintArgb) && - _deepEquals(imageBytes, other.imageBytes); + _deepEquals(imageBytes, other.imageBytes) && + _deepEquals(imagePixelRatio, other.imagePixelRatio); } @override @@ -526,7 +533,7 @@ class PlatformAnnotationIcon { @override String toString() { - return 'PlatformAnnotationIcon(type: $type, markerTintArgb: $markerTintArgb, glyphText: $glyphText, glyphSystemImage: $glyphSystemImage, glyphTintArgb: $glyphTintArgb, imageBytes: $imageBytes)'; + return 'PlatformAnnotationIcon(type: $type, markerTintArgb: $markerTintArgb, glyphText: $glyphText, glyphSystemImage: $glyphSystemImage, glyphTintArgb: $glyphTintArgb, imageBytes: $imageBytes, imagePixelRatio: $imagePixelRatio)'; } } diff --git a/lib/src/mk_annotation_icon.dart b/lib/src/mk_annotation_icon.dart index 4f92283..d6c5608 100644 --- a/lib/src/mk_annotation_icon.dart +++ b/lib/src/mk_annotation_icon.dart @@ -27,18 +27,24 @@ final class MKAnnotationIcon { _glyphText = glyphText, _glyphSystemImage = systemImage, _glyphTintColor = glyphTintColor, - _imageBytes = null; + _imageBytes = null, + _imagePixelRatio = null; /// Raw PNG bytes as the annotation image (`MKAnnotationView.image`). Render /// any Flutter widget to a PNG (e.g. via `RenderRepaintBoundary.toImage`) /// and pass it here for a fully custom marker. - const MKAnnotationIcon.image(Uint8List png) + /// + /// [imagePixelRatio] is the pixels-per-logical-point of [png] (pass the + /// `pixelRatio` the widget was rendered with); defaults to the device pixel + /// ratio, matching bytes rendered at native resolution. + const MKAnnotationIcon.image(Uint8List png, {double? imagePixelRatio}) : _type = .image, _markerTintColor = null, _glyphText = null, _glyphSystemImage = null, _glyphTintColor = null, - _imageBytes = png; + _imageBytes = png, + _imagePixelRatio = imagePixelRatio; /// Load an annotation image from the asset bundle into an /// [MKAnnotationIcon.image]. @@ -57,7 +63,9 @@ final class MKAnnotationIcon { final provider = AssetImage(name, bundle: bundle, package: package); final key = await provider.obtainKey(config); final data = await (bundle ?? rootBundle).load(key.name); - return .image(data.buffer.asUint8List()); + // key.scale is the resolved variant's scale (1x/2x/3x), which is the + // ratio of the bytes actually loaded — not necessarily the screen's. + return .image(data.buffer.asUint8List(), imagePixelRatio: key.scale); } final PlatformAnnotationIconType _type; @@ -66,6 +74,7 @@ final class MKAnnotationIcon { final String? _glyphSystemImage; final Color? _glyphTintColor; final Uint8List? _imageBytes; + final double? _imagePixelRatio; @internal /// Creates a new Platform object. @@ -78,6 +87,10 @@ final class MKAnnotationIcon { glyphSystemImage: _glyphSystemImage, glyphTintArgb: _glyphTintColor?.toARGB32(), imageBytes: _imageBytes, + imagePixelRatio: _imageBytes == null + ? null + : _imagePixelRatio ?? + PlatformDispatcher.instance.implicitView?.devicePixelRatio, ); @override @@ -88,7 +101,8 @@ final class MKAnnotationIcon { other._glyphText == _glyphText && other._glyphSystemImage == _glyphSystemImage && other._glyphTintColor == _glyphTintColor && - listEquals(other._imageBytes, _imageBytes); + listEquals(other._imageBytes, _imageBytes) && + other._imagePixelRatio == _imagePixelRatio; @override int get hashCode => Object.hash( @@ -98,6 +112,7 @@ final class MKAnnotationIcon { _glyphSystemImage, _glyphTintColor, _imageBytes == null ? null : Object.hashAll(_imageBytes), + _imagePixelRatio, ); @override @@ -108,6 +123,7 @@ final class MKAnnotationIcon { if (_glyphSystemImage case final s?) b.write(', systemImage: $s'); if (_glyphTintColor case final c?) b.write(', glyphTintColor: $c'); if (_imageBytes case final bytes?) b.write(', image: ${bytes.length}B'); + if (_imagePixelRatio case final r?) b.write(', imagePixelRatio: $r'); return (b..write(')')).toString(); } } diff --git a/pigeons/messages.dart b/pigeons/messages.dart index 6fe7ed9..8621952 100644 --- a/pigeons/messages.dart +++ b/pigeons/messages.dart @@ -2,10 +2,20 @@ // // Regenerate after editing: // dart run pigeon --input pigeons/messages.dart && dart format lib/src/messages.g.dart +// then in the regenerated messages.g.swift: +// 1. Re-apply the O(N) dictionary branch of `deepEquals` (direct +// `rhsDictionary[lhsKey]` lookup) — stock pigeon emits an O(N*M) scan +// and regenerating silently reverts the 0.3.4 perf fix. +// 2. Mark the MapKitFlutterApi completions `@Sendable` — Xcode 26+ rejects +// capturing a plain completion in the channel reply closure: +// perl -pi -e 's/completion: \@escaping \(Result\) -> Void/completion: \@escaping \@Sendable (Result) -> Void/g' \ +// ios/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift +// 3. Copy it over `darwin/mapkit_flutter/Sources/mapkit_flutter/` — the +// darwin tree builds from its own checked-in copy, not pigeon's output. // // Generated files (`lib/src/messages.g.dart`, -// `ios/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift`) are checked in -// and must not be hand-edited. +// `ios/mapkit_flutter/Sources/mapkit_flutter/messages.g.swift`, and the +// darwin copy above) are checked in; hand-edit only per the steps above. // // Naming: every type here carries a `Platform` prefix — including enums that // the public API re-exports via `typedef` (e.g. `MKUserTrackingMode = @@ -181,6 +191,7 @@ class PlatformAnnotationIcon { this.glyphSystemImage, this.glyphTintArgb, this.imageBytes, + this.imagePixelRatio, }); final PlatformAnnotationIconType type; @@ -199,6 +210,9 @@ class PlatformAnnotationIcon { /// Raw PNG bytes for the `image` variant (`MKAnnotationView.image`). final Uint8List? imageBytes; + + /// Pixels per logical point in [imageBytes] (`UIImage(data:scale:)`). + final double? imagePixelRatio; } class PlatformAnnotation { diff --git a/test/mk_annotation_icon_test.dart b/test/mk_annotation_icon_test.dart index 689f706..59c9b77 100644 --- a/test/mk_annotation_icon_test.dart +++ b/test/mk_annotation_icon_test.dart @@ -1,5 +1,5 @@ import 'dart:typed_data'; -import 'dart:ui' show Color; +import 'dart:ui' show Color, PlatformDispatcher; import 'package:checks/checks.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -16,6 +16,7 @@ void main() { check(platform.glyphSystemImage).isNull(); check(platform.glyphTintArgb).isNull(); check(platform.imageBytes).isNull(); + check(platform.imagePixelRatio).isNull(); }); test('serializes MKMarkerAnnotationView styling as ARGB32', () { @@ -42,6 +43,24 @@ void main() { check(platform.imageBytes!).deepEquals(png); check(platform.markerTintArgb).isNull(); }); + + test('serializes explicit imagePixelRatio', () { + final platform = MKAnnotationIcon.image( + Uint8List.fromList([1]), + imagePixelRatio: 2.5, + ).toPlatform(); + check(platform.imagePixelRatio).equals(2.5); + }); + + test('imagePixelRatio defaults to the implicit view ratio', () { + TestWidgetsFlutterBinding.ensureInitialized(); + final platform = MKAnnotationIcon.image( + Uint8List.fromList([1]), + ).toPlatform(); + check( + platform.imagePixelRatio, + ).equals(PlatformDispatcher.instance.implicitView!.devicePixelRatio); + }); }); group('equality', () { @@ -62,6 +81,14 @@ void main() { check(a == c).isFalse(); }); + test('images with different pixel ratios never compare equal', () { + final bytes = Uint8List.fromList([1, 2]); + final a = MKAnnotationIcon.image(bytes, imagePixelRatio: 2); + final b = MKAnnotationIcon.image(bytes, imagePixelRatio: 3); + check(a == b).isFalse(); + check(a).equals(MKAnnotationIcon.image(bytes, imagePixelRatio: 2)); + }); + test('marker and image never compare equal', () { check( const MKAnnotationIcon.marker() == MKAnnotationIcon.image(Uint8List(0)),