diff --git a/Sources/MapConductorForMapbox/MapboxMapHost.swift b/Sources/MapConductorForMapbox/MapboxMapHost.swift index 8c79e77..afc2457 100644 --- a/Sources/MapConductorForMapbox/MapboxMapHost.swift +++ b/Sources/MapConductorForMapbox/MapboxMapHost.swift @@ -218,6 +218,7 @@ public final class MapboxMapHost: MapViewCoordinatorBase { controller.registerOverlayController(polygonController) controller.registerOverlayController(groundImageController) controller.registerOverlayController(rasterController) + controller.clearOverlaysHandler = { [weak self] in await self?.clearOverlays() } let overlayScope = MapOverlayScope() self.overlayScope = overlayScope @@ -303,6 +304,17 @@ public final class MapboxMapHost: MapViewCoordinatorBase { } } + /// android-sdk の `clearOverlays()` と同じく、各オーバーレイのコントローラを空にする。 + /// `controller.clearOverlaysHandler` から呼ばれる。 + func clearOverlays() async { + await markerController?.clear() + await polylineController?.clear() + await polygonController?.clear() + await groundImageController?.clear() + await circleController?.clear() + await rasterController?.clear() + } + public func unbind() { // 登録した capability を取り下げる。レジストリの持ち主は state で、ビューより長生きするため、 // ここで外さないと破棄済みのコントローラを掴んだまま残る。 diff --git a/Sources/MapConductorForMapbox/controller/MapboxViewController.swift b/Sources/MapConductorForMapbox/controller/MapboxViewController.swift index 781794a..ee932f5 100644 --- a/Sources/MapConductorForMapbox/controller/MapboxViewController.swift +++ b/Sources/MapConductorForMapbox/controller/MapboxViewController.swift @@ -33,7 +33,13 @@ final class MapboxViewController: MapViewControllerProtocol { self.holder = AnyMapViewHolder(typedHolder) } - func clearOverlays() async {} + /// ``clearOverlays()`` の中身。オーバーレイのコントローラはホストが持っているので、 + /// ホストが生成時に設定する(android-sdk と同じく、各コントローラの `clear()` を呼ぶ)。 + var clearOverlaysHandler: (@MainActor () async -> Void)? + + func clearOverlays() async { + await clearOverlaysHandler?() + } func setCameraMoveStartListener(listener: OnCameraMoveHandler?) { cameraMoveStartListener = listener } func setCameraMoveListener(listener: OnCameraMoveHandler?) { cameraMoveListener = listener } diff --git a/Sources/MapConductorForMapbox/groundimage/MapboxGroundImageController.swift b/Sources/MapConductorForMapbox/groundimage/MapboxGroundImageController.swift index 31c75b2..46e798c 100644 --- a/Sources/MapConductorForMapbox/groundimage/MapboxGroundImageController.swift +++ b/Sources/MapConductorForMapbox/groundimage/MapboxGroundImageController.swift @@ -9,6 +9,13 @@ final class MapboxGroundImageController: SlottedOverlayController { private var groundImageSubscriptions: [String: AnyCancellable] = [:] private var groundImageStatesById: [String: GroundImageState] = [:] + /// `clear()` の時点で送られていた一式。同じ一式が送り直されている間は描かない。 + /// + /// core の `OverlayCollector` と振る舞いをそろえるため。あちらは clear 後も一式を覚えていて、 + /// 同じインスタンスの再送では何もせず、顔ぶれが変わったか作り直された state が来たときだけ + /// 全部を描き直す。これが無いと、SwiftUI が body を再評価するたび(clear の直後にも来る)に + /// 消したはずの画像が戻っていた。 + private var clearedStates: [String: GroundImageState]? /// スタイル読み込み待ちの取り込み。捨てずに保留し、`onStyleLoaded` で流す。 /// 「なぜ待つ必要があるか」は `DeferredUntilReady` の説明にある。 private lazy var styleGate = DeferredUntilReady<[GroundImageState]> { [weak self] states in @@ -34,6 +41,12 @@ final class MapboxGroundImageController: SlottedOverlayController { } func syncGroundImages(_ groundImages: [GroundImage]) { + if let cleared = clearedStates { + let resent = groundImages.count == cleared.count + && groundImages.allSatisfy { cleared[$0.state.id] === $0.state } + if resent { return } + clearedStates = nil + } let newIds = Set(groundImages.map { $0.id }) let oldIds = Set(groundImageStatesById.keys) var newStatesById: [String: GroundImageState] = [:] @@ -116,10 +129,23 @@ final class MapboxGroundImageController: SlottedOverlayController { } } + /// `clearOverlays()` 用。描画済みの画像を外して空にする(地図と renderer は残す)。 + /// + /// コアの GroundImageController を継承していないので、他の種類と同じ `clear()` をここで持つ。 + /// 空の一式を styleGate へ渡すので、スタイル読み込み前でも後でも、読み込み直しても戻らない。 + func clear() async { + groundImageSubscriptions.values.forEach { $0.cancel() } + groundImageSubscriptions.removeAll() + clearedStates = groundImageStatesById + groundImageStatesById.removeAll() + styleGate.submit([]) + } + func unbind() { groundImageSubscriptions.values.forEach { $0.cancel() } groundImageSubscriptions.removeAll() groundImageStatesById.removeAll() + clearedStates = nil styleGate.reset() renderer.unbind() groundImageManager.destroy()