feat: TIMapUtils, TIAppleMapUtils, TIGoogleMapUtils and TIYandexMapUtils modules for map items clustering and interacting with them
This commit is contained in:
parent
ceab75e3b5
commit
47ff4d949c
|
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
### 1.16.0
|
||||
|
||||
- **Add**: `TIMapUtils`, `TIAppleMapUtils`, `TIGoogleMapUtils` and `TIYandexMapUtils` modules for map items clustering and interacting with them.
|
||||
|
||||
### 1.15.0
|
||||
|
||||
- **Update**: Network services in TIMoyaNetworking now passes MoyaError in result of EnpointRequest execution.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = "LeadKit"
|
||||
s.version = "1.15.0"
|
||||
s.version = "1.16.0"
|
||||
s.summary = "iOS framework with a bunch of tools for rapid development"
|
||||
s.homepage = "https://github.com/TouchInstinct/LeadKit"
|
||||
s.license = "Apache License, Version 2.0"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ let package = Package(
|
|||
.library(name: "TINetworking", targets: ["TINetworking"]),
|
||||
.library(name: "TIMoyaNetworking", targets: ["TIMoyaNetworking"]),
|
||||
.library(name: "TINetworkingCache", targets: ["TINetworkingCache"]),
|
||||
|
||||
// MARK: - Maps
|
||||
|
||||
.library(name: "TIMapUtils", targets: ["TIMapUtils"]),
|
||||
.library(name: "TIAppleMapUtils", targets: ["TIAppleMapUtils"]),
|
||||
|
||||
// MARK: - Elements
|
||||
.library(name: "OTPSwiftView", targets: ["OTPSwiftView"]),
|
||||
|
|
@ -54,6 +59,11 @@ let package = Package(
|
|||
.target(name: "TINetworking", dependencies: ["TISwiftUtils", "Alamofire"], path: "TINetworking/Sources"),
|
||||
.target(name: "TIMoyaNetworking", dependencies: ["TINetworking", "TIFoundationUtils", "Moya"], path: "TIMoyaNetworking"),
|
||||
.target(name: "TINetworkingCache", dependencies: ["TIFoundationUtils", "TINetworking", "Cache"], path: "TINetworkingCache/Sources"),
|
||||
|
||||
// MARK: - Maps
|
||||
|
||||
.target(name: "TIMapUtils", dependencies: [], path: "TIMapUtils/Sources"),
|
||||
.target(name: "TIAppleMapUtils", dependencies: ["TIMapUtils"], path: "TIAppleMapUtils/Sources"),
|
||||
|
||||
// MARK: - Elements
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
|
||||
import TIMapUtils
|
||||
import MapKit
|
||||
import UIKit
|
||||
|
||||
open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView, [ApplePlacemarkManager<Model>], MKCoordinateRegion>, MKMapViewDelegate {
|
||||
public weak var mapViewDelegate: MKMapViewDelegate?
|
||||
|
||||
private let mapDelegateSelectors = NSObject.instanceMethodSelectors(of: MKMapViewDelegate.self)
|
||||
|
||||
public init(placemarkManagers: [ApplePlacemarkManager<Model>],
|
||||
mapViewDelegate: MKMapViewDelegate? = nil,
|
||||
iconProvider: @escaping IconProviderClosure,
|
||||
tapHandler: TapHandlerClosure?) {
|
||||
|
||||
self.mapViewDelegate = mapViewDelegate
|
||||
|
||||
super.init(dataModel: placemarkManagers,
|
||||
iconProvider: iconProvider,
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
public convenience init<IF: MarkerIconFactory>(placemarkManagers: [ApplePlacemarkManager<Model>],
|
||||
mapViewDelegate: MKMapViewDelegate? = nil,
|
||||
iconFactory: IF,
|
||||
tapHandler: TapHandlerClosure?) where IF.Model == [Model] {
|
||||
|
||||
self.init(placemarkManagers: placemarkManagers,
|
||||
mapViewDelegate: mapViewDelegate,
|
||||
iconProvider: { iconFactory.markerIcon(for: $0.map { $0.dataModel }) },
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
open func addMarkers(to map: MKMapView) {
|
||||
map.delegate = self
|
||||
map.addAnnotations(dataModel)
|
||||
}
|
||||
|
||||
// MARK: - PlacemarkManager
|
||||
|
||||
override open func configure(placemark: MKAnnotationView) {
|
||||
guard let clusterAnnotation = placemark.annotation as? MKClusterAnnotation,
|
||||
let placemarkManagers = clusterAnnotation.memberAnnotations as? [ApplePlacemarkManager<Model>] else {
|
||||
return
|
||||
}
|
||||
|
||||
placemark.image = iconProvider(placemarkManagers)
|
||||
}
|
||||
|
||||
// MARK: - MKMapViewDelegate
|
||||
|
||||
open func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
guard !(mapViewDelegate?.responds(to: #selector(mapView(_:viewFor:))) ?? false) else {
|
||||
return mapViewDelegate?.mapView?(mapView, viewFor: annotation)
|
||||
}
|
||||
|
||||
switch annotation {
|
||||
case is MKClusterAnnotation:
|
||||
let clusterAnnotationView = MKAnnotationView(annotation: annotation,
|
||||
reuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)
|
||||
configure(placemark: clusterAnnotationView)
|
||||
|
||||
return clusterAnnotationView
|
||||
case let placemarkManager as ApplePlacemarkManager<Model>:
|
||||
let defaultAnnotationView = MKAnnotationView(annotation: annotation,
|
||||
reuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
|
||||
|
||||
placemarkManager.configure(placemark: defaultAnnotationView)
|
||||
|
||||
return defaultAnnotationView
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
open func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
|
||||
guard !(mapViewDelegate?.responds(to: #selector(mapView(_:didSelect:))) ?? false) else {
|
||||
mapViewDelegate?.mapView?(mapView, didSelect: view)
|
||||
return
|
||||
}
|
||||
|
||||
switch view.annotation {
|
||||
case let clusterAnnotation as MKClusterAnnotation:
|
||||
guard let placemarkManagers = clusterAnnotation.memberAnnotations as? [ApplePlacemarkManager<Model>] else {
|
||||
return
|
||||
}
|
||||
|
||||
_ = tapHandler?(placemarkManagers, .from(coordinates: placemarkManagers.map { $0.coordinate }))
|
||||
case let placemarkManager as ApplePlacemarkManager<Model>:
|
||||
_ = placemarkManager.tapHandler?(placemarkManager.dataModel, placemarkManager.coordinate)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MKMapViewDelegate selectors forwarding
|
||||
|
||||
open override func responds(to aSelector: Selector) -> Bool {
|
||||
let superResponds = super.responds(to: aSelector)
|
||||
|
||||
guard !superResponds else {
|
||||
return superResponds
|
||||
}
|
||||
|
||||
guard mapDelegateSelectors.contains(aSelector) else {
|
||||
return superResponds
|
||||
}
|
||||
|
||||
return mapViewDelegate?.responds(to: aSelector) ?? false
|
||||
}
|
||||
|
||||
open override func forwardingTarget(for aSelector: Selector) -> Any? {
|
||||
guard mapDelegateSelectors.contains(aSelector) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return mapViewDelegate
|
||||
}
|
||||
}
|
||||
|
||||
private extension MKCoordinateRegion {
|
||||
static func from(coordinates: [CLLocationCoordinate2D]) -> MKCoordinateRegion {
|
||||
guard !coordinates.isEmpty else {
|
||||
return MKCoordinateRegion()
|
||||
}
|
||||
|
||||
let bbox = CoordinateBounds.from(coordinates: coordinates)
|
||||
|
||||
let span = MKCoordinateSpan(latitudeDelta: bbox.northEast.latitude - bbox.southWest.latitude,
|
||||
longitudeDelta: bbox.northEast.longitude - bbox.southWest.longitude)
|
||||
let center = CLLocationCoordinate2D(latitude: bbox.northEast.latitude - (span.latitudeDelta / 2),
|
||||
longitude: bbox.southWest.longitude + (span.longitudeDelta / 2))
|
||||
|
||||
return MKCoordinateRegion(center: center, span: span)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import TIMapUtils
|
||||
import MapKit
|
||||
|
||||
open class ApplePlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView, Model, CLLocationCoordinate2D>, MKAnnotation {
|
||||
// MARK: - MKAnnotation
|
||||
|
||||
public let coordinate: CLLocationCoordinate2D
|
||||
|
||||
public var clusteringIdentifier: String?
|
||||
|
||||
public init(dataModel: Model,
|
||||
coordinate: CLLocationCoordinate2D,
|
||||
clusteringIdentifier: String?,
|
||||
iconProvider: @escaping IconProviderClosure,
|
||||
tapHandler: TapHandlerClosure?) {
|
||||
|
||||
self.coordinate = coordinate
|
||||
self.clusteringIdentifier = clusteringIdentifier
|
||||
|
||||
super.init(dataModel: dataModel,
|
||||
iconProvider: iconProvider,
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
public convenience init<IF: MarkerIconFactory>(dataModel: Model,
|
||||
coordinate: CLLocationCoordinate2D,
|
||||
clusteringIdentifier: String?,
|
||||
iconFactory: IF,
|
||||
tapHandler: TapHandlerClosure?) where IF.Model == Model {
|
||||
|
||||
self.init(dataModel: dataModel,
|
||||
coordinate: coordinate,
|
||||
clusteringIdentifier: clusteringIdentifier,
|
||||
iconProvider: { iconFactory.markerIcon(for: $0) },
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
// MARK: - PlacemarkManager
|
||||
|
||||
override open func configure(placemark: MKAnnotationView) {
|
||||
placemark.image = iconProvider(dataModel)
|
||||
placemark.clusteringIdentifier = clusteringIdentifier
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import ObjectiveC
|
||||
|
||||
public extension NSObject {
|
||||
static func instanceMethodSelectors(of protocol: Protocol) -> [Selector] {
|
||||
var methodsCount: UInt32 = 0
|
||||
let methodsList = protocol_copyMethodDescriptionList(`protocol`, false, true, &methodsCount)
|
||||
|
||||
defer {
|
||||
methodsList?.deallocate()
|
||||
}
|
||||
|
||||
var runtimeSelectors: [Selector?] = []
|
||||
|
||||
for offset in 0..<Int(methodsCount) {
|
||||
runtimeSelectors.append(methodsList?.advanced(by: offset).pointee.name)
|
||||
}
|
||||
|
||||
return runtimeSelectors.compactMap { $0 }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIAppleMapUtils'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
|
||||
s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
|
||||
|
||||
s.ios.deployment_target = '11.0'
|
||||
s.swift_versions = ['5.3']
|
||||
|
||||
s.source_files = s.name + '/Sources/**/*'
|
||||
|
||||
s.dependency 'TIMapUtils', s.version.to_s
|
||||
end
|
||||
|
|
@ -32,9 +32,9 @@ public extension KeyedDecodingContainer {
|
|||
using: try userInfo.dateFormatter(for: dateFormat))
|
||||
}
|
||||
|
||||
func decodeDate<Format: DateFormat>(forKey key: Key,
|
||||
userInfo: [CodingUserInfoKey: Any],
|
||||
dateFormat: Format) throws -> [Date] {
|
||||
func decodeDates<Format: DateFormat>(forKey key: Key,
|
||||
userInfo: [CodingUserInfoKey: Any],
|
||||
dateFormat: Format) throws -> [Date] {
|
||||
|
||||
let dateFormatter = try userInfo.dateFormatter(for: dateFormat)
|
||||
|
||||
|
|
@ -61,10 +61,10 @@ public extension KeyedDecodingContainer {
|
|||
using: try userInfo.dateFormatter(for: dateFormat))
|
||||
}
|
||||
|
||||
func decodeDate<Format: DateFormat>(forKey key: Key,
|
||||
userInfo: [CodingUserInfoKey: Any],
|
||||
dateFormat: Format,
|
||||
required: Bool) throws -> [Date]? {
|
||||
func decodeDates<Format: DateFormat>(forKey key: Key,
|
||||
userInfo: [CodingUserInfoKey: Any],
|
||||
dateFormat: Format,
|
||||
required: Bool) throws -> [Date]? {
|
||||
|
||||
guard let stringDates = try decode([String]?.self, forKey: key, required: required) else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIFoundationUtils'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Set of helpers for Foundation framework classes.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import TIMapUtils
|
||||
import GoogleMapsUtils
|
||||
import GoogleMaps
|
||||
|
||||
open class GoogleClusterPlacemarkManager<Model>: BasePlacemarkManager<GMSMarker, [GooglePlacemarkManager<Model>], GMSCoordinateBounds>, GMUClusterRendererDelegate, GMUClusterManagerDelegate, GMUClusterIconGenerator {
|
||||
public var mapDelegate: GMSMapViewDelegate? {
|
||||
didSet {
|
||||
clusterManager?.setMapDelegate(mapDelegate)
|
||||
}
|
||||
}
|
||||
|
||||
public private(set) var clusterManager: GMUClusterManager?
|
||||
|
||||
public init(placemarkManagers: [GooglePlacemarkManager<Model>],
|
||||
iconProvider: @escaping IconProviderClosure,
|
||||
tapHandler: TapHandlerClosure?) {
|
||||
|
||||
super.init(dataModel: placemarkManagers,
|
||||
iconProvider: iconProvider,
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
public convenience init<IF: MarkerIconFactory>(placemarkManagers: [GooglePlacemarkManager<Model>],
|
||||
iconFactory: IF,
|
||||
tapHandler: TapHandlerClosure?) where IF.Model == [Model] {
|
||||
|
||||
self.init(placemarkManagers: placemarkManagers,
|
||||
iconProvider: { iconFactory.markerIcon(for: $0.map { $0.dataModel }) },
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
open func addMarkers(to map: GMSMapView) {
|
||||
let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
|
||||
let renderer = GMUDefaultClusterRenderer(mapView: map,
|
||||
clusterIconGenerator: self)
|
||||
|
||||
renderer.delegate = self
|
||||
|
||||
clusterManager = GMUClusterManager(map: map,
|
||||
algorithm: algorithm,
|
||||
renderer: renderer)
|
||||
|
||||
clusterManager?.setDelegate(self, mapDelegate: mapDelegate)
|
||||
clusterManager?.add(dataModel)
|
||||
clusterManager?.cluster()
|
||||
}
|
||||
|
||||
// MARK: - GMUClusterRendererDelegate
|
||||
|
||||
open func renderer(_ renderer: GMUClusterRenderer, markerFor object: Any) -> GMSMarker? {
|
||||
nil
|
||||
}
|
||||
|
||||
open func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker) {
|
||||
switch marker.userData {
|
||||
case let cluster as GMUCluster:
|
||||
guard let placemarkManagers = cluster.items as? [GooglePlacemarkManager<Model>] else {
|
||||
return
|
||||
}
|
||||
|
||||
marker.icon = iconProvider(placemarkManagers)
|
||||
case let clusterItem as GooglePlacemarkManager<Model>:
|
||||
clusterItem.configure(placemark: marker)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
open func renderer(_ renderer: GMUClusterRenderer, didRenderMarker marker: GMSMarker) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
// MARK: - GMUClusterManagerDelegate
|
||||
|
||||
open func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool {
|
||||
guard let placemarkManagers = cluster.items as? [GooglePlacemarkManager<Model>] else {
|
||||
return false
|
||||
}
|
||||
|
||||
let bounds = placemarkManagers.reduce(GMSCoordinateBounds()) {
|
||||
$0.includingCoordinate($1.position)
|
||||
}
|
||||
|
||||
return tapHandler?(placemarkManagers, bounds) ?? false
|
||||
}
|
||||
|
||||
open func clusterManager(_ clusterManager: GMUClusterManager, didTap clusterItem: GMUClusterItem) -> Bool {
|
||||
guard let placemarkManager = clusterItem as? GooglePlacemarkManager<Model> else {
|
||||
return false
|
||||
}
|
||||
|
||||
return placemarkManager.tapHandler?(placemarkManager.dataModel, clusterItem.position) ?? false
|
||||
}
|
||||
|
||||
// MARK: - GMUClusterIconGenerator
|
||||
|
||||
open func icon(forSize size: UInt) -> UIImage? {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import TIMapUtils
|
||||
import GoogleMaps
|
||||
import GoogleMapsUtils
|
||||
|
||||
open class GooglePlacemarkManager<Model>: BasePlacemarkManager<GMSMarker, Model, CLLocationCoordinate2D>, GMUClusterItem {
|
||||
// MARK: - GMUClusterItem
|
||||
|
||||
public let position: CLLocationCoordinate2D
|
||||
|
||||
public init(dataModel: Model,
|
||||
position: CLLocationCoordinate2D,
|
||||
iconProvider: @escaping IconProviderClosure,
|
||||
tapHandler: TapHandlerClosure?) {
|
||||
|
||||
self.position = position
|
||||
|
||||
super.init(dataModel: dataModel,
|
||||
iconProvider: iconProvider,
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
public convenience init<IF: MarkerIconFactory>(dataModel: Model,
|
||||
position: CLLocationCoordinate2D,
|
||||
iconFactory: IF,
|
||||
tapHandler: TapHandlerClosure?) where IF.Model == Model {
|
||||
|
||||
self.init(dataModel: dataModel,
|
||||
position: position,
|
||||
iconProvider: { iconFactory.markerIcon(for: $0) },
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
// MARK: - PlacemarkManager
|
||||
|
||||
override open func configure(placemark: GMSMarker) {
|
||||
placemark.icon = iconProvider(dataModel)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIGoogleMapUtils'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting using Google Maps SDK.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
|
||||
s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
|
||||
|
||||
s.ios.deployment_target = '12.0'
|
||||
s.swift_versions = ['5.3']
|
||||
|
||||
s.source_files = s.name + '/Sources/**/*'
|
||||
|
||||
s.static_framework = true
|
||||
s.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }
|
||||
s.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }
|
||||
|
||||
s.dependency 'TIMapUtils', s.version.to_s
|
||||
s.dependency 'Google-Maps-iOS-Utils', '~> 4'
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIKeychainUtils'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Set of helpers for Keychain classes.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import CoreGraphics.CGGeometry
|
||||
|
||||
public typealias CGContextSize = (width: Int, height: Int)
|
||||
|
||||
public extension CGSize {
|
||||
var ceiledContextSize: CGContextSize {
|
||||
(width: Int(ceil(width)), height: Int(ceil(height)))
|
||||
}
|
||||
}
|
||||
|
||||
public extension CGPoint {
|
||||
func horizontallyFlipped() -> Self {
|
||||
CGPoint(x: x, y: -y)
|
||||
}
|
||||
}
|
||||
|
||||
public extension CGRect {
|
||||
func offset(by point: CGPoint) -> Self {
|
||||
offsetBy(dx: point.x, dy: point.y)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import CoreGraphics.CGGeometry
|
||||
|
||||
public extension CGSize {
|
||||
func resizeRect(forNewSize newSize: CGSize, resizeMode: ResizeMode) -> CGRect {
|
||||
let horizontalRatio = newSize.width / width
|
||||
let verticalRatio = newSize.height / height
|
||||
|
||||
let ratio: CGFloat
|
||||
|
||||
switch resizeMode {
|
||||
case .scaleToFill:
|
||||
ratio = 1
|
||||
|
||||
case .scaleAspectFill:
|
||||
ratio = max(horizontalRatio, verticalRatio)
|
||||
|
||||
case .scaleAspectFit:
|
||||
ratio = min(horizontalRatio, verticalRatio)
|
||||
}
|
||||
|
||||
let newWidth = resizeMode == .scaleToFill ? newSize.width : width * ratio
|
||||
let newHeight = resizeMode == .scaleToFill ? newSize.height : height * ratio
|
||||
|
||||
let originX: CGFloat
|
||||
let originY: CGFloat
|
||||
|
||||
if newWidth > newSize.width {
|
||||
originX = (newSize.width - newWidth) / 2
|
||||
} else if newWidth < newSize.width {
|
||||
originX = newSize.width / 2 - newWidth / 2
|
||||
} else {
|
||||
originX = 0
|
||||
}
|
||||
|
||||
if newHeight > newSize.height {
|
||||
originY = (newSize.height - newHeight) / 2
|
||||
} else if newHeight < newSize.height {
|
||||
originY = newSize.height / 2 - newHeight / 2
|
||||
} else {
|
||||
originY = 0
|
||||
}
|
||||
|
||||
return CGRect(origin: CGPoint(x: originX, y: originY),
|
||||
size: CGSize(width: newWidth, height: newHeight))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
public enum ResizeMode {
|
||||
case scaleToFill, scaleAspectFit, scaleAspectFill
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct BorderDrawingOperation: DrawingOperation {
|
||||
public var frameableContentSize: CGSize
|
||||
public var border: CGFloat
|
||||
public var color: CGColor
|
||||
public var radius: CGFloat
|
||||
public var exteriorBorder: Bool
|
||||
|
||||
public init(frameableContentSize: CGSize,
|
||||
border: CGFloat,
|
||||
color: CGColor,
|
||||
radius: CGFloat,
|
||||
exteriorBorder: Bool) {
|
||||
|
||||
self.frameableContentSize = frameableContentSize
|
||||
self.border = border
|
||||
self.color = color
|
||||
self.radius = radius
|
||||
self.exteriorBorder = exteriorBorder
|
||||
}
|
||||
|
||||
// MARK: - DrawingOperation
|
||||
|
||||
public func affectedArea(in context: CGContext?) -> CGRect {
|
||||
let margin = exteriorBorder ? border : 0
|
||||
|
||||
let width = frameableContentSize.width + margin * 2
|
||||
let height = frameableContentSize.height + margin * 2
|
||||
|
||||
return CGRect(origin: .zero,
|
||||
size: CGSize(width: width, height: height))
|
||||
}
|
||||
|
||||
public func apply(in context: CGContext) {
|
||||
let drawArea = affectedArea(in: context)
|
||||
|
||||
let ctxSize = drawArea.size.ceiledContextSize
|
||||
|
||||
let ctxRect = CGRect(origin: .zero, size: CGSize(width: ctxSize.width, height: ctxSize.height))
|
||||
|
||||
let widthDiff = CGFloat(ctxSize.width) - drawArea.width // difference between context width and real width
|
||||
let heightDiff = CGFloat(ctxSize.height) - drawArea.height // difference between context height and real height
|
||||
let inset = ctxRect.insetBy(dx: border / 2 + widthDiff, dy: border / 2 + heightDiff)
|
||||
|
||||
context.setStrokeColor(color)
|
||||
|
||||
if radius != 0 {
|
||||
context.setLineWidth(border)
|
||||
|
||||
let path = CGPath(roundedRect: inset,
|
||||
cornerWidth: radius,
|
||||
cornerHeight: radius,
|
||||
transform: nil)
|
||||
context.addPath(path)
|
||||
context.strokePath()
|
||||
} else {
|
||||
context.stroke(inset, width: border)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
import QuartzCore
|
||||
|
||||
public struct CALayerDrawingOperation: DrawingOperation {
|
||||
public var layer: CALayer
|
||||
public var offset: CGPoint
|
||||
|
||||
public init(layer: CALayer,
|
||||
offset: CGPoint) {
|
||||
|
||||
self.layer = layer
|
||||
self.offset = offset
|
||||
}
|
||||
|
||||
// MARK: - DrawingOperation
|
||||
|
||||
public func affectedArea(in context: CGContext? = nil) -> CGRect {
|
||||
CGRect(origin: offset, size: layer.bounds.size)
|
||||
}
|
||||
|
||||
public func apply(in context: CGContext) {
|
||||
let offsetTransform = CGAffineTransform(translationX: offset.x, y: offset.y)
|
||||
|
||||
context.concatenate(offsetTransform)
|
||||
layer.render(in: context)
|
||||
offsetTransform.concatenating(offsetTransform.inverted())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
|
||||
public protocol DrawingOperation {
|
||||
func affectedArea(in context: CGContext?) -> CGRect
|
||||
func apply(in context: CGContext)
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
|
||||
public protocol OrientationAwareDrawingOperation: DrawingOperation {
|
||||
var flipHorizontallyDuringDrawing: Bool { get set }
|
||||
}
|
||||
|
||||
extension OrientationAwareDrawingOperation {
|
||||
func apply(in context: CGContext, operation: (CGContext) -> Void) {
|
||||
if flipHorizontallyDuringDrawing {
|
||||
let flipVertical = CGAffineTransform(a: 1,
|
||||
b: 0,
|
||||
c: 0,
|
||||
d: -1,
|
||||
tx: 0,
|
||||
ty: affectedArea(in: context).height)
|
||||
|
||||
context.concatenate(flipVertical)
|
||||
operation(context)
|
||||
context.concatenate(flipVertical.inverted())
|
||||
} else {
|
||||
operation(context)
|
||||
}
|
||||
}
|
||||
|
||||
func offsetForDrawing(_ offset: CGPoint) -> CGPoint {
|
||||
flipHorizontallyDuringDrawing ? offset.horizontallyFlipped() : offset
|
||||
}
|
||||
|
||||
func affectedAreaForDrawing(in context: CGContext?) -> CGRect {
|
||||
var area = affectedArea(in: context)
|
||||
area.origin = offsetForDrawing(area.origin)
|
||||
|
||||
return area
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
|
||||
public struct SolidFillDrawingOperation: DrawingOperation {
|
||||
public enum Shape {
|
||||
case rect(CGRect)
|
||||
case ellipse(CGRect)
|
||||
case path(CGPath)
|
||||
}
|
||||
|
||||
public var color: CGColor
|
||||
public var shape: Shape
|
||||
|
||||
public init(color: CGColor, shape: Shape) {
|
||||
self.color = color
|
||||
self.shape = shape
|
||||
}
|
||||
|
||||
public init(color: CGColor, rect: CGRect) {
|
||||
self.init(color: color, shape: .rect(rect))
|
||||
}
|
||||
|
||||
public init(color: CGColor, ellipseRect: CGRect) {
|
||||
self.init(color: color, shape: .ellipse(ellipseRect))
|
||||
}
|
||||
|
||||
public init(color: CGColor, path: CGPath) {
|
||||
self.init(color: color, shape: .path(path))
|
||||
}
|
||||
|
||||
// MARK: - DrawingOperation
|
||||
|
||||
public func affectedArea(in context: CGContext? = nil) -> CGRect {
|
||||
switch shape {
|
||||
case let .rect(rect):
|
||||
return rect
|
||||
case let .ellipse(rect):
|
||||
return rect
|
||||
case let .path(path):
|
||||
return path.boundingBox
|
||||
}
|
||||
}
|
||||
|
||||
public func apply(in context: CGContext) {
|
||||
context.setFillColor(color)
|
||||
|
||||
switch shape {
|
||||
case let .rect(rect):
|
||||
context.fill(rect)
|
||||
case let .ellipse(rect):
|
||||
context.fillEllipse(in: rect)
|
||||
case let .path(path):
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
import Foundation.NSAttributedString
|
||||
import CoreText
|
||||
|
||||
public struct TextDrawingOperation: OrientationAwareDrawingOperation {
|
||||
public var text: String
|
||||
public var font: CTFont
|
||||
public var textColor: CGColor
|
||||
public var flipHorizontallyDuringDrawing: Bool
|
||||
public var desiredOffset: CGPoint
|
||||
|
||||
private var line: CTLine {
|
||||
let textAttributes: [NSAttributedString.Key : Any] = [
|
||||
.font: font,
|
||||
.foregroundColor: textColor
|
||||
]
|
||||
|
||||
let attributedString = NSAttributedString(string: text, attributes: textAttributes)
|
||||
|
||||
return CTLineCreateWithAttributedString(attributedString)
|
||||
}
|
||||
|
||||
public init(text: String,
|
||||
font: CTFont,
|
||||
textColor: CGColor,
|
||||
flipHorizontallyDuringDrawing: Bool = true,
|
||||
desiredOffset: CGPoint = .zero) {
|
||||
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.textColor = textColor
|
||||
self.flipHorizontallyDuringDrawing = flipHorizontallyDuringDrawing
|
||||
self.desiredOffset = desiredOffset
|
||||
}
|
||||
|
||||
public func affectedArea(in context: CGContext? = nil) -> CGRect {
|
||||
CGRect(origin: desiredOffset, size: CTLineGetImageBounds(line, context).size)
|
||||
}
|
||||
|
||||
public func apply(in context: CGContext) {
|
||||
apply(in: context) {
|
||||
let originForDrawing = offsetForDrawing(CTLineGetImageBounds(line, context).origin)
|
||||
let desiredOffsetForDrawing = offsetForDrawing(desiredOffset)
|
||||
let textPosition = CGPoint(x: desiredOffsetForDrawing.x - originForDrawing.x,
|
||||
y: desiredOffsetForDrawing.y - originForDrawing.y)
|
||||
|
||||
$0.textPosition = textPosition
|
||||
|
||||
CTLineDraw(line, $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
|
||||
public struct TransformDrawingOperation: OrientationAwareDrawingOperation {
|
||||
public var image: CGImage
|
||||
public var imageSize: CGSize
|
||||
public var maxNewSize: CGSize
|
||||
public var resizeMode: ResizeMode
|
||||
public var offset: CGPoint
|
||||
public var flipHorizontallyDuringDrawing: Bool
|
||||
public var cropToImageBounds: Bool
|
||||
public var interpolationQuality: CGInterpolationQuality
|
||||
|
||||
private var resizedRect: CGRect {
|
||||
imageSize.resizeRect(forNewSize: maxNewSize, resizeMode: resizeMode)
|
||||
}
|
||||
|
||||
public init(image: CGImage,
|
||||
imageSize: CGSize,
|
||||
maxNewSize: CGSize,
|
||||
resizeMode: ResizeMode = .scaleAspectFit,
|
||||
offset: CGPoint = .zero,
|
||||
flipHorizontallyDuringDrawing: Bool = true,
|
||||
cropToImageBounds: Bool = false,
|
||||
interpolationQuality: CGInterpolationQuality = .default) {
|
||||
|
||||
self.image = image
|
||||
self.imageSize = imageSize
|
||||
self.maxNewSize = maxNewSize
|
||||
self.resizeMode = resizeMode
|
||||
self.offset = offset
|
||||
self.flipHorizontallyDuringDrawing = flipHorizontallyDuringDrawing
|
||||
self.cropToImageBounds = cropToImageBounds
|
||||
self.interpolationQuality = interpolationQuality
|
||||
}
|
||||
|
||||
// MARK: - DrawingOperation
|
||||
|
||||
public func affectedArea(in context: CGContext? = nil) -> CGRect {
|
||||
if cropToImageBounds {
|
||||
return CGRect(origin: offset, size: resizedRect.size)
|
||||
} else {
|
||||
return resizedRect.offset(by: offset)
|
||||
}
|
||||
}
|
||||
|
||||
public func apply(in context: CGContext) {
|
||||
apply(in: context) {
|
||||
$0.interpolationQuality = interpolationQuality
|
||||
|
||||
$0.draw(image, in: affectedAreaForDrawing(in: $0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
|
||||
public struct CoordinateBounds {
|
||||
public let southWest: CLLocationCoordinate2D
|
||||
public let northEast: CLLocationCoordinate2D
|
||||
|
||||
public init(southWest: CLLocationCoordinate2D, northEast: CLLocationCoordinate2D) {
|
||||
self.southWest = southWest
|
||||
self.northEast = northEast
|
||||
}
|
||||
}
|
||||
|
||||
public extension CoordinateBounds {
|
||||
static func from<C: Collection>(coordinates: C) -> CoordinateBounds where C.Element == CLLocationCoordinate2D {
|
||||
guard let first = coordinates.first else {
|
||||
return .init(southWest: CLLocationCoordinate2D(),
|
||||
northEast: CLLocationCoordinate2D())
|
||||
}
|
||||
|
||||
let initialBox = CoordinateBounds(southWest: first, northEast: first)
|
||||
|
||||
return coordinates.dropFirst().reduce(initialBox) {
|
||||
CoordinateBounds(southWest: CLLocationCoordinate2D(latitude: min($0.southWest.latitude, $1.latitude),
|
||||
longitude: min($0.southWest.longitude, $1.longitude)),
|
||||
northEast: CLLocationCoordinate2D(latitude: max($0.northEast.latitude, $1.latitude),
|
||||
longitude: max($0.northEast.longitude, $1.longitude)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import UIKit.UIImage
|
||||
|
||||
open class DefaultCachableMarkerIconFactory<M, K: AnyObject>: DefaultMarkerIconFactory<M> {
|
||||
public typealias CacheKeyProvider = (M) -> K
|
||||
|
||||
public let cache = NSCache<K, UIImage>()
|
||||
|
||||
private let cacheKeyProvider: CacheKeyProvider
|
||||
|
||||
public init(createIconClosure: @escaping CreateIconClosure,
|
||||
cacheKeyProvider: @escaping CacheKeyProvider) {
|
||||
|
||||
self.cacheKeyProvider = cacheKeyProvider
|
||||
|
||||
super.init(createIconClosure: createIconClosure)
|
||||
}
|
||||
|
||||
open override func markerIcon(for model: M) -> UIImage {
|
||||
let cacheKey = cacheKeyProvider(model)
|
||||
|
||||
guard let cachedIcon = cache.object(forKey: cacheKey) else {
|
||||
let icon = super.markerIcon(for: model)
|
||||
cache.setObject(icon, forKey: cacheKey)
|
||||
|
||||
return icon
|
||||
}
|
||||
|
||||
return cachedIcon
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class DefaultClusterIconRenderer {
|
||||
public struct TextAttributes {
|
||||
public var font: UIFont
|
||||
public var color: UIColor
|
||||
|
||||
public init(font: UIFont, color: UIColor) {
|
||||
self.font = font
|
||||
self.color = color
|
||||
}
|
||||
}
|
||||
|
||||
public enum Background {
|
||||
case color(UIColor)
|
||||
case image(UIImage)
|
||||
}
|
||||
|
||||
public struct Border {
|
||||
public var strokeSize: CGFloat
|
||||
public var color: UIColor
|
||||
|
||||
public init(strokeSize: CGFloat, color: UIColor) {
|
||||
self.strokeSize = strokeSize
|
||||
self.color = color
|
||||
}
|
||||
}
|
||||
|
||||
public var screenScale: CGFloat
|
||||
public var textAttributes: TextAttributes
|
||||
public var marginToText: CGFloat
|
||||
public var background: Background
|
||||
public var border: Border
|
||||
|
||||
private var borderWidth: CGFloat {
|
||||
border.strokeSize
|
||||
}
|
||||
|
||||
public init(screenScale: CGFloat,
|
||||
textAttributes: TextAttributes = .init(font: .systemFont(ofSize: 48),
|
||||
color: .black),
|
||||
marginToText: CGFloat = 8,
|
||||
background: Background = .color(.orange),
|
||||
border: Border = .init(strokeSize: 4, color: .white)) {
|
||||
|
||||
self.screenScale = screenScale
|
||||
self.textAttributes = textAttributes
|
||||
self.marginToText = marginToText
|
||||
self.background = background
|
||||
self.border = border
|
||||
}
|
||||
|
||||
open func format(clusterSize: Int) -> String {
|
||||
String(clusterSize)
|
||||
}
|
||||
|
||||
open func textDrawingOperation(for text: String) -> TextDrawingOperation {
|
||||
let ctFont = CTFontCreateWithFontDescriptorAndOptions(textAttributes.font.fontDescriptor,
|
||||
textAttributes.font.pointSize,
|
||||
nil,
|
||||
[])
|
||||
|
||||
return TextDrawingOperation(text: text,
|
||||
font: ctFont,
|
||||
textColor: textAttributes.color.cgColor)
|
||||
}
|
||||
|
||||
open func backgroundDrawingOperation(iconSize: CGSize,
|
||||
iconSizeWithBorder: CGSize,
|
||||
cornerRadius: CGFloat) -> DrawingOperation? {
|
||||
|
||||
switch background {
|
||||
case let .color(color):
|
||||
let path = CGPath(roundedRect: CGRect(origin: CGPoint(x: borderWidth, y: borderWidth),
|
||||
size: iconSize),
|
||||
cornerWidth: cornerRadius,
|
||||
cornerHeight: cornerRadius,
|
||||
transform: nil)
|
||||
|
||||
return SolidFillDrawingOperation(color: color.cgColor,
|
||||
path: path)
|
||||
case let .image(image):
|
||||
if let cgImage = image.cgImage {
|
||||
return TransformDrawingOperation(image: cgImage,
|
||||
imageSize: image.size,
|
||||
maxNewSize: iconSize,
|
||||
flipHorizontallyDuringDrawing: true)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func borderDrawingOperation(iconSize: CGSize,
|
||||
cornerRadius: CGFloat) -> DrawingOperation {
|
||||
|
||||
BorderDrawingOperation(frameableContentSize: iconSize,
|
||||
border: borderWidth,
|
||||
color: border.color.cgColor,
|
||||
radius: cornerRadius,
|
||||
exteriorBorder: true)
|
||||
}
|
||||
|
||||
open func execute(drawingOperations: [DrawingOperation], inContextWithSize size: CGSize) -> UIImage {
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.opaque = false
|
||||
format.scale = screenScale
|
||||
|
||||
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
||||
|
||||
return renderer.image {
|
||||
for operation in drawingOperations {
|
||||
operation.apply(in: $0.cgContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func renderCluster(of size: Int) -> UIImage {
|
||||
let text = format(clusterSize: size)
|
||||
|
||||
var textDrawingOperation = textDrawingOperation(for: text)
|
||||
|
||||
let textSize = textDrawingOperation.affectedArea().size
|
||||
let textRadius = sqrt(textSize.height * textSize.height + textSize.width * textSize.width) / 2
|
||||
let internalRadius = textRadius + marginToText
|
||||
|
||||
let iconSize = CGSize(width: internalRadius * 2, height: internalRadius * 2)
|
||||
let iconSizeWithBorder = CGSize(width: iconSize.width + borderWidth * 2,
|
||||
height: iconSize.height + borderWidth * 2)
|
||||
|
||||
let radius = CGFloat(min(iconSizeWithBorder.width, iconSizeWithBorder.height) / 2)
|
||||
|
||||
textDrawingOperation.desiredOffset = CGPoint(x: (iconSizeWithBorder.width - textSize.width) / 2,
|
||||
y: (iconSizeWithBorder.height - textSize.height) / 2)
|
||||
|
||||
let backgroundDrawingOperation = backgroundDrawingOperation(iconSize: iconSize,
|
||||
iconSizeWithBorder: iconSizeWithBorder,
|
||||
cornerRadius: radius)
|
||||
|
||||
let borderDrawindOperation = borderDrawingOperation(iconSize: iconSize,
|
||||
cornerRadius: radius)
|
||||
|
||||
let operations = [backgroundDrawingOperation,
|
||||
textDrawingOperation,
|
||||
borderDrawindOperation]
|
||||
.compactMap { $0 }
|
||||
|
||||
return execute(drawingOperations: operations,
|
||||
inContextWithSize: iconSizeWithBorder)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import UIKit.UIImage
|
||||
|
||||
open class DefaultMarkerIconFactory<M>: MarkerIconFactory {
|
||||
public typealias CreateIconClosure = (M) -> UIImage
|
||||
|
||||
private let createIconClosure: CreateIconClosure
|
||||
|
||||
public init(createIconClosure: @escaping CreateIconClosure) {
|
||||
self.createIconClosure = createIconClosure
|
||||
}
|
||||
|
||||
open func markerIcon(for model: M) -> UIImage {
|
||||
postprocess(icon: createIconClosure(model))
|
||||
}
|
||||
|
||||
open func postprocess(icon: UIImage) -> UIImage {
|
||||
icon
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import UIKit.UIImage
|
||||
|
||||
public protocol MarkerIconFactory {
|
||||
associatedtype Model
|
||||
|
||||
func markerIcon(for model: Model) -> UIImage
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class BasePlacemarkManager<Placemark, Model, Position>: NSObject, PlacemarkManager {
|
||||
public typealias TapHandlerClosure = (Model, Position) -> Bool
|
||||
public typealias IconProviderClosure = (Model) -> UIImage
|
||||
|
||||
public var tapHandler: TapHandlerClosure?
|
||||
public var iconProvider: IconProviderClosure
|
||||
|
||||
public let dataModel: Model
|
||||
|
||||
public init(dataModel: Model,
|
||||
iconProvider: @escaping IconProviderClosure,
|
||||
tapHandler: TapHandlerClosure?) {
|
||||
|
||||
self.dataModel = dataModel
|
||||
self.iconProvider = iconProvider
|
||||
self.tapHandler = tapHandler
|
||||
}
|
||||
|
||||
public convenience init<IF: MarkerIconFactory>(dataModel: Model,
|
||||
iconFactory: IF,
|
||||
tapHandler: TapHandlerClosure?) where IF.Model == Model {
|
||||
|
||||
self.init(dataModel: dataModel,
|
||||
iconProvider: { iconFactory.markerIcon(for: $0) },
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
// MARK: - PlacemarkManager
|
||||
|
||||
open func configure(placemark: Placemark) {
|
||||
// override in subclass
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
public protocol PlacemarkManager {
|
||||
associatedtype Placemark
|
||||
|
||||
func configure(placemark: Placemark)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMapUtils'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
|
||||
s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
|
||||
|
||||
s.ios.deployment_target = '10.0'
|
||||
s.swift_versions = ['5.3']
|
||||
|
||||
s.source_files = s.name + '/Sources/**/*'
|
||||
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMoyaNetworking'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Moya + Swagger network service.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TINetworking'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Swagger-frendly networking layer helpers.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TINetworkingCache'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Caching results of EndpointRequests.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIPagination'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Generic pagination component.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TISwiftUtils'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Bunch of useful helpers for Swift development.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TITableKitUtils'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Set of helpers for TableKit classes.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TITransitions'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Set of custom transitions to present controller. '
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIElements'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Bunch of useful protocols and views.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIKitCore'
|
||||
s.version = '1.15.0'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Core UI elements: protocols, views and helpers.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import TIMapUtils
|
||||
import YandexMapsMobile
|
||||
import UIKit
|
||||
|
||||
open class YandexClusterPlacemarkManager<Model>: BasePlacemarkManager<YMKCluster, [YandexPlacemarkManager<Model>], YMKBoundingBox>, YMKClusterListener, YMKClusterTapListener {
|
||||
public var placemarksMapping: Zip2Sequence<[YMKPlacemarkMapObject], [YandexPlacemarkManager<Model>]>?
|
||||
|
||||
public init(placemarkManagers: [YandexPlacemarkManager<Model>],
|
||||
iconProvider: @escaping IconProviderClosure,
|
||||
tapHandler: TapHandlerClosure?) {
|
||||
|
||||
super.init(dataModel: placemarkManagers,
|
||||
iconProvider: iconProvider,
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
public convenience init<IF: MarkerIconFactory>(placemarkManagers: [YandexPlacemarkManager<Model>],
|
||||
iconFactory: IF,
|
||||
tapHandler: TapHandlerClosure?) where IF.Model == [Model] {
|
||||
|
||||
self.init(placemarkManagers: placemarkManagers,
|
||||
iconProvider: { iconFactory.markerIcon(for: $0.map { $0.dataModel }) },
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
open func addMarkers(to map: YMKMap, clusterRadius: Double = 60, minZoom: UInt = 15) {
|
||||
let clusterizedPlacemarkCollection = map.mapObjects.addClusterizedPlacemarkCollection(with: self)
|
||||
|
||||
let emptyPlacemarks = clusterizedPlacemarkCollection.addEmptyPlacemarks(with: dataModel.map { $0.position })
|
||||
|
||||
self.placemarksMapping = zip(emptyPlacemarks, dataModel)
|
||||
|
||||
placemarksMapping?.forEach { (placemark, manager) in
|
||||
manager.configure(placemark: placemark)
|
||||
}
|
||||
|
||||
clusterizedPlacemarkCollection.clusterPlacemarks(withClusterRadius: clusterRadius,
|
||||
minZoom: minZoom)
|
||||
}
|
||||
|
||||
// MARK: - YMKClusterListener
|
||||
|
||||
open func onClusterAdded(with cluster: YMKCluster) {
|
||||
configure(placemark: cluster)
|
||||
}
|
||||
|
||||
// MARK: - YMKClusterTapListener
|
||||
|
||||
open func onClusterTap(with cluster: YMKCluster) -> Bool {
|
||||
guard let tapHandler = tapHandler else {
|
||||
return false
|
||||
}
|
||||
|
||||
return tapHandler(managers(in: cluster), .from(coordinates: managers(in: cluster).map { $0.position }))
|
||||
}
|
||||
|
||||
open func managers(in cluster: YMKCluster) -> [YandexPlacemarkManager<Model>] {
|
||||
cluster.placemarks.compactMap { placemark in
|
||||
placemarksMapping?.first { $0.0 == placemark }?.1
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PlacemarkManager
|
||||
|
||||
open override func configure(placemark: YMKCluster) {
|
||||
placemark.addClusterTapListener(with: self)
|
||||
placemark.appearance.setIconWith(iconProvider(managers(in: placemark)))
|
||||
}
|
||||
}
|
||||
|
||||
private extension YMKBoundingBox {
|
||||
static func from(coordinates: [YMKPoint]) -> YMKBoundingBox {
|
||||
guard let first = coordinates.first else {
|
||||
return YMKBoundingBox()
|
||||
}
|
||||
|
||||
let initialBox = YMKBoundingBox(southWest: first, northEast: first)
|
||||
|
||||
return coordinates.dropFirst().reduce(initialBox) {
|
||||
YMKBoundingBox(southWest: YMKPoint(latitude: min($0.southWest.latitude, $1.latitude),
|
||||
longitude: min($0.southWest.longitude, $1.longitude)),
|
||||
northEast: YMKPoint(latitude: max($0.northEast.latitude, $1.latitude),
|
||||
longitude: max($0.northEast.longitude, $1.longitude)))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// Copyright (c) 2022 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.
|
||||
//
|
||||
|
||||
import TIMapUtils
|
||||
import YandexMapsMobile
|
||||
|
||||
open class YandexPlacemarkManager<Model>: BasePlacemarkManager<YMKPlacemarkMapObject, Model, YMKPoint>, YMKMapObjectTapListener {
|
||||
public let position: YMKPoint
|
||||
|
||||
public init(dataModel: Model,
|
||||
position: YMKPoint,
|
||||
iconProvider: @escaping IconProviderClosure,
|
||||
tapHandler: TapHandlerClosure?) {
|
||||
|
||||
self.position = position
|
||||
|
||||
super.init(dataModel: dataModel,
|
||||
iconProvider: iconProvider,
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
public convenience init<IF: MarkerIconFactory>(dataModel: Model,
|
||||
position: YMKPoint,
|
||||
iconFactory: IF,
|
||||
tapHandler: TapHandlerClosure?) where IF.Model == Model {
|
||||
|
||||
self.init(dataModel: dataModel,
|
||||
position: position,
|
||||
iconProvider: { iconFactory.markerIcon(for: $0) },
|
||||
tapHandler: tapHandler)
|
||||
}
|
||||
|
||||
// MARK: - YMKMapObjectTapListener
|
||||
|
||||
public func onMapObjectTap(with mapObject: YMKMapObject, point: YMKPoint) -> Bool {
|
||||
tapHandler?(dataModel, point) ?? false
|
||||
}
|
||||
|
||||
// MARK: - PlacemarkManager
|
||||
|
||||
override open func configure(placemark: YMKPlacemarkMapObject) {
|
||||
placemark.addTapListener(with: self)
|
||||
placemark.setIconWith(iconProvider(dataModel))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIYandexMapUtils'
|
||||
s.version = '1.16.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting using Yandex Maps SDK.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
|
||||
s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
|
||||
|
||||
s.ios.deployment_target = '10.0'
|
||||
s.swift_versions = ['5.3']
|
||||
|
||||
s.source_files = s.name + '/Sources/**/*'
|
||||
|
||||
s.static_framework = true
|
||||
s.user_target_xcconfig = { 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
|
||||
s.pod_target_xcconfig = { 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
|
||||
|
||||
s.dependency 'TIMapUtils', s.version.to_s
|
||||
s.dependency 'YandexMapsMobile', '4.0.0-lite'
|
||||
end
|
||||
|
|
@ -14,7 +14,11 @@ ORDERED_PODSPECS="../TISwiftUtils/TISwiftUtils.podspec
|
|||
../TITableKitUtils/TITableKitUtils.podspec
|
||||
../TINetworking/TINetworking.podspec
|
||||
../TINetworkingCache/TINetworkingCache.podspec
|
||||
../TIMoyaNetworking/TIMoyaNetworking.podspec"
|
||||
../TIMoyaNetworking/TIMoyaNetworking.podspec
|
||||
../TIMapUtils/TIMapUtils.podspec
|
||||
../TIAppleMapUtils/TIAppleMapUtils.podspec
|
||||
../TIGoogleMapUtils/TIGoogleMapUtils.podspec
|
||||
../TIYandexMapUtils/TIYandexMapUtils.podspec"
|
||||
|
||||
for podspec_path in ${ORDERED_PODSPECS}; do
|
||||
bundle exec pod repo push git@github.com:TouchInstinct/Podspecs ${podspec_path} --allow-warnings
|
||||
|
|
|
|||
Loading…
Reference in New Issue