latest refactoring of dropdown
This commit is contained in:
parent
bef4348d2b
commit
68042be1d0
@ -12,26 +12,39 @@ public typealias TextFieldAndPickerDelegate = (UITextFieldDelegate & UIPickerVie
|
|||||||
|
|
||||||
|
|
||||||
open class ItemDropdownEntryField: BaseDropdownEntryField {
|
open class ItemDropdownEntryField: BaseDropdownEntryField {
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Outlets
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
open var pickerView: UIPickerView?
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
open var pickerData: [String] = []
|
/// Datasource of the picker view.
|
||||||
open var pickerView: UIPickerView?
|
open var pickerComponents: [[String]] {
|
||||||
|
dropdownModel?.options ?? [[]]
|
||||||
|
}
|
||||||
|
|
||||||
/// When selecting for first responder, allow initial selected value to appear in empty text field.
|
/// When selecting for first responder, allow initial selected value to appear in empty text field.
|
||||||
public var setInitialValueInTextField = true
|
public var setInitialValueInTextField = true
|
||||||
|
|
||||||
/// Closure passed here will run as picker changes items.
|
/// Closure passed here will run as picker changes items.
|
||||||
public var observeDropdownChange: ((String, String)->())?
|
public var observeDropdownChange: ((String, String) -> ())?
|
||||||
|
|
||||||
/// Closure passed here will run upon dismissing the selection picker.
|
/// Closure passed here will run upon dismissing the selection picker.
|
||||||
public var observeDropdownSelection: ((String)->())?
|
public var observeDropdownSelection: ((String) -> ())?
|
||||||
|
|
||||||
public var itemDropdownEntryFieldModel: ItemDropdownEntryFieldModel? {
|
public var dropdownModel: ItemDropdownEntryFieldModel? {
|
||||||
model as? ItemDropdownEntryFieldModel
|
model as? ItemDropdownEntryFieldModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The number of components available
|
||||||
|
public var componentCount: Int {
|
||||||
|
pickerComponents.count
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Initializers
|
// MARK: - Initializers
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -44,9 +57,9 @@ open class ItemDropdownEntryField: BaseDropdownEntryField {
|
|||||||
self.init(frame: .zero)
|
self.init(frame: .zero)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public convenience init(pickerData: [String]) {
|
@objc public convenience init(pickerComponents: [[String]]) {
|
||||||
self.init(frame: .zero)
|
self.init(frame: .zero)
|
||||||
self.pickerData = pickerData
|
self.dropdownModel?.options = pickerComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc required public init?(coder: NSCoder) {
|
@objc required public init?(coder: NSCoder) {
|
||||||
@ -76,43 +89,60 @@ open class ItemDropdownEntryField: BaseDropdownEntryField {
|
|||||||
pickerView?.dataSource = delegate
|
pickerView?.dataSource = delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the textField with the first value of the available picker data.
|
||||||
@objc private func setInitialValueFromPicker() {
|
@objc private func setInitialValueFromPicker() {
|
||||||
|
|
||||||
guard !pickerData.isEmpty else { return }
|
guard setInitialValueInTextField,
|
||||||
|
!pickerComponents.isEmpty,
|
||||||
|
let rowText = dropdownModel?.selectedRowText
|
||||||
|
else { return }
|
||||||
|
|
||||||
if setInitialValueInTextField, let pickerIndex = pickerView?.selectedRow(inComponent: 0) {
|
observeDropdownChange?(text ?? "", rowText)
|
||||||
observeDropdownChange?(text ?? "", pickerData[pickerIndex])
|
text = rowText
|
||||||
text = pickerData[pickerIndex]
|
|
||||||
itemDropdownEntryFieldModel?.selectedIndex = pickerIndex
|
for component in 0..<componentCount {
|
||||||
|
let pickerIndex = pickerView?.selectedRow(inComponent: component)
|
||||||
|
dropdownModel?.selectedIndicies[component] = pickerIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - TextField Observation
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
/// Observing action of the textfield for initial interaction with the picker.
|
||||||
@objc override func startEditing() {
|
@objc override func startEditing() {
|
||||||
super.startEditing()
|
super.startEditing()
|
||||||
|
|
||||||
setInitialValueFromPicker()
|
setInitialValueFromPicker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Observing action for when the user has ended inputting with the picker.
|
||||||
@objc override func endInputing() {
|
@objc override func endInputing() {
|
||||||
super.endInputing()
|
super.endInputing()
|
||||||
|
|
||||||
guard !pickerData.isEmpty else { return }
|
guard !pickerComponents.isEmpty,
|
||||||
|
let rowText = dropdownModel?.selectedRowText
|
||||||
|
else { return }
|
||||||
|
|
||||||
if let pickerIndex = pickerView?.selectedRow(inComponent: 0) {
|
observeDropdownSelection?(rowText)
|
||||||
observeDropdownSelection?(pickerData[pickerIndex])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - MoleculeViewProtocol
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||||
super.set(with: model, delegateObject, additionalData)
|
super.set(with: model, delegateObject, additionalData)
|
||||||
|
|
||||||
guard let model = model as? ItemDropdownEntryFieldModel else { return }
|
guard let model = model as? ItemDropdownEntryFieldModel else { return }
|
||||||
|
|
||||||
pickerData = model.options
|
|
||||||
setPickerDelegates(delegate: self)
|
setPickerDelegates(delegate: self)
|
||||||
|
|
||||||
if let pickerView = pickerView, let index = model.selectedIndex {
|
if let pickerView = pickerView {
|
||||||
self.pickerView(pickerView, didSelectRow: index, inComponent: 0)
|
for (component, index) in model.selectedIndicies {
|
||||||
|
self.pickerView(pickerView, didSelectRow: index, inComponent: component)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,24 +150,31 @@ open class ItemDropdownEntryField: BaseDropdownEntryField {
|
|||||||
// MARK:- Picker Delegate
|
// MARK:- Picker Delegate
|
||||||
extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource {
|
extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource {
|
||||||
|
|
||||||
@objc public func numberOfComponents(in pickerView: UIPickerView) -> Int { 1 }
|
@objc public func numberOfComponents(in pickerView: UIPickerView) -> Int { componentCount }
|
||||||
|
|
||||||
@objc public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
@objc public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||||
pickerData.count
|
pickerComponents[component].count
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
@objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
||||||
guard !pickerData.isEmpty else { return nil }
|
|
||||||
|
|
||||||
return pickerData[row]
|
guard !pickerComponents.isEmpty,
|
||||||
|
!pickerComponents[component].isEmpty
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
return pickerComponents[component][row]
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
@objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
||||||
guard !pickerData.isEmpty else { return }
|
|
||||||
|
|
||||||
observeDropdownChange?(text ?? "", pickerData[row])
|
guard !pickerComponents.isEmpty,
|
||||||
text = pickerData[row]
|
!pickerComponents[component].isEmpty,
|
||||||
itemDropdownEntryFieldModel?.selectedIndex = row
|
let rowText = dropdownModel?.selectedRowText
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
observeDropdownChange?(text ?? "", rowText)
|
||||||
|
text = rowText
|
||||||
|
dropdownModel?.selectedIndicies[component] = row
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,16 +13,46 @@
|
|||||||
|
|
||||||
public override class var identifier: String { "dropDown" }
|
public override class var identifier: String { "dropDown" }
|
||||||
|
|
||||||
public var options: [String] = []
|
public var options: [[String]] = [[]]
|
||||||
|
public var selectedIndicies: [Int: Int] = [:]
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Here for backwards compatibility for when this options was a single array.")
|
||||||
public var selectedIndex: Int?
|
public var selectedIndex: Int?
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Validation
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
public override func formFieldValue() -> AnyHashable? {
|
public override func formFieldValue() -> AnyHashable? {
|
||||||
guard !options.isEmpty,
|
|
||||||
let index = selectedIndex
|
|
||||||
else { return nil }
|
|
||||||
|
|
||||||
return options[index]
|
guard !options.isEmpty && !selectedIndicies.isEmpty else { return nil }
|
||||||
|
|
||||||
|
return selectedRowText
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A string of the picker row concatenated by whitespace.
|
||||||
|
public var selectedRowText: String {
|
||||||
|
|
||||||
|
var text = ""
|
||||||
|
|
||||||
|
for i in 0..<options.count {
|
||||||
|
let pickerIndex = selectedIndicies[i] ?? 0
|
||||||
|
text += options[i][pickerIndex] + (i == options.count - 1 ? "" : " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
public var selectedIndiciesArray: [Int] {
|
||||||
|
|
||||||
|
var indexArray: [Int] = []
|
||||||
|
|
||||||
|
for i in 0..<selectedIndicies.count {
|
||||||
|
guard let selectIndex = selectedIndicies[i] else { return [] }
|
||||||
|
indexArray.append(selectIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexArray
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -31,7 +61,8 @@
|
|||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case options
|
case options
|
||||||
case selectedIndex
|
case selectedIndex // Deprecated: for backwards compatability
|
||||||
|
case selectedIndicies
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -42,14 +73,25 @@
|
|||||||
try super.init(from: decoder)
|
try super.init(from: decoder)
|
||||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
options = try typeContainer.decode([String].self, forKey: .options)
|
if let singleOptions = try? typeContainer.decodeIfPresent([String].self, forKey: .options) {
|
||||||
|
options = [singleOptions]
|
||||||
if let selectedIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) {
|
} else {
|
||||||
self.selectedIndex = selectedIndex
|
options = try typeContainer.decode([[String]].self, forKey: .options)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let index = selectedIndex {
|
if let selectedIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) {
|
||||||
|
self.selectedIndicies[0] = selectedIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
if let index = self.selectedIndicies[0] {
|
||||||
baseValue = options.indices.contains(index) ? options[index] : nil
|
baseValue = options.indices.contains(index) ? options[index] : nil
|
||||||
|
|
||||||
|
} else if let indicies = try typeContainer.decodeIfPresent([Int].self, forKey: .selectedIndicies) {
|
||||||
|
for (component, index) in indicies.enumerated() {
|
||||||
|
self.selectedIndicies[component] = index
|
||||||
|
}
|
||||||
|
|
||||||
|
baseValue = selectedRowText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,5 +100,6 @@
|
|||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
try container.encode(options, forKey: .options)
|
try container.encode(options, forKey: .options)
|
||||||
try container.encodeIfPresent(options, forKey: .selectedIndex)
|
try container.encodeIfPresent(options, forKey: .selectedIndex)
|
||||||
|
try container.encode(selectedIndiciesArray, forKey: .selectedIndicies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -329,6 +329,11 @@ import UIKit
|
|||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override the preset keyboard set in type.
|
||||||
|
if let keyboardType = model.assignKeyboardType() {
|
||||||
|
textField.keyboardType = keyboardType
|
||||||
|
}
|
||||||
|
|
||||||
textField.accessibilityIdentifier = model.accessibilityIdentifier
|
textField.accessibilityIdentifier = model.accessibilityIdentifier
|
||||||
uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate
|
uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate
|
||||||
observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate
|
observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate
|
||||||
|
|||||||
@ -32,8 +32,61 @@
|
|||||||
public var enabledTextColor: Color = Color(uiColor: .mvmBlack)
|
public var enabledTextColor: Color = Color(uiColor: .mvmBlack)
|
||||||
public var disabledTextColor: Color = Color(uiColor: .mvmCoolGray3)
|
public var disabledTextColor: Color = Color(uiColor: .mvmCoolGray3)
|
||||||
public var textAlignment: NSTextAlignment = .left
|
public var textAlignment: NSTextAlignment = .left
|
||||||
|
public var keyboardOverride: String?
|
||||||
public var type: EntryType?
|
public var type: EntryType?
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Methods
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
/// Reads the keyboardOverride set by server and returns the keyboard type associated with it.
|
||||||
|
func assignKeyboardType() -> UIKeyboardType? {
|
||||||
|
|
||||||
|
guard let keyboardType = keyboardOverride else { return nil }
|
||||||
|
|
||||||
|
var typeInt = 0
|
||||||
|
|
||||||
|
switch keyboardType {
|
||||||
|
case "asciiCapable":
|
||||||
|
typeInt = 1 // Displays a keyboard which can enter ASCII characters
|
||||||
|
|
||||||
|
case "numbersAndPunctuation":
|
||||||
|
typeInt = 2 // Numbers and assorted punctuation.
|
||||||
|
|
||||||
|
case "URL":
|
||||||
|
typeInt = 3 // A type optimized for URL entry (shows . / .com prominently).
|
||||||
|
|
||||||
|
case "numberPad":
|
||||||
|
typeInt = 4 // A number pad with locale-appropriate digits (0-9, ۰-۹, ०-९, etc.). Suitable for PIN entry.
|
||||||
|
|
||||||
|
case "phonePad":
|
||||||
|
typeInt = 5 // A phone pad (1-9, *, 0, #, with letters under the numbers).
|
||||||
|
|
||||||
|
case "namePhonePad":
|
||||||
|
typeInt = 6 // A type optimized for entering a person's name or phone number.
|
||||||
|
|
||||||
|
case "emailAddress":
|
||||||
|
typeInt = 7 // A type optimized for multiple email address entry (shows space @ . prominently).
|
||||||
|
|
||||||
|
case "decimalPad":
|
||||||
|
typeInt = 8 // A number pad with a decimal point.
|
||||||
|
|
||||||
|
case "twitter":
|
||||||
|
typeInt = 9 // A type optimized for twitter text entry (easy access to @ #)
|
||||||
|
|
||||||
|
case "webSearch":
|
||||||
|
typeInt = 10 // A default keyboard type with URL-oriented addition (shows space . prominently).
|
||||||
|
|
||||||
|
case "asciiCapableNumberPad":
|
||||||
|
typeInt = 11 // A number pad (0-9) that will always be ASCII digits.
|
||||||
|
|
||||||
|
default:
|
||||||
|
typeInt = 0 // Default type for the current input method.
|
||||||
|
}
|
||||||
|
|
||||||
|
return UIKeyboardType(rawValue: typeInt)
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Keys
|
// MARK: - Keys
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -43,6 +96,7 @@
|
|||||||
case textAlignment
|
case textAlignment
|
||||||
case enabledTextColor
|
case enabledTextColor
|
||||||
case disabledTextColor
|
case disabledTextColor
|
||||||
|
case keyboardOverride
|
||||||
case type
|
case type
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +109,7 @@
|
|||||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
placeholder = try typeContainer.decodeIfPresent(String.self, forKey: .placeholder)
|
placeholder = try typeContainer.decodeIfPresent(String.self, forKey: .placeholder)
|
||||||
|
keyboardOverride = try typeContainer.decodeIfPresent(String.self, forKey: .keyboardOverride)
|
||||||
type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type)
|
type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type)
|
||||||
|
|
||||||
if let enabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledTextColor) {
|
if let enabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledTextColor) {
|
||||||
@ -76,6 +131,7 @@
|
|||||||
try container.encodeIfPresent(placeholder, forKey: .placeholder)
|
try container.encodeIfPresent(placeholder, forKey: .placeholder)
|
||||||
try container.encodeIfPresent(textAlignment, forKey: .textAlignment)
|
try container.encodeIfPresent(textAlignment, forKey: .textAlignment)
|
||||||
try container.encodeIfPresent(type, forKey: .type)
|
try container.encodeIfPresent(type, forKey: .type)
|
||||||
|
try container.encodeIfPresent(keyboardOverride, forKey: .keyboardOverride)
|
||||||
try container.encode(enabledTextColor, forKey: .enabledTextColor)
|
try container.encode(enabledTextColor, forKey: .enabledTextColor)
|
||||||
try container.encode(disabledTextColor, forKey: .disabledTextColor)
|
try container.encode(disabledTextColor, forKey: .disabledTextColor)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import UIKit
|
|||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
let dropDown = ItemDropdownEntryField()
|
let dropDown = ItemDropdownEntryField()
|
||||||
var delegateObject: MVMCoreUIDelegateObject?
|
var delegateObject: MVMCoreUIDelegateObject?
|
||||||
var previousIndex = NSNotFound
|
var previousIndex = NSNotFound
|
||||||
@ -22,16 +23,16 @@ import UIKit
|
|||||||
|
|
||||||
override public func setupView() {
|
override public func setupView() {
|
||||||
super.setupView()
|
super.setupView()
|
||||||
|
|
||||||
addMolecule(dropDown)
|
addMolecule(dropDown)
|
||||||
dropDown.observeDropdownChange = { [weak self] oldValue, newValue in
|
dropDown.observeDropdownChange = { [weak self] oldValue, newValue in
|
||||||
|
|
||||||
guard newValue != oldValue,
|
guard oldValue != newValue,
|
||||||
let self = self,
|
let self = self,
|
||||||
let index = self.dropDown.pickerData.firstIndex(of: newValue),
|
let index = self.dropDown.pickerComponents.first?.firstIndex(of: newValue),
|
||||||
let model = self.listItemModel as? DropDownListItemModel
|
let model = self.listItemModel as? DropDownListItemModel
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
if self.previousIndex != NSNotFound {
|
if self.previousIndex != NSNotFound {
|
||||||
self.delegateObject?.moleculeDelegate?.removeMolecules(model.molecules[self.previousIndex], animation: .fade)
|
self.delegateObject?.moleculeDelegate?.removeMolecules(model.molecules[self.previousIndex], animation: .fade)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user