aligns behavior for user intiaited corner cases.
This commit is contained in:
parent
cf8d0f967e
commit
be196a289e
@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModel, FormFieldProtocol {
|
@objcMembers public class CheckboxModel: NSObject, MoleculeModelProtocol, SelectableMoleculeModel, FormFieldProtocol {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -15,7 +15,7 @@
|
|||||||
public static var identifier: String = "checkbox"
|
public static var identifier: String = "checkbox"
|
||||||
public var backgroundColor: Color?
|
public var backgroundColor: Color?
|
||||||
public var accessibilityIdentifier: String?
|
public var accessibilityIdentifier: String?
|
||||||
public var checked: Bool = false
|
public dynamic var checked: Bool = false
|
||||||
public var enabled: Bool = true
|
public var enabled: Bool = true
|
||||||
public var animated: Bool = true
|
public var animated: Bool = true
|
||||||
public var inverted: Bool = false
|
public var inverted: Bool = false
|
||||||
|
|||||||
@ -24,7 +24,7 @@ public class SelectAllBoxesBehaviorModel: PageBehaviorModelProtocol {
|
|||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Codable
|
// MARK: - Codable
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case selectAllTitle
|
case selectAllTitle
|
||||||
case deselectAllTitle
|
case deselectAllTitle
|
||||||
@ -41,7 +41,7 @@ public class SelectAllBoxesBehaviorModel: PageBehaviorModelProtocol {
|
|||||||
self.deselectAllTitle = deselectAllTitle
|
self.deselectAllTitle = deselectAllTitle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
try container.encode(selectAllTitle, forKey: .selectAllTitle)
|
try container.encode(selectAllTitle, forKey: .selectAllTitle)
|
||||||
@ -50,16 +50,23 @@ public class SelectAllBoxesBehaviorModel: PageBehaviorModelProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Selects all the control models presented on a page.
|
/// Selects all the control models presented on a page.
|
||||||
public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior {
|
public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior, PageMoleculeTransformationBehavior {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
/// Status of the select all behavior. Initially false as the action has not been enaged.
|
/// Status of the select all behavior. Initially false as the action has not been enaged.
|
||||||
var selectAllState = false
|
var didSelectAllState = false
|
||||||
|
|
||||||
|
/// Reference to the general PageBehaviorModel.
|
||||||
var model: PageBehaviorModelProtocol
|
var model: PageBehaviorModelProtocol
|
||||||
|
|
||||||
|
/// Dictionary of KVOs to observing the selected property of each `SelectableMoleculeModel`.
|
||||||
|
private var observers = [String: NSKeyValueObservation?]()
|
||||||
|
|
||||||
|
/// A store representing the values of the `SelectableMoleculeModel`.
|
||||||
|
private var valuesMirror = [String: Bool]()
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Delegate
|
// MARK: - Delegate
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -75,6 +82,45 @@ public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior {
|
|||||||
self.delegate = delegateObject
|
self.delegate = delegateObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject) {
|
||||||
|
|
||||||
|
let selectableModels: [SelectableMoleculeModel] = rootMolecules.allMoleculesOfType()
|
||||||
|
|
||||||
|
guard !selectableModels.isEmpty else { return }
|
||||||
|
|
||||||
|
for model in selectableModels {
|
||||||
|
if let checkboxModel = model as? CheckboxModel, let key = checkboxModel.fieldKey {
|
||||||
|
|
||||||
|
valuesMirror[key] = checkboxModel.checked
|
||||||
|
|
||||||
|
observers[key] = checkboxModel.observe(\.checked, options: [.new]) { [weak self] model, change in
|
||||||
|
guard let self = self,
|
||||||
|
let isChecked = change.newValue,
|
||||||
|
let key = model.fieldKey
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
self.valuesMirror[key] = isChecked
|
||||||
|
|
||||||
|
// If all are models are in the opposite state of the behavior, then realign.
|
||||||
|
if self.selectAllIsMisaligned() {
|
||||||
|
self.realignPageBehavior(asSelectAll: true)
|
||||||
|
|
||||||
|
} else if self.deselectAllIsMisaligned() {
|
||||||
|
self.realignPageBehavior(asSelectAll: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Deinit
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
observers.values.forEach { $0?.invalidate() }
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Custom Action
|
// MARK: - Custom Action
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -91,25 +137,45 @@ public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior {
|
|||||||
// Verify we have the correct action type and necessary values.
|
// Verify we have the correct action type and necessary values.
|
||||||
guard actionType == "selectAllBoxes",
|
guard actionType == "selectAllBoxes",
|
||||||
let selectableModels: [SelectableMoleculeModel] = delegate?.moleculeDelegate?.getRootMolecules().allMoleculesOfType(),
|
let selectableModels: [SelectableMoleculeModel] = delegate?.moleculeDelegate?.getRootMolecules().allMoleculesOfType(),
|
||||||
!selectableModels.isEmpty,
|
!selectableModels.isEmpty
|
||||||
let model = model as? SelectAllBoxesBehaviorModel
|
|
||||||
else { return false }
|
else { return false }
|
||||||
|
|
||||||
// Flip the selected state of the behavior.
|
// Flip the selected state of the behavior.
|
||||||
selectAllState.toggle()
|
didSelectAllState.toggle()
|
||||||
|
|
||||||
// Iterate through selectable molecules.
|
// Iterate through selectable molecules.
|
||||||
for selectableModel in selectableModels {
|
for selectableModel in selectableModels {
|
||||||
if toSelect(model: selectableModel) || toDeselect(model: selectableModel) {
|
if toSelect(model: selectableModel) || toDeselect(model: selectableModel) {
|
||||||
selectableModel.select(as: selectAllState)
|
selectableModel.select(as: didSelectAllState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get title to update the nav button title.
|
updatePageNavigationUI()
|
||||||
let navButtonTitle: String? = selectAllState ? model.deselectAllTitle : model.selectAllTitle
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Methods
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
/// In the event that the user manually selects or deselects all `SelectableMoleculeModel`
|
||||||
|
/// the behavior will need to reflect the inverse of its previously expected action.
|
||||||
|
/// Initiates the navigation and page behavior realignment
|
||||||
|
/// - Parameter asSelectAll: The actual value didSelectAllState ought to be.
|
||||||
|
func realignPageBehavior(asSelectAll: Bool) {
|
||||||
|
didSelectAllState = asSelectAll
|
||||||
|
updatePageNavigationUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the navigation UI to correctly reflect the behavior's state.
|
||||||
|
func updatePageNavigationUI() {
|
||||||
|
|
||||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
guard let model = model as? SelectAllBoxesBehaviorModel else { return }
|
||||||
guard let controller = self.delegate?.moleculeDelegate as? ViewController else { return }
|
|
||||||
|
let navButtonTitle: String? = didSelectAllState ? model.deselectAllTitle : model.selectAllTitle
|
||||||
|
|
||||||
|
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
|
||||||
|
guard let controller = self?.delegate?.moleculeDelegate as? ViewController else { return }
|
||||||
controller.handleNewDataAndUpdateUI()
|
controller.handleNewDataAndUpdateUI()
|
||||||
|
|
||||||
if MVMCoreUIUtility.getCurrentVisibleController() == controller {
|
if MVMCoreUIUtility.getCurrentVisibleController() == controller {
|
||||||
@ -117,21 +183,31 @@ public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior {
|
|||||||
controller.manager?.refreshNavigationUI()
|
controller.manager?.refreshNavigationUI()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
return true
|
|
||||||
|
/// Convenience function for readability to confirmt he state of the behavior.
|
||||||
|
/// - Returns: Boolean indicating that the behavior's `didSelectAllState` is false while all model values are true (selected).
|
||||||
|
func selectAllIsMisaligned() -> Bool {
|
||||||
|
!didSelectAllState && valuesMirror.values.allSatisfy { $0 == true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for readability to confirmt he state of the behavior.
|
||||||
|
/// - Returns: Boolean indicating that the behavior's `didSelectAllState` is true while all model values are false (deselected).
|
||||||
|
func deselectAllIsMisaligned() -> Bool {
|
||||||
|
didSelectAllState && valuesMirror.values.allSatisfy { $0 == false }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function making it easier to read if a current selectable model should be acted on.
|
/// Convenience function making it easier to read if a current selectable model should be acted on.
|
||||||
/// - Parameter model: A model object assined to the SelectableModel protocol
|
/// - Parameter model: A model object assined to the SelectableModel protocol
|
||||||
/// - Returns: Boolean determining if the passed model should be selected.
|
/// - Returns: Boolean determining if the passed model should be selected.
|
||||||
func toSelect(model: SelectableMoleculeModel) -> Bool {
|
func toSelect(model: SelectableMoleculeModel) -> Bool {
|
||||||
selectAllState && !model.selectedValue
|
didSelectAllState && !model.selectedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function making it easier to read if a current selectable model should be acted on.
|
/// Convenience function making it easier to read if a current selectable model should be acted on.
|
||||||
/// - Parameter model: A model object assined to the SelectableModel protocol
|
/// - Parameter model: A model object assined to the SelectableModel protocol
|
||||||
/// - Returns: Boolean determining if the passed model should be deselected.
|
/// - Returns: Boolean determining if the passed model should be deselected.
|
||||||
func toDeselect(model: SelectableMoleculeModel) -> Bool {
|
func toDeselect(model: SelectableMoleculeModel) -> Bool {
|
||||||
!selectAllState && model.selectedValue
|
!didSelectAllState && model.selectedValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user