From c2f1cd200d6228c1dfb4651208346cf1895b8dc4 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 11 Feb 2021 17:18:06 -0500 Subject: [PATCH] review comments --- MVMCoreUI.xcodeproj/project.pbxproj | 12 ++- MVMCoreUI/Atomic/Atoms/Views/Video.swift | 13 ++-- .../Atomic/Atoms/Views/VideoDataManager.swift | 4 +- MVMCoreUI/Atomic/Atoms/Views/VideoModel.swift | 72 +++++++++++------- .../Atoms/Views/VideoPauseBehavior.swift | 75 ------------------- MVMCoreUI/Behaviors/PageBehavior.swift | 14 +++- .../PageScrolledClosureBehavior.swift | 33 ++++++++ .../PageVisibilityClosureBehavior.swift | 40 ++++++++++ MVMCoreUI/OtherHandlers/CoreUIObject.swift | 2 + MVMCoreUI/Utility/MVMCoreUIUtility.h | 8 ++ MVMCoreUI/Utility/MVMCoreUIUtility.m | 45 +++++++++++ 11 files changed, 200 insertions(+), 118 deletions(-) delete mode 100644 MVMCoreUI/Atomic/Atoms/Views/VideoPauseBehavior.swift create mode 100644 MVMCoreUI/Behaviors/PageScrolledClosureBehavior.swift create mode 100644 MVMCoreUI/Behaviors/PageVisibilityClosureBehavior.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 693b46ce..5c490b5a 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -347,6 +347,8 @@ D23EA800247EBD6C00D60C34 /* LabelBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA7FF247EBD6C00D60C34 /* LabelBarButtonItem.swift */; }; D23EA802247EBED400D60C34 /* ImageBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */; }; D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.swift */; }; + D24918F625D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */; }; + D24918FA25D5ADBB00CAB4B1 /* PageScrolledClosureBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */; }; D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */; }; D2509ED62472EE2F001BFB9D /* NavigationImageButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */; }; D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106423D0CEA700764D80 /* StackModel.swift */; }; @@ -408,7 +410,6 @@ D29C559025C095210082E7D6 /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C558F25C095210082E7D6 /* Video.swift */; }; D29C559325C0992D0082E7D6 /* VideoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C559225C0992D0082E7D6 /* VideoModel.swift */; }; D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C559525C099630082E7D6 /* VideoDataManager.swift */; }; - D29C559C25C20D6D0082E7D6 /* VideoPauseBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C559B25C20D6D0082E7D6 /* VideoPauseBehavior.swift */; }; D29C94D5242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift */; }; D29DF0D121E404D4003B2FB9 /* MVMCoreUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF0E621E4F3C7003B2FB9 /* MVMCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */; }; @@ -898,6 +899,8 @@ D23EA7FF247EBD6C00D60C34 /* LabelBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelBarButtonItem.swift; sourceTree = ""; }; D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBarButtonItem.swift; sourceTree = ""; }; D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageVisibilityClosureBehavior.swift; sourceTree = ""; }; + D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageScrolledClosureBehavior.swift; sourceTree = ""; }; D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = ""; }; D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationImageButtonModel.swift; sourceTree = ""; }; D253BB9B245874F8002DE544 /* BGImageMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMolecule.swift; sourceTree = ""; }; @@ -957,7 +960,6 @@ D29C558F25C095210082E7D6 /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = ""; }; D29C559225C0992D0082E7D6 /* VideoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoModel.swift; sourceTree = ""; }; D29C559525C099630082E7D6 /* VideoDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDataManager.swift; sourceTree = ""; }; - D29C559B25C20D6D0082E7D6 /* VideoPauseBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPauseBehavior.swift; sourceTree = ""; }; D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUICommonViewsUtility+Extension.swift"; sourceTree = ""; }; D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUI.h; sourceTree = ""; }; @@ -1240,6 +1242,8 @@ children = ( 27F973522466074500CAB5C5 /* PageBehavior.swift */, 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */, + D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */, + D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */, ); path = Behaviors; sourceTree = ""; @@ -2038,7 +2042,6 @@ AA07EA902510A442009A2AE3 /* StarModel.swift */, AA07EA922510A451009A2AE3 /* Star.swift */, D29C559525C099630082E7D6 /* VideoDataManager.swift */, - D29C559B25C20D6D0082E7D6 /* VideoPauseBehavior.swift */, D29C559225C0992D0082E7D6 /* VideoModel.swift */, D29C558F25C095210082E7D6 /* Video.swift */, ); @@ -2672,7 +2675,6 @@ 0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */, BB54C5212434D92F0038326C /* ListRightVariableButtonAllTextAndLinksModel.swift in Sources */, D2092349244A51D40044AD09 /* RadioSwatchModel.swift in Sources */, - D29C559C25C20D6D0082E7D6 /* VideoPauseBehavior.swift in Sources */, 0A775F2824893937009EFB58 /* ThreeHeadlineBodyLinkModel.swift in Sources */, 8DD1E370243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift in Sources */, D2EC7BDD2527B83700F540AF /* SectionHeaderFooterView.swift in Sources */, @@ -2707,6 +2709,7 @@ 94C2D9A523872C350006CF46 /* LabelAttributeFontModel.swift in Sources */, 011D958724042492000E3791 /* FormFieldProtocol.swift in Sources */, 011D95AF2407266E000E3791 /* RadioButtonModel.swift in Sources */, + D24918F625D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift in Sources */, D20492A624329CE200A5EED6 /* LoadImageView.swift in Sources */, 017BEB7F23676E870024EF95 /* MoleculeObjectMapping.swift in Sources */, D274CA332236A78900B01B62 /* FooterView.swift in Sources */, @@ -2792,6 +2795,7 @@ 012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */, D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */, 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, + D24918FA25D5ADBB00CAB4B1 /* PageScrolledClosureBehavior.swift in Sources */, 0A51F3E22475CB73002E08B6 /* LoadingSpinnerModel.swift in Sources */, D2169303251E53D9002A6324 /* SectionListTemplateModel.swift in Sources */, 0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Video.swift b/MVMCoreUI/Atomic/Atoms/Views/Video.swift index 6e0fffbc..f869ab06 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Video.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Video.swift @@ -10,13 +10,8 @@ import AVKit open class Video: View { public let videoViewController = AVPlayerViewController() - // TODO: reuse. - private let videoQueue = DispatchQueue(label: "serial.video.queue") - /// Used to track the state and respond.. private var stateKVOToken: NSKeyValueObservation? - - private var visible = true open override func setupView() { super.setupView() @@ -29,6 +24,10 @@ open class Video: View { open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? VideoModel else { return } + if let controller = delegateObject?.moleculeDelegate as? UIViewController { + controller.addChild(videoViewController) + videoViewController.didMove(toParent: controller) + } videoViewController.showsPlaybackControls = model.showControls videoViewController.player = model.videoDataManager.player addStateObserver() @@ -50,8 +49,8 @@ open class Video: View { } // Handle pause behavior - model.addVisibleBehavoir(for: self, delegateObject: delegateObject) - model.addScrollBehavoir(for: self, delegateObject: delegateObject) + model.addVisibleBehavior(for: self, delegateObject: delegateObject) + model.addScrollBehavior(for: self, delegateObject: delegateObject) } /// Listens and responds to video loading state changes. diff --git a/MVMCoreUI/Atomic/Atoms/Views/VideoDataManager.swift b/MVMCoreUI/Atomic/Atoms/Views/VideoDataManager.swift index c616f4b6..046ba3bb 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/VideoDataManager.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/VideoDataManager.swift @@ -7,9 +7,11 @@ // import Foundation +import AVFoundation @objcMembers open class VideoDataManager: NSObject { + /// The state of the video. @objc public enum State: Int { case none case loading @@ -22,7 +24,7 @@ import Foundation // Thread Safe video state handling. private var _videoState = State.none - private let videoStatueQueue = DispatchQueue(label: "concurrent.video.state.queue", attributes: .concurrent) + private let videoStatueQueue = DispatchQueue(label: "com.vzw.mvmcoreui.videoDataManager.state", attributes: .concurrent) /// The state of the video. Use KVO to listen for state changes. @objc public var videoState: State { diff --git a/MVMCoreUI/Atomic/Atoms/Views/VideoModel.swift b/MVMCoreUI/Atomic/Atoms/Views/VideoModel.swift index 0c1033e8..c64b13e5 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/VideoModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/VideoModel.swift @@ -15,12 +15,15 @@ open class VideoModel: MoleculeModelProtocol { public var showControls = false public var autoPlay = true public var alwaysReset = false + + /// When the video is halted because it is no longer visible + private var halted = false /// Keeps a reference to the video data. public var videoDataManager: VideoDataManager - private weak var visibleBehavior: BlockPageVisibilityBehavior? - private weak var scrollBehavior: BlockPageScrolledBehavior? + private weak var visibleBehavior: PageVisibilityClosureBehavior? + private weak var scrollBehavior: PageScrolledClosureBehavior? private enum CodingKeys: String, CodingKey { case moleculeName @@ -59,22 +62,38 @@ open class VideoModel: MoleculeModelProtocol { try container.encode(alwaysReset, forKey: .alwaysReset) } - // Temporary - func isVisible(view: UIView) -> Bool { - return true + private func haltVideo() { + guard !halted else { return } + halted = true + print("halted \(video)") + videoDataManager.player?.pause() } - /// Adds a behavoir to pause the video on page hidden behavoir and unpause if necessary on page shown. - open func addVisibleBehavoir(for view: Video, delegateObject: MVMCoreUIDelegateObject?) { + private func unhaltVideo() { + guard halted else { return } + halted = false + print("unhalted \(video)") + guard videoDataManager.videoState == .loaded else { return } + if alwaysReset { + // Always start video at the beginning. + videoDataManager.player?.seek(to: .zero) + } + if autoPlay { + videoDataManager.player?.play() + } + } + + /// Adds a behavior to pause the video on page hidden behavior and unpause if necessary on page shown. + open func addVisibleBehavior(for view: Video, delegateObject: MVMCoreUIDelegateObject?) { let onShow = { [weak self] in guard let self = self, - self.isVisible(view: view), - self.autoPlay else { return } - self.videoDataManager.player?.play() + let containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view, + MVMCoreUIUtility.isView(view, visibleIn: containingView) else { return } + self.unhaltVideo() } let onHide: () -> Void = { [weak self] in guard let self = self else { return } - self.videoDataManager.player?.pause() + self.haltVideo() } guard visibleBehavior == nil else { @@ -83,25 +102,24 @@ open class VideoModel: MoleculeModelProtocol { return } - guard let delegate = delegateObject?.moleculeDelegate, - let page = delegate as? PageProtocol, - var pageModel = page.pageModel as? PageBehaviorsTemplateProtocol else { return } - let pauseBehavior = BlockPageVisibilityBehavior(with: onShow, onPageHiddenHandler: onHide) - pageModel.add(behavior: pauseBehavior) + guard var delegate = delegateObject?.behaviorTemplateDelegate else { return } + let pauseBehavior = PageVisibilityClosureBehavior(with: onShow, onPageHiddenHandler: onHide) + delegate.add(behavior: pauseBehavior) self.visibleBehavior = pauseBehavior } - /// Adds a behavoir to pause the video if scrolled off of the page and unpause if necessary if scrolled on. - open func addScrollBehavoir(for view: Video, delegateObject: MVMCoreUIDelegateObject?) { + /// Adds a behavior to pause the video if scrolled off of the page and unpause if necessary if scrolled on. + open func addScrollBehavior(for view: Video, delegateObject: MVMCoreUIDelegateObject?) { let onScroll = { [weak self] (scrollView: UIScrollView) in // If visible to not visible, pause video. // If not visible to visible, unpause if needed, add visible behavior - guard let self = self else { return } - if !self.isVisible(view: view) { - self.videoDataManager.player?.pause() - } else if self.autoPlay { - self.videoDataManager.player?.play() + guard let self = self, + let containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view else { return } + if !MVMCoreUIUtility.isView(view, visibleIn: containingView) { + self.haltVideo() + } else { + self.unhaltVideo() } } @@ -110,11 +128,9 @@ open class VideoModel: MoleculeModelProtocol { return } - guard let delegate = delegateObject?.moleculeDelegate, - let page = delegate as? PageProtocol, - var pageModel = page.pageModel as? PageBehaviorsTemplateProtocol else { return } - let scrollBehavior = BlockPageScrolledBehavior(with: onScroll) - pageModel.add(behavior: scrollBehavior) + guard var delegate = delegateObject?.behaviorTemplateDelegate else { return } + let scrollBehavior = PageScrolledClosureBehavior(with: onScroll) + delegate.add(behavior: scrollBehavior) self.scrollBehavior = scrollBehavior } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/VideoPauseBehavior.swift b/MVMCoreUI/Atomic/Atoms/Views/VideoPauseBehavior.swift deleted file mode 100644 index 1524381e..00000000 --- a/MVMCoreUI/Atomic/Atoms/Views/VideoPauseBehavior.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// VideoPauseBehavior.swift -// MVMCoreUI -// -// Created by Scott Pfeil on 1/27/21. -// Copyright © 2021 Verizon Wireless. All rights reserved. -// - -import Foundation - -public class BlockPageVisibilityBehavior: PageVisibilityBehavior { - - public static var identifier = "blockPageVisibilityBehavior" - - public var pageShownHandler: () -> Void - public var pageHiddenHandler: () -> Void - - public init(with onPageShownHandler: @escaping () -> Void, onPageHiddenHandler: @escaping () -> Void) { - self.pageShownHandler = onPageShownHandler - self.pageHiddenHandler = onPageHiddenHandler - } - - private enum CodingKeys: String, CodingKey { - case behaviorName - } - - // This class is not meant to be decoded and encoded really. - public required init(from decoder: Decoder) throws { - pageShownHandler = {} - pageHiddenHandler = {} - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(behaviorName, forKey: .behaviorName) - } - - //MARK:- PageVisibilityBehavior - public func onPageShown() { - pageShownHandler() - } - - public func onPageHidden() { - pageHiddenHandler() - } -} - -public class BlockPageScrolledBehavior: PageScrolledBehavior { - - public static var identifier = "blockPageScrolledBehavior" - - public var pageScrolledHandler: (_ scrollView: UIScrollView) -> Void - - public init(with onPageScrolledHandler: @escaping (_ scrollView: UIScrollView) -> Void) { - self.pageScrolledHandler = onPageScrolledHandler - } - - private enum CodingKeys: String, CodingKey { - case behaviorName - } - - // This class is not meant to be decoded and encoded really. - public required init(from decoder: Decoder) throws { - pageScrolledHandler = { (scrollView) in } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(behaviorName, forKey: .behaviorName) - } - - public func pageScrolled(scrollView: UIScrollView) { - pageScrolledHandler(scrollView) - } -} diff --git a/MVMCoreUI/Behaviors/PageBehavior.swift b/MVMCoreUI/Behaviors/PageBehavior.swift index 59e41031..aaf915a1 100644 --- a/MVMCoreUI/Behaviors/PageBehavior.swift +++ b/MVMCoreUI/Behaviors/PageBehavior.swift @@ -48,8 +48,16 @@ public protocol PageBehaviorsTemplateProtocol { public extension PageBehaviorsTemplateProtocol { mutating func add(behavior: PageBehaviorProtocol) { - var behavoirs = behaviors ?? [] - behavoirs.append(behavior) - self.behaviors = behavoirs + var newBehaviors = behaviors ?? [] + newBehaviors.append(behavior) + self.behaviors = newBehaviors + } +} + +public extension MVMCoreUIDelegateObject { + weak var behaviorTemplateDelegate: (PageBehaviorsTemplateProtocol & NSObjectProtocol)? { + get { + return (moleculeDelegate as? PageProtocol)?.pageModel as? (PageBehaviorsTemplateProtocol & NSObjectProtocol) + } } } diff --git a/MVMCoreUI/Behaviors/PageScrolledClosureBehavior.swift b/MVMCoreUI/Behaviors/PageScrolledClosureBehavior.swift new file mode 100644 index 00000000..c43d4ecc --- /dev/null +++ b/MVMCoreUI/Behaviors/PageScrolledClosureBehavior.swift @@ -0,0 +1,33 @@ +// +// PageScrolledClosureBehavior.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 2/11/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class PageScrolledClosureBehavior: PageScrolledBehavior { + + public static var identifier = "pageScrolledClosureBehavior" + + public var pageScrolledHandler: (_ scrollView: UIScrollView) -> Void + + public init(with onPageScrolledHandler: @escaping (_ scrollView: UIScrollView) -> Void) { + self.pageScrolledHandler = onPageScrolledHandler + } + + // This class is not meant to be decoded and encoded really. + public required init(from decoder: Decoder) throws { + throw ModelRegistry.Error.decoderOther(message: "PageScrolledClosureBehavior does not decode.") + } + + public func encode(to encoder: Encoder) throws { + throw ModelRegistry.Error.decoderOther(message: "PageScrolledClosureBehavior does not encode.") + } + + public func pageScrolled(scrollView: UIScrollView) { + pageScrolledHandler(scrollView) + } +} diff --git a/MVMCoreUI/Behaviors/PageVisibilityClosureBehavior.swift b/MVMCoreUI/Behaviors/PageVisibilityClosureBehavior.swift new file mode 100644 index 00000000..f5ecd82c --- /dev/null +++ b/MVMCoreUI/Behaviors/PageVisibilityClosureBehavior.swift @@ -0,0 +1,40 @@ +// +// PageVisibilityClosureBehavior.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 2/11/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class PageVisibilityClosureBehavior: PageVisibilityBehavior { + + public static var identifier = "pageVisibilityClosureBehavior" + + public var pageShownHandler: () -> Void + public var pageHiddenHandler: () -> Void + + public init(with onPageShownHandler: @escaping () -> Void, onPageHiddenHandler: @escaping () -> Void) { + self.pageShownHandler = onPageShownHandler + self.pageHiddenHandler = onPageHiddenHandler + } + + // This class is not meant to be decoded and encoded really. + public required init(from decoder: Decoder) throws { + throw ModelRegistry.Error.decoderOther(message: "PageVisibilityClosureBehavior does not decode.") + } + + public func encode(to encoder: Encoder) throws { + throw ModelRegistry.Error.decoderOther(message: "PageVisibilityClosureBehavior does not encode.") + } + + //MARK:- PageVisibilityBehavior + public func onPageShown() { + pageShownHandler() + } + + public func onPageHidden() { + pageHiddenHandler() + } +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index 3a4d2cbc..90532f98 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -13,6 +13,7 @@ import UIKit public var globalTopAlertDelegate: MVMCoreGlobalTopAlertDelegateProtocol? open override func defaultInitialSetup() { + loadHandler = MVMCoreLoadHandler() cache = MVMCoreCache() sessionHandler = MVMCoreSessionTimeHandler() actionHandler = MVMCoreActionHandler() @@ -21,5 +22,6 @@ import UIKit loggingDelegate = MVMCoreUILoggingHandler() moleculeMap = MoleculeObjectMapping() MoleculeObjectMapping.registerObjects() + clientParameterRegistry = ClientParameterRegistry() } } diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.h b/MVMCoreUI/Utility/MVMCoreUIUtility.h index 4bf6e8d5..74b19d0e 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.h +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.h @@ -37,6 +37,14 @@ NS_ASSUME_NONNULL_BEGIN /// Checks if the view or any descendents of the view is currently focused for voice over. + (BOOL)viewContainsAccessiblityFocus:(nonnull UIView *)view; ++ (BOOL)isView:(nonnull UIView *)view visibleIn:(nonnull UIView *)rootView; + ++ (BOOL)isViewVisibleInParent:(nonnull UIView *)view; + ++ (BOOL)doesView:(nonnull UIView *)overlappingView cover:(nonnull UIView *)view; + ++ (BOOL)isViewTransparent:(nonnull UIView *)view; + #pragma mark - Setters + (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom; diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index fed0bb30..fad2378c 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -96,6 +96,51 @@ return containsFocus; } ++ (BOOL)isView:(nonnull UIView *)view visibleIn:(nonnull UIView *)rootView { + return [view isDescendantOfView:rootView] && [self isViewVisibleInParent:view]; +} + +// Climb up the tree. Break early. ++ (BOOL)isViewVisibleInParent:(nonnull UIView *)view { + UIView *superview = view.superview; + UIView *ancestor = view; + // Begin climbing its ancestor views, checking if it remains visible in *each* of their bounds and if the children at views closer to the front block the visibility of this view. + while (superview) { + if (superview.clipsToBounds && !CGRectIntersectsRect(superview.bounds, [view convertRect:view.bounds toView:superview])) { + return false; + } else { + // Check the superview's children up to the common ancestor. (Reworking the ancestor would put us in an infinite loop and we want to ignore the children with a lower z-order.) + for (UIView *subview in [superview.subviews reverseObjectEnumerator]) { + if (subview == ancestor) { + break; + } else if ([self doesView:subview cover:view]) { + return false; + } + } + } + ancestor = superview; + superview = superview.superview; + } + return true; +} + +// Climb down the tree. ++ (BOOL)doesView:(nonnull UIView *)overlappingView cover:(nonnull UIView *)view { + if (![self isViewTransparent:overlappingView] && CGRectContainsRect(overlappingView.bounds, [view convertRect:view.bounds toView:overlappingView])) { + return true; + } + for (UIView *subview in overlappingView.subviews) { + if ([self doesView:subview cover:view]) { + return true; + } + } + return false; +} + ++ (BOOL)isViewTransparent:(nonnull UIView *)view { + return view.alpha < 1 || view.backgroundColor == nil || CGColorGetAlpha(view.backgroundColor.CGColor) < 1; +} + #pragma mark - Setters + (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom {