Rearrange how UIAlertActions are generated by splitting out the legacy model and using a protocol to drive.

This commit is contained in:
Scott Pfeil 2023-05-03 12:54:55 -04:00
parent 14117c84a6
commit cc9a39e599
3 changed files with 51 additions and 40 deletions

View File

@ -44,10 +44,10 @@ public class AlertHandler {
} }
@MainActor @MainActor
public func createAlertController(with alertModel: AlertModel) -> AlertController { public func createAlertController(with alertModel: AlertModelProtocol, additionalData: [AnyHashable: Any]? = nil, delegateObject: DelegateObject? = nil) -> AlertController {
// ActionSheets are not supported on iPad interfaces without a source rect (i.e. a source element) which isn't currently supported for our generic handling. // ActionSheets are not supported on iPad interfaces without a source rect (i.e. a source element) which isn't currently supported for our generic handling.
// TODO: Find a way to support this. // TODO: Find a way to support this.
var alertStyle = alertModel.style var alertStyle = alertModel.preferredStyle
if alertStyle == .actionSheet, UIDevice.current.userInterfaceIdiom != .phone { if alertStyle == .actionSheet, UIDevice.current.userInterfaceIdiom != .phone {
alertStyle = .alert alertStyle = .alert
} }
@ -57,12 +57,15 @@ public class AlertHandler {
for action in alertModel.actions { for action in alertModel.actions {
alertController.addAction(action) alertController.addAction(action)
} }
if let index = alertModel.preferredActionIndex {
alertController.preferredAction = alertModel.actions[index]
}
return alertController return alertController
} }
/// Shows an alert using the alert object. /// Shows an alert using the alert object.
@MainActor @MainActor
public func queueAlertToShow(with alertObject: AlertObject) -> UIAlertController { public func queueAlertToShow(with alertObject: AlertObject, additionalData: [AnyHashable: Any]? = nil, delegateObject: DelegateObject? = nil) -> UIAlertController {
// It's a greedy alert! Clear all alerts that are queued up and the one that is showing // It's a greedy alert! Clear all alerts that are queued up and the one that is showing
if alertObject.isGreedy { if alertObject.isGreedy {
@ -75,15 +78,6 @@ public class AlertHandler {
return alertController return alertController
} }
/// Cancel Alert with ID.
public func cancelAlert(with id: String) {
queue.operations.first { operation in
guard let operation = operation as? AlertOperation,
operation.alertObject.alertModel.id == id else { return false }
return true
}?.cancel()
}
/** Iterates through all scheduled alerts and cancels any that match the provided predicate. /** Iterates through all scheduled alerts and cancels any that match the provided predicate.
* @param predicate The predicate block to decide whether to cancel an alert. * @param predicate The predicate block to decide whether to cancel an alert.
*/ */

View File

@ -8,6 +8,14 @@
import MVMCore import MVMCore
public protocol AlertModelProtocol {
var title: String? { get }
var message: String? { get }
var actions: [UIAlertAction] { get }
var preferredActionIndex: Int? { get }
var preferredStyle: UIAlertController.Style { get }
}
/// An object with properties for managing the alert. /// An object with properties for managing the alert.
public struct AlertObject { public struct AlertObject {
@ -15,11 +23,11 @@ public struct AlertObject {
public var isGreedy = false public var isGreedy = false
/// The alert model for the alert to show. /// The alert model for the alert to show.
public var alertModel: AlertModel public var alertModel: AlertModelProtocol
public weak var alertDelegate: AlertDelegateProtocol? public weak var alertDelegate: AlertDelegateProtocol?
public init(alertModel: AlertModel, isGreedy: Bool = false, alertDelegate: AlertDelegateProtocol? = nil) { public init(alertModel: AlertModelProtocol, isGreedy: Bool = false, alertDelegate: AlertDelegateProtocol? = nil) {
self.alertModel = alertModel self.alertModel = alertModel
self.isGreedy = isGreedy self.isGreedy = isGreedy
self.alertDelegate = alertDelegate self.alertDelegate = alertDelegate

View File

@ -17,6 +17,7 @@ public struct AlertButtonModel: Codable {
public var title: String public var title: String
public var action: ActionModelProtocol public var action: ActionModelProtocol
public var style: UIAlertAction.Style = .default public var style: UIAlertAction.Style = .default
public var preferred: Bool = false
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
@ -36,6 +37,7 @@ public struct AlertButtonModel: Codable {
case title case title
case action case action
case style case style
case preferred
} }
//-------------------------------------------------- //--------------------------------------------------
@ -50,6 +52,7 @@ public struct AlertButtonModel: Codable {
self.style = UIAlertAction.Style(rawValue: style) self.style = UIAlertAction.Style(rawValue: style)
} }
action = try typeContainer.decodeModel(codingKey: .action) action = try typeContainer.decodeModel(codingKey: .action)
preferred = try typeContainer.decodeIfPresent(Bool.self, forKey: .preferred) ?? false
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -57,42 +60,51 @@ public struct AlertButtonModel: Codable {
try container.encode(title, forKey: .title) try container.encode(title, forKey: .title)
try container.encode(style.rawValueString, forKey: .style) try container.encode(style.rawValueString, forKey: .style)
try container.encodeModel(action, forKey: .action) try container.encodeModel(action, forKey: .action)
try container.encodeIfPresent(preferred, forKey: .preferred)
} }
} }
public struct AlertModel: Codable, Identifiable { public struct AlertModel: Codable, Identifiable, AlertModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public var title: String public var title: String?
public var message: String public var message: String?
public var style: UIAlertController.Style = .alert public var preferredStyle: UIAlertController.Style = .alert
public var actions: [UIAlertAction] public var buttonModels: [AlertButtonModel]
public var buttonModels: [AlertButtonModel]?
public var analyticsData: JSONValueDictionary? public var analyticsData: JSONValueDictionary?
public var id: String public var id: String
public var delegateObject: DelegateObject?
public var actions: [UIAlertAction] {
get {
buttonModels.map({ alertButtonModel in
return alertButtonModel.generateAction(delegateObject: delegateObject)
})
}
}
public var preferredActionIndex: Int? {
get {
buttonModels.firstIndex(where: { alertButtonModel in
return alertButtonModel.preferred
})
}
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Init
//-------------------------------------------------- //--------------------------------------------------
public init(title: String, message: String, actions: [UIAlertAction], style: UIAlertController.Style = .alert, id: String = UUID().uuidString) {
self.title = title
self.message = message
self.actions = actions
self.style = style
self.id = id
}
public init(title: String, message: String, buttonModels: [AlertButtonModel], style: UIAlertController.Style = .alert, delegateObject: DelegateObject?, id: String = UUID().uuidString) { public init(title: String, message: String, buttonModels: [AlertButtonModel], style: UIAlertController.Style = .alert, delegateObject: DelegateObject?, id: String = UUID().uuidString) {
self.title = title self.title = title
self.message = message self.message = message
self.buttonModels = buttonModels self.buttonModels = buttonModels
actions = buttonModels.map({ alertButtonModel in self.preferredStyle = style
return alertButtonModel.generateAction(delegateObject: delegateObject)
})
self.style = style
self.id = id self.id = id
} }
@ -115,17 +127,14 @@ public struct AlertModel: Codable, Identifiable {
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
let delegateObject = try decoder.get() delegateObject = try decoder.get()
title = try typeContainer.decode(String.self, forKey: .title) title = try typeContainer.decode(String.self, forKey: .title)
message = try typeContainer.decode(String.self, forKey: .message) message = try typeContainer.decode(String.self, forKey: .message)
buttonModels = try typeContainer.decode([AlertButtonModel].self, forKey: .alertActions) buttonModels = try typeContainer.decode([AlertButtonModel].self, forKey: .alertActions)
actions = buttonModels!.map({ alertButtonModel in
return alertButtonModel.generateAction(delegateObject: delegateObject)
})
analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData) analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData)
if let style = try typeContainer.decodeIfPresent(String.self, forKey: .style) { if let style = try typeContainer.decodeIfPresent(String.self, forKey: .style) {
self.style = UIAlertController.Style(rawValue: style) self.preferredStyle = UIAlertController.Style(rawValue: style)
} }
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
} }
@ -135,7 +144,7 @@ public struct AlertModel: Codable, Identifiable {
try container.encode(title, forKey: .title) try container.encode(title, forKey: .title)
try container.encode(message, forKey: .message) try container.encode(message, forKey: .message)
try container.encodeIfPresent(buttonModels, forKey: .alertActions) try container.encodeIfPresent(buttonModels, forKey: .alertActions)
try container.encode(style.rawValueString, forKey: .style) try container.encode(preferredStyle.rawValueString, forKey: .style)
try container.encodeIfPresent(analyticsData, forKey: .analyticsData) try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
try container.encode(id, forKey: .id) try container.encode(id, forKey: .id)
} }