formatitgn and commenting

This commit is contained in:
Kevin G Christiano 2020-07-28 10:02:54 -04:00
parent f9ce6e9f0f
commit ecba5863ff
2 changed files with 123 additions and 52 deletions

View File

@ -8,7 +8,12 @@
import Foundation
open class ScrollingViewController: ViewController {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var dismissKeyboardTapGesture: UITapGestureRecognizer?
@IBOutlet public var scrollView: UIScrollView!
public var contentView: UIView?
@ -17,6 +22,10 @@ open class ScrollingViewController: ViewController {
private var keyboardIsShowing = false
private var preKeyboardContentInset: UIEdgeInsets?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public init(with scrollView: UIScrollView) {
self.scrollView = scrollView
super.init(nibName: nil, bundle: nil)
@ -30,7 +39,10 @@ open class ScrollingViewController: ViewController {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
// MARK: - View Life Cycle
//--------------------------------------------------
// MARK: - View Lifecycle
//--------------------------------------------------
open override func viewDidLoad() {
super.viewDidLoad()
@ -41,7 +53,7 @@ open class ScrollingViewController: ViewController {
scrollView.alwaysBounceVertical = false
scrollView.delegate = self
}
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.setNeedsUpdateConstraints()
@ -53,7 +65,10 @@ open class ScrollingViewController: ViewController {
registerForKeyboardNotifications()
}
//--------------------------------------------------
// MARK: - Keyboard Handling
//--------------------------------------------------
open func registerForKeyboardNotifications() {
if !keyboardNotificationsAdded {
keyboardNotificationsAdded = true
@ -87,16 +102,18 @@ open class ScrollingViewController: ViewController {
@objc open func keyboardWillBeHidden(notification: Notification) {
keyboardIsShowing = false
// Disables the tap gesture.
dismissKeyboardTapGesture?.isEnabled = false
MVMCoreUIUtility.setScrollViewInsetForKeyboardHide(notification, scrollView: scrollView, viewController: self, contentInset: preKeyboardContentInset ?? scrollView.contentInset)
}
open func rectToScrollToWhenKeyboardPopsUp() -> CGRect? {
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)
}
}

View File

@ -9,6 +9,10 @@
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?
@ -33,16 +37,21 @@ import UIKit
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 }
(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)
}
@ -52,7 +61,7 @@ import UIKit
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]
@ -69,7 +78,9 @@ import UIKit
let pageType = pagesToListenFor()?.first(where: { (pageTypeListened) -> Bool in
guard let page = pagesLoaded.optionalDictionaryForKey(pageTypeListened),
let pageType = page.optionalStringForKey(KeyPageType),
pageType == pageTypeListened else { return false }
pageType == pageTypeListened
else { return false }
return true
}) {
newData = true
@ -90,6 +101,7 @@ import UIKit
}
guard newData else { return }
do {
try parsePageJSON()
MVMCoreDispatchUtility.performBlock(onMainThread: {
@ -134,8 +146,10 @@ import UIKit
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)")
}
@ -144,34 +158,39 @@ import UIKit
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 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 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
@ -194,14 +213,14 @@ import UIKit
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
}
@ -210,7 +229,9 @@ import UIKit
setNavigationItem()
}
//--------------------------------------------------
// MARK: - Navigation Item (Move to model base)
//--------------------------------------------------
open func getNavigationModel() -> NavigationItemModelProtocol? {
// TODO: remove legacy. Temporary, convert legacy to navigation model.
@ -224,7 +245,8 @@ import UIKit
/// Sets the navigation item for this view controller.
open func setNavigationItem() {
guard let navigationItemModel = getNavigationModel(),
let navigationController = navigationController else { return }
let navigationController = navigationController
else { return }
// We additionally want our left items
navigationItem.leftItemsSupplementBackButton = true
@ -238,8 +260,8 @@ import UIKit
let viewController = manager ?? self
guard let navigationItemModel = getNavigationModel(),
let navigationController = viewController.navigationController else {
MVMCoreUISession.sharedGlobal()?.splitViewController?.parent?.setNeedsStatusBarAppearanceUpdate()
return
MVMCoreUISession.sharedGlobal()?.splitViewController?.parent?.setNeedsStatusBarAppearanceUpdate()
return
}
// Utilize helper function to set the split view and navigation item state.
@ -265,33 +287,35 @@ import UIKit
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
}
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
}
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))
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 }
@ -301,28 +325,30 @@ import UIKit
}
MVMCoreUISplitViewController.main()?.updateTabBarShowing(!tabModel.tabBarHidden)
}
// MARK: - View lifecycle
//--------------------------------------------------
// 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
@ -392,7 +418,7 @@ import UIKit
stopObservingForResponseJSONUpdates()
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "View Controller Deallocated : \(self)")
}
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return MVMCoreGetterUtility.isOnIPad() ? UIInterfaceOrientationMask.all : UIInterfaceOrientationMask.portrait
}
@ -401,18 +427,22 @@ import UIKit
super.viewWillTransition(to: size, with: coordinator)
// Updates the detail view width
coordinator.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
}) { (UIViewControllerTransitionCoordinatorContext) in
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?) {
@ -433,8 +463,11 @@ import UIKit
})
}
}
//--------------------------------------------------
// MARK: - MVMCoreActionDelegateProtocol
//--------------------------------------------------
open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
formValidator?.addFormParams(requestParameters: requestParameters)
requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType")
@ -445,7 +478,10 @@ import UIKit
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)
@ -456,6 +492,7 @@ import UIKit
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 {
@ -471,28 +508,37 @@ import UIKit
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 (Check if this is still needed)
//--------------------------------------------------
// 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 {
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)
}
@ -500,11 +546,13 @@ import UIKit
}
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
}
}
@ -513,7 +561,10 @@ import UIKit
selectedField?.resignFirstResponder()
}
// MARK: - UITextViewDelegate (Check if this is still needed)
//--------------------------------------------------
// MARK: - UITextViewDelegate
//--------------------------------------------------
open func textViewDidBeginEditing(_ textView: UITextView) {
selectedField = textView
}
@ -524,8 +575,11 @@ import UIKit
}
}
//--------------------------------------------------
// MARK: - Behavior Execution
//--------------------------------------------------
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) }
}
}