diff --git a/Sources/MapConductorForMapKit/MapKitMapView.swift b/Sources/MapConductorForMapKit/MapKitMapView.swift index a5772cf..2461e97 100644 --- a/Sources/MapConductorForMapKit/MapKitMapView.swift +++ b/Sources/MapConductorForMapKit/MapKitMapView.swift @@ -9,6 +9,8 @@ public struct MapKitMapView: View { @ObservedObject private var state: MapKitViewState private let handlers: MapViewHandlers private let cameraRestriction: CameraRestriction? + private let style: MapViewStyle? + private let onStyleDiagnostics: (([String]) -> Void)? private let content: () -> MapViewContent public init( @@ -21,10 +23,18 @@ public struct MapKitMapView: View { onCameraMove: OnCameraMoveHandler? = nil, onCameraMoveEnd: OnCameraMoveHandler? = nil, sdkInitialize: (() -> Void)? = nil, + /// How the map looks, when the app states it rather than naming a + /// design. `MapConductorVectorStyle` builds one; what happens + /// underneath depends on this backend and the app does not have to + /// know. + style: MapViewStyle? = nil, + onStyleDiagnostics: (([String]) -> Void)? = nil, @MapViewContentBuilder content: @escaping () -> MapViewContent = { MapViewContent() } ) { self.state = state self.cameraRestriction = cameraRestriction + self.style = style + self.onStyleDiagnostics = onStyleDiagnostics self.handlers = MapViewHandlers( onMapLoaded: onMapLoaded, onMapClick: onMapClick, @@ -54,6 +64,8 @@ public struct MapKitMapView: View { state: state, cameraRestriction: cameraRestriction, handlers: handlers, + style: style, + onStyleDiagnostics: onStyleDiagnostics, content: mapContent ) } @@ -64,6 +76,8 @@ private struct MapKitMapViewRepresentable: UIViewRepresentable { @ObservedObject var state: MapKitViewState let cameraRestriction: CameraRestriction? let handlers: MapViewHandlers + let style: MapViewStyle? + let onStyleDiagnostics: (([String]) -> Void)? let content: MapViewContent func makeCoordinator() -> Coordinator { @@ -109,6 +123,9 @@ private struct MapKitMapViewRepresentable: UIViewRepresentable { } func updateUIView(_ uiView: MKMapView, context: Context) { + // Every evaluation of the app's `body` lands here; most calls do + // nothing. See `MapViewStyleHost.apply`. + context.coordinator.applyStyle(style, onDiagnostics: onStyleDiagnostics) // 制限値が変わったときだけ再適用する。 context.coordinator.applyCameraRestriction(cameraRestriction) uiView.mapType = state.mapDesignType.getValue() @@ -128,6 +145,7 @@ private struct MapKitMapViewRepresentable: UIViewRepresentable { } static func dismantleUIView(_ uiView: MKMapView, coordinator: Coordinator) { + coordinator.disposeStyle() coordinator.unbind() uiView.delegate = nil } diff --git a/Sources/MapConductorForMapKit/marker/MapKitMarkerController.swift b/Sources/MapConductorForMapKit/marker/MapKitMarkerController.swift index 6c306b8..0ac85b0 100644 --- a/Sources/MapConductorForMapKit/marker/MapKitMarkerController.swift +++ b/Sources/MapConductorForMapKit/marker/MapKitMarkerController.swift @@ -21,8 +21,18 @@ final class MapKitMarkerController: AbstractMarkerController MKTileOverlay? { guard let mapView else { return nil } - guard let overlay = await makeTileOverlay(from: state) else { return nil } + guard let overlay = await makeTileOverlay(from: state, displayScale: mapView.traitCollection.displayScale) else { return nil } overlayStates[overlay] = state let renderer = MKTileOverlayRenderer(tileOverlay: overlay) @@ -97,14 +97,18 @@ final class MapKitRasterLayerOverlayRenderer: AbstractRasterLayerOverlayRenderer mapView = nil } - private func makeTileOverlay(from state: RasterLayerState) async -> MKTileOverlay? { + private func makeTileOverlay(from state: RasterLayerState, displayScale: CGFloat) async -> MKTileOverlay? { let source = await resolveSource(state: state) switch source { case let .urlTemplate(template, tileSize, minZoom, maxZoom, _, scheme): + // RasterLayerSource expresses display size in points; MKTileOverlay + // expects pixels. Without the screen scale, Retina maps select a + // finer tile level and shrink the style's labels and road widths. + let pixelSize = max(1, Int((CGFloat(tileSize) * max(1, displayScale)).rounded())) let overlay = CustomURLTileOverlay( urlTemplate: template, - tileSize: tileSize, + tileSize: pixelSize, minZoom: minZoom, maxZoom: maxZoom, scheme: scheme, diff --git a/api/MapConductorForMapKit.api.swift b/api/MapConductorForMapKit.api.swift index 2dfbbb1..b909ad1 100644 --- a/api/MapConductorForMapKit.api.swift +++ b/api/MapConductorForMapKit.api.swift @@ -12,70 +12,70 @@ import _Concurrency import _MapKit_SwiftUI import _StringProcessing import _SwiftConcurrencyShims -extension MapConductorCore.MapCameraPosition { - final public func toMKMapCamera(on mapView: MapKit.MKMapView? = nil) -> MapKit.MKMapCamera +extension MapConductorCore::MapCameraPosition { + final public func toMKMapCamera(on mapView: MapKit::MKMapView? = nil) -> MapKit::MKMapCamera } -extension MapKit.MKMapView { - @_Concurrency.MainActor @preconcurrency public func toMapCameraPosition(logicalTiltHint: Swift.Double? = nil, visibleRegion: MapConductorCore.VisibleRegion? = nil) -> MapConductorCore.MapCameraPosition +extension MapKit::MKMapView { + @_Concurrency::MainActor @preconcurrency public func toMapCameraPosition(logicalTiltHint: Swift::Double? = nil, visibleRegion: MapConductorCore::VisibleRegion? = nil) -> MapConductorCore::MapCameraPosition } -public protocol MapKitMapDesignTypeProtocol : MapConductorCore.MapDesignTypeProtocol where Self.Identifier == MapKit.MKMapType { +public protocol MapKitMapDesignTypeProtocol : MapConductorCore::MapDesignTypeProtocol where Self.Identifier == MapKit::MKMapType { } -public typealias MapKitMapDesignType = any MapConductorForMapKit.MapKitMapDesignTypeProtocol -public struct MapKitMapDesign : MapConductorForMapKit.MapKitMapDesignTypeProtocol, Swift.Hashable { - public let id: MapKit.MKMapType - public let attributionRules: [MapConductorCore.AttributionRule] - public init(id: MapKit.MKMapType, attributionRules: [MapConductorCore.AttributionRule] = []) - public func getValue() -> MapKit.MKMapType - public static let Standard: MapConductorForMapKit.MapKitMapDesign - public static let Satellite: MapConductorForMapKit.MapKitMapDesign - public static let Hybrid: MapConductorForMapKit.MapKitMapDesign - public static let SatelliteFlyover: MapConductorForMapKit.MapKitMapDesign - public static let HybridFlyover: MapConductorForMapKit.MapKitMapDesign - public static let MutedStandard: MapConductorForMapKit.MapKitMapDesign - public static func Create(id: MapKit.MKMapType) -> MapConductorForMapKit.MapKitMapDesign - public static func toMapDesignType(id: MapKit.MKMapType) -> MapConductorForMapKit.MapKitMapDesignType - public static func == (a: MapConductorForMapKit.MapKitMapDesign, b: MapConductorForMapKit.MapKitMapDesign) -> Swift.Bool - public typealias Identifier = MapKit.MKMapType - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { +public typealias MapKitMapDesignType = any MapConductorForMapKit::MapKitMapDesignTypeProtocol +public struct MapKitMapDesign : MapConductorForMapKit::MapKitMapDesignTypeProtocol, Swift::Hashable { + public let id: MapKit::MKMapType + public let attributionRules: [MapConductorCore::AttributionRule] + public init(id: MapKit::MKMapType, attributionRules: [MapConductorCore::AttributionRule] = []) + public func getValue() -> MapKit::MKMapType + public static let Standard: MapConductorForMapKit::MapKitMapDesign + public static let Satellite: MapConductorForMapKit::MapKitMapDesign + public static let Hybrid: MapConductorForMapKit::MapKitMapDesign + public static let SatelliteFlyover: MapConductorForMapKit::MapKitMapDesign + public static let HybridFlyover: MapConductorForMapKit::MapKitMapDesign + public static let MutedStandard: MapConductorForMapKit::MapKitMapDesign + public static func Create(id: MapKit::MKMapType) -> MapConductorForMapKit::MapKitMapDesign + public static func toMapDesignType(id: MapKit::MKMapType) -> MapConductorForMapKit::MapKitMapDesignType + public static func == (a: MapConductorForMapKit::MapKitMapDesign, b: MapConductorForMapKit::MapKitMapDesign) -> Swift::Bool + public typealias Identifier = MapKit::MKMapType + public func hash(into hasher: inout Swift::Hasher) + public var hashValue: Swift::Int { get } } -@_Concurrency.MainActor @preconcurrency public struct MapKitMapView : SwiftUICore.View { - @_Concurrency.MainActor @preconcurrency public init(state: MapConductorForMapKit.MapKitViewState, cameraRestriction: MapConductorCore.CameraRestriction? = nil, onMapLoaded: MapConductorCore.OnMapLoadedHandler? = nil, onMapClick: MapConductorCore.OnMapEventHandler? = nil, onMapLongClick: MapConductorCore.OnMapEventHandler? = nil, onCameraMoveStart: MapConductorCore.OnCameraMoveHandler? = nil, onCameraMove: MapConductorCore.OnCameraMoveHandler? = nil, onCameraMoveEnd: MapConductorCore.OnCameraMoveHandler? = nil, sdkInitialize: (() -> Swift.Void)? = nil, @MapConductorCore.MapViewContentBuilder content: @escaping () -> MapConductorCore.MapViewContent = { MapViewContent() }) - @_Concurrency.MainActor @preconcurrency public var body: some SwiftUICore.View { +@_Concurrency::MainActor @preconcurrency public struct MapKitMapView : SwiftUICore::View { + @_Concurrency::MainActor @preconcurrency public init(state: MapConductorForMapKit::MapKitViewState, cameraRestriction: MapConductorCore::CameraRestriction? = nil, onMapLoaded: MapConductorCore::OnMapLoadedHandler? = nil, onMapClick: MapConductorCore::OnMapEventHandler? = nil, onMapLongClick: MapConductorCore::OnMapEventHandler? = nil, onCameraMoveStart: MapConductorCore::OnCameraMoveHandler? = nil, onCameraMove: MapConductorCore::OnCameraMoveHandler? = nil, onCameraMoveEnd: MapConductorCore::OnCameraMoveHandler? = nil, sdkInitialize: (() -> Swift::Void)? = nil, style: (any MapConductorCore::MapViewStyle)? = nil, onStyleDiagnostics: (([Swift::String]) -> Swift::Void)? = nil, @MapConductorCore::MapViewContentBuilder content: @escaping () -> MapConductorCore::MapViewContent = { MapViewContent() }) + @_Concurrency::MainActor @preconcurrency public var body: some SwiftUICore::View { get } public typealias Body = @_opaqueReturnTypeOf("$s015MapConductorForA3Kit0adA4ViewV4bodyQrvp", 0) __ } -public typealias MapKitActualMarker = MapKit.MKPointAnnotation -public typealias MapKitActualPolyline = MapKit.MKPolyline -public typealias MapKitActualCircle = MapKit.MKCircle -public typealias MapKitActualPolygon = MapKit.MKPolygon -final public class MapKitViewState : MapConductorCore.MapViewState { - final public var mapViewHolder: MapConductorForMapKit.MapKitViewHolder? { +public typealias MapKitActualMarker = MapKit::MKPointAnnotation +public typealias MapKitActualPolyline = MapKit::MKPolyline +public typealias MapKitActualCircle = MapKit::MKCircle +public typealias MapKitActualPolygon = MapKit::MKPolygon +final public class MapKitViewState : MapConductorCore::MapViewState { + final public var mapViewHolder: MapConductorForMapKit::MapKitViewHolder? { get } - override final public var mapDesignType: MapConductorForMapKit.MapKitMapDesignType { + override final public var mapDesignType: MapConductorForMapKit::MapKitMapDesignType { get set } - public init(id: Swift.String, mapDesignType: MapConductorForMapKit.MapKitMapDesignType = MapKitMapDesign.Standard, cameraPosition: MapConductorCore.MapCameraPosition = .Default, uiSettings: MapConductorCore.MapUISettings = MapUISettings()) - convenience public init(mapDesignType: MapConductorForMapKit.MapKitMapDesignType = MapKitMapDesign.Standard, cameraPosition: MapConductorCore.MapCameraPosition = .Default, uiSettings: MapConductorCore.MapUISettings = MapUISettings()) - override final public func getMapViewHolder() -> MapConductorCore.AnyMapViewHolder? + public init(id: Swift::String, mapDesignType: MapConductorForMapKit::MapKitMapDesignType = MapKitMapDesign.Standard, cameraPosition: MapConductorCore::MapCameraPosition = .Default, uiSettings: MapConductorCore::MapUISettings = MapUISettings()) + convenience public init(mapDesignType: MapConductorForMapKit::MapKitMapDesignType = MapKitMapDesign.Standard, cameraPosition: MapConductorCore::MapCameraPosition = .Default, uiSettings: MapConductorCore::MapUISettings = MapUISettings()) + override final public func getMapViewHolder() -> MapConductorCore::AnyMapViewHolder? @objc deinit } -public class MapKitZoomAltitudeConverter : MapConductorCore.WebMercatorZoomAltitudeConverter { - public init(zoom0Altitude: Swift.Double = AbstractZoomAltitudeConverter.defaultZoom0Altitude) +public class MapKitZoomAltitudeConverter : MapConductorCore::WebMercatorZoomAltitudeConverter { + public init(zoom0Altitude: Swift::Double = AbstractZoomAltitudeConverter.defaultZoom0Altitude) @objc deinit } -@_hasMissingDesignatedInitializers final public class MapKitViewHolder : MapConductorCore.MapViewHolderProtocol { - final public let mapView: MapKit.MKMapView - final public let map: MapKit.MKMapView - final public func toScreenOffset(position: any MapConductorCore.GeoPointProtocol) -> CoreFoundation.CGPoint? - final public func fromScreenOffsetSync(offset: CoreFoundation.CGPoint) -> MapConductorCore.GeoPoint? - public typealias ActualMap = MapKit.MKMapView - public typealias ActualMapView = MapKit.MKMapView +@_hasMissingDesignatedInitializers final public class MapKitViewHolder : MapConductorCore::MapViewHolderProtocol { + final public let mapView: MapKit::MKMapView + final public let map: MapKit::MKMapView + final public func toScreenOffset(position: any MapConductorCore::GeoPointProtocol) -> CoreFoundation::CGPoint? + final public func fromScreenOffsetSync(offset: CoreFoundation::CGPoint) -> MapConductorCore::GeoPoint? + public typealias ActualMap = MapKit::MKMapView + public typealias ActualMapView = MapKit::MKMapView @objc deinit } -extension MapConductorForMapKit.MapKitMapView : Swift.Sendable {} +extension MapConductorForMapKit::MapKitMapView : Swift::Sendable {}