diff --git a/CHANGELOG.md b/CHANGELOG.md index c8b83a52..accec575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### 1.24.0 + +- **Add**: `AlertFactory` for presenting alerts in SwiftUI and UIKit. + ### 1.23.0 - **Update**: `UITextView` now support configuration with `BaseTextAttributes` diff --git a/LeadKit.podspec b/LeadKit.podspec index 4887cb1f..cb70b7b8 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "1.23.0" + s.version = "1.24.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" diff --git a/TIAppleMapUtils/TIAppleMapUtils.podspec b/TIAppleMapUtils/TIAppleMapUtils.podspec index 82814370..b3f84580 100644 --- a/TIAppleMapUtils/TIAppleMapUtils.podspec +++ b/TIAppleMapUtils/TIAppleMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIAppleMapUtils' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TIAuth/TIAuth.podspec b/TIAuth/TIAuth.podspec index c96a5dc6..9bc961f6 100644 --- a/TIAuth/TIAuth.podspec +++ b/TIAuth/TIAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIAuth' - s.version = '1.23.0' + s.version = '1.24.0' s.summary = 'Login, registration, confirmation and other related actions' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIFoundationUtils/TIFoundationUtils.podspec b/TIFoundationUtils/TIFoundationUtils.podspec index e9337522..39fd763a 100644 --- a/TIFoundationUtils/TIFoundationUtils.podspec +++ b/TIFoundationUtils/TIFoundationUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIFoundationUtils' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TIGoogleMapUtils/TIGoogleMapUtils.podspec b/TIGoogleMapUtils/TIGoogleMapUtils.podspec index 51afd867..68ad58e9 100644 --- a/TIGoogleMapUtils/TIGoogleMapUtils.podspec +++ b/TIGoogleMapUtils/TIGoogleMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIGoogleMapUtils' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TIKeychainUtils/TIKeychainUtils.podspec b/TIKeychainUtils/TIKeychainUtils.podspec index 8a5bf760..f68e2a3b 100644 --- a/TIKeychainUtils/TIKeychainUtils.podspec +++ b/TIKeychainUtils/TIKeychainUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIKeychainUtils' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TIMapUtils/TIMapUtils.podspec b/TIMapUtils/TIMapUtils.podspec index 1fcf1bfd..29fb1077 100644 --- a/TIMapUtils/TIMapUtils.podspec +++ b/TIMapUtils/TIMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIMapUtils' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TIMoyaNetworking/TIMoyaNetworking.podspec b/TIMoyaNetworking/TIMoyaNetworking.podspec index f97087fd..9ceeffb5 100644 --- a/TIMoyaNetworking/TIMoyaNetworking.podspec +++ b/TIMoyaNetworking/TIMoyaNetworking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIMoyaNetworking' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TINetworking/TINetworking.podspec b/TINetworking/TINetworking.podspec index ec0b42af..f6631a9a 100644 --- a/TINetworking/TINetworking.podspec +++ b/TINetworking/TINetworking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TINetworking' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TINetworkingCache/TINetworkingCache.podspec b/TINetworkingCache/TINetworkingCache.podspec index 2750e15a..a12b2177 100644 --- a/TINetworkingCache/TINetworkingCache.podspec +++ b/TINetworkingCache/TINetworkingCache.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TINetworkingCache' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TIPagination/TIPagination.podspec b/TIPagination/TIPagination.podspec index 39f0d82b..eea01a31 100644 --- a/TIPagination/TIPagination.podspec +++ b/TIPagination/TIPagination.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIPagination' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TISwiftUICore/README.md b/TISwiftUICore/README.md index 75092272..98a9cb3a 100644 --- a/TISwiftUICore/README.md +++ b/TISwiftUICore/README.md @@ -1,3 +1,46 @@ # TISwiftUICore -Core UI elements: protocols, views and helpers. \ No newline at end of file +Core UI elements: protocols, views and helpers. + +## SwiftUI alerts + +### SwiftUI views should conform to protocol `SwiftUIAlertContext` to present alerts. +This means that the view must implement the presentingViewController property. This controller is a context from which the alert will be shown. + +```swift +// View that can present alerts. +struct ContentView: View, SwiftUIAlerContext { + var presentingViewController: UIViewController + + var body: some View { + // View realization. + } +} +``` + +## Alerts usage example + +```swift +struct ContentView: View, SwiftUIAlertContext { + + private let factory = AlertFactory() + + @State private var alertDescription: AlertDescriptor + @State private var isAlertPresented = false + + var presentingViewController: UIViewController + + var body: some View { + Button("Show custom alert with binding property") { + alertDescription = factory.okAlert(title: "Title", message: "Message") + isAlertPresented = true + } + } + .alert(isPresented: $isAlertPresented, on: self, alert: alertDescription) + } +} +``` + +# Installation via SPM + +You can install this framework as a target of LeadKit. \ No newline at end of file diff --git a/TISwiftUICore/Sources/Alerts/Modifiers/View+Alerts.swift b/TISwiftUICore/Sources/Alerts/Modifiers/View+Alerts.swift new file mode 100644 index 00000000..50c335fe --- /dev/null +++ b/TISwiftUICore/Sources/Alerts/Modifiers/View+Alerts.swift @@ -0,0 +1,67 @@ +// +// 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 TISwiftUtils +import TIUIKitCore +import SwiftUI + +@available(iOS 13, *) +public extension View { + + /// Presents an alert with a description on a context when a given condition is true. + /// - Parameters: + /// - isPresented: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the alert’s actions, the system sets this value to false and dismisses. + /// - context: The view that will show the alert + /// - alert: Descriptor of the alert. + func alert(isPresented: Binding, + on context: AlertPresentationContext, + alert: AlertDescriptor) -> some View { + + if isPresented.wrappedValue { + alert.present(on: context) { + isPresented.wrappedValue = false + } + } + + return self + } + + /// Presents an alert with a description on a context with custom configuration of the alert when a given condition is true. + /// - Parameters: + /// - isPresented: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the alert’s actions, the system sets this value to false and dismisses. + /// - context: The view that will show the alert + /// - descriptor: Descriptor of the alert. + /// - alertViewFactory: A closure called to configure custom alert. + func alert(isPresented: Binding, + on context: AlertPresentationContext, + alertDescriptor descriptor: AlertDescriptor, + alertViewFactory: Closure) -> some View { + + if isPresented.wrappedValue { + alertViewFactory(descriptor).present(on: context) { + isPresented.wrappedValue = false + } + } + + return self + } +} diff --git a/TISwiftUICore/Sources/Alerts/Protocols/SwiftUIAlertContext.swift b/TISwiftUICore/Sources/Alerts/Protocols/SwiftUIAlertContext.swift new file mode 100644 index 00000000..68bad482 --- /dev/null +++ b/TISwiftUICore/Sources/Alerts/Protocols/SwiftUIAlertContext.swift @@ -0,0 +1,37 @@ +// +// 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 TISwiftUtils +import TIUIKitCore +import UIKit + +/// A SwiftUI context from where the alert can be presented. +public protocol SwiftUIAlertContext: AlertPresentationContext { + /// A view controller that represents a context from which the alert will be shown. + var presentingViewController: UIViewController { get set } +} + +public extension SwiftUIAlertContext { + func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: VoidClosure?) { + presentingViewController.present(viewControllerToPresent, animated: flag, completion: completion) + } +} diff --git a/TISwiftUICore/TISwiftUICore.podspec b/TISwiftUICore/TISwiftUICore.podspec index b4275ad7..9546161c 100644 --- a/TISwiftUICore/TISwiftUICore.podspec +++ b/TISwiftUICore/TISwiftUICore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUICore' - s.version = '1.23.0' + s.version = '1.24.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' } @@ -12,4 +12,6 @@ Pod::Spec.new do |s| s.source_files = s.name + '/Sources/**/*' + s.dependency 'TIUIKitCore', s.version.to_s + s.dependency 'TISwiftUtils', s.version.to_s end diff --git a/TISwiftUtils/TISwiftUtils.podspec b/TISwiftUtils/TISwiftUtils.podspec index 8bf7e61f..9aebb85f 100644 --- a/TISwiftUtils/TISwiftUtils.podspec +++ b/TISwiftUtils/TISwiftUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUtils' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TITableKitUtils/TITableKitUtils.podspec b/TITableKitUtils/TITableKitUtils.podspec index ec633a93..20966a66 100644 --- a/TITableKitUtils/TITableKitUtils.podspec +++ b/TITableKitUtils/TITableKitUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITableKitUtils' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TITransitions/TITransitions.podspec b/TITransitions/TITransitions.podspec index 8da93086..1cb39350 100644 --- a/TITransitions/TITransitions.podspec +++ b/TITransitions/TITransitions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITransitions' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TIUIElements/README.md b/TIUIElements/README.md index 0bafa5af..1814f815 100644 --- a/TIUIElements/README.md +++ b/TIUIElements/README.md @@ -126,7 +126,7 @@ class ViewController: UITableViewController, CollapsibleViewsContainer {

