Skip to content

Commit

Permalink
Merge pull request #87 from surfstudio/map-routing-service
Browse files Browse the repository at this point in the history
MapRoutingService
  • Loading branch information
Alexander Chausov authored Mar 18, 2022
2 parents 9380395 + e3eb3ff commit 7ffdd29
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 13 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pod 'SurfUtils/$UTIL_NAME$', :git => "https://github.com/surfstudio/iOS-Utils.gi
- [TouchableControl](#touchablecontrol) – аналог кнопки с кастомизированным анимированием
- [CustomSwitch](#customswitch) – более гибкая реализация Switch ui элемента
- [MoneyModel](#moneymodel) - структура для работы с деньгами
- [MapRoutingService](#maproutingservice) - сервис для построения маршрутов и отображения точек в сторонних навигационных приложениях

## Утилиты

Expand Down Expand Up @@ -930,6 +931,34 @@ mailSender.send()
print(MoneyModel(decimal: 10, digit: 99).asString()) // выведет -- "10.99"
```

### MapRoutingService

Сервис позволяет получить список приложений для работы с навигацией, установленных на устройстве пользователя, а также отобразить точку/построить маршрут до заданной точки в одном из них.

```swift
/// получение списка возможных приложений, которые можно отобразить для выбора пользователю
let apps = service.availableApplications

/// построение маршрута
service.buildRoute(to: point, in: app, onComplete: nil)
```

Для работы с сервисом требуется заинжектить в его конструктор небольшой объект, позволяющий понять информацию о текущей геопозиции пользователя. Его интерфейс выглядит следующим образом

```swift
public protocol MapRoutingLocationServiceInterface: AnyObject {
/// Равно true, когда пользователь разрешил доступ к геопозиции
var isLocationAccessAllowed: Bool { get }
/// Равно true, когда пользователь разрешил использование точной геопозиции
var isAllowedFullAccuracyLocation: Bool { get }
/// Вовращает текущую геопозицию пользователя, если она известна,
/// и nil во всех остальных случаях
func getCurrentLocation(_ completion: @escaping ((CLLocationCoordinate2D?) -> Void))
}
```

Вы можете написать небольшую обертку поверх [GeolocationService](#geolocationservice), либо использовать вместо него сервис для работы с геопозицией из своего проекта.

## Версионирование

В качестве принципа версионирования используется [Семантическое версионирования (Semantic Versioning)](https://semver.org/).
Expand Down
2 changes: 1 addition & 1 deletion SurfUtils.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|

s.name = "SurfUtils"
s.version = "12.1.0"
s.version = "13.0.0"
s.summary = "Contains a set of utils in subspecs"
s.description = <<-DESC
Contains:
Expand Down
44 changes: 34 additions & 10 deletions Utils/Utils.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
89F23A6D23576452005112E1 /* OTPFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F23A6C23576452005112E1 /* OTPFieldStyle.swift */; };
902C67EC21E4D20B007B13CC /* ItemsScrollManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902C67EB21E4D20B007B13CC /* ItemsScrollManager.swift */; };
902CA34121E7331E00396923 /* UIView+BlurBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902CA34021E7331E00396923 /* UIView+BlurBuilder.swift */; };
904DE287272DCAD1004FDC64 /* MapApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 904DE286272DCAD1004FDC64 /* MapApplication.swift */; };
904DE289272DCF14004FDC64 /* MapRoutingServiceInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 904DE288272DCF14004FDC64 /* MapRoutingServiceInterface.swift */; };
904DE28B272DCF70004FDC64 /* MapRoutingLocationServiceInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 904DE28A272DCF70004FDC64 /* MapRoutingLocationServiceInterface.swift */; };
904DE28D272DD094004FDC64 /* MapRoutingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 904DE28C272DD094004FDC64 /* MapRoutingService.swift */; };
90718AF121EA370000C81002 /* KeyboardNotificationsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90718AF021EA370000C81002 /* KeyboardNotificationsObserver.swift */; };
907F0FE321DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE221DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift */; };
907F0FE621DCD3A0001CCB07 /* SettingsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */; };
Expand Down Expand Up @@ -206,6 +210,10 @@
89F23A6C23576452005112E1 /* OTPFieldStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPFieldStyle.swift; sourceTree = "<group>"; };
902C67EB21E4D20B007B13CC /* ItemsScrollManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsScrollManager.swift; sourceTree = "<group>"; };
902CA34021E7331E00396923 /* UIView+BlurBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+BlurBuilder.swift"; sourceTree = "<group>"; };
904DE286272DCAD1004FDC64 /* MapApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapApplication.swift; sourceTree = "<group>"; };
904DE288272DCF14004FDC64 /* MapRoutingServiceInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapRoutingServiceInterface.swift; sourceTree = "<group>"; };
904DE28A272DCF70004FDC64 /* MapRoutingLocationServiceInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapRoutingLocationServiceInterface.swift; sourceTree = "<group>"; };
904DE28C272DD094004FDC64 /* MapRoutingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapRoutingService.swift; sourceTree = "<group>"; };
90718AF021EA370000C81002 /* KeyboardNotificationsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardNotificationsObserver.swift; sourceTree = "<group>"; };
907F0FE221DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+AdvancedNavigationStackManagement.swift"; sourceTree = "<group>"; };
907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRouter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -460,33 +468,34 @@
4F3ED9E1211C27CF0030DD45 /* Utils */ = {
isa = PBXGroup;
children = (
C11CEB3924D44DD300C1CD0F /* MailSender */,
3946575124EC1B4C0069BDB0 /* LoadingView */,
A4F4B13524ED2490009FA920 /* BeanPageControl */,
39DCF1A324EEC5D0007CCFBC /* SecurityService */,
EE0C5D9024102671006B8C28 /* UIControl */,
87239D7D24D41E6500D38EC7 /* MoneyModel */,
80437D24214045B30095A8D0 /* BrightSide */,
5709EC5B236F4C2200EEBD93 /* CommonButton */,
A4D5D5FB24E57A50004ABFBC /* CustomSwitch */,
18F2361221D214CC00169AC9 /* Dictionary */,
90AC855D2385924600DF7F3B /* GeolocationService */,
902C67EA21E4D1EF007B13CC /* ItemsScrollManager */,
80437D24214045B30095A8D0 /* BrightSide */,
90718AED21EA36CA00C81002 /* KeyboardPresentable */,
573117D423BF888500491781 /* LayoutHelper */,
3946575124EC1B4C0069BDB0 /* LoadingView */,
89293B2421F59F6A0016C6BE /* LocalStorage */,
C11CEB3924D44DD300C1CD0F /* MailSender */,
904DE285272DCABA004FDC64 /* MapRoutingService */,
87239D7D24D41E6500D38EC7 /* MoneyModel */,
8953A46B23560726007AD110 /* OTPField */,
907F0FEA21DCD7C6001CCB07 /* RouteMeasurer */,
39DCF1A324EEC5D0007CCFBC /* SecurityService */,
907F0FE421DCD375001CCB07 /* SettingsRouter */,
A439074C21F5C5510034C455 /* SkeletonView */,
4F3ED9FA211C27E80030DD45 /* String */,
EE0C5D9024102671006B8C28 /* UIControl */,
573117CF23BF84B300491781 /* UIDevice */,
5709EC68236F55AA00EEBD93 /* UIImage */,
907F0FE121DCD1DB001CCB07 /* UINavigationController */,
573117D723BFD26800491781 /* UIStyle */,
902CA33F21E732F700396923 /* UIView */,
E9B06306214691F30080C391 /* VibrationFeedbackManager */,
907F0FED21DCDB1F001CCB07 /* WordDeclinationSelector */,
573117D423BF888500491781 /* LayoutHelper */,
89293B2421F59F6A0016C6BE /* LocalStorage */,
8953A46B23560726007AD110 /* OTPField */,
90AC855D2385924600DF7F3B /* GeolocationService */,
4F3ED9E2211C27CF0030DD45 /* Utils.h */,
4F3ED9E3211C27CF0030DD45 /* Info.plist */,
);
Expand Down Expand Up @@ -613,6 +622,17 @@
path = UIView;
sourceTree = "<group>";
};
904DE285272DCABA004FDC64 /* MapRoutingService */ = {
isa = PBXGroup;
children = (
904DE28C272DD094004FDC64 /* MapRoutingService.swift */,
904DE286272DCAD1004FDC64 /* MapApplication.swift */,
904DE288272DCF14004FDC64 /* MapRoutingServiceInterface.swift */,
904DE28A272DCF70004FDC64 /* MapRoutingLocationServiceInterface.swift */,
);
path = MapRoutingService;
sourceTree = "<group>";
};
90718AED21EA36CA00C81002 /* KeyboardPresentable */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1096,6 +1116,7 @@
39DCF1C824EEC8F8007CCFBC /* HackWrapperCryptoBox.swift in Sources */,
3971F72624F1843900597F9D /* SecureStoreError.swift in Sources */,
A4BBCB0E27442CFE0010E266 /* Device.swift in Sources */,
904DE28D272DD094004FDC64 /* MapRoutingService.swift in Sources */,
87239D7F24D41E8700D38EC7 /* MoneyModel.swift in Sources */,
90AC85652385929E00DF7F3B /* LocationManagerInterface.swift in Sources */,
C11CEB4824D44E2C00C1CD0F /* MailSenderRouterHelper.swift in Sources */,
Expand Down Expand Up @@ -1144,9 +1165,11 @@
397CDAD324EF9F7F00FB0EAA /* PinHackCryptoBox.swift in Sources */,
E9B0630E214693160080C391 /* UIDevice+hasHapticFeedback.swift in Sources */,
3946575324EC1B580069BDB0 /* LoadingDataProvider.swift in Sources */,
904DE289272DCF14004FDC64 /* MapRoutingServiceInterface.swift in Sources */,
90718AF121EA370000C81002 /* KeyboardNotificationsObserver.swift in Sources */,
397CDAD124EF9F6C00FB0EAA /* PinCryptoBox.swift in Sources */,
39DCF1A224EE7C66007CCFBC /* UIImage+badgedImage.swift in Sources */,
904DE28B272DCF70004FDC64 /* MapRoutingLocationServiceInterface.swift in Sources */,
90AC8569238592AF00DF7F3B /* GeolocationAuthResult.swift in Sources */,
5709EC6A236F562400EEBD93 /* UIImageExtensions.swift in Sources */,
C11CEB4324D44E2C00C1CD0F /* MailSenderErrorDisplaying.swift in Sources */,
Expand All @@ -1171,6 +1194,7 @@
9087BC5A21EF6C9F00FCE1E1 /* KeyboardNotificationsObserverPool.swift in Sources */,
907F0FE321DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift in Sources */,
3946575D24EC21890069BDB0 /* DefaultLoadingModel.swift in Sources */,
904DE287272DCAD1004FDC64 /* MapApplication.swift in Sources */,
3946575B24EC21480069BDB0 /* LoadingSubviewConfigurable.swift in Sources */,
573117D923BFD28300491781 /* UIStyle.swift in Sources */,
);
Expand Down
172 changes: 172 additions & 0 deletions Utils/Utils/MapRoutingService/MapApplication.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//
// MapApplication.swift
// Utils
//
// Created by Александр Чаусов on 30.10.2021.
// Copyright © 2021 Surf. All rights reserved.
//

import CoreLocation

/// Возможные приложения, способные работать с картами
public enum MapApplication: CaseIterable {
case apple
case googleApp
case yandex
case twoGIS
case googleUrl

var schemaUrl: URL? {
let schemaString: String
switch self {
case .apple:
schemaString = "https://maps.apple.com/maps"
case .googleApp:
schemaString = "comgooglemaps://"
case .yandex:
schemaString = "yandexmaps://"
case .twoGIS:
schemaString = "dgis://"
case .googleUrl:
schemaString = "https://www.google.com/maps"
}

return URL(string: schemaString)
}

// MARK: - Public Methods

/// Выполняет построение URL, открыв который - можно перейти к маршруту в выбранном приложении
public func routeUrl(startCoordinate: CLLocationCoordinate2D?,
endCoordinate: CLLocationCoordinate2D) -> URL? {
switch self {
case .apple:
return Self.appleMapsRoute(startCoordinate: startCoordinate, endCoordinate: endCoordinate)
case .googleApp:
return Self.googleAppRoute(startCoordinate: startCoordinate, endCoordinate: endCoordinate)
case .yandex:
return Self.yandexRoute(startCoordinate: startCoordinate, endCoordinate: endCoordinate)
case .twoGIS:
return Self.twoGISRoute(startCoordinate: startCoordinate, endCoordinate: endCoordinate)
case .googleUrl:
return Self.googleUrlRoute(startCoordinate: startCoordinate, endCoordinate: endCoordinate)
}
}

}

// MARK: - Private Methods

private extension MapApplication {

static func generateLocationParameters(startCoordinate: CLLocationCoordinate2D?,
endCoordinate: CLLocationCoordinate2D) -> (saddr: String, daddr: String) {
var saddr = ""
if let startCoordinate = startCoordinate {
saddr = String(format: "%f,%f", startCoordinate.latitude, startCoordinate.longitude)
}
let daddr = String(format: "%f,%f", endCoordinate.latitude, endCoordinate.longitude)

return (saddr: saddr, daddr: daddr)
}

/// Построение URL-схемы для открытия приложения apple-карт
///
/// https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html
static func appleMapsRoute(startCoordinate: CLLocationCoordinate2D?,
endCoordinate: CLLocationCoordinate2D) -> URL? {

var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "maps.apple.com"
urlComponents.path = "/maps"

let (saddr, daddr) = generateLocationParameters(startCoordinate: startCoordinate,
endCoordinate: endCoordinate)
urlComponents.queryItems = [
URLQueryItem(name: "saddr", value: saddr),
URLQueryItem(name: "daddr", value: daddr)
]

return urlComponents.url
}

/// Построение URL-схемы для открытия приложения google-карт
///
/// https://developers.google.com/maps/documentation/urls/ios-urlscheme
static func googleAppRoute(startCoordinate: CLLocationCoordinate2D?,
endCoordinate: CLLocationCoordinate2D) -> URL? {
var urlComponents = URLComponents()
urlComponents.scheme = "comgooglemaps"
urlComponents.host = ""

let (saddr, daddr) = generateLocationParameters(startCoordinate: startCoordinate,
endCoordinate: endCoordinate)
urlComponents.queryItems = [
URLQueryItem(name: "saddr", value: saddr),
URLQueryItem(name: "daddr", value: daddr)
]

return urlComponents.url
}

/// Построение URL-схемы для открытия приложения yandex-карт
///
/// https://yandex.ru/dev/yandex-apps-launch/maps/doc/concepts/yandexmaps-ios-app.html
static func yandexRoute(startCoordinate: CLLocationCoordinate2D?,
endCoordinate: CLLocationCoordinate2D) -> URL? {
var urlComponents = URLComponents()
urlComponents.scheme = "yandexmaps"
urlComponents.host = "maps.yandex.ru"
urlComponents.path = "/"

let (saddr, daddr) = generateLocationParameters(startCoordinate: startCoordinate,
endCoordinate: endCoordinate)
let rtext = [saddr, daddr].joined(separator: "~")
urlComponents.queryItems = [
URLQueryItem(name: "rtext", value: rtext)
]

return urlComponents.url
}

/// Построение URL-схемы для открытия приложения 2gis
///
/// https://help.2gis.ru/question/razrabotchikam-zapusk-mobilnogo-prilozheniya-2gis
static func twoGISRoute(startCoordinate: CLLocationCoordinate2D?,
endCoordinate: CLLocationCoordinate2D) -> URL? {
var scheme = "dgis://2gis.ru/routeSearch/rsType/car"
if let startCoordinate = startCoordinate {
let fromLocation = String(format: "/from/%f,%f",
startCoordinate.longitude,
startCoordinate.latitude)
scheme.append(fromLocation)
}
let toLocation = String(format: "/to/%f,%f",
endCoordinate.longitude,
endCoordinate.latitude)
scheme.append(toLocation)
return URL(string: scheme)
}

/// Построение URL-схемы для открытия google-карт в Safari
///
/// https://developers.google.com/maps/documentation/urls/ios-urlscheme
static func googleUrlRoute(startCoordinate: CLLocationCoordinate2D?,
endCoordinate: CLLocationCoordinate2D) -> URL? {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "www.google.com"
urlComponents.path = "/maps"

let (saddr, daddr) = generateLocationParameters(startCoordinate: startCoordinate,
endCoordinate: endCoordinate)
urlComponents.queryItems = [
URLQueryItem(name: "saddr", value: saddr),
URLQueryItem(name: "daddr", value: daddr)
]

return urlComponents.url
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// MapRoutingLocationServiceInterface.swift
// Utils
//
// Created by Александр Чаусов on 30.10.2021.
// Copyright © 2021 Surf. All rights reserved.
//

import CoreLocation

/// Вспомогательный протокол для сервиса геолокации,
/// необходимого для использования MapRoutingService
public protocol MapRoutingLocationServiceInterface: AnyObject {
/// Равно true, когда пользователь разрешил доступ к геопозиции
var isLocationAccessAllowed: Bool { get }
/// Равно true, когда пользователь разрешил использование точной геопозиции
var isAllowedFullAccuracyLocation: Bool { get }
/// Вовращает текущую геопозицию пользователя, если она известна,
/// и nil во всех остальных случаях
func getCurrentLocation(_ completion: @escaping ((CLLocationCoordinate2D?) -> Void))
}
Loading

0 comments on commit 7ffdd29

Please sign in to comment.