diff --git a/TIAppleMapUtils/Sources/AppleClusterPlacemarkManager.swift b/TIAppleMapUtils/Sources/AppleClusterPlacemarkManager.swift index 0cc48109..e5016f73 100644 --- a/TIAppleMapUtils/Sources/AppleClusterPlacemarkManager.swift +++ b/TIAppleMapUtils/Sources/AppleClusterPlacemarkManager.swift @@ -24,7 +24,9 @@ import TIMapUtils import MapKit import UIKit -open class AppleClusterPlacemarkManager: BasePlacemarkManager], MKMapRect>, MKMapViewDelegate { +open class AppleClusterPlacemarkManager: BasePlacemarkManager], MKMapRect>, + MKMapViewDelegate { + public weak var mapViewDelegate: MKMapViewDelegate? private let mapDelegateSelectors = NSObject.instanceMethodSelectors(of: MKMapViewDelegate.self) @@ -49,16 +51,26 @@ open class AppleClusterPlacemarkManager: BasePlacemarkManager] else { - return - } + return + } - placemark.image = iconFactory?.markerIcon(for: placemarkManagers) + placemark.image = iconFactory?.markerIcon(for: placemarkManagers, state: .default) } // MARK: - MKMapViewDelegate @@ -79,6 +91,7 @@ open class AppleClusterPlacemarkManager: BasePlacemarkManager: let defaultAnnotationView = placemarkManager.iconFactory != nil ? MKAnnotationView(annotation: annotation, @@ -89,6 +102,7 @@ open class AppleClusterPlacemarkManager: BasePlacemarkManager: BasePlacemarkManager: - _ = placemarkManager.tapHandler?(placemarkManager.dataModel, placemarkManager.coordinate) + let isTapHandled = placemarkManager.tapHandler?(placemarkManager.dataModel, placemarkManager.coordinate) ?? false + + if isTapHandled { + placemarkManager.state = .selected + } + + default: + return + } + } + + open func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) { + guard !(mapViewDelegate?.responds(to: #selector(mapView(_:didDeselect:))) ?? false) else { + mapViewDelegate?.mapView?(mapView, didDeselect: view) + return + } + + switch view.annotation { + case let placemarkManager as ApplePlacemarkManager: + placemarkManager.state = .default + default: return } diff --git a/TIAppleMapUtils/Sources/AppleMapManager.swift b/TIAppleMapUtils/Sources/AppleMapManager.swift index 4f15d02d..e4994236 100644 --- a/TIAppleMapUtils/Sources/AppleMapManager.swift +++ b/TIAppleMapUtils/Sources/AppleMapManager.swift @@ -44,7 +44,8 @@ open class AppleMapManager: BaseMapManager: BasePlacemarkManager, MKAnnotation { +open class ApplePlacemarkManager: BasePlacemarkManager, + MKAnnotation { + // MARK: - MKAnnotation + + /// A map where all placemarks are placed + public let map: MKMapView + /// Point (coordinates) itself of the current placemark manager public let coordinate: CLLocationCoordinate2D + /// Identifier required for correct cluster placement public var clusteringIdentifier: String? + + /// Placemark itself of the current placemark manager + public private(set) var placemark: MKAnnotationView? + + /// The current state of a manager's placemark + public var state: MarkerState = .default { + didSet { + guard let placemark = placemark else { + return + } + + /* + Although the icon is being updated, it is necessary to manually deselect + the annotation of the current placemark if it is currently selected. + */ + if state == .default, let annotation = placemark.annotation { + map.deselectAnnotation(annotation, animated: true) + } + + placemark.image = iconFactory?.markerIcon(for: dataModel, state: state) + } + } - public init(dataModel: Model, + public init(map: MKMapView, + dataModel: Model, position: CLLocationCoordinate2D, clusteringIdentifier: String?, iconFactory: AnyMarkerIconFactory?, tapHandler: TapHandlerClosure?) { + self.map = map self.coordinate = position self.clusteringIdentifier = clusteringIdentifier @@ -47,7 +78,9 @@ open class ApplePlacemarkManager: BasePlacemarkManager: BasePlacemarkManager: BasePlacemarkManager: clusterItem.configure(placemark: marker) diff --git a/TIGoogleMapUtils/Sources/GooglePlacemarkManager.swift b/TIGoogleMapUtils/Sources/GooglePlacemarkManager.swift index c3e06a57..9907ec26 100644 --- a/TIGoogleMapUtils/Sources/GooglePlacemarkManager.swift +++ b/TIGoogleMapUtils/Sources/GooglePlacemarkManager.swift @@ -24,10 +24,27 @@ import TIMapUtils import GoogleMaps import GoogleMapsUtils -open class GooglePlacemarkManager: BasePlacemarkManager, GMUClusterItem { +open class GooglePlacemarkManager: BasePlacemarkManager, + GMUClusterItem { + // MARK: - GMUClusterItem + /// Point (coordinates) itself of the current placemark manager public let position: CLLocationCoordinate2D + + /// Placemark itself of the current placemark manager + public private(set) var placemark: GMSMarker? + + /// The current state of a manager's placemark + public var state: MarkerState = .default { + didSet { + guard let placemark = placemark else { + return + } + + placemark.icon = iconFactory?.markerIcon(for: dataModel, state: state) + } + } public init(dataModel: Model, position: CLLocationCoordinate2D, @@ -44,6 +61,14 @@ open class GooglePlacemarkManager: BasePlacemarkManager Bool { + lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude + } +} diff --git a/TIMapUtils/Sources/IconProviders/AnyMarkerIconFactory.swift b/TIMapUtils/Sources/IconProviders/AnyMarkerIconFactory.swift index c6a9da8a..ccebd849 100644 --- a/TIMapUtils/Sources/IconProviders/AnyMarkerIconFactory.swift +++ b/TIMapUtils/Sources/IconProviders/AnyMarkerIconFactory.swift @@ -23,20 +23,20 @@ import UIKit.UIImage public final class AnyMarkerIconFactory: MarkerIconFactory { - public typealias IconProviderClosure = (Model) -> UIImage + public typealias IconProviderClosure = (Model, MarkerState) -> UIImage public var iconProviderClosure: IconProviderClosure public init(iconFactory: IF) where IF.Model == Model { - self.iconProviderClosure = { iconFactory.markerIcon(for: $0) } + self.iconProviderClosure = { iconFactory.markerIcon(for: $0, state: $1) } } public init(iconFactory: IF, transform: @escaping (Model) -> T) where IF.Model == T { - self.iconProviderClosure = { iconFactory.markerIcon(for: transform($0)) } + self.iconProviderClosure = { iconFactory.markerIcon(for: transform($0), state: $1) } } - public func markerIcon(for model: Model) -> UIImage { - iconProviderClosure(model) + public func markerIcon(for model: Model, state: MarkerState) -> UIImage { + iconProviderClosure(model, state) } } diff --git a/TIMapUtils/Sources/IconProviders/DefaultCachableMarkerIconFactory.swift b/TIMapUtils/Sources/IconProviders/DefaultCachableMarkerIconFactory.swift index 10083d31..9ad5fdbc 100644 --- a/TIMapUtils/Sources/IconProviders/DefaultCachableMarkerIconFactory.swift +++ b/TIMapUtils/Sources/IconProviders/DefaultCachableMarkerIconFactory.swift @@ -37,11 +37,11 @@ open class DefaultCachableMarkerIconFactory: DefaultMarkerIconF super.init(createIconClosure: createIconClosure) } - open override func markerIcon(for model: M) -> UIImage { + open override func markerIcon(for model: M, state: MarkerState) -> UIImage { let cacheKey = cacheKeyProvider(model) guard let cachedIcon = cache.object(forKey: cacheKey) else { - let icon = super.markerIcon(for: model) + let icon = super.markerIcon(for: model, state: .default) cache.setObject(icon, forKey: cacheKey) return icon diff --git a/TIMapUtils/Sources/IconProviders/DefaultClusterMarkerIconFactory.swift b/TIMapUtils/Sources/IconProviders/DefaultClusterMarkerIconFactory.swift index c2ab65b7..eccfdca6 100644 --- a/TIMapUtils/Sources/IconProviders/DefaultClusterMarkerIconFactory.swift +++ b/TIMapUtils/Sources/IconProviders/DefaultClusterMarkerIconFactory.swift @@ -32,10 +32,10 @@ public final class DefaultClusterMarkerIconFactory: DefaultCachableMarker self.beforeRenderCallback = beforeRenderCallback self.clusterIconRenderer = DefaultClusterIconRenderer() - super.init { [clusterIconRenderer] in - beforeRenderCallback?($0, clusterIconRenderer) + super.init { [clusterIconRenderer] models, _ in + beforeRenderCallback?(models, clusterIconRenderer) - return clusterIconRenderer.renderCluster(of: $0.count) + return clusterIconRenderer.renderCluster(of: models.count) } cacheKeyProvider: { String($0.count) as NSString } diff --git a/TIMapUtils/Sources/IconProviders/DefaultMarkerIconFactory.swift b/TIMapUtils/Sources/IconProviders/DefaultMarkerIconFactory.swift index b4d4b35a..2fb403f5 100644 --- a/TIMapUtils/Sources/IconProviders/DefaultMarkerIconFactory.swift +++ b/TIMapUtils/Sources/IconProviders/DefaultMarkerIconFactory.swift @@ -23,7 +23,7 @@ import UIKit.UIImage open class DefaultMarkerIconFactory: MarkerIconFactory { - public typealias CreateIconClosure = (M) -> UIImage + public typealias CreateIconClosure = (M, MarkerState) -> UIImage private let createIconClosure: CreateIconClosure @@ -31,8 +31,8 @@ open class DefaultMarkerIconFactory: MarkerIconFactory { self.createIconClosure = createIconClosure } - open func markerIcon(for model: M) -> UIImage { - postprocess(icon: createIconClosure(model)) + open func markerIcon(for model: M, state: MarkerState) -> UIImage { + postprocess(icon: createIconClosure(model, state)) } open func postprocess(icon: UIImage) -> UIImage { diff --git a/TIMapUtils/Sources/IconProviders/MarkerIconFactory.swift b/TIMapUtils/Sources/IconProviders/MarkerIconFactory.swift index 405688bb..919e338c 100644 --- a/TIMapUtils/Sources/IconProviders/MarkerIconFactory.swift +++ b/TIMapUtils/Sources/IconProviders/MarkerIconFactory.swift @@ -25,5 +25,5 @@ import UIKit.UIImage public protocol MarkerIconFactory { associatedtype Model - func markerIcon(for model: Model) -> UIImage + func markerIcon(for model: Model, state: MarkerState) -> UIImage } diff --git a/TIMapUtils/Sources/IconProviders/MarkerState.swift b/TIMapUtils/Sources/IconProviders/MarkerState.swift new file mode 100644 index 00000000..c3d53f05 --- /dev/null +++ b/TIMapUtils/Sources/IconProviders/MarkerState.swift @@ -0,0 +1,31 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +/// Available marker states on any map +public enum MarkerState { + + /// A state where a map point is selected and a marker is highlighted + case selected + + /// A default state where a map point is shown on a map + case `default` +} diff --git a/TIMapUtils/Sources/IconProviders/StaticImageIconFactory.swift b/TIMapUtils/Sources/IconProviders/StaticImageIconFactory.swift index 8e28bdbe..65fad0aa 100644 --- a/TIMapUtils/Sources/IconProviders/StaticImageIconFactory.swift +++ b/TIMapUtils/Sources/IconProviders/StaticImageIconFactory.swift @@ -29,7 +29,7 @@ public final class StaticImageIconFactory: MarkerIconFactory { self.image = image } - public func markerIcon(for model: Model) -> UIImage { + public func markerIcon(for model: Model, state: MarkerState) -> UIImage { image } } diff --git a/TIMapUtils/Sources/Managers/PlacemarkManager.swift b/TIMapUtils/Sources/Managers/PlacemarkManager.swift index b58ad67f..59066c3b 100644 --- a/TIMapUtils/Sources/Managers/PlacemarkManager.swift +++ b/TIMapUtils/Sources/Managers/PlacemarkManager.swift @@ -27,5 +27,15 @@ public protocol PlacemarkManager { typealias TapHandlerClosure = (DataModel, Position) -> Bool var dataModel: DataModel { get } + + /// + /// Validates whether the current tap could be handled or not + /// + /// - Parameters: + /// - DataModel: A data model of the current placemark manager + /// - Position: A position of the current placemark + /// + /// - Returns: A `Bool` value of the handling desicion + /// var tapHandler: TapHandlerClosure? { get set } } diff --git a/TIYandexMapUtils/Sources/YandexClusterPlacemarkManager.swift b/TIYandexMapUtils/Sources/YandexClusterPlacemarkManager.swift index f5b42af4..d4cdf37e 100644 --- a/TIYandexMapUtils/Sources/YandexClusterPlacemarkManager.swift +++ b/TIYandexMapUtils/Sources/YandexClusterPlacemarkManager.swift @@ -25,7 +25,9 @@ import YandexMapsMobile import UIKit import CoreLocation -open class YandexClusterPlacemarkManager: BasePlacemarkManager], [YMKPoint]>, YMKClusterListener, YMKClusterTapListener { +open class YandexClusterPlacemarkManager: BasePlacemarkManager], [YMKPoint]>, + YMKClusterListener, + YMKClusterTapListener { private var placemarkCollection: YMKClusterizedPlacemarkCollection? @@ -63,6 +65,16 @@ open class YandexClusterPlacemarkManager: BasePlacemarkManager: BasePlacemarkManager: BasePlacemarkManager, YMKMapObjectTapListener { +open class YandexPlacemarkManager: BasePlacemarkManager, + YMKMapObjectTapListener { + + /// Point (coordinates) itself of the current placemark manager public let position: YMKPoint + + /// Placemark itself of the current placemark manager + public private(set) var placemark: YMKPlacemarkMapObject? + + /// The current state of a manager's placemark + public var state: MarkerState = .default { + didSet { + guard let placemark = placemark else { + return + } + + if let customIcon = iconFactory?.markerIcon(for: dataModel, state: state) { + placemark.setIconWith(customIcon) + } + } + } public init(dataModel: Model, position: YMKPoint, @@ -41,16 +60,26 @@ open class YandexPlacemarkManager: BasePlacemarkManager Bool { - tapHandler?(dataModel, point) ?? false + // Tap handling and receiving a result flag + let isTapHandled = tapHandler?(dataModel, point) ?? false + + + // If a tap was handled successfully then additionally update pin state for appearance configuration + if isTapHandled { + state = .selected + } + + return isTapHandled } // MARK: - PlacemarkManager override open func configure(placemark: YMKPlacemarkMapObject) { + // Setting initial values in cluster configuration and each placemark respectively + self.placemark = placemark + self.state = .default + + // Setting tap listener for a pin tap handling placemark.addTapListener(with: self) - - if let customIcon = iconFactory?.markerIcon(for: dataModel) { - placemark.setIconWith(customIcon) - } } }