592 lines
25 KiB
Swift
592 lines
25 KiB
Swift
//
|
|
// ViewController.swift
|
|
// MVMCoreUI
|
|
//
|
|
// Created by Scott Pfeil on 11/5/19.
|
|
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
|
|
@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 loadObject: MVMCoreLoadObject?
|
|
public var pageModel: MVMControllerModelProtocol?
|
|
|
|
/// Set if this page is containted in a manager.
|
|
public var manager: (UIViewController & MVMCoreViewManagerProtocol)?
|
|
|
|
/// A temporary iVar backer for delegateObject() until we change the protocol
|
|
public lazy var delegateObjectIVar: MVMCoreUIDelegateObject = {
|
|
return MVMCoreUIDelegateObject.create(withDelegateForAll: self)
|
|
}()
|
|
|
|
public func delegateObject() -> DelegateObject? {
|
|
return delegateObjectIVar
|
|
}
|
|
|
|
public var formValidator: FormValidator?
|
|
|
|
public var needsUpdateUI = false
|
|
private var observingForResponses = false
|
|
private var initialLoadFinished = false
|
|
private var previousScreenSize = CGSize.zero
|
|
|
|
public var selectedField: UIView?
|
|
|
|
/// Checks if the screen width has changed
|
|
open func screenSizeChanged() -> Bool {
|
|
return !MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1)
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Response handling
|
|
//--------------------------------------------------
|
|
|
|
open func observeForResponseJSONUpdates() {
|
|
guard !observingForResponses,
|
|
(pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0)
|
|
else { return }
|
|
|
|
observingForResponses = true
|
|
NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil)
|
|
}
|
|
|
|
open func stopObservingForResponseJSONUpdates() {
|
|
guard observingForResponses else { return }
|
|
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil)
|
|
observingForResponses = false
|
|
}
|
|
|
|
open func pagesToListenFor() -> [String]? {
|
|
guard let pageType = loadObject?.pageType else { return nil }
|
|
return [pageType]
|
|
}
|
|
|
|
open func modulesToListenFor() -> [String]? {
|
|
return loadObject?.requestParameters?.modules as? [String]
|
|
}
|
|
|
|
@objc open func responseJSONUpdated(notification: Notification) {
|
|
// Checks for a page we are listening for.
|
|
var newData = false
|
|
if let pagesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyPageMap),
|
|
let pageType = pagesToListenFor()?.first(where: { (pageTypeListened) -> Bool in
|
|
guard let page = pagesLoaded.optionalDictionaryForKey(pageTypeListened),
|
|
let pageType = page.optionalStringForKey(KeyPageType),
|
|
pageType == pageTypeListened
|
|
else { return false }
|
|
|
|
return true
|
|
}) {
|
|
newData = true
|
|
loadObject?.pageJSON = pagesLoaded.optionalDictionaryForKey(pageType)
|
|
}
|
|
|
|
// Checks for modules we are listening for.
|
|
if let modulesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyModuleMap),
|
|
let modulesListened = modulesToListenFor() {
|
|
for moduleName in modulesListened {
|
|
if let module = modulesLoaded.optionalDictionaryForKey(moduleName) {
|
|
newData = true
|
|
var currentModules = loadObject?.modulesJSON ?? [:]
|
|
currentModules.updateValue(module, forKey: moduleName)
|
|
loadObject?.modulesJSON = currentModules
|
|
}
|
|
}
|
|
}
|
|
|
|
guard newData else { return }
|
|
|
|
do {
|
|
try parsePageJSON()
|
|
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
|
self.handleNewDataAndUpdateUI()
|
|
|
|
// Update navigation bar if showing.
|
|
if MVMCoreUIUtility.getCurrentVisibleController() == self {
|
|
self.setNavigationBar()
|
|
}
|
|
})
|
|
} catch {
|
|
if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") {
|
|
MVMCoreLoggingHandler.shared()?.addError(toLog: coreError)
|
|
}
|
|
}
|
|
}
|
|
|
|
open func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject>) -> Bool {
|
|
pageType = loadObject.pageType
|
|
self.loadObject = loadObject
|
|
|
|
// Verifies all modules needed are loaded. TODO: change to ViewController
|
|
guard ViewController.verifyRequiredModulesLoaded(for: loadObject, error: error) else { return false }
|
|
|
|
// Parse the model for the page.
|
|
do {
|
|
try parsePageJSON()
|
|
return true
|
|
} catch let parsingError {
|
|
// Log all parsing errors and fail load.
|
|
handleLoggingFor(parsingError: parsingError)
|
|
if let errorObject = MVMCoreErrorObject.createErrorObject(for: parsingError, location: MVMCoreLoadHandler.sharedGlobal()?.errorLocation(forRequest: loadObject)) {
|
|
errorObject.messageToDisplay = MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess)
|
|
error.pointee = errorObject
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
func handleLoggingFor(parsingError: Error) {
|
|
if let registryError = parsingError as? ModelRegistry.Error {
|
|
switch (registryError) {
|
|
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 })")
|
|
|
|
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 })")
|
|
|
|
default:
|
|
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Registry error: \(registryError)")
|
|
}
|
|
}
|
|
if let decodingError = parsingError as? DecodingError {
|
|
switch (decodingError) {
|
|
case .keyNotFound(let codingKey, let context):
|
|
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Key \(codingKey.stringValue) was not found @ \(context.codingPath.map { return $0.stringValue })")
|
|
|
|
case .valueNotFound(_, let context):
|
|
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Value not found @ \(context.codingPath.map { return $0.stringValue })")
|
|
|
|
case .typeMismatch(_, let context):
|
|
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Type mismatch @ \(context.codingPath.map { return $0.stringValue })")
|
|
|
|
case .dataCorrupted(let context):
|
|
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: Data corrupted @ \(context.codingPath.map { return $0.stringValue })")
|
|
|
|
@unknown default:
|
|
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Error parsing template: \(parsingError)")
|
|
}
|
|
}
|
|
}
|
|
|
|
open func parsePageJSON() throws { }
|
|
|
|
open class func verifyRequiredModulesLoaded(for loadObject: MVMCoreLoadObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject>) -> Bool {
|
|
guard let pageType = loadObject?.pageType,
|
|
var modulesRequired = MVMCoreUIViewControllerMappingObject.shared()?.modulesRequired(forPageType: pageType),
|
|
!modulesRequired.isEmpty
|
|
else { return true }
|
|
|
|
guard let loadedModules = loadObject?.modulesJSON else { return false }
|
|
|
|
for case let key as String in Array(loadedModules.keys) {
|
|
guard modulesRequired.count > 0 else { break }
|
|
if let index = modulesRequired.firstIndex(where: {($0 as? String) == key}) {
|
|
modulesRequired.remove(at: index)
|
|
}
|
|
}
|
|
|
|
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!)) {
|
|
error.pointee = errorObject
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/// Calls processNewData and then sets the ui to update with updateView
|
|
open func handleNewDataAndUpdateUI() {
|
|
handleNewData()
|
|
needsUpdateUI = true
|
|
view.setNeedsLayout()
|
|
}
|
|
|
|
/// Creates a legacy navigation model.
|
|
open func createDefaultLegacyNavigationModel() -> NavigationItemModel {
|
|
let navigationModel = NavigationItemModel()
|
|
navigationModel.title = pageModel?.screenHeading
|
|
return navigationModel
|
|
}
|
|
|
|
/// Processes any new data. Called after the page is loaded the first time and on response updates for this page,
|
|
open func handleNewData() {
|
|
if formValidator == nil {
|
|
let rules = pageModel?.formRules
|
|
formValidator = FormValidator(rules)
|
|
}
|
|
|
|
if let backgroundColor = pageModel?.backgroundColor {
|
|
view.backgroundColor = backgroundColor.uiColor
|
|
}
|
|
|
|
// Sets up the navigation item based on the data.
|
|
setNavigationItem()
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Navigation Item (Move to model base)
|
|
//--------------------------------------------------
|
|
|
|
open func getNavigationModel() -> NavigationItemModelProtocol? {
|
|
// TODO: remove legacy. Temporary, convert legacy to navigation model.
|
|
if pageModel?.navigationBar == nil {
|
|
let navigationItem = createDefaultLegacyNavigationModel()
|
|
pageModel?.navigationBar = navigationItem
|
|
}
|
|
return pageModel?.navigationBar
|
|
}
|
|
|
|
/// Sets the navigation item for this view controller.
|
|
open func setNavigationItem() {
|
|
guard let navigationItemModel = getNavigationModel(),
|
|
let navigationController = navigationController
|
|
else { return }
|
|
|
|
// We additionally want our left items
|
|
navigationItem.leftItemsSupplementBackButton = true
|
|
|
|
// Utilize helper function to set the navigation item state.
|
|
NavigationController.setNavigationItem(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: self)
|
|
}
|
|
|
|
/// Sets the appearance of the navigation bar based on the model.
|
|
open func setNavigationBar() {
|
|
let viewController = manager ?? self
|
|
guard let navigationItemModel = getNavigationModel(),
|
|
let navigationController = viewController.navigationController else {
|
|
MVMCoreUISession.sharedGlobal()?.splitViewController?.parent?.setNeedsStatusBarAppearanceUpdate()
|
|
return
|
|
}
|
|
|
|
// Utilize helper function to set the split view and navigation item state.
|
|
MVMCoreUISplitViewController.setNavigationBarUI(for: viewController, navigationController: navigationController, navigationItemModel: navigationItemModel, leftPanelAccessible: isMasterInitiallyAccessible(), rightPanelAccessible: isSupportInitiallyAccessible(), progress: bottomProgress() ?? 0)
|
|
}
|
|
|
|
// Eventually will be moved to server
|
|
open func isMasterInitiallyAccessible() -> Bool {
|
|
if loadObject?.pageJSON?.boolForKey(KeyHideMainMenu) ?? false {
|
|
return false
|
|
}
|
|
return MVMCoreUISession.sharedGlobal()?.launchAppLoadedSuccessfully ?? false
|
|
}
|
|
|
|
// Eventually will be moved to server
|
|
open func isSupportInitiallyAccessible() -> Bool {
|
|
if loadObject?.pageJSON?.boolForKey(KeyHideMainMenu) ?? false {
|
|
return false
|
|
}
|
|
return (MVMCoreUISession.sharedGlobal()?.launchAppLoadedSuccessfully ?? false) || showRightPanelForScreenBeforeLaunchApp()
|
|
}
|
|
|
|
open func showRightPanelForScreenBeforeLaunchApp() -> Bool {
|
|
return loadObject?.pageJSON?.lenientBoolForKey("showRightPanel") ?? false
|
|
}
|
|
|
|
// Eventually will be moved to separate button in navigation item model
|
|
open func isOverridingRightButton() -> Bool {
|
|
guard let rightPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("rightPanelButtonLink")
|
|
else { return false }
|
|
MVMCoreActionHandler.shared()?.handleAction(with: rightPanelLink, additionalData: nil, delegateObject: delegateObject())
|
|
return true
|
|
}
|
|
|
|
// Eventually will be moved to separate button in navigation item model
|
|
open func isOverridingLeftButton() -> Bool {
|
|
guard let leftPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("leftPanelButtonLink")
|
|
else { return false }
|
|
MVMCoreActionHandler.shared()?.handleAction(with: leftPanelLink, additionalData: nil, delegateObject: delegateObject())
|
|
return true
|
|
}
|
|
|
|
// Eventually will be moved to Model
|
|
open func bottomProgress() -> Float? {
|
|
guard let progressString = loadObject?.pageJSON?.optionalStringForKey(KeyProgressPercent),
|
|
let progress = Float(progressString)
|
|
else { return nil }
|
|
|
|
return progress / Float(100)
|
|
}
|
|
//--------------------------------------------------
|
|
// MARK: - TabBar
|
|
//--------------------------------------------------
|
|
|
|
open func updateTabBar() {
|
|
guard MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() == self,
|
|
let tabModel = pageModel as? TabPageModelProtocol else { return }
|
|
MVMCoreUISplitViewController.main()?.tabBar?.delegateObject = delegateObjectIVar
|
|
if let index = tabModel.tabBarIndex {
|
|
MVMCoreUISplitViewController.main()?.tabBar?.highlightTab(at: index)
|
|
}
|
|
MVMCoreUISplitViewController.main()?.updateTabBarShowing(!tabModel.tabBarHidden)
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - View Lifecycle
|
|
//--------------------------------------------------
|
|
|
|
/// Called only once in viewDidLoad
|
|
open func initialLoad() {
|
|
observeForResponseJSONUpdates()
|
|
}
|
|
|
|
/// Called on screen size update.
|
|
open func updateViews() {
|
|
_ = formValidator?.validate()
|
|
}
|
|
|
|
override open func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
// Do any additional setup after loading the view.
|
|
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "View Controller Loaded : \(self)")
|
|
|
|
// We use our own margins.
|
|
viewRespectsSystemMinimumLayoutMargins = false
|
|
|
|
// Presents from the bottom.
|
|
modalPresentationStyle = MVMCoreGetterUtility.isOnIPad() ? .formSheet : .overCurrentContext
|
|
|
|
// Do some initial loading.
|
|
if !initialLoadFinished {
|
|
initialLoadFinished = true
|
|
initialLoad()
|
|
}
|
|
|
|
handleNewDataAndUpdateUI()
|
|
}
|
|
|
|
open override func viewDidLayoutSubviews() {
|
|
// Add to fix a constraint bug where the width is zero and things get messed up.
|
|
guard isViewLoaded, view.bounds.width > 1 else {
|
|
super.viewDidLayoutSubviews()
|
|
return
|
|
}
|
|
|
|
// First update should be explicit (hence the zero check)
|
|
if needsUpdateUI || (previousScreenSize != .zero && screenSizeChanged()) {
|
|
updateViews()
|
|
needsUpdateUI = false
|
|
}
|
|
|
|
previousScreenSize = view.bounds.size;
|
|
|
|
super.viewDidLayoutSubviews()
|
|
}
|
|
|
|
open func pageShown() {
|
|
// Update the navigation bar ui when view is appearing.
|
|
if self == MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() {
|
|
MVMCoreUISplitViewController.main()?.setupPanels()
|
|
}
|
|
setNavigationBar()
|
|
|
|
// Update tab if needed.
|
|
updateTabBar()
|
|
|
|
// Track.
|
|
MVMCoreUISession.sharedGlobal()?.currentPageType = pageType
|
|
MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self)
|
|
}
|
|
|
|
open override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
|
|
if manager == nil {
|
|
pageShown()
|
|
}
|
|
|
|
executeBehaviors { (behavior: PageVisibilityBehavior) in
|
|
behavior.onPageShown()
|
|
}
|
|
}
|
|
|
|
open override func viewDidDisappear(_ animated: Bool) {
|
|
super.viewDidDisappear(animated)
|
|
|
|
executeBehaviors { (behavior: PageVisibilityBehavior) in
|
|
behavior.onPageHidden()
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
stopObservingForResponseJSONUpdates()
|
|
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "View Controller Deallocated : \(self)")
|
|
}
|
|
|
|
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
|
return MVMCoreGetterUtility.isOnIPad() ? UIInterfaceOrientationMask.all : UIInterfaceOrientationMask.portrait
|
|
}
|
|
|
|
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
|
super.viewWillTransition(to: size, with: coordinator)
|
|
|
|
// Updates the detail view width
|
|
coordinator.animate(alongsideTransition: { UIViewControllerTransitionCoordinatorContext in
|
|
}) { UIViewControllerTransitionCoordinatorContext in
|
|
self.view.setNeedsLayout()
|
|
}
|
|
}
|
|
//--------------------------------------------------
|
|
// MARK: - MVMCoreViewManagerViewControllerProtocol
|
|
//--------------------------------------------------
|
|
|
|
open func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) {
|
|
pageShown()
|
|
}
|
|
//--------------------------------------------------
|
|
// MARK: - MVMCoreLoadDelegateProtocol
|
|
//--------------------------------------------------
|
|
|
|
// TODO: Move this function out of here after architecture cleanup.
|
|
open func loadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: (UIViewController & MVMCoreViewControllerProtocol)?, error: MVMCoreErrorObject?) {
|
|
|
|
MVMCoreUILoggingHandler.log(withDelegateLoadFinished: loadObject, loadedViewController: loadedViewController, error: error)
|
|
|
|
// Open the support panel
|
|
if error == nil,
|
|
loadObject?.requestParameters?.openSupportPanel ?? (loadObject?.systemParametersJSON?.boolForKey(KeyOpenSupport) ?? false) == true {
|
|
MVMCoreUISession.sharedGlobal()?.splitViewController?.showRightPanel(animated: true)
|
|
}
|
|
|
|
// Selects the tab if needed. Page driven takes priority over action driven (see viewWillAppear)
|
|
if let tab: Int = loadObject?.requestParameters?.actionMap?["tabBarIndex"] as? Int,
|
|
error == nil,
|
|
loadObject?.pageJSON?["tabBarIndex"] == nil {
|
|
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
|
MVMCoreUISplitViewController.main()?.tabBar?.highlightTab(at: tab)
|
|
})
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - MVMCoreActionDelegateProtocol
|
|
//--------------------------------------------------
|
|
|
|
open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
|
|
formValidator?.addFormParams(requestParameters: requestParameters)
|
|
requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType")
|
|
MVMCoreActionHandler.defaultHandleOpenPage(for: requestParameters, additionalData: additionalData, delegateObject: delegateObject())
|
|
}
|
|
|
|
open func logAction(withActionInformation actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
|
|
MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: self, actionInformation: actionInformation, additionalData: additionalData)
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - MoleculeDelegateProtocol
|
|
//--------------------------------------------------
|
|
|
|
open func getModuleWithName(_ name: String?) -> [AnyHashable: Any]? {
|
|
guard let name = name else { return nil }
|
|
return loadObject?.modulesJSON?.optionalDictionaryForKey(name)
|
|
}
|
|
|
|
open func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? {
|
|
guard let moduleJSON = loadObject?.modulesJSON?.optionalDictionaryForKey(moleculeName),
|
|
let moleculeName = moduleJSON.optionalStringForKey("moleculeName"),
|
|
let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self)
|
|
else { return nil }
|
|
|
|
do {
|
|
return try modelType.decode(jsonDict: moduleJSON) as? MoleculeModelProtocol
|
|
} catch {
|
|
MVMCoreUILoggingHandler.logDebugMessage(withDelegate: "error: \(error)")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Needed otherwise when subclassed, the extension gets called.
|
|
open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) {}
|
|
open func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { return nil }
|
|
open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) {}
|
|
open func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) {}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - MVMCoreUIDetailViewProtocol
|
|
//--------------------------------------------------
|
|
|
|
// Reset the navigation state.
|
|
public func splitViewDidReset() {
|
|
setNavigationBar()
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - UITextFieldDelegate
|
|
//--------------------------------------------------
|
|
|
|
// To Remove TextFields Bug: Keyboard is not dismissing after reaching textfield max length limit
|
|
open func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
|
textField.resignFirstResponder()
|
|
return true
|
|
}
|
|
|
|
open func textFieldDidBeginEditing(_ textField: UITextField) {
|
|
selectedField = textField
|
|
|
|
// TODO: Make this into a protocol
|
|
if UIAccessibility.isVoiceOverRunning {
|
|
if let toolBar = textField.inputAccessoryView as? UIToolbar,
|
|
let _ = toolBar.items?.last,
|
|
let pickerView = textField.inputView as? UIPickerView {
|
|
|
|
view.accessibilityElements = [pickerView, toolBar]
|
|
}
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
UIAccessibility.post(notification: .layoutChanged, argument: textField.inputView)
|
|
}
|
|
}
|
|
}
|
|
|
|
open func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
|
|
|
|
if textField === selectedField {
|
|
if UIAccessibility.isVoiceOverRunning {
|
|
view.accessibilityElements = nil
|
|
UIAccessibility.post(notification: .layoutChanged, argument: textField)
|
|
}
|
|
|
|
selectedField = nil
|
|
}
|
|
}
|
|
|
|
@objc open func dismissFieldInput(_ sender: Any?) {
|
|
selectedField?.resignFirstResponder()
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - UITextViewDelegate
|
|
//--------------------------------------------------
|
|
|
|
public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
|
|
selectedField = textView
|
|
return true
|
|
}
|
|
|
|
open func textViewDidBeginEditing(_ textView: UITextView) {
|
|
selectedField = textView
|
|
}
|
|
|
|
open func textViewDidEndEditing(_ textView: UITextView) {
|
|
if textView === selectedField {
|
|
selectedField = nil
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Behavior Execution
|
|
//--------------------------------------------------
|
|
|
|
func executeBehaviors<T>(_ behaviorBlock:(_ behavior:T)->Void) {
|
|
pageModel?.behaviors?.compactMap({ $0 as? T }).forEach { behaviorBlock($0) }
|
|
}
|
|
}
|