diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index ebb38987..a6588ea8 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -272,6 +272,7 @@ AAE7270E24AC8B9300A3ED0E /* HeadersH2CaretLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7270D24AC8B9300A3ED0E /* HeadersH2CaretLink.swift */; }; AAE96FA225341F6A0037A989 /* ListStoreLocatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE96FA125341F6A0037A989 /* ListStoreLocatorModel.swift */; }; AAE96FA525341F7D0037A989 /* ListStoreLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */; }; + AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; }; AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; }; BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; }; BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */; }; @@ -858,6 +859,7 @@ AAE7270D24AC8B9300A3ED0E /* HeadersH2CaretLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2CaretLink.swift; sourceTree = ""; }; AAE96FA125341F6A0037A989 /* ListStoreLocatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocatorModel.swift; sourceTree = ""; }; AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocator.swift; sourceTree = ""; }; + AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = ""; }; BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = ""; }; BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMediumModel.swift; sourceTree = ""; }; @@ -1538,6 +1540,15 @@ path = Miscellaneous; sourceTree = ""; }; + AFE4A1D427DFBB2700C458D0 /* NavigationController */ = { + isa = PBXGroup; + children = ( + D2B18B93236214AD00A9AEDC /* NavigationController.swift */, + AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */, + ); + path = NavigationController; + sourceTree = ""; + }; D202AFE2242A5F1400E5BEDF /* Extensions */ = { isa = PBXGroup; children = ( @@ -2076,9 +2087,9 @@ D29DF11921E68467003B2FB9 /* Containers */ = { isa = PBXGroup; children = ( + AFE4A1D427DFBB2700C458D0 /* NavigationController */, 0ABD1369237B18EE0081388D /* Views */, D29DF2B621E7BE66003B2FB9 /* SplitViewController */, - D2B18B93236214AD00A9AEDC /* NavigationController.swift */, ); path = Containers; sourceTree = ""; @@ -2641,6 +2652,7 @@ DBC4391922442197001AB423 /* DashLine.swift in Sources */, D2ED27FC254B0E0300A1C293 /* MVMCoreAlertObject+Swift.swift in Sources */, D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */, + AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */, AAC23FAD24D92A0D009208DF /* ListThreeColumnSpeedTestModel.swift in Sources */, BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */, 01F2C20527C81F9700DC3D36 /* SubNavSwipeAnimator.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index 22d33b1f..76016292 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -18,10 +18,6 @@ import UIKit get { return model as? LineModel } } - var lineBackgroundColor: Color? { - return (lineModel?.inverted ?? false) ? lineModel?.backgroundColor_inverted : lineModel?.backgroundColor - } - //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- @@ -50,28 +46,33 @@ import UIKit addLine(to: view, edge: edge, useMargin: useMargin) } + public init() { + super.init(frame: .zero) + model = LineModel(type: .standard) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + model = LineModel(type: .standard) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + model = LineModel(type: .standard) + } + + public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.init(model: model, delegateObject, additionalData) + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- open func setStyle(_ style: LineModel.Style) { - - switch style { - case .standard: - updateLineConstraints(constant: 1) - backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmCoolGray3 - case .thin: - updateLineConstraints(constant: 1) - backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmBlack - case .medium: - updateLineConstraints(constant: 2) - backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmBlack - case .heavy: - updateLineConstraints(constant: 4) - backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmBlack - case .none: - updateLineConstraints(constant: 0) - } + lineModel?.type = style + backgroundColor = lineModel?.backgroundColor?.uiColor + updateLineConstraints(constant: lineModel?.thickness ?? 1) } open func shouldBeVisible() -> Bool { @@ -90,12 +91,10 @@ import UIKit open override func setupView() { super.setupView() - heightConstraint = heightAnchor.constraint(equalToConstant: 1) heightConstraint?.isActive = true widthConstraint = widthAnchor.constraint(equalToConstant: 1) widthConstraint?.isActive = false - setStyle(.standard) } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { @@ -111,19 +110,7 @@ import UIKit } public override static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { - - guard let type = (model as? LineModel)?.type else { return 1 } - - switch type { - case .none: - return 0 - case .medium: - return 2 - case .heavy: - return 4 - default: - return 1 - } + return (model as? LineModel)?.thickness ?? 1 } } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift index beeb6149..95e07a34 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift @@ -86,7 +86,7 @@ open class TabBarModel: MoleculeModelProtocol { if let index = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedTab) { selectedTab = index } - style = try typeContainer.decodeIfPresent(NavigationItemStyle.self, forKey: .style) ?? .dark + style = try typeContainer.decodeIfPresent(NavigationItemStyle.self, forKey: .style) } open func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift index 8f82c946..b9fb111a 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift @@ -20,4 +20,5 @@ public protocol NavigationItemModelProtocol { var additionalLeftButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? { get set } var additionalRightButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? { get set } var titleView: MoleculeModelProtocol? { get set } + var titleOffset: UIOffset? { get } } diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift deleted file mode 100644 index c2b63a6e..00000000 --- a/MVMCoreUI/Containers/NavigationController.swift +++ /dev/null @@ -1,201 +0,0 @@ -// -// NavigationController.swift -// MVMCoreUI -// -// Created by Scott Pfeil on 10/24/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - -@objcMembers open class NavigationController: UINavigationController, MVMCoreViewManagerViewControllerProtocol { - public var separatorView: Line? - public weak var manager: (UIViewController & MVMCoreViewManagerProtocol)? - - /// Getter for the main navigation controller - public static func navigationController() -> Self? { - return MVMCoreActionUtility.initializerClassCheck(MVMCoreUISession.sharedGlobal()?.navigationController, classToVerify: self) as? Self - } - - /// Provides MVM styling to the navigation bar. Returns a reference to the line. - public static func style(_ navigationBar: UINavigationBar) -> Line { - navigationBar.backgroundColor = .white - navigationBar.shadowImage = UIImage() - navigationBar.isOpaque = true - navigationBar.tintColor = .black - navigationBar.titleTextAttributes = [NSAttributedString.Key.font: MFStyler.fontBoldBodySmall(false)]; - return Line(pinTo: navigationBar, edge: .bottom, useMargin: false) - } - - /// Sets up the application with a navigation controller - public static func setupNavigationController() -> Self? { - let navigationController = self.init() - navigationController.separatorView = style(navigationController.navigationBar) - MVMCoreUISession.sharedGlobal()?.navigationController = navigationController - MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn = navigationController - MVMCoreNavigationHandler.shared()?.navigationController = navigationController - MVMCoreNavigationHandler.shared()?.addDelegate(navigationController) - return navigationController - } - - /// Sets up the application with a navigation controller as the main container. - public static func setupNavigationControllerAsMainController() -> Self? { - guard let navigationController = setupNavigationController() else { return nil } - MVMCoreUISession.sharedGlobal()?.setup(asStandardLoadViewDelegate: navigationController) - return navigationController - } - - /// Convenience function for setting the navigation item. - public static func setNavigationItem(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { - viewController.navigationItem.title = navigationItemModel.title - viewController.navigationItem.accessibilityLabel = navigationItemModel.title - viewController.navigationItem.hidesBackButton = navigationItemModel.hidesSystemBackButton - viewController.navigationItem.leftItemsSupplementBackButton = !navigationItemModel.hidesSystemBackButton - setNavigationButtons(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) - setNavigationTitleView(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) - } - - /// Convenience function for setting the navigation buttons. - public static func setNavigationButtons(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { - let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject - var leftItems: [UIBarButtonItem] = [] - if navigationItemModel.hidesSystemBackButton, - navigationItemModel.alwaysShowBackButton != false { - if let backButtonModel = navigationItemModel.backButton, - MVMCoreNavigationHandler.shared()?.getViewControllers(for: navigationController)?.count ?? 0 > 1 || navigationItemModel.alwaysShowBackButton ?? false { - leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) - } - if let leftItemModels = navigationItemModel.additionalLeftButtons { - for item in leftItemModels { - leftItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) - } - } - } - viewController.navigationItem.leftBarButtonItems = leftItems.count > 0 ? leftItems : nil - - var rightItems: [UIBarButtonItem] = [] - if let rightItemModels = navigationItemModel.additionalRightButtons { - for item in rightItemModels { - rightItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) - } - } - viewController.navigationItem.rightBarButtonItems = rightItems.count > 0 ? rightItems : nil - } - - /// Convenience function for setting the navigation bar ui, except for the buttons. - public static func setNavigationBarUI(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { - navigationController.setNavigationBarHidden(navigationItemModel.hidden, animated: true) - navigationController.navigationBar.barTintColor = navigationItemModel.backgroundColor?.uiColor ?? .white - - let tint = navigationItemModel.tintColor.uiColor - navigationController.navigationBar.tintColor = tint - - // Have the navigation title match the tint color - navigationController.navigationBar.titleTextAttributes?.updateValue(tint, forKey: .foregroundColor) - - // Update line. - if let navigationController = navigationController as? NavigationController { - navigationController.separatorView?.isHidden = navigationItemModel.line?.type ?? .standard == .none - } - } - - /// Convenience function for setting the navigation titleView. - public static func setNavigationTitleView(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol?, viewController: UIViewController) { - let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject - if let titleViewModel = navigationItemModel?.titleView, let molecule = ModelRegistry.createMolecule(titleViewModel, delegateObject: delegate, additionalData: nil) { - viewController.navigationItem.titleView = molecule - } - } - - /// Convenience function to return the navigation model of the lowest controller traversing managers if applicable. - public func getNavigationModel(from viewController: UIViewController) -> NavigationItemModelProtocol? { - return (viewController as? PageProtocol)?.pageModel?.navigationBar - } - - /// Verifies the controller is the currently displayed controller. - public func isDisplayed(viewController: UIViewController) -> Bool { - guard let topViewController = topViewController, - viewController == MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) else { - return false - } - return true - } -} - -extension NavigationController: MVMCoreViewManagerProtocol { - public func getCurrentViewController() -> UIViewController? { - guard let topViewController = topViewController else { return nil } - return MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) - } - - public func containsPage(withPageType pageType: String?) -> Bool { - for controller in viewControllers { - if let manager = controller as? MVMCoreViewManagerProtocol, - manager.containsPage(withPageType: pageType) { - return true - } else if let controller = controller as? MVMCoreViewControllerProtocol, - controller.pageType == pageType { - return true - } - } - return false - } - - public func newDataReceived(in viewController: UIViewController) { - - if isDisplayed(viewController: viewController), - let topViewController = topViewController, - let model = getNavigationModel(from: viewController) { - Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: topViewController) - Self.setNavigationBarUI(navigationController: self, navigationItemModel: model, viewController: topViewController) - } - manager?.newDataReceived?(in: viewController) - } - - public func willDisplay(_ viewController: UIViewController) { - if let topViewController = topViewController, - let model = getNavigationModel(from: viewController) { - Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: topViewController) - } - manager?.willDisplay?(viewController) - } - - public func displayedViewController(_ viewController: UIViewController) { - if isDisplayed(viewController: viewController), - let topViewController = topViewController, - let model = getNavigationModel(from: viewController) { - Self.setNavigationBarUI(navigationController: self, navigationItemModel: model, viewController: topViewController) - } - manager?.displayedViewController?(viewController) - } -} - -extension NavigationController: MVMCorePresentationDelegateProtocol { - public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { - guard self == navigationController, - let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController), - let model = getNavigationModel(from: newViewController) else { return } - Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: viewController) - } - - public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) { - guard self == navigationController, - let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } - if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { - MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller) - } - manager?.willDisplay?(newViewController) - } - - public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { - guard self == navigationController, - let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } - if let model = getNavigationModel(from: newViewController) { - Self.setNavigationBarUI(navigationController: self, navigationItemModel: model, viewController: viewController) - } - manager?.displayedViewController?(newViewController) - if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { - controller.viewControllerReady?(inManager: self) - } - } -} diff --git a/MVMCoreUI/Containers/NavigationController/NavigationController.swift b/MVMCoreUI/Containers/NavigationController/NavigationController.swift new file mode 100644 index 00000000..03c10595 --- /dev/null +++ b/MVMCoreUI/Containers/NavigationController/NavigationController.swift @@ -0,0 +1,136 @@ +// +// NavigationController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 10/24/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class NavigationController: UINavigationController, MVMCoreViewManagerViewControllerProtocol { + public weak var manager: (UIViewController & MVMCoreViewManagerProtocol)? + + /// Getter for the main navigation controller + public static func navigationController() -> Self? { + return MVMCoreActionUtility.initializerClassCheck(MVMCoreUISession.sharedGlobal()?.navigationController, classToVerify: self) as? Self + } + + /// Sets up the application with a navigation controller + public static func setupNavigationController() -> Self? { + let navigationController = self.init() + MVMCoreUISession.sharedGlobal()?.navigationController = navigationController + MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn = navigationController + MVMCoreNavigationHandler.shared()?.navigationController = navigationController + MVMCoreNavigationHandler.shared()?.addDelegate(navigationController) + navigationController.setNavigationBarUI(with: NavigationItemModel()) + return navigationController + } + + /// Sets up the application with a navigation controller as the main container. + public static func setupNavigationControllerAsMainController() -> Self? { + guard let navigationController = setupNavigationController() else { return nil } + MVMCoreUISession.sharedGlobal()?.setup(asStandardLoadViewDelegate: navigationController) + return navigationController + } + + /// Convenience function to return the navigation model of the lowest controller traversing managers if applicable. + public func getNavigationModel(from viewController: UIViewController) -> NavigationItemModelProtocol? { + return (viewController as? PageProtocol)?.pageModel?.navigationBar + } + + /// Verifies the controller is the currently displayed controller. + public func isDisplayed(viewController: UIViewController) -> Bool { + guard let topViewController = topViewController, + viewController == MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) else { + return false + } + return true + } +} + +extension NavigationController: MVMCoreViewManagerProtocol { + public func getCurrentViewController() -> UIViewController? { + guard let topViewController = topViewController else { return nil } + return MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) + } + + public func containsPage(withPageType pageType: String?) -> Bool { + for controller in viewControllers { + if let manager = controller as? MVMCoreViewManagerProtocol, + manager.containsPage(withPageType: pageType) { + return true + } else if let controller = controller as? MVMCoreViewControllerProtocol, + controller.pageType == pageType { + return true + } + } + return false + } + + public func newDataReceived(in viewController: UIViewController) { + + if isDisplayed(viewController: viewController), + let topViewController = topViewController, + let model = getNavigationModel(from: viewController) { + setNavigationItem(with: model, for: topViewController) + setNavigationBarUI(with: model) + } + manager?.newDataReceived?(in: viewController) + } + + public func willDisplay(_ viewController: UIViewController) { + if let topViewController = topViewController, + let model = getNavigationModel(from: viewController) { + setNavigationItem(with: model, for: topViewController) + } + manager?.willDisplay?(viewController) + } + + public func displayedViewController(_ viewController: UIViewController) { + if isDisplayed(viewController: viewController), + let model = getNavigationModel(from: viewController) { + setNavigationBarUI(with: model) + } + manager?.displayedViewController?(viewController) + } +} + +extension NavigationController: MVMCorePresentationDelegateProtocol { + public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { + guard self == navigationController, + let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController), + let model = getNavigationModel(from: newViewController) else { return } + setNavigationItem(with: model, for: viewController) + } + + public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) { + guard self == navigationController, + let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } + if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { + MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller) + } + manager?.willDisplay?(newViewController) + } + + public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { + guard self == navigationController, + let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } + if let model = getNavigationModel(from: newViewController) { + setNavigationBarUI(with: model) + } + manager?.displayedViewController?(newViewController) + if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { + controller.viewControllerReady?(inManager: self) + } + } +} + +extension UIColor { + func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage { + return UIGraphicsImageRenderer(size: size).image { rendererContext in + self.setFill() + rendererContext.fill(CGRect(origin: .zero, size: size)) + } + } +} diff --git a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift new file mode 100644 index 00000000..ea5003f4 --- /dev/null +++ b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift @@ -0,0 +1,88 @@ +// +// UINavigationController+Extension.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 3/14/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation + +public extension UINavigationController { + + /// Convenience function for setting the navigation item. + func setNavigationItem(with model: NavigationItemModelProtocol, for viewController: UIViewController) { + viewController.navigationItem.title = model.title + viewController.navigationItem.accessibilityLabel = model.title + viewController.navigationItem.hidesBackButton = model.hidesSystemBackButton + viewController.navigationItem.leftItemsSupplementBackButton = !model.hidesSystemBackButton + setNavigationButtons(with: model, for: viewController) + setNavigationTitleView(with: model, for: viewController) + } + + /// Convenience function for setting the navigation buttons. + func setNavigationButtons(with model: NavigationItemModelProtocol, for viewController: UIViewController) { + let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject + var leftItems: [UIBarButtonItem] = [] + if model.hidesSystemBackButton, + model.alwaysShowBackButton != false { + if let backButtonModel = model.backButton, + MVMCoreNavigationHandler.shared()?.getViewControllers(for: self)?.count ?? 0 > 1 || model.alwaysShowBackButton ?? false { + leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) + } + if let leftItemModels = model.additionalLeftButtons { + for item in leftItemModels { + leftItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) + } + } + } + viewController.navigationItem.leftBarButtonItems = leftItems.count > 0 ? leftItems : nil + + var rightItems: [UIBarButtonItem] = [] + if let rightItemModels = model.additionalRightButtons { + for item in rightItemModels { + rightItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) + } + } + viewController.navigationItem.rightBarButtonItems = rightItems.count > 0 ? rightItems : nil + } + + /// Convenience function for setting the navigation titleView. + func setNavigationTitleView(with model: NavigationItemModelProtocol, for viewController: UIViewController) { + let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject + if let titleViewModel = model.titleView, + let molecule = ModelRegistry.createMolecule(titleViewModel, delegateObject: delegate, additionalData: nil) { + viewController.navigationItem.titleView = molecule + } + } + + /// Returns a ShadowImage based on the line property of NavigationItemModelProtocol + func getNavigationBarShadowImage(for navigationItemModel: NavigationItemModelProtocol) -> UIImage? { + guard let thickness = navigationItemModel.line?.thickness, + let backgroundColor = navigationItemModel.line?.backgroundColor else { return nil } + return backgroundColor.uiColor.image(CGSize(width: thickness, height: thickness)) + } + + /// Convenience function for setting the navigation bar ui + func setNavigationBarUI(with model: NavigationItemModelProtocol) { + let navigationBar = navigationBar + let font = MFStyler.fontBoldBodySmall(false) + let backgroundColor = model.backgroundColor?.uiColor + let tint = model.tintColor.uiColor + navigationBar.tintColor = tint + + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.titleTextAttributes = [NSAttributedString.Key.font: font, + NSAttributedString.Key.foregroundColor: tint]; + appearance.backgroundColor = backgroundColor + appearance.titleTextAttributes.updateValue(tint, forKey: .foregroundColor) + appearance.titlePositionAdjustment = model.titleOffset ?? .zero + appearance.shadowColor = model.line?.backgroundColor?.uiColor ?? .clear + appearance.shadowImage = getNavigationBarShadowImage(for: model)?.withRenderingMode(.alwaysTemplate) + navigationBar.standardAppearance = appearance + navigationBar.scrollEdgeAppearance = appearance + + setNavigationBarHidden(model.hidden, animated: true) + } +} diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift index a4bbcb08..76afeca8 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift @@ -7,23 +7,19 @@ // import Foundation +import UIKit // Navigation bar update functions public extension MVMCoreUISplitViewController { /// Convenience function. Sets the navigation and split view properties for the view controller. Panel access is determined if view controller is a detail view protocol. - static func setNavigationBarUI(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) { - guard let splitView = MVMCoreUISplitViewController.main(), - navigationController == splitView.navigationController, - viewController == MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() else { + func setNavigationBar(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) { + guard navigationController == self.navigationController, + viewController == getCurrentDetailViewController() else { /// Not the split view navigation controller, skip split functions. return } - splitView.set(for: viewController, navigationController: navigationController, navigationItemModel: navigationItemModel) - } - - /// Sets the navigation item for the view controller based on the model and splitview controller - private func set(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) { + setLeftPanelIsAccessible((viewController as? MVMCoreUIDetailViewProtocol)?.isLeftPanelAccessible?() ?? false, for: viewController, updateNavigationButtons: false) setRightPanelIsAccessible((viewController as? MVMCoreUIDetailViewProtocol)?.isRightPanelAccessible?() ?? false, for: viewController, updateNavigationButtons: false) @@ -121,7 +117,30 @@ public extension MVMCoreUISplitViewController { guard let navigationController = navigationController, navigationController.isDisplayed(viewController: viewController), let model = navigationController.getNavigationModel(from: viewController) else { return } - set(for: viewController, navigationController: navigationController, navigationItemModel: model) + setNavigationBar(for: viewController, navigationController: navigationController, navigationItemModel: model) + guard !(topAlertView?.overridingStatusBar() ?? false) else { return } + setStatusBarForCurrentViewController() + } + + /// Returns the bar style for the background color. Light if on a dark background, otherwise default. + func getStatusBarStyle(for backgroundColor: UIColor?) -> UIStatusBarStyle { + var greyScale: CGFloat = 0 + if backgroundColor?.getWhite(&greyScale, alpha: nil) == true, + greyScale < 0.5 { return .lightContent } + return .default + } + + /// Updates the status bar background color and style. + @objc func setStatusBarForCurrentViewController() { + let viewController = getCurrentViewController() as? MVMCoreUIDetailViewProtocol + let backgroundColor = viewController?.defaultStatusBarBackgroundColor?() ?? + navigationController?.navigationBar.standardAppearance.backgroundColor ?? + statusBarView?.backgroundColor + + let style = viewController?.defaultStatusBarStyle?() ?? + getStatusBarStyle(for: backgroundColor) + + setStatusBarBackgroundColor(backgroundColor, style: style) } } diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h index 9c07374d..db5e9cd9 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h @@ -44,6 +44,9 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) { // Reference to the top alert view @property (nullable, weak, nonatomic) MVMCoreUITopAlertView *topAlertView; +// Reference to the status bar view +@property (nullable, weak, nonatomic) UIView *statusBarView; + // References to the current navigation item settings. @property (nonatomic, readonly) BOOL leftPanelIsAccessible; @property (nonatomic, readonly) BOOL rightPanelIsAccessible; @@ -122,10 +125,6 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) { + (CGFloat)getApplicationViewWidth; + (CGFloat)getApplicationViewMaxSize; -// return subviewcontrollers' prefer status bar style -- (UIStatusBarStyle)getDefaultStatusBarStyle; -- (nullable UIColor *)getDefaultStatusBarBackgroundColor; - /// Returns true if a panel is showing. - (BOOL)isAPanelShowing; @@ -166,4 +165,15 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) { /// Updates if the tab bar is showing or not. - (void)updateTabBarShowing:(BOOL)showing; +#pragma mark - Status Bar + +/// Updates the status bar with the given style and background color +- (void)setStatusBarBackgroundColor:(nullable UIColor *)backgroundColor style:(UIStatusBarStyle)style; + +/// Shows the view under the status bar. +- (void)expandStatusBarView; + +/// Hides the view under the status bar. +- (void)collapseStatusBarView; + @end diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m index e16daa56..5c337072 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m @@ -63,6 +63,10 @@ typedef NS_OPTIONS(NSInteger, MFExtendedDrawer) { @property (weak, nonatomic, readwrite) UIViewController *navigationItemViewController; @property (strong, nonatomic) NSNumber *transitionWidth; +@property (nonatomic) UIStatusBarStyle statusBarStyle; +@property (strong, nonatomic) NSLayoutConstraint *statusBarHeightConstraint; +@property (strong, nonatomic) NSLayoutConstraint *statusBarBottomConstraint; + // Dismisses any panel - (void)dismissPanels:(id)sender; @@ -841,6 +845,38 @@ CGFloat const PanelAnimationDuration = 0.2; }]; } +#pragma mark - Status Bar + +- (void)setStatusBarBackgroundColor:(UIColor *)backgroundColor style:(UIStatusBarStyle)style { + self.statusBarView.backgroundColor = backgroundColor; + self.statusBarStyle = style; + + // Triggers preferredStatusBarStyle + [self.parentViewController setNeedsStatusBarAppearanceUpdate]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return self.statusBarStyle; +} + +- (void)expandStatusBarView { + __weak typeof(self) weakSelf = self; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + weakSelf.statusBarBottomConstraint.active = YES; + weakSelf.statusBarHeightConstraint.active = NO; + [weakSelf.view layoutIfNeeded]; + }]; +} + +- (void)collapseStatusBarView { + __weak typeof(self) weakSelf = self; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + weakSelf.statusBarBottomConstraint.active = NO; + weakSelf.statusBarHeightConstraint.active = YES; + [weakSelf.view layoutIfNeeded]; + }]; +} + #pragma mark - View Cyle - (void)loadView { @@ -849,6 +885,17 @@ CGFloat const PanelAnimationDuration = 0.2; view.translatesAutoresizingMaskIntoConstraints = NO; self.view = view; + // Status bar + UIView *statusBarView = [MVMCoreUICommonViewsUtility commonView]; + statusBarView.backgroundColor = [UIColor whiteColor]; + [self.view addSubview:statusBarView]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[statusBarView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView)]]; + id topGuide = view.safeAreaLayoutGuide; + self.statusBarBottomConstraint = [NSLayoutConstraint constraintWithItem:statusBarView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:topGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; + self.statusBarBottomConstraint.active = YES; + self.statusBarHeightConstraint = [statusBarView.heightAnchor constraintEqualToConstant:0]; + self.statusBarView = statusBarView; + // Top Alert MVMCoreUITopAlertView *topAlertView = nil; if ([[CoreUIObject sharedInstance].globalTopAlertDelegate respondsToSelector:@selector(getTopAlertView)]) { @@ -894,9 +941,9 @@ CGFloat const PanelAnimationDuration = 0.2; self.bottomProgressBarHeightConstraint = bottomProgressHeight; if (topAlertView) { - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[topAlertView]-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topAlertView, mainView, progressView)]]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[statusBarView]-0-[topAlertView]-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView,topAlertView, mainView, progressView)]]; } else { - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(mainView, progressView)]]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[statusBarView]-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView,mainView, progressView)]]; } // Add tabbar if we have it. @@ -924,7 +971,6 @@ CGFloat const PanelAnimationDuration = 0.2; - (void)viewDidLoad { [super viewDidLoad]; - [self.topAlertView pinATopViewController:self]; // Creates the back button self.backButton = [[UIBarButtonItem alloc] initWithImage:[self imageForBackButton] style:UIBarButtonItemStylePlain target:self action:@selector(backButtonPressed:)]; @@ -1008,36 +1054,6 @@ CGFloat const PanelAnimationDuration = 0.2; } } -- (UIStatusBarStyle)preferredStatusBarStyle { - if (self.topAlertView.topAlertObject) { - return self.topAlertView.statusBarStyle; - } else { - UIStatusBarStyle style = [self getDefaultStatusBarStyle]; - [self.topAlertView resetDefaultBackgroundColor:[self getDefaultStatusBarBackgroundColor] basedOnStatusBarStyle:style]; - return style; - } -} - -- (UIStatusBarStyle)getDefaultStatusBarStyle { - UIViewController *viewController = [self getCurrentDetailViewController]; - if ([viewController conformsToProtocol:@protocol(MVMCoreUIDetailViewProtocol)] - && [viewController respondsToSelector:@selector(defaultStatusBarStyle)] - && [((UIViewController *)viewController) defaultStatusBarStyle]) { - return [((UIViewController *)viewController) defaultStatusBarStyle]; - } - return UIStatusBarStyleDefault; -} - -- (UIColor *)getDefaultStatusBarBackgroundColor { - UIViewController *viewController = [self getCurrentDetailViewController]; - if ([viewController conformsToProtocol:@protocol(MVMCoreUIDetailViewProtocol)] - && [viewController respondsToSelector:@selector(defaultStatusBarBackgroundColor)] - && [((UIViewController *)viewController) defaultStatusBarBackgroundColor]) { - return [((UIViewController *)viewController) defaultStatusBarBackgroundColor]; - } - return nil; -} - - (BOOL)isAPanelShowing { return fabs(self.mainViewLeading.constant) > 1; } diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift index c33d0238..c3be1868 100644 --- a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift @@ -78,7 +78,6 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller) } - hideNavigationBarLine(for: viewController) } required public init?(coder: NSCoder) { @@ -111,16 +110,27 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, } } + open override func pageShown() { + // Currently not calling super until we can decouple page shown logics for managers. + hideNavigationBarLine(true) + } + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - // Notify showing view we will disappear. (viewController as? MVMCoreViewManagerViewControllerProtocol)?.managerWillDisappear?(self) } - /// Hides the navigation bar for the page. - open func hideNavigationBarLine(for viewController: UIViewController) { - (viewController as? PageProtocol)?.pageModel?.navigationBar?.line?.type = .none + /// Hides/Shows the navigation bar for the page. + open func hideNavigationBarLine(_ isHidden: Bool) { + guard self == navigationController?.topViewController else { return } + var color = UIColor.clear + if !isHidden, + let backgroundColor = (getCurrentViewController() as? PageProtocol)?.pageModel?.navigationBar?.line?.backgroundColor?.uiColor { + color = backgroundColor + } + navigationController?.navigationBar.standardAppearance.shadowColor = color + navigationController?.navigationBar.scrollEdgeAppearance?.shadowColor = color } open override func updateViews() { @@ -213,7 +223,6 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, index != tabs.selectedIndex else { return } viewController = controller pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType - hideNavigationBarLine(for: controller) if let viewController = getCurrentViewController() { manager?.willDisplay?(viewController) } @@ -253,6 +262,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, if let viewController = getCurrentViewController() { manager?.displayedViewController?(viewController) } + hideNavigationBarLine(true) } // MARK: - TabsDelegate @@ -279,6 +289,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, // MARK: - MVMCoreViewManagerViewControllerProtocol open override func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) { + super.viewControllerReady(inManager: manager) // Pass on down (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self) } @@ -297,19 +308,17 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, } open func newDataReceived(in viewController: UIViewController) { - if viewController == self.viewController { - hideNavigationBarLine(for: viewController) - } manager?.newDataReceived?(in: viewController) + hideNavigationBarLine(true) } public func willDisplay(_ viewController: UIViewController) { - hideNavigationBarLine(for: viewController) manager?.willDisplay?(viewController) } public func displayedViewController(_ viewController: UIViewController) { manager?.displayedViewController?(viewController) + hideNavigationBarLine(true) } // MARK: - MVMCoreUISwipeNavigationProtocol diff --git a/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h b/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h index e10cec10..e799e858 100644 --- a/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h +++ b/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h @@ -13,16 +13,19 @@ @optional -// Show based on the object +/// Show based on the object - (void)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler; -// Hides +/// Removes the notification - (void)hideAlertView:(BOOL)forceful completionHandler:(void (^ __nullable)(BOOL finished))completionHandler; -// Collapses the notification if it has a short top message. Otherwise removes notification. +/// Collapses the notification if it has a short top message. Otherwise removes notification. - (void)collapseNotification; /// Updates the existing top alert with the new object - (void)updateTopAlertWith:(nullable MVMCoreTopAlertObject *)topAlertObject; +/// Returns if the top alert is currently utilizing the status bar. +- (BOOL)overridingStatusBar; + @end diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift index 0328f03c..46765385 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift @@ -109,7 +109,7 @@ public extension MVMCoreUITopAlertView { // Update status bar. guard let statusBarDelegate = molecule as? StatusBarUI else { return } let statusBarUI = statusBarDelegate.getStatusBarUI() - self.setStatusBarColor(statusBarUI.color, statusBarStyle: statusBarUI.style) + MVMCoreUISplitViewController.main()?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style) }) } diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h index 5bb033d7..fad65957 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h @@ -19,8 +19,6 @@ @interface MVMCoreUITopAlertView : UIView -@property (nonatomic, readonly) UIStatusBarStyle statusBarStyle; - // Delegate for the top alert view @property (nonatomic, nullable, weak) id animationDelegate; @@ -36,16 +34,6 @@ // Returns a TopAlertView with the mvm styling. Also sets the property in the session. + (nullable instancetype)setupTopAlertView; -// Pins the status bar view at the top of the passed in view controller. -- (void)pinATopViewController:(nonnull UIViewController *)viewController; - -// For controlling the status bar view -- (void)expandStatusBarView; -- (void)collapseStatusBarView; - -/// reset status bar background color, when backgroundColor is nil corresponding background color will be set based on style -- (void)resetDefaultBackgroundColor:(nullable UIColor *)backgroundColor basedOnStatusBarStyle:(UIStatusBarStyle)style; - // Can be subclassed for custom views. - (nonnull UIView *)topAlertViewForTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate statusBarColor:(UIColor *_Nullable *_Nullable)statusBarColor statusBarStyle:(UIStatusBarStyle *_Nullable)statusBarStyle; @@ -55,7 +43,4 @@ /// Get the content color based on the type - (nonnull UIColor *)getContentColorForType:(nullable NSString *)type; -// Set the status bar color. Used for updating the status bar when the view changes. -- (void)setStatusBarColor:(nullable UIColor *)statusBarColor statusBarStyle:(UIStatusBarStyle)style; - @end diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m index eba4cfcc..c0835541 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m @@ -33,12 +33,6 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; @interface MVMCoreUITopAlertView () -@property (nonatomic, readwrite) UIStatusBarStyle statusBarStyle; -@property (weak, nonatomic) UIView *statusBarView; -@property (strong, nonatomic) NSLayoutConstraint *statusBarHeightConstraint; -@property (strong, nonatomic) NSLayoutConstraint *statusBarBottomConstraint; - -@property (weak, nonatomic) UIView *alertView; @property (weak, nullable, nonatomic, readwrite) UIView *currentAlert; @property (strong, nonatomic) NSLayoutConstraint *height; @@ -48,6 +42,8 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; /// Used if we delayed the collapse due to accessibility. @property (copy, nonatomic) void (^ hideCompletionHandler)(BOOL finished); +@property (nonatomic) BOOL currentAlertOverridingStatusBar; + @end @implementation MVMCoreUITopAlertView @@ -88,34 +84,11 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } - (void)setupView { - if (!self.statusBarView) { - self.clipsToBounds = YES; - - UIView *statusBarView = [MVMCoreUICommonViewsUtility commonView]; - UIView *alertView = [MVMCoreUICommonViewsUtility commonView]; - [self addSubview:alertView]; - [self addSubview:statusBarView]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[statusBarView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView)]]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[alertView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(alertView)]]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[statusBarView]-0-[alertView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView,alertView)]]; - self.statusBarHeightConstraint = [statusBarView.heightAnchor constraintEqualToConstant:0]; - self.statusBarHeightConstraint.active = YES; - self.height = [alertView.heightAnchor constraintEqualToConstant:0]; - self.height.active = YES; - self.alertView = alertView; - self.statusBarView = statusBarView; - - [self setStatusBarColor:[UIColor whiteColor] statusBarStyle:UIStatusBarStyleDefault]; - - [self registerWithNotificationCenter]; - } -} - -- (void)pinATopViewController:(UIViewController *)viewController { - self.statusBarHeightConstraint.active = NO; - id topGuide = viewController.view.safeAreaLayoutGuide; - self.statusBarBottomConstraint = [NSLayoutConstraint constraintWithItem:self.statusBarView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:topGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; - self.statusBarBottomConstraint.active = YES; + if (self.height) { return; } + self.clipsToBounds = YES; + self.height = [self.heightAnchor constraintEqualToConstant:0]; + self.height.active = YES; + [self registerWithNotificationCenter]; } - (void)updateView:(CGFloat)size { @@ -159,38 +132,6 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } } -- (void)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { - - self.animationDelegate = animationDelegate; - dispatch_async(dispatch_get_main_queue(), ^{ - - self.topAlertObject = topAlertObject; - self.topAlertClearspotView = nil; - - UIColor *statusBarColor = nil; - UIStatusBarStyle statusBarStyle = UIStatusBarStyleDefault; - UIView *view = [self topAlertViewForTopAlertObject:topAlertObject animationDelegate:animationDelegate statusBarColor:&statusBarColor statusBarStyle:&statusBarStyle]; - if ([view conformsToProtocol:@protocol(MVMCoreViewProtocol)]) { - [((UIView *)view) updateView:CGRectGetWidth(self.bounds)]; - } - if (!statusBarColor) { - statusBarColor = [UIColor whiteColor]; - } - [self setStatusBarColor:statusBarColor statusBarStyle:statusBarStyle]; - [self showAlertView:view topAlertObject:topAlertObject completionHandler:completionHandler]; - }); -} - -- (void)updateTopAlertWith:(MVMCoreTopAlertObject *)topAlertObject { - [self updateMoleculeWith:topAlertObject]; -} - -- (void)setStatusBarColor:(nullable UIColor *)statusBarColor statusBarStyle:(UIStatusBarStyle)style { - self.statusBarView.backgroundColor = statusBarColor; - self.statusBarStyle = style; - [[MVMCoreUISession sharedGlobal].splitViewController.parentViewController setNeedsStatusBarAppearanceUpdate]; -} - - (void)updateAccessibilityForTopAlert:(nullable UIView *)view { // Update accessibility with top alert if ([view isKindOfClass:[MVMCoreUITopAlertBaseView class]]) { @@ -212,7 +153,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; if (weakSelf.currentAlert.superview) { [weakSelf.currentAlert removeFromSuperview]; } - [weakSelf.alertView addSubview:view]; + [weakSelf addSubview:view]; [NSLayoutConstraint constraintPinSubviewToSuperview:view]; weakSelf.currentAlert = view; @@ -241,6 +182,40 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; [[MVMCoreNavigationHandler sharedNavigationHandler] addNavigationOperation:operation]; } + +/// If the voice over user leaves top alert focus, hide. +- (void)accessibilityFocusChanged:(NSNotification *)notification { + if (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil]; + [self hideAlertView:YES completionHandler:self.hideCompletionHandler]; + self.hideCompletionHandler = nil; + } +} + +#pragma mark - MVMCoreTopAlertViewProtocol + +- (void)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { + + self.animationDelegate = animationDelegate; + dispatch_async(dispatch_get_main_queue(), ^{ + + self.topAlertObject = topAlertObject; + self.topAlertClearspotView = nil; + + UIColor *statusBarColor = nil; + UIStatusBarStyle statusBarStyle = UIStatusBarStyleDefault; + UIView *view = [self topAlertViewForTopAlertObject:topAlertObject animationDelegate:animationDelegate statusBarColor:&statusBarColor statusBarStyle:&statusBarStyle]; + if ([view conformsToProtocol:@protocol(MVMCoreViewProtocol)]) { + [((UIView *)view) updateView:CGRectGetWidth(self.bounds)]; + } + if (statusBarColor) { + self.currentAlertOverridingStatusBar = YES; + [[MVMCoreUISplitViewController mainSplitViewController] setStatusBarBackgroundColor:statusBarColor style:statusBarStyle]; + } + [self showAlertView:view topAlertObject:topAlertObject completionHandler:completionHandler]; + }); +} + - (void)hideAlertView:(BOOL)forceful completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { // If accessible and focused, do not collapse until unfocused. if (!forceful && [MVMCoreUIUtility viewContainsAccessiblityFocus:self]) { @@ -278,9 +253,12 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; completionHandler(finished); } weakSelf.topAlertObject = nil; - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - [weakSelf setStatusBarColor:[UIColor whiteColor] statusBarStyle:UIStatusBarStyleDefault]; - }]; + if (weakSelf.currentAlertOverridingStatusBar) { + weakSelf.currentAlertOverridingStatusBar = NO; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + [[MVMCoreUISplitViewController mainSplitViewController] setStatusBarForCurrentViewController]; + }]; + } }]; }]; }]; @@ -288,6 +266,11 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; [[MVMCoreNavigationHandler sharedNavigationHandler] addNavigationOperation:operation]; } + +- (void)updateTopAlertWith:(MVMCoreTopAlertObject *)topAlertObject { + [self updateMoleculeWith:topAlertObject]; +} + - (void)collapseNotification { if (self.currentAlert) { if ([self.currentAlert isKindOfClass:[MVMCoreUITopAlertExpandableView class]] && ((MVMCoreUITopAlertExpandableView *)self.currentAlert).shortView.label.text.length > 0) { @@ -301,45 +284,8 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } } -- (void)expandStatusBarView { - __weak typeof(self) weakSelf = self; - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - weakSelf.statusBarBottomConstraint.active = YES; - weakSelf.statusBarHeightConstraint.active = NO; - [weakSelf.superview layoutIfNeeded]; - }]; -} - -- (void)collapseStatusBarView { - __weak typeof(self) weakSelf = self; - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - weakSelf.statusBarBottomConstraint.active = NO; - weakSelf.statusBarHeightConstraint.active = YES; - [weakSelf.superview layoutIfNeeded]; - }]; -} - -- (void)resetDefaultBackgroundColor:(UIColor *)backgroundColor basedOnStatusBarStyle:(UIStatusBarStyle)style { - if (!self.topAlertObject) { - UIColor *defaultStatusBarBackgroundColor = backgroundColor; - if (!defaultStatusBarBackgroundColor) { - defaultStatusBarBackgroundColor = style == UIStatusBarStyleDefault ? [UIColor whiteColor] : [UIColor blackColor]; - } - - //color doesn't match the current default value - if (!CGColorEqualToColor(defaultStatusBarBackgroundColor.CGColor, self.statusBarView.backgroundColor.CGColor)) { - self.statusBarView.backgroundColor = defaultStatusBarBackgroundColor; - } - } -} - -/// If the voice over user leaves top alert focus, hide. -- (void)accessibilityFocusChanged:(NSNotification *)notification { - if (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil]; - [self hideAlertView:YES completionHandler:self.hideCompletionHandler]; - self.hideCompletionHandler = nil; - } +- (BOOL)overridingStatusBar { + return self.currentAlertOverridingStatusBar; } @end