From 9a427adab713b2d481b9907db5b4df77ff88f2b7 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Wed, 27 Jul 2022 13:58:44 +0300 Subject: [PATCH] fix: code review notes + added custom modifier for alerts --- TISwiftUICore/README.md | 22 +++++--- .../Alerts/Modifiers/AlertModifier.swift | 51 +++++++++++++++++++ .../Alerts/Modifiers/View+Alerts.swift | 22 +++----- TIUIKitCore/README.md | 28 +++++++++- .../Alerts/Factories/AlertFactory.swift | 10 ++-- .../Sources/Alerts/Models/AlertAction.swift | 2 +- .../Alerts/Models/AlertDescriptor.swift | 2 +- .../Alerts/Protocols/AlertPresentable.swift | 1 + .../Protocols/AlertPresentationContext.swift | 1 + .../Alerts/Protocols/UIKitAlertContext.swift | 2 - .../DefaultAlertLocalizationProvider.swift | 2 +- 11 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 TISwiftUICore/Sources/Alerts/Modifiers/AlertModifier.swift diff --git a/TISwiftUICore/README.md b/TISwiftUICore/README.md index c06adcf8..95ebf38e 100644 --- a/TISwiftUICore/README.md +++ b/TISwiftUICore/README.md @@ -3,16 +3,26 @@ Core UI elements: protocols, views and helpers. ## SwiftUI alerts -> SwiftUI views should conform to protocol `SwiftUIAlertContext` to present alert +> 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 -var body: some View { - Button("Show custom alert with binding property") { - alertDescription = factory.okAlert(title: "Title", message: "Message") - isPresentedCustomAlert = true +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) } - .alert(isPresented: $isPresentedAlert, on: self, alert: alertDescription) } ``` diff --git a/TISwiftUICore/Sources/Alerts/Modifiers/AlertModifier.swift b/TISwiftUICore/Sources/Alerts/Modifiers/AlertModifier.swift new file mode 100644 index 00000000..c406ae15 --- /dev/null +++ b/TISwiftUICore/Sources/Alerts/Modifiers/AlertModifier.swift @@ -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 SwiftUI +import TISwiftUtils +import TIUIKitCore + +@available(iOS 13, *) +struct AlertModifier: ViewModifier { + + @Binding var isPresented: Bool + + let context: AlertPresentationContext + let alertDescriptor: AlertDescriptor + let alertPresentable: AlertPresentable? + + func body(content: Content) -> some View { + if isPresented { + let completion: VoidClosure = { + isPresented = false + } + + if let alertPresentable = alertPresentable { + alertPresentable.present(on: context, completion: completion) + } else { + alertDescriptor.present(on: context, completion: completion) + } + } + + return content + } +} diff --git a/TISwiftUICore/Sources/Alerts/Modifiers/View+Alerts.swift b/TISwiftUICore/Sources/Alerts/Modifiers/View+Alerts.swift index 50c335fe..ed5cf6f2 100644 --- a/TISwiftUICore/Sources/Alerts/Modifiers/View+Alerts.swift +++ b/TISwiftUICore/Sources/Alerts/Modifiers/View+Alerts.swift @@ -36,13 +36,10 @@ public extension View { on context: AlertPresentationContext, alert: AlertDescriptor) -> some View { - if isPresented.wrappedValue { - alert.present(on: context) { - isPresented.wrappedValue = false - } - } - - return self + modifier(AlertModifier(isPresented: isPresented, + context: context, + alertDescriptor: alert, + alertPresentable: nil)) } /// Presents an alert with a description on a context with custom configuration of the alert when a given condition is true. @@ -56,12 +53,9 @@ public extension View { alertDescriptor descriptor: AlertDescriptor, alertViewFactory: Closure) -> some View { - if isPresented.wrappedValue { - alertViewFactory(descriptor).present(on: context) { - isPresented.wrappedValue = false - } - } - - return self + modifier(AlertModifier(isPresented: isPresented, + context: context, + alertDescriptor: descriptor, + alertPresentable: alertViewFactory(descriptor))) } } diff --git a/TIUIKitCore/README.md b/TIUIKitCore/README.md index bbd3408d..22f9601d 100644 --- a/TIUIKitCore/README.md +++ b/TIUIKitCore/README.md @@ -27,8 +27,32 @@ Core UI elements: protocols, views and helpers. 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 PresentationContext protocol -There are `UIKitAlertContext` protocol that are 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`. +### 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`. + +### 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 diff --git a/TIUIKitCore/Sources/Alerts/Factories/AlertFactory.swift b/TIUIKitCore/Sources/Alerts/Factories/AlertFactory.swift index 9a50f530..9376a8ea 100644 --- a/TIUIKitCore/Sources/Alerts/Factories/AlertFactory.swift +++ b/TIUIKitCore/Sources/Alerts/Factories/AlertFactory.swift @@ -36,7 +36,7 @@ open class AlertFactory { /// - 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. + /// - Returns: Alert descriptor which can be used to present alert view. open func alert(title: String? = nil, message: String? = nil, tint: UIColor = .systemBlue, @@ -54,7 +54,7 @@ open class AlertFactory { /// - 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. + /// - Returns: Alert descriptor which can be used to present sheet alert view. open func sheetAlert(title: String? = nil, message: String? = nil, tint: UIColor = .systemBlue, @@ -72,7 +72,7 @@ open class AlertFactory { /// - 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. + /// - Returns: Alert descriptor which can be used to present alert view. open func okAlert(title: String? = nil, message: String? = nil, tint: UIColor = .systemBlue) -> AlertDescriptor { @@ -89,7 +89,7 @@ open class AlertFactory { /// - 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. + /// - Returns: Alert descriptor which can be used to present alert view. open func retryAlert(title: String? = nil, message: String? = nil, tint: UIColor = .systemBlue, @@ -111,7 +111,7 @@ open class AlertFactory { /// - 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. + /// - Returns: Alert descriptor which can be used to present alert view. open func dialogueAlert(title: String? = nil, message: String? = nil, tint: UIColor = .systemBlue, diff --git a/TIUIKitCore/Sources/Alerts/Models/AlertAction.swift b/TIUIKitCore/Sources/Alerts/Models/AlertAction.swift index c90b8996..d919c7ee 100644 --- a/TIUIKitCore/Sources/Alerts/Models/AlertAction.swift +++ b/TIUIKitCore/Sources/Alerts/Models/AlertAction.swift @@ -23,7 +23,7 @@ import TISwiftUtils import UIKit -// struct describe alert button information +/// A struct describes an alert button information public struct AlertAction { public let id = UUID() diff --git a/TIUIKitCore/Sources/Alerts/Models/AlertDescriptor.swift b/TIUIKitCore/Sources/Alerts/Models/AlertDescriptor.swift index 2efb8190..2c6c81b8 100644 --- a/TIUIKitCore/Sources/Alerts/Models/AlertDescriptor.swift +++ b/TIUIKitCore/Sources/Alerts/Models/AlertDescriptor.swift @@ -22,7 +22,7 @@ import UIKit -/// Struct describes alert data +/// A struct describes alert data public struct AlertDescriptor { /// Alert title diff --git a/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentable.swift b/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentable.swift index 64eda4e1..163177a5 100644 --- a/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentable.swift +++ b/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentable.swift @@ -22,6 +22,7 @@ import TISwiftUtils +/// A protocol represents an alert which can be presented on the given context 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 index c060a1ef..72dd720d 100644 --- a/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentationContext.swift +++ b/TIUIKitCore/Sources/Alerts/Protocols/AlertPresentationContext.swift @@ -23,6 +23,7 @@ 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 index 3551febb..3652242b 100644 --- a/TIUIKitCore/Sources/Alerts/Protocols/UIKitAlertContext.swift +++ b/TIUIKitCore/Sources/Alerts/Protocols/UIKitAlertContext.swift @@ -20,6 +20,4 @@ // THE SOFTWARE. // -import UIKit - public protocol UIKitAlertContext: AlertPresentationContext { } diff --git a/TIUIKitCore/Sources/Localization/AlertsLocalization/DefaultAlertLocalizationProvider.swift b/TIUIKitCore/Sources/Localization/AlertsLocalization/DefaultAlertLocalizationProvider.swift index 9b506998..b3c0e95d 100644 --- a/TIUIKitCore/Sources/Localization/AlertsLocalization/DefaultAlertLocalizationProvider.swift +++ b/TIUIKitCore/Sources/Localization/AlertsLocalization/DefaultAlertLocalizationProvider.swift @@ -22,7 +22,7 @@ import Foundation -/// Provide default localization for alerts' buttons +/// Provides default localization for alerts' buttons open class DefaultAlertLocalizationProvider: AlertLocalizationProvider { public var bundle: Bundle