- + # Installation via SPM You can install this framework as a target of LeadKit. diff --git a/TIUIElements/TIUIElements.podspec b/TIUIElements/TIUIElements.podspec index 41ad1437..620f2c14 100644 --- a/TIUIElements/TIUIElements.podspec +++ b/TIUIElements/TIUIElements.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIElements' - s.version = '1.23.0' + s.version = '1.24.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' } diff --git a/TIUIKitCore/README.md b/TIUIKitCore/README.md index 96d028a7..276ef7ff 100644 --- a/TIUIKitCore/README.md +++ b/TIUIKitCore/README.md @@ -4,11 +4,118 @@ Core UI elements: protocols, views and helpers. # Protocols -- [InitializableView](InitializableView/InitializableView.swift) - protocol with methods that should be called in constructor methods of view. -- [Animatable](Animatable/Animatable.swift) - protocol that ensures that specific type support basic animation actions. -- [ActivityIndicator](ActivityIndicator/ActivityIndicator.swift) - basic activity indicator. -- [ActivityIndicatorHolder](ActivityIndicator/ActivityIndicatorHolder.swift) - placeholder view, containing activity indicator. +- [InitializableView](Sources/InitializableView/InitializableViewProtocol.swift) - protocol with methods that should be called in constructor methods of view. +- [Animatable](Sources/ActivityIndicator/Animatable.swift) - protocol that ensures that specific type support basic animation actions. +- [ActivityIndicator](Sources/ActivityIndicator/ActivityIndicator.swift) - basic activity indicator. +- [ActivityIndicatorHolder](Sources/ActivityIndicator/ActivityIndicatorHolder.swift) - placeholder view, containing activity indicator. +- [AlertLocalizationProvider](Sources/Localization/AlertsLocalization/AlertLocalizationProvider.swift) - protocol that ensures that localization for alerts will be provided. +- [AlertPresentable](Sources/Alerts/Protocols/AlertPresentable.swift) - protocol indicates that certain object can present alerts. +- [AlertPresentationContext](Sources/Alerts/Protocols/AlertPresentationContext.swift) - protocol indicates that certain object can present alert on top of itself. +- [UIKitAlertContext](Sources/Alerts/Protocols/UIKitAlertContext.swift) - helper to provide easy conformance of `UIViewController` to `AlertPresentationContext`. -# Views +# Models -- [BaseInitializableView](BaseInitializableView/BaseInitializableView.swift) - UIView conformance to InitializableView. +- [DefaultAlertLocalizationProvider](Sources/Localization/AlertsLocalization/DefaultAlertLocalizationProvider.swift) - default localization provider for alerts. +- [AlertAction](Sources/Alerts/Models/AlertAction.swift) - representation of alert action +- [AlertDescriptor](Sources/Alerts/Models/AlertDescriptor.swift) - struct that holds all needed information to present alert + +# Factories + +- [AlertsFactory](Sources/Alerts/Factories/AlertFactory.swift) - factory to present alerts. + +## AlertsFactory +Use to present alerts in a few lines of code. Can be used for UIKit and SwiftUI +> You can initialize `AlertsFactory` with your own *LocalizationProvider* or use `DefaultAlertLocalizationProvider` + +### Your view or view controller must implement AlertPresentationContext protocol +The implementation of the protocol says that an alert can be shown from this object. Also there is a `UIKitAlertContext` protocol designed to make it easier to work with `AlertPresentationContext` protocol. By default, no changes need to be made for UIKit view controllers to make them conform to `UIKitAlertContext`. + +```swift +// View controller that can present alerts. +class ViewController: UIViewController, UIKitAlerContext { + // Realization of the view controller +} +``` + +### Your alert controller must implement AlertPresentable protocol +The implementation of this protocol says that an alert can be shown from the context. By default, the standard `UIAlertController` conforms to the protocol. Accordingly, when using a custom alert, it must also conform to the protocol: + +```swift +import PopupDialog + +extension PopupDialog: AlertPresentable { + @discardableResult + public func configured(with configuration: AlertDescriptor) -> Self { + title = configuration.title + + for action in configuration.actions { + addButton(DefaultButton(title: action.title, action: action.action)) + } + + return self + } + + public func present(on context: AlertPresentationContext, completion: VoidClosure?) { + context.present(self, animated: true, completion: completion) + } +} +``` + +## Custom alerts +```swift +// Presents alert +func presentAlert() { + factory + .alert(title: "Alert's title", + message: "Alert's message", + tint: .systemBlue, + actions: [ + AlertAction(title: "Ok", style: .default, action: nil), + AlertAction(title: "Cancel", style: .cancel, action: nil) + ]) + .present(on: self) +} + +// Presents sheet alert +func presentSheetAlert() { + factory + .sheetAlert(title: "Alert's title", + message: "Alert's message", + tint: .systemBlue, + actions: [ + AlertAction(title: "Ok", style: .default, action: nil), + AlertAction(title: "Cancel", style: .cancel, action: nil) + ]) + .present(on: self) +} +``` + +## Default alerts +```swift +// Ok alert +func presentOkAlert() { + factory + .okAlert(title: "Title", message: "Message") + .present(on: self) +} + +// Retry alert +func presentRetryAlert() { + factory + .retryAlert(title: "Title", message: "Message") { [weak self] in + self?.presentOkAlert() + } + .present(on: self) +} + +// Dialogue alert +func presentDialogueAlert() { + factory + .dialogueAlert(title: "Title", message: "Message") + .present(on: self) +} +``` + +# Installation via SPM + +You can install this framework as a target of LeadKit. diff --git a/TIUIKitCore/Sources/Alerts/Factories/AlertFactory.swift b/TIUIKitCore/Sources/Alerts/Factories/AlertFactory.swift new file mode 100644 index 00000000..9376a8ea --- /dev/null +++ b/TIUIKitCore/Sources/Alerts/Factories/AlertFactory.swift @@ -0,0 +1,143 @@ +// +// 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 TISwiftUtils +import UIKit + +open class AlertFactory { + public var localizationProvider: AlertLocalizationProvider + + public init(localizationProvider: AlertLocalizationProvider = DefaultAlertLocalizationProvider()) { + self.localizationProvider = localizationProvider + } + + /// Provides general alert description. + /// - Parameters: + /// - title: A text string used as the title of the alert. + /// - message: A text string used as the message of the alert. + /// - tint: A color used as a tint color of the alert. Default color is UIColor.systemBlue. + /// - actions: An array of actions of the alert. + /// - Returns: Alert descriptor which can be used to present alert view. + open func alert(title: String? = nil, + message: String? = nil, + tint: UIColor = .systemBlue, + actions: [AlertAction]) -> AlertDescriptor { + + AlertDescriptor(title: title, + message: message, + tintColor: tint, + actions: actions) + } + + /// Provides general sheet alert description. + /// - Parameters: + /// - title: A text string used as the title of the sheet alert. + /// - message: A text string used as the message of the sheet alert. + /// - tint: A color used as a tint color of the sheet alert. Default color is UIColor.systemBlue. + /// - actions: An array of actions of the sheet alert. + /// - Returns: Alert descriptor which can be used to present sheet alert view. + open func sheetAlert(title: String? = nil, + message: String? = nil, + tint: UIColor = .systemBlue, + actions: [AlertAction]) -> AlertDescriptor { + + AlertDescriptor(title: title, + message: message, + style: .actionSheet, + tintColor: tint, + actions: actions) + } + + /// Provides ok type alert description. + /// - Parameters: + /// - title: A text string used as the title of the alert. + /// - message: A text string used as the message of the alert. + /// - tint: A color used as a tint color of the alert. Default color is UIColor.systemBlue. + /// - Returns: Alert descriptor which can be used to present alert view. + open func okAlert(title: String? = nil, + message: String? = nil, + tint: UIColor = .systemBlue) -> AlertDescriptor { + + AlertDescriptor(title: title, + message: message, + tintColor: tint, + actions: [.simpleAction(localizationProvider.okTitle)]) + } + + /// Provides retry type alert description. + /// - Parameters: + /// - title: A text string used as the title of the alert. + /// - message: A text string used as the message of the alert. + /// - tint: A color used as a tint color of the alert. Default color is UIColor.systemBlue. + /// - retryAction: A closure called by tapping on the retry button of the alert. + /// - Returns: Alert descriptor which can be used to present alert view. + open func retryAlert(title: String? = nil, + message: String? = nil, + tint: UIColor = .systemBlue, + retryAction: VoidClosure? = nil) -> AlertDescriptor { + + AlertDescriptor(title: title, + message: message, + tintColor: tint, + actions: [ + .cancelAction(localizationProvider.cancelTitle), + .init(title: localizationProvider.retryTitle, action: retryAction) + ]) + } + + /// Provides dialogue type alert description. Dialogue type alert containes to buttons: Yes and No. + /// - Parameters: + /// - title: A text string used as the title of the alert. + /// - message: A text string used as the message of the alert. + /// - tint: A color used as a tint color of the alert. Default color is UIColor.systemBlue. + /// - yesAction: A closure called by tapping on the yes button of the alert. + /// - noAction: A closure called by tapping on the no button of the alert. + /// - Returns: Alert descriptor which can be used to present alert view. + open func dialogueAlert(title: String? = nil, + message: String? = nil, + tint: UIColor = .systemBlue, + yesAction: VoidClosure? = nil, + noAction: VoidClosure? = nil) -> AlertDescriptor { + + AlertDescriptor(title: title, + message: message, + tintColor: tint, + actions: [ + .init(title: localizationProvider.yesTitle, action: yesAction), + .init(title: localizationProvider.noTitle, style: .destructive, action: noAction) + ]) + } +} + +// MARK: - AlertAction + Helpers + +private extension AlertAction { + static func simpleAction(_ title: String, + style: UIAlertAction.Style = .default) -> AlertAction { + + AlertAction(title: title, style: style, action: nil) + } + + static func cancelAction(_ title: String) -> AlertAction { + .simpleAction(title, style: .cancel) + } +} diff --git a/TIUIKitCore/Sources/Alerts/Models/AlertAction.swift b/TIUIKitCore/Sources/Alerts/Models/AlertAction.swift new file mode 100644 index 00000000..d919c7ee --- /dev/null +++ b/TIUIKitCore/Sources/Alerts/Models/AlertAction.swift @@ -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 TISwiftUtils +import UIKit + +/// A struct describes an alert button information +public struct AlertAction { + + public let id = UUID() + + /// Alert button title + public let title: String + + /// Alert button style + public let style: UIAlertAction.Style + + /// Alert button action + public let action: VoidClosure? + + public init(title: String, style: UIAlertAction.Style = .default, action: VoidClosure? = nil) { + self.title = title + self.style = style + self.action = action + } +} + +// MARK: - AlertAction + Equatable + +extension AlertAction: Equatable { + public static func == (lhs: AlertAction, rhs: AlertAction) -> Bool { + return false + } +} + +// MARK: - AlertAction + Identifiable + +@available(iOS 13, *) +extension AlertAction: Identifiable { } + +// MARK: - AlertAction + Helpers + +extension AlertAction { + var asUIAlertAction: UIAlertAction { + UIAlertAction(title: title, style: style, handler: { _ in action?() }) + } +} diff --git a/TIUIKitCore/Sources/Alerts/Models/AlertDescriptor.swift b/TIUIKitCore/Sources/Alerts/Models/AlertDescriptor.swift new file mode 100644 index 00000000..2c6c81b8 --- /dev/null +++ b/TIUIKitCore/Sources/Alerts/Models/AlertDescriptor.swift @@ -0,0 +1,55 @@ +// +// 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 + +/// A struct describes alert data +public struct AlertDescriptor { + + /// Alert title + public let title: String? + + /// Alert message + public let message: String? + + /// Alert style + public let style: UIAlertController.Style + + /// Alert tint color + public let tintColor: UIColor + + /// Alert actions + public let actions: [AlertAction] + + public init(title: String? = nil, + message: String? = nil, + style: UIAlertController.Style = .alert, + tintColor: UIColor = .systemBlue, + actions: [AlertAction] = []) { + + self.title = title + self.message = message + self.style = style + self.tintColor = tintColor + self.actions = actions + } +} diff --git a/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentable.swift b/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentable.swift new file mode 100644 index 00000000..b24d66cd --- /dev/null +++ b/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentable.swift @@ -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 TISwiftUtils + +/// A protocol represents an alert which can be presented on the given context. +/// The implementation of this protocol says that an alert can be shown from the context. By default, the standard `UIAlertController` conforms to the protocol. +public protocol AlertPresentable { + func present(on context: AlertPresentationContext, completion: VoidClosure?) +} diff --git a/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentationContext.swift b/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentationContext.swift new file mode 100644 index 00000000..377a4086 --- /dev/null +++ b/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentationContext.swift @@ -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 TISwiftUtils +import UIKit + +/// A context from where the alert can be presented. +public protocol AlertPresentationContext { + func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: VoidClosure?) +} diff --git a/TIUIKitCore/Sources/Alerts/Protocols/UIKitAlertContext.swift b/TIUIKitCore/Sources/Alerts/Protocols/UIKitAlertContext.swift new file mode 100644 index 00000000..7fdcf8e1 --- /dev/null +++ b/TIUIKitCore/Sources/Alerts/Protocols/UIKitAlertContext.swift @@ -0,0 +1,24 @@ +// +// 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. +// + +/// An UIKit context from where the alert can be presented. +public protocol UIKitAlertContext: AlertPresentationContext { } diff --git a/TIUIKitCore/Sources/Extensions/Alerts/AlertDescriptor+Helpers.swift b/TIUIKitCore/Sources/Extensions/Alerts/AlertDescriptor+Helpers.swift new file mode 100644 index 00000000..716d2522 --- /dev/null +++ b/TIUIKitCore/Sources/Extensions/Alerts/AlertDescriptor+Helpers.swift @@ -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 TISwiftUtils +import UIKit + +public extension AlertDescriptor { + func present(on context: AlertPresentationContext, completion: VoidClosure? = nil) { + let alertFactory: Closure = { configuration in + UIAlertController(title: configuration.title, + message: configuration.message, + preferredStyle: configuration.style) + .configured(with: configuration) + } + + present(on: context, alertViewFactory: alertFactory, completion: completion) + } + + func present(on context: AlertPresentationContext, + alertViewFactory: Closure, + completion: VoidClosure? = nil) { + alertViewFactory(self).present(on: context, completion: completion) + } +} diff --git a/TIUIKitCore/Sources/Extensions/Alerts/UIAlertController+AlertPresentable.swift b/TIUIKitCore/Sources/Extensions/Alerts/UIAlertController+AlertPresentable.swift new file mode 100644 index 00000000..d40ca0ef --- /dev/null +++ b/TIUIKitCore/Sources/Extensions/Alerts/UIAlertController+AlertPresentable.swift @@ -0,0 +1,46 @@ +// +// 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 TISwiftUtils +import UIKit + +public extension UIAlertController { + + @discardableResult + func configured(with configuration: AlertDescriptor) -> Self { + title = configuration.title + message = configuration.message + view.tintColor = configuration.tintColor + + for action in configuration.actions { + addAction(action.asUIAlertAction) + } + + return self + } +} + +extension UIAlertController: AlertPresentable { + public func present(on context: AlertPresentationContext, completion: VoidClosure?) { + context.present(self, animated: true, completion: completion) + } +} diff --git a/TIUIKitCore/Sources/Localization/AlertsLocalization/AlertLocalizationProvider.swift b/TIUIKitCore/Sources/Localization/AlertsLocalization/AlertLocalizationProvider.swift new file mode 100644 index 00000000..85fb451a --- /dev/null +++ b/TIUIKitCore/Sources/Localization/AlertsLocalization/AlertLocalizationProvider.swift @@ -0,0 +1,30 @@ +// +// 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. +// + +/// A provider of localization for common buttons' titles of the alerts. +public protocol AlertLocalizationProvider { + var okTitle: String { get } + var cancelTitle: String { get } + var retryTitle: String { get } + var yesTitle: String { get } + var noTitle: String { get } +} diff --git a/TIUIKitCore/Sources/Localization/AlertsLocalization/DefaultAlertLocalizationProvider.swift b/TIUIKitCore/Sources/Localization/AlertsLocalization/DefaultAlertLocalizationProvider.swift new file mode 100644 index 00000000..b3c0e95d --- /dev/null +++ b/TIUIKitCore/Sources/Localization/AlertsLocalization/DefaultAlertLocalizationProvider.swift @@ -0,0 +1,53 @@ +// +// 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 Foundation + +/// Provides default localization for alerts' buttons +open class DefaultAlertLocalizationProvider: AlertLocalizationProvider { + + public var bundle: Bundle + + public init(bundle: Bundle = .main) { + self.bundle = bundle + } + + open var okTitle: String { + bundle.localizedString(forKey: "common_ok", value: "Ок", table: nil) + } + + open var cancelTitle: String { + bundle.localizedString(forKey: "common_cancel", value: "Отмена", table: nil) + } + + open var retryTitle: String { + bundle.localizedString(forKey: "common_retry", value: "Повторить", table: nil) + } + + open var yesTitle: String { + bundle.localizedString(forKey: "common_yes", value: "Да", table: nil) + } + + open var noTitle: String { + bundle.localizedString(forKey: "common_no", value: "Нет", table: nil) + } +} diff --git a/TIUIKitCore/TIUIKitCore.podspec b/TIUIKitCore/TIUIKitCore.podspec index e88542df..2da9cae0 100644 --- a/TIUIKitCore/TIUIKitCore.podspec +++ b/TIUIKitCore/TIUIKitCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIKitCore' - s.version = '1.23.0' + s.version = '1.24.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' } @@ -12,4 +12,6 @@ Pod::Spec.new do |s| s.source_files = s.name + '/Sources/**/*' s.framework = 'UIKit' + + s.dependency 'TISwiftUtils', s.version.to_s end diff --git a/TIYandexMapUtils/TIYandexMapUtils.podspec b/TIYandexMapUtils/TIYandexMapUtils.podspec index 5d3b0c9c..943e1a6d 100644 --- a/TIYandexMapUtils/TIYandexMapUtils.podspec +++ b/TIYandexMapUtils/TIYandexMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIYandexMapUtils' - s.version = '1.23.0' + s.version = '1.24.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' }