Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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