Merge branch 'feature/keyboard_responsiveness' into 'develop'

TextView Keyboard responsiveness

See merge request BPHV_MIPS/mvm_core_ui!549
This commit is contained in:
Pfeil, Scott Robert 2020-07-28 14:01:49 -04:00
commit b428d76129
2 changed files with 129 additions and 53 deletions

View File

@ -6,9 +6,12 @@
// Copyright © 2020 Verizon Wireless. All rights reserved. // Copyright © 2020 Verizon Wireless. All rights reserved.
// //
import Foundation
open class ScrollingViewController: ViewController { open class ScrollingViewController: ViewController {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var dismissKeyboardTapGesture: UITapGestureRecognizer? public var dismissKeyboardTapGesture: UITapGestureRecognizer?
@IBOutlet public var scrollView: UIScrollView! @IBOutlet public var scrollView: UIScrollView!
public var contentView: UIView? public var contentView: UIView?
@ -17,6 +20,10 @@ open class ScrollingViewController: ViewController {
private var keyboardIsShowing = false private var keyboardIsShowing = false
private var preKeyboardContentInset: UIEdgeInsets? private var preKeyboardContentInset: UIEdgeInsets?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public init(with scrollView: UIScrollView) { public init(with scrollView: UIScrollView) {
self.scrollView = scrollView self.scrollView = scrollView
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
@ -30,7 +37,10 @@ open class ScrollingViewController: ViewController {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
} }
// MARK: - View Life Cycle //--------------------------------------------------
// MARK: - View Lifecycle
//--------------------------------------------------
open override func viewDidLoad() { open override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -41,7 +51,7 @@ open class ScrollingViewController: ViewController {
scrollView.alwaysBounceVertical = false scrollView.alwaysBounceVertical = false
scrollView.delegate = self scrollView.delegate = self
} }
open override func viewDidLayoutSubviews() { open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews() super.viewDidLayoutSubviews()
view.setNeedsUpdateConstraints() view.setNeedsUpdateConstraints()
@ -53,7 +63,10 @@ open class ScrollingViewController: ViewController {
registerForKeyboardNotifications() registerForKeyboardNotifications()
} }
//--------------------------------------------------
// MARK: - Keyboard Handling // MARK: - Keyboard Handling
//--------------------------------------------------
open func registerForKeyboardNotifications() { open func registerForKeyboardNotifications() {
if !keyboardNotificationsAdded { if !keyboardNotificationsAdded {
keyboardNotificationsAdded = true keyboardNotificationsAdded = true
@ -87,16 +100,19 @@ open class ScrollingViewController: ViewController {
@objc open func keyboardWillBeHidden(notification: Notification) { @objc open func keyboardWillBeHidden(notification: Notification) {
keyboardIsShowing = false keyboardIsShowing = false
// Disables the tap gesture. // Disables the tap gesture.
dismissKeyboardTapGesture?.isEnabled = false dismissKeyboardTapGesture?.isEnabled = false
MVMCoreUIUtility.setScrollViewInsetForKeyboardHide(notification, scrollView: scrollView, viewController: self, contentInset: preKeyboardContentInset ?? scrollView.contentInset) MVMCoreUIUtility.setScrollViewInsetForKeyboardHide(notification, scrollView: scrollView, viewController: self, contentInset: preKeyboardContentInset ?? scrollView.contentInset)
} }
open func rectToScrollToWhenKeyboardPopsUp() -> CGRect? { open func rectToScrollToWhenKeyboardPopsUp() -> CGRect? {
guard let field = selectedField, guard let field = selectedField,
let parent = selectedField?.superview else { return nil } let parent = selectedField?.superview
else { return nil }
return scrollView.convert(field.frame, from: parent) return scrollView.convert(field.frame, from: parent)
} }
} }

View File

@ -8,7 +8,12 @@
import UIKit import UIKit
@objc open class ViewController: UIViewController, MVMCoreViewControllerProtocol, MVMCoreViewManagerViewControllerProtocol, MoleculeDelegateProtocol, FormHolderProtocol, MVMCoreActionDelegateProtocol, MVMCoreLoadDelegateProtocol, UITextFieldDelegate, UITextViewDelegate, ObservingTextFieldDelegate, MVMCoreUIDetailViewProtocol { @objc open class ViewController: UIViewController, MVMCoreViewControllerProtocol, MVMCoreViewManagerViewControllerProtocol, MoleculeDelegateProtocol, FormHolderProtocol, MVMCoreActionDelegateProtocol, MVMCoreLoadDelegateProtocol, UITextFieldDelegate, UITextViewDelegate, ObservingTextFieldDelegate, MVMCoreUIDetailViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@objc public var pageType: String? @objc public var pageType: String?
@objc public var loadObject: MVMCoreLoadObject? @objc public var loadObject: MVMCoreLoadObject?
public var pageModel: MVMControllerModelProtocol? public var pageModel: MVMControllerModelProtocol?
@ -33,16 +38,21 @@ import UIKit
private var previousScreenSize = CGSize.zero private var previousScreenSize = CGSize.zero
public var selectedField: UIView? public var selectedField: UIView?
/// Checks if the screen width has changed /// Checks if the screen width has changed
open func screenSizeChanged() -> Bool { open func screenSizeChanged() -> Bool {
return !MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1) return !MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1)
} }
//--------------------------------------------------
// MARK: - Response handling // MARK: - Response handling
//--------------------------------------------------
open func observeForResponseJSONUpdates() { open func observeForResponseJSONUpdates() {
guard !observingForResponses, guard !observingForResponses,
(pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0) else { return } (pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0)
else { return }
observingForResponses = true observingForResponses = true
NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil)
} }
@ -52,7 +62,7 @@ import UIKit
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil)
observingForResponses = false observingForResponses = false
} }
open func pagesToListenFor() -> [String]? { open func pagesToListenFor() -> [String]? {
guard let pageType = loadObject?.pageType else { return nil } guard let pageType = loadObject?.pageType else { return nil }
return [pageType] return [pageType]
@ -69,7 +79,9 @@ import UIKit
let pageType = pagesToListenFor()?.first(where: { (pageTypeListened) -> Bool in let pageType = pagesToListenFor()?.first(where: { (pageTypeListened) -> Bool in
guard let page = pagesLoaded.optionalDictionaryForKey(pageTypeListened), guard let page = pagesLoaded.optionalDictionaryForKey(pageTypeListened),
let pageType = page.optionalStringForKey(KeyPageType), let pageType = page.optionalStringForKey(KeyPageType),
pageType == pageTypeListened else { return false } pageType == pageTypeListened
else { return false }
return true return true
}) { }) {
newData = true newData = true
@ -90,6 +102,7 @@ import UIKit
} }
guard newData else { return } guard newData else { return }
do { do {
try parsePageJSON() try parsePageJSON()
MVMCoreDispatchUtility.performBlock(onMainThread: { MVMCoreDispatchUtility.performBlock(onMainThread: {
@ -134,8 +147,10 @@ import UIKit
switch (registryError) { switch (registryError) {
case .decoderErrorModelNotMapped(let identifier, let codingKey, let codingPath) where identifier != nil && codingKey != nil && codingPath != nil: case .decoderErrorModelNotMapped(let identifier, let codingKey, let codingPath) where identifier != nil && codingKey != nil && codingPath != nil:
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Model identifier \"\(identifier!)\" is not mapped for \"\(codingKey!.stringValue)\" @ \(codingPath!.map { return $0.stringValue })") MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Model identifier \"\(identifier!)\" is not mapped for \"\(codingKey!.stringValue)\" @ \(codingPath!.map { return $0.stringValue })")
case .decoderErrorObjectNotPresent(let codingKey, codingPath: let codingPath): case .decoderErrorObjectNotPresent(let codingKey, codingPath: let codingPath):
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Required model \"\(codingKey.stringValue)\" was not found @ \(codingPath.map { return $0.stringValue })") MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Required model \"\(codingKey.stringValue)\" was not found @ \(codingPath.map { return $0.stringValue })")
default: default:
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Registry error: \(registryError)") MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Registry error: \(registryError)")
} }
@ -144,34 +159,39 @@ import UIKit
switch (decodingError) { switch (decodingError) {
case .keyNotFound(let codingKey, let context): case .keyNotFound(let codingKey, let context):
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Key \(codingKey.stringValue) was not found @ \(context.codingPath.map { return $0.stringValue })") MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Key \(codingKey.stringValue) was not found @ \(context.codingPath.map { return $0.stringValue })")
case .valueNotFound(_, let context): case .valueNotFound(_, let context):
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Value not found @ \(context.codingPath.map { return $0.stringValue })") MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Value not found @ \(context.codingPath.map { return $0.stringValue })")
case .typeMismatch(_, let context): case .typeMismatch(_, let context):
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Type mismatch @ \(context.codingPath.map { return $0.stringValue })") MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Type mismatch @ \(context.codingPath.map { return $0.stringValue })")
case .dataCorrupted(let context): case .dataCorrupted(let context):
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Data corrupted @ \(context.codingPath.map { return $0.stringValue })") MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Data corrupted @ \(context.codingPath.map { return $0.stringValue })")
@unknown default: @unknown default:
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: \(parsingError)") MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: \(parsingError)")
} }
} }
} }
open func parsePageJSON() throws { open func parsePageJSON() throws { }
}
open class func verifyRequiredModulesLoaded(for loadObject: MVMCoreLoadObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject>) -> Bool { open class func verifyRequiredModulesLoaded(for loadObject: MVMCoreLoadObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject>) -> Bool {
guard let pageType = loadObject?.pageType, var modulesRequired = MVMCoreUIViewControllerMappingObject.shared()?.modulesRequired(forPageType: pageType), guard let pageType = loadObject?.pageType,
!modulesRequired.isEmpty else { return true } var modulesRequired = MVMCoreUIViewControllerMappingObject.shared()?.modulesRequired(forPageType: pageType),
!modulesRequired.isEmpty
else { return true }
guard let loadedModules = loadObject?.modulesJSON else { return false } guard let loadedModules = loadObject?.modulesJSON else { return false }
for case let key as String in Array(loadedModules.keys) { for case let key as String in Array(loadedModules.keys) {
guard modulesRequired.count > 0 else { break } guard modulesRequired.count > 0 else { break }
if let index = modulesRequired.firstIndex(where: {($0 as? String) == key}) { if let index = modulesRequired.firstIndex(where: {($0 as? String) == key}) {
modulesRequired.remove(at: index) modulesRequired.remove(at: index)
} }
} }
guard modulesRequired.count == 0 else { guard modulesRequired.count == 0 else {
if let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorCritical), messageToLog: modulesRequired.description, code: ErrorCode.requiredModuleNotPresent.rawValue, domain: ErrorDomainNative, location: MVMCoreLoadHandler.sharedGlobal()?.errorLocation(forRequest: loadObject!)) { if let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorCritical), messageToLog: modulesRequired.description, code: ErrorCode.requiredModuleNotPresent.rawValue, domain: ErrorDomainNative, location: MVMCoreLoadHandler.sharedGlobal()?.errorLocation(forRequest: loadObject!)) {
error.pointee = errorObject error.pointee = errorObject
@ -194,14 +214,14 @@ import UIKit
navigationModel.title = pageModel?.screenHeading navigationModel.title = pageModel?.screenHeading
return navigationModel return navigationModel
} }
/// Processes any new data. Called after the page is loaded the first time and on response updates for this page, /// Processes any new data. Called after the page is loaded the first time and on response updates for this page,
open func handleNewData() { open func handleNewData() {
if formValidator == nil { if formValidator == nil {
let rules = pageModel?.formRules let rules = pageModel?.formRules
formValidator = FormValidator(rules) formValidator = FormValidator(rules)
} }
if let backgroundColor = pageModel?.backgroundColor { if let backgroundColor = pageModel?.backgroundColor {
view.backgroundColor = backgroundColor.uiColor view.backgroundColor = backgroundColor.uiColor
} }
@ -210,7 +230,9 @@ import UIKit
setNavigationItem() setNavigationItem()
} }
//--------------------------------------------------
// MARK: - Navigation Item (Move to model base) // MARK: - Navigation Item (Move to model base)
//--------------------------------------------------
open func getNavigationModel() -> NavigationItemModelProtocol? { open func getNavigationModel() -> NavigationItemModelProtocol? {
// TODO: remove legacy. Temporary, convert legacy to navigation model. // TODO: remove legacy. Temporary, convert legacy to navigation model.
@ -224,7 +246,8 @@ import UIKit
/// Sets the navigation item for this view controller. /// Sets the navigation item for this view controller.
open func setNavigationItem() { open func setNavigationItem() {
guard let navigationItemModel = getNavigationModel(), guard let navigationItemModel = getNavigationModel(),
let navigationController = navigationController else { return } let navigationController = navigationController
else { return }
// We additionally want our left items // We additionally want our left items
navigationItem.leftItemsSupplementBackButton = true navigationItem.leftItemsSupplementBackButton = true
@ -238,8 +261,8 @@ import UIKit
let viewController = manager ?? self let viewController = manager ?? self
guard let navigationItemModel = getNavigationModel(), guard let navigationItemModel = getNavigationModel(),
let navigationController = viewController.navigationController else { let navigationController = viewController.navigationController else {
MVMCoreUISession.sharedGlobal()?.splitViewController?.parent?.setNeedsStatusBarAppearanceUpdate() MVMCoreUISession.sharedGlobal()?.splitViewController?.parent?.setNeedsStatusBarAppearanceUpdate()
return return
} }
// Utilize helper function to set the split view and navigation item state. // Utilize helper function to set the split view and navigation item state.
@ -265,33 +288,35 @@ import UIKit
open func showRightPanelForScreenBeforeLaunchApp() -> Bool { open func showRightPanelForScreenBeforeLaunchApp() -> Bool {
return loadObject?.pageJSON?.lenientBoolForKey("showRightPanel") ?? false return loadObject?.pageJSON?.lenientBoolForKey("showRightPanel") ?? false
} }
// Eventually will be moved to separate button in navigation item model // Eventually will be moved to separate button in navigation item model
open func isOverridingRightButton() -> Bool { open func isOverridingRightButton() -> Bool {
guard let rightPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("rightPanelButtonLink") else { guard let rightPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("rightPanelButtonLink")
return false else { return false }
}
MVMCoreActionHandler.shared()?.handleAction(with: rightPanelLink, additionalData: nil, delegateObject: delegateObject()) MVMCoreActionHandler.shared()?.handleAction(with: rightPanelLink, additionalData: nil, delegateObject: delegateObject())
return true return true
} }
// Eventually will be moved to separate button in navigation item model // Eventually will be moved to separate button in navigation item model
open func isOverridingLeftButton() -> Bool { open func isOverridingLeftButton() -> Bool {
guard let leftPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("leftPanelButtonLink") else { guard let leftPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("leftPanelButtonLink")
return false else { return false }
}
MVMCoreActionHandler.shared()?.handleAction(with: leftPanelLink, additionalData: nil, delegateObject: delegateObject()) MVMCoreActionHandler.shared()?.handleAction(with: leftPanelLink, additionalData: nil, delegateObject: delegateObject())
return true return true
} }
// Eventually will be moved to Model // Eventually will be moved to Model
open func bottomProgress() -> Float? { open func bottomProgress() -> Float? {
guard let progressString = loadObject?.pageJSON?.optionalStringForKey(KeyProgressPercent), guard let progressString = loadObject?.pageJSON?.optionalStringForKey(KeyProgressPercent),
let progress = Float(progressString) else { return nil } let progress = Float(progressString)
return (progress / Float(100)) else { return nil }
return progress / Float(100)
} }
//--------------------------------------------------
// MARK: - TabBar // MARK: - TabBar
//--------------------------------------------------
open func updateTabBar() { open func updateTabBar() {
guard MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() == self, guard MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() == self,
let tabModel = pageModel as? TabPageModelProtocol else { return } let tabModel = pageModel as? TabPageModelProtocol else { return }
@ -301,28 +326,30 @@ import UIKit
} }
MVMCoreUISplitViewController.main()?.updateTabBarShowing(!tabModel.tabBarHidden) MVMCoreUISplitViewController.main()?.updateTabBarShowing(!tabModel.tabBarHidden)
} }
// MARK: - View lifecycle //--------------------------------------------------
// MARK: - View Lifecycle
//--------------------------------------------------
/// Called only once in viewDidLoad /// Called only once in viewDidLoad
open func initialLoad() { open func initialLoad() {
observeForResponseJSONUpdates() observeForResponseJSONUpdates()
} }
/// Called on screen size update. /// Called on screen size update.
open func updateViews() { open func updateViews() {
_ = formValidator?.validate() _ = formValidator?.validate()
} }
override open func viewDidLoad() { override open func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// Do any additional setup after loading the view. // Do any additional setup after loading the view.
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "View Controller Loaded : \(self)") MVMCoreLoggingHandler.logDebugMessage(withDelegate: "View Controller Loaded : \(self)")
// We use our own margins. // We use our own margins.
viewRespectsSystemMinimumLayoutMargins = false viewRespectsSystemMinimumLayoutMargins = false
// Presents from the bottom. // Presents from the bottom.
modalPresentationStyle = MVMCoreGetterUtility.isOnIPad() ? .formSheet : .overCurrentContext modalPresentationStyle = MVMCoreGetterUtility.isOnIPad() ? .formSheet : .overCurrentContext
@ -392,7 +419,7 @@ import UIKit
stopObservingForResponseJSONUpdates() stopObservingForResponseJSONUpdates()
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "View Controller Deallocated : \(self)") MVMCoreLoggingHandler.logDebugMessage(withDelegate: "View Controller Deallocated : \(self)")
} }
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask { open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return MVMCoreGetterUtility.isOnIPad() ? UIInterfaceOrientationMask.all : UIInterfaceOrientationMask.portrait return MVMCoreGetterUtility.isOnIPad() ? UIInterfaceOrientationMask.all : UIInterfaceOrientationMask.portrait
} }
@ -401,18 +428,22 @@ import UIKit
super.viewWillTransition(to: size, with: coordinator) super.viewWillTransition(to: size, with: coordinator)
// Updates the detail view width // Updates the detail view width
coordinator.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in coordinator.animate(alongsideTransition: { UIViewControllerTransitionCoordinatorContext in
}) { (UIViewControllerTransitionCoordinatorContext) in }) { UIViewControllerTransitionCoordinatorContext in
self.view.setNeedsLayout() self.view.setNeedsLayout()
} }
} }
//--------------------------------------------------
// MARK: - MVMCoreViewManagerViewControllerProtocol // MARK: - MVMCoreViewManagerViewControllerProtocol
//--------------------------------------------------
open func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) { open func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) {
pageShown() pageShown()
} }
//--------------------------------------------------
// MARK: - MVMCoreLoadDelegateProtocol // MARK: - MVMCoreLoadDelegateProtocol
//--------------------------------------------------
// TODO: Move this function out of here after architecture cleanup. // TODO: Move this function out of here after architecture cleanup.
open func loadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: (UIViewController & MVMCoreViewControllerProtocol)?, error: MVMCoreErrorObject?) { open func loadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: (UIViewController & MVMCoreViewControllerProtocol)?, error: MVMCoreErrorObject?) {
@ -433,8 +464,11 @@ import UIKit
}) })
} }
} }
//--------------------------------------------------
// MARK: - MVMCoreActionDelegateProtocol // MARK: - MVMCoreActionDelegateProtocol
//--------------------------------------------------
open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) { open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
formValidator?.addFormParams(requestParameters: requestParameters) formValidator?.addFormParams(requestParameters: requestParameters)
requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType") requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType")
@ -445,7 +479,10 @@ import UIKit
MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: self, actionInformation: actionInformation, additionalData: additionalData) MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: self, actionInformation: actionInformation, additionalData: additionalData)
} }
//--------------------------------------------------
// MARK: - MoleculeDelegateProtocol // MARK: - MoleculeDelegateProtocol
//--------------------------------------------------
open func getModuleWithName(_ name: String?) -> [AnyHashable: Any]? { open func getModuleWithName(_ name: String?) -> [AnyHashable: Any]? {
guard let name = name else { return nil } guard let name = name else { return nil }
return loadObject?.modulesJSON?.optionalDictionaryForKey(name) return loadObject?.modulesJSON?.optionalDictionaryForKey(name)
@ -456,6 +493,7 @@ import UIKit
let moleculeName = moduleJSON.optionalStringForKey("moleculeName"), let moleculeName = moduleJSON.optionalStringForKey("moleculeName"),
let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self)
else { return nil } else { return nil }
do { do {
return try modelType.decode(jsonDict: moduleJSON) as? MoleculeModelProtocol return try modelType.decode(jsonDict: moduleJSON) as? MoleculeModelProtocol
} catch { } catch {
@ -471,28 +509,37 @@ import UIKit
open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) {} open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) {}
open func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) {} open func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) {}
//--------------------------------------------------
// MARK: - MVMCoreUIDetailViewProtocol // MARK: - MVMCoreUIDetailViewProtocol
//--------------------------------------------------
// Reset the navigation state. // Reset the navigation state.
public func splitViewDidReset() { public func splitViewDidReset() {
setNavigationBar() setNavigationBar()
} }
// MARK: - UITextFieldDelegate (Check if this is still needed) //--------------------------------------------------
// MARK: - UITextFieldDelegate
//--------------------------------------------------
// To Remove TextFields Bug: Keyboard is not dismissing after reaching textfield max length limit // To Remove TextFields Bug: Keyboard is not dismissing after reaching textfield max length limit
open func textFieldShouldReturn(_ textField: UITextField) -> Bool { open func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder() textField.resignFirstResponder()
return true return true
} }
open func textFieldDidBeginEditing(_ textField: UITextField) { open func textFieldDidBeginEditing(_ textField: UITextField) {
selectedField = textField selectedField = textField
// TODO: Make this into a protocol // TODO: Make this into a protocol
if UIAccessibility.isVoiceOverRunning { if UIAccessibility.isVoiceOverRunning {
if let toolBar = textField.inputAccessoryView as? UIToolbar, let _ = toolBar.items?.last, let pickerView = textField.inputView as? UIPickerView { if let toolBar = textField.inputAccessoryView as? UIToolbar,
let _ = toolBar.items?.last,
let pickerView = textField.inputView as? UIPickerView {
view.accessibilityElements = [pickerView, toolBar] view.accessibilityElements = [pickerView, toolBar]
} }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
UIAccessibility.post(notification: .layoutChanged, argument: textField.inputView) UIAccessibility.post(notification: .layoutChanged, argument: textField.inputView)
} }
@ -500,11 +547,13 @@ import UIKit
} }
open func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) { open func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
if textField === selectedField { if textField === selectedField {
if UIAccessibility.isVoiceOverRunning { if UIAccessibility.isVoiceOverRunning {
view.accessibilityElements = nil view.accessibilityElements = nil
UIAccessibility.post(notification: .layoutChanged, argument: textField) UIAccessibility.post(notification: .layoutChanged, argument: textField)
} }
selectedField = nil selectedField = nil
} }
} }
@ -513,7 +562,15 @@ import UIKit
selectedField?.resignFirstResponder() selectedField?.resignFirstResponder()
} }
// MARK: - UITextViewDelegate (Check if this is still needed) //--------------------------------------------------
// MARK: - UITextViewDelegate
//--------------------------------------------------
public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
selectedField = textView
return true
}
open func textViewDidBeginEditing(_ textView: UITextView) { open func textViewDidBeginEditing(_ textView: UITextView) {
selectedField = textView selectedField = textView
} }
@ -524,8 +581,11 @@ import UIKit
} }
} }
//--------------------------------------------------
// MARK: - Behavior Execution // MARK: - Behavior Execution
//--------------------------------------------------
func executeBehaviors<T>(_ behaviorBlock:(_ behavior:T)->Void) { func executeBehaviors<T>(_ behaviorBlock:(_ behavior:T)->Void) {
pageModel?.behaviors?.compactMap({ $0 as? T }).forEach({ behaviorBlock($0) }) pageModel?.behaviors?.compactMap({ $0 as? T }).forEach { behaviorBlock($0) }
} }
} }