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
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.
// TODO: Find a way to support this.
var alertStyle = alertModel.style
var alertStyle = alertModel.preferredStyle
if alertStyle == .actionSheet, UIDevice.current.userInterfaceIdiom != .phone {
alertStyle = .alert
}
@ -57,12 +57,15 @@ public class AlertHandler {
for action in alertModel.actions {
alertController.addAction(action)
}
if let index = alertModel.preferredActionIndex {
alertController.preferredAction = alertModel.actions[index]
}
return alertController
}
/// Shows an alert using the alert object.
@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
if alertObject.isGreedy {
@ -75,15 +78,6 @@ public class AlertHandler {
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.
* @param predicate The predicate block to decide whether to cancel an alert.
*/

View File

@ -8,6 +8,14 @@
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.
public struct AlertObject {
@ -15,11 +23,11 @@ public struct AlertObject {
public var isGreedy = false
/// The alert model for the alert to show.
public var alertModel: AlertModel
public var alertModel: AlertModelProtocol
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.isGreedy = isGreedy
self.alertDelegate = alertDelegate

View File

@ -17,6 +17,7 @@ public struct AlertButtonModel: Codable {
public var title: String
public var action: ActionModelProtocol
public var style: UIAlertAction.Style = .default
public var preferred: Bool = false
//--------------------------------------------------
// MARK: - Initializer
@ -36,6 +37,7 @@ public struct AlertButtonModel: Codable {
case title
case action
case style
case preferred
}
//--------------------------------------------------
@ -50,6 +52,7 @@ public struct AlertButtonModel: Codable {
self.style = UIAlertAction.Style(rawValue: style)
}
action = try typeContainer.decodeModel(codingKey: .action)
preferred = try typeContainer.decodeIfPresent(Bool.self, forKey: .preferred) ?? false
}
public func encode(to encoder: Encoder) throws {
@ -57,42 +60,51 @@ public struct AlertButtonModel: Codable {
try container.encode(title, forKey: .title)
try container.encode(style.rawValueString, forKey: .style)
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
//--------------------------------------------------
public var title: String
public var message: String
public var style: UIAlertController.Style = .alert
public var actions: [UIAlertAction]
public var buttonModels: [AlertButtonModel]?
public var title: String?
public var message: String?
public var preferredStyle: UIAlertController.Style = .alert
public var buttonModels: [AlertButtonModel]
public var analyticsData: JSONValueDictionary?
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) {
self.title = title
self.message = message
self.buttonModels = buttonModels
actions = buttonModels.map({ alertButtonModel in
return alertButtonModel.generateAction(delegateObject: delegateObject)
})
self.style = style
self.preferredStyle = style
self.id = id
}
@ -115,17 +127,14 @@ public struct AlertModel: Codable, Identifiable {
public init(from decoder: Decoder) throws {
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)
message = try typeContainer.decode(String.self, forKey: .message)
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)
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
}
@ -135,7 +144,7 @@ public struct AlertModel: Codable, Identifiable {
try container.encode(title, forKey: .title)
try container.encode(message, forKey: .message)
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.encode(id, forKey: .id)
}