merge
This commit is contained in:
commit
a79ceb85f8
@ -347,6 +347,8 @@
|
|||||||
D23EA800247EBD6C00D60C34 /* LabelBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA7FF247EBD6C00D60C34 /* LabelBarButtonItem.swift */; };
|
D23EA800247EBD6C00D60C34 /* LabelBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA7FF247EBD6C00D60C34 /* LabelBarButtonItem.swift */; };
|
||||||
D23EA802247EBED400D60C34 /* ImageBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */; };
|
D23EA802247EBED400D60C34 /* ImageBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */; };
|
||||||
D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.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 */; };
|
D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */; };
|
||||||
D2509ED62472EE2F001BFB9D /* NavigationImageButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */; };
|
D2509ED62472EE2F001BFB9D /* NavigationImageButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */; };
|
||||||
D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106423D0CEA700764D80 /* StackModel.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 */; };
|
D29C559025C095210082E7D6 /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C558F25C095210082E7D6 /* Video.swift */; };
|
||||||
D29C559325C0992D0082E7D6 /* VideoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C559225C0992D0082E7D6 /* VideoModel.swift */; };
|
D29C559325C0992D0082E7D6 /* VideoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C559225C0992D0082E7D6 /* VideoModel.swift */; };
|
||||||
D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C559525C099630082E7D6 /* VideoDataManager.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 */; };
|
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, ); }; };
|
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 */; };
|
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 = "<group>"; };
|
D23EA7FF247EBD6C00D60C34 /* LabelBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelBarButtonItem.swift; sourceTree = "<group>"; };
|
||||||
D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBarButtonItem.swift; sourceTree = "<group>"; };
|
D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBarButtonItem.swift; sourceTree = "<group>"; };
|
||||||
D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
|
D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
|
||||||
|
D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageVisibilityClosureBehavior.swift; sourceTree = "<group>"; };
|
||||||
|
D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageScrolledClosureBehavior.swift; sourceTree = "<group>"; };
|
||||||
D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = "<group>"; };
|
D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationImageButtonModel.swift; sourceTree = "<group>"; };
|
D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationImageButtonModel.swift; sourceTree = "<group>"; };
|
||||||
D253BB9B245874F8002DE544 /* BGImageMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMolecule.swift; sourceTree = "<group>"; };
|
D253BB9B245874F8002DE544 /* BGImageMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMolecule.swift; sourceTree = "<group>"; };
|
||||||
@ -957,7 +960,6 @@
|
|||||||
D29C558F25C095210082E7D6 /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = "<group>"; };
|
D29C558F25C095210082E7D6 /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = "<group>"; };
|
||||||
D29C559225C0992D0082E7D6 /* VideoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoModel.swift; sourceTree = "<group>"; };
|
D29C559225C0992D0082E7D6 /* VideoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoModel.swift; sourceTree = "<group>"; };
|
||||||
D29C559525C099630082E7D6 /* VideoDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDataManager.swift; sourceTree = "<group>"; };
|
D29C559525C099630082E7D6 /* VideoDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDataManager.swift; sourceTree = "<group>"; };
|
||||||
D29C559B25C20D6D0082E7D6 /* VideoPauseBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPauseBehavior.swift; sourceTree = "<group>"; };
|
|
||||||
D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUICommonViewsUtility+Extension.swift"; sourceTree = "<group>"; };
|
D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUICommonViewsUtility+Extension.swift"; sourceTree = "<group>"; };
|
||||||
D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUI.h; sourceTree = "<group>"; };
|
||||||
@ -1240,6 +1242,8 @@
|
|||||||
children = (
|
children = (
|
||||||
27F973522466074500CAB5C5 /* PageBehavior.swift */,
|
27F973522466074500CAB5C5 /* PageBehavior.swift */,
|
||||||
27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */,
|
27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */,
|
||||||
|
D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */,
|
||||||
|
D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */,
|
||||||
);
|
);
|
||||||
path = Behaviors;
|
path = Behaviors;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2038,7 +2042,6 @@
|
|||||||
AA07EA902510A442009A2AE3 /* StarModel.swift */,
|
AA07EA902510A442009A2AE3 /* StarModel.swift */,
|
||||||
AA07EA922510A451009A2AE3 /* Star.swift */,
|
AA07EA922510A451009A2AE3 /* Star.swift */,
|
||||||
D29C559525C099630082E7D6 /* VideoDataManager.swift */,
|
D29C559525C099630082E7D6 /* VideoDataManager.swift */,
|
||||||
D29C559B25C20D6D0082E7D6 /* VideoPauseBehavior.swift */,
|
|
||||||
D29C559225C0992D0082E7D6 /* VideoModel.swift */,
|
D29C559225C0992D0082E7D6 /* VideoModel.swift */,
|
||||||
D29C558F25C095210082E7D6 /* Video.swift */,
|
D29C558F25C095210082E7D6 /* Video.swift */,
|
||||||
);
|
);
|
||||||
@ -2672,7 +2675,6 @@
|
|||||||
0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */,
|
0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */,
|
||||||
BB54C5212434D92F0038326C /* ListRightVariableButtonAllTextAndLinksModel.swift in Sources */,
|
BB54C5212434D92F0038326C /* ListRightVariableButtonAllTextAndLinksModel.swift in Sources */,
|
||||||
D2092349244A51D40044AD09 /* RadioSwatchModel.swift in Sources */,
|
D2092349244A51D40044AD09 /* RadioSwatchModel.swift in Sources */,
|
||||||
D29C559C25C20D6D0082E7D6 /* VideoPauseBehavior.swift in Sources */,
|
|
||||||
0A775F2824893937009EFB58 /* ThreeHeadlineBodyLinkModel.swift in Sources */,
|
0A775F2824893937009EFB58 /* ThreeHeadlineBodyLinkModel.swift in Sources */,
|
||||||
8DD1E370243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift in Sources */,
|
8DD1E370243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift in Sources */,
|
||||||
D2EC7BDD2527B83700F540AF /* SectionHeaderFooterView.swift in Sources */,
|
D2EC7BDD2527B83700F540AF /* SectionHeaderFooterView.swift in Sources */,
|
||||||
@ -2707,6 +2709,7 @@
|
|||||||
94C2D9A523872C350006CF46 /* LabelAttributeFontModel.swift in Sources */,
|
94C2D9A523872C350006CF46 /* LabelAttributeFontModel.swift in Sources */,
|
||||||
011D958724042492000E3791 /* FormFieldProtocol.swift in Sources */,
|
011D958724042492000E3791 /* FormFieldProtocol.swift in Sources */,
|
||||||
011D95AF2407266E000E3791 /* RadioButtonModel.swift in Sources */,
|
011D95AF2407266E000E3791 /* RadioButtonModel.swift in Sources */,
|
||||||
|
D24918F625D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift in Sources */,
|
||||||
D20492A624329CE200A5EED6 /* LoadImageView.swift in Sources */,
|
D20492A624329CE200A5EED6 /* LoadImageView.swift in Sources */,
|
||||||
017BEB7F23676E870024EF95 /* MoleculeObjectMapping.swift in Sources */,
|
017BEB7F23676E870024EF95 /* MoleculeObjectMapping.swift in Sources */,
|
||||||
D274CA332236A78900B01B62 /* FooterView.swift in Sources */,
|
D274CA332236A78900B01B62 /* FooterView.swift in Sources */,
|
||||||
@ -2792,6 +2795,7 @@
|
|||||||
012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */,
|
012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */,
|
||||||
D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */,
|
D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */,
|
||||||
0AE14F64238315D2005417F8 /* TextField.swift in Sources */,
|
0AE14F64238315D2005417F8 /* TextField.swift in Sources */,
|
||||||
|
D24918FA25D5ADBB00CAB4B1 /* PageScrolledClosureBehavior.swift in Sources */,
|
||||||
0A51F3E22475CB73002E08B6 /* LoadingSpinnerModel.swift in Sources */,
|
0A51F3E22475CB73002E08B6 /* LoadingSpinnerModel.swift in Sources */,
|
||||||
D2169303251E53D9002A6324 /* SectionListTemplateModel.swift in Sources */,
|
D2169303251E53D9002A6324 /* SectionListTemplateModel.swift in Sources */,
|
||||||
0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */,
|
0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */,
|
||||||
|
|||||||
@ -10,18 +10,9 @@ import AVKit
|
|||||||
open class Video: View {
|
open class Video: View {
|
||||||
public let videoViewController = AVPlayerViewController()
|
public let videoViewController = AVPlayerViewController()
|
||||||
|
|
||||||
// TODO: reuse.
|
|
||||||
private let videoQueue = DispatchQueue(label: "serial.video.queue")
|
|
||||||
|
|
||||||
/// Used to track the state and respond..
|
/// Used to track the state and respond..
|
||||||
private var stateKVOToken: NSKeyValueObservation?
|
private var stateKVOToken: NSKeyValueObservation?
|
||||||
|
|
||||||
private weak var pauseBehavior: BlockPageVisibilityBehavior?
|
|
||||||
|
|
||||||
private weak var scrollBehavior: BlockPageScrolledBehavior?
|
|
||||||
|
|
||||||
private var visible = true
|
|
||||||
|
|
||||||
open override func setupView() {
|
open override func setupView() {
|
||||||
super.setupView()
|
super.setupView()
|
||||||
videoViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
videoViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
@ -33,6 +24,10 @@ open class Video: View {
|
|||||||
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||||
super.set(with: model, delegateObject, additionalData)
|
super.set(with: model, delegateObject, additionalData)
|
||||||
guard let model = model as? VideoModel else { return }
|
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.showsPlaybackControls = model.showControls
|
||||||
videoViewController.player = model.videoDataManager.player
|
videoViewController.player = model.videoDataManager.player
|
||||||
addStateObserver()
|
addStateObserver()
|
||||||
@ -54,8 +49,8 @@ open class Video: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle pause behavior
|
// Handle pause behavior
|
||||||
addVisibleBehavoir(to: model, delegateObject: delegateObject)
|
model.addVisibleBehavior(for: self, delegateObject: delegateObject)
|
||||||
addScrolledBehavoir(to: model, delegateObject: delegateObject)
|
model.addScrollBehavior(for: self, delegateObject: delegateObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Listens and responds to video loading state changes.
|
/// Listens and responds to video loading state changes.
|
||||||
@ -101,62 +96,4 @@ open class Video: View {
|
|||||||
deinit {
|
deinit {
|
||||||
removeStateObserver()
|
removeStateObserver()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a behavoir to pause the video on page hidden behavoir and unpause if necessary on page shown.
|
|
||||||
open func addVisibleBehavoir(to model: VideoModel, delegateObject: MVMCoreUIDelegateObject?) {
|
|
||||||
let onShow = { [weak self] in
|
|
||||||
guard self?.visible == true,
|
|
||||||
let model = self?.model as? VideoModel,
|
|
||||||
model.autoPlay else { return }
|
|
||||||
model.videoDataManager.player?.play()
|
|
||||||
}
|
|
||||||
let onHide: () -> Void = { [weak self] in
|
|
||||||
guard self?.visible == true,
|
|
||||||
let model = self?.model as? VideoModel else { return }
|
|
||||||
model.videoDataManager.player?.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
if pauseBehavior != nil {
|
|
||||||
pauseBehavior?.pageShownHandler = onShow
|
|
||||||
pauseBehavior?.pageHiddenHandler = onHide
|
|
||||||
} else {
|
|
||||||
guard let delegate = delegateObject?.moleculeDelegate,
|
|
||||||
let page = delegate as? PageProtocol,
|
|
||||||
var pageModel = page.pageModel as? PageBehaviorsTemplateProtocol else { return }
|
|
||||||
var behavoirs = pageModel.behaviors ?? []
|
|
||||||
let pauseBehavior = BlockPageVisibilityBehavior(with: onShow, onPageHiddenHandler: onHide)
|
|
||||||
behavoirs.append(pauseBehavior)
|
|
||||||
pageModel.behaviors = behavoirs
|
|
||||||
self.pauseBehavior = pauseBehavior
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open func addScrolledBehavoir(to model: VideoModel, 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
|
|
||||||
// visible ==
|
|
||||||
// if !visible {
|
|
||||||
// pause
|
|
||||||
// } else {
|
|
||||||
// let model = self?.model as? VideoModel,
|
|
||||||
// model.autoPlay else { return }
|
|
||||||
// model.videoDataManager.player?.play()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
if scrollBehavior != nil {
|
|
||||||
scrollBehavior?.pageScrolledHandler = onScroll
|
|
||||||
} else {
|
|
||||||
guard let delegate = delegateObject?.moleculeDelegate,
|
|
||||||
let page = delegate as? PageProtocol,
|
|
||||||
var pageModel = page.pageModel as? PageBehaviorsTemplateProtocol else { return }
|
|
||||||
var behavoirs = pageModel.behaviors ?? []
|
|
||||||
let scrollBehavior = BlockPageScrolledBehavior(with: onScroll)
|
|
||||||
behavoirs.append(scrollBehavior)
|
|
||||||
pageModel.behaviors = behavoirs
|
|
||||||
self.scrollBehavior = scrollBehavior
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,9 +7,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
@objcMembers open class VideoDataManager: NSObject {
|
@objcMembers open class VideoDataManager: NSObject {
|
||||||
|
|
||||||
|
/// The state of the video.
|
||||||
@objc public enum State: Int {
|
@objc public enum State: Int {
|
||||||
case none
|
case none
|
||||||
case loading
|
case loading
|
||||||
@ -22,7 +24,7 @@ import Foundation
|
|||||||
|
|
||||||
// Thread Safe video state handling.
|
// Thread Safe video state handling.
|
||||||
private var _videoState = State.none
|
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.
|
/// The state of the video. Use KVO to listen for state changes.
|
||||||
@objc public var videoState: State {
|
@objc public var videoState: State {
|
||||||
@ -47,8 +49,12 @@ import Foundation
|
|||||||
|
|
||||||
private var kvoToken: NSKeyValueObservation?
|
private var kvoToken: NSKeyValueObservation?
|
||||||
|
|
||||||
|
private var memoryWarningListener: Any?
|
||||||
|
|
||||||
public init(with videoURLString: String) {
|
public init(with videoURLString: String) {
|
||||||
self.videoURLString = videoURLString
|
self.videoURLString = videoURLString
|
||||||
|
super.init()
|
||||||
|
self.addMemoryWarningListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadVideo() {
|
public func loadVideo() {
|
||||||
@ -114,7 +120,22 @@ import Foundation
|
|||||||
kvoToken = nil
|
kvoToken = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func addMemoryWarningListener() {
|
||||||
|
memoryWarningListener = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
|
||||||
|
self?.removeVideoObserver()
|
||||||
|
self?.player = nil
|
||||||
|
self?.videoState = .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removeMemoryWarningListener() {
|
||||||
|
guard let observer = memoryWarningListener else { return }
|
||||||
|
NotificationCenter.default.removeObserver(observer)
|
||||||
|
memoryWarningListener = nil
|
||||||
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
removeVideoObserver()
|
removeVideoObserver()
|
||||||
|
removeMemoryWarningListener()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,9 +16,15 @@ open class VideoModel: MoleculeModelProtocol {
|
|||||||
public var autoPlay = true
|
public var autoPlay = true
|
||||||
public var alwaysReset = false
|
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.
|
/// Keeps a reference to the video data.
|
||||||
public var videoDataManager: VideoDataManager
|
public var videoDataManager: VideoDataManager
|
||||||
|
|
||||||
|
private weak var visibleBehavior: PageVisibilityClosureBehavior?
|
||||||
|
private weak var scrollBehavior: PageScrolledClosureBehavior?
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case moleculeName
|
case moleculeName
|
||||||
case video
|
case video
|
||||||
@ -35,7 +41,6 @@ open class VideoModel: MoleculeModelProtocol {
|
|||||||
required public init(from decoder: Decoder) throws {
|
required public init(from decoder: Decoder) throws {
|
||||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
video = try typeContainer.decode(String.self, forKey:.video)
|
video = try typeContainer.decode(String.self, forKey:.video)
|
||||||
videoDataManager = VideoDataManager(with: video)
|
|
||||||
if let showControls = try typeContainer.decodeIfPresent(Bool.self, forKey: .showControls) {
|
if let showControls = try typeContainer.decodeIfPresent(Bool.self, forKey: .showControls) {
|
||||||
self.showControls = showControls
|
self.showControls = showControls
|
||||||
}
|
}
|
||||||
@ -45,6 +50,7 @@ open class VideoModel: MoleculeModelProtocol {
|
|||||||
if let alwaysReset = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysReset) {
|
if let alwaysReset = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysReset) {
|
||||||
self.alwaysReset = alwaysReset
|
self.alwaysReset = alwaysReset
|
||||||
}
|
}
|
||||||
|
videoDataManager = VideoDataManager(with: video)
|
||||||
}
|
}
|
||||||
|
|
||||||
open func encode(to encoder: Encoder) throws {
|
open func encode(to encoder: Encoder) throws {
|
||||||
@ -55,4 +61,76 @@ open class VideoModel: MoleculeModelProtocol {
|
|||||||
try container.encode(autoPlay, forKey: .autoPlay)
|
try container.encode(autoPlay, forKey: .autoPlay)
|
||||||
try container.encode(alwaysReset, forKey: .alwaysReset)
|
try container.encode(alwaysReset, forKey: .alwaysReset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func haltVideo() {
|
||||||
|
guard !halted else { return }
|
||||||
|
halted = true
|
||||||
|
print("halted \(video)")
|
||||||
|
videoDataManager.player?.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
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.haltVideo()
|
||||||
|
}
|
||||||
|
|
||||||
|
guard visibleBehavior == nil else {
|
||||||
|
visibleBehavior?.pageShownHandler = onShow
|
||||||
|
visibleBehavior?.pageHiddenHandler = onHide
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard var delegate = delegateObject?.behaviorTemplateDelegate else { return }
|
||||||
|
let pauseBehavior = PageVisibilityClosureBehavior(with: onShow, onPageHiddenHandler: onHide)
|
||||||
|
delegate.add(behavior: pauseBehavior)
|
||||||
|
self.visibleBehavior = pauseBehavior
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
let containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view else { return }
|
||||||
|
if !MVMCoreUIUtility.isView(view, visibleIn: containingView) {
|
||||||
|
self.haltVideo()
|
||||||
|
} else {
|
||||||
|
self.unhaltVideo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard scrollBehavior == nil else {
|
||||||
|
scrollBehavior?.pageScrolledHandler = onScroll
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard var delegate = delegateObject?.behaviorTemplateDelegate else { return }
|
||||||
|
let scrollBehavior = PageScrolledClosureBehavior(with: onScroll)
|
||||||
|
delegate.add(behavior: scrollBehavior)
|
||||||
|
self.scrollBehavior = scrollBehavior
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,110 +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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class VideoPauseBehavior: PageVisibilityBehavior {
|
|
||||||
|
|
||||||
public static var identifier = "videoPause"
|
|
||||||
|
|
||||||
public weak var videoModel: VideoModel?
|
|
||||||
public init(with videoModel: VideoModel) {
|
|
||||||
self.videoModel = videoModel
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
|
||||||
case behaviorName
|
|
||||||
}
|
|
||||||
|
|
||||||
//MARK:- PageVisibilityBehavior
|
|
||||||
|
|
||||||
public func onPageShown() {
|
|
||||||
// TODO: Only do this if attached to a visible view...
|
|
||||||
if videoModel?.autoPlay == true {
|
|
||||||
videoModel?.videoDataManager.player?.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func onPageHidden() {
|
|
||||||
videoModel?.videoDataManager.player?.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
public required init(from decoder: Decoder) throws {}
|
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
||||||
try container.encode(behaviorName, forKey: .behaviorName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -27,6 +27,12 @@ open class BGImageMoleculeModel: MoleculeContainerModel {
|
|||||||
if bottomPadding == nil {
|
if bottomPadding == nil {
|
||||||
bottomPadding = PaddingDefaultVerticalSpacing3
|
bottomPadding = PaddingDefaultVerticalSpacing3
|
||||||
}
|
}
|
||||||
|
if image.contentMode == nil {
|
||||||
|
image.contentMode = .scaleAspectFill
|
||||||
|
}
|
||||||
|
if image.imageFormat == nil {
|
||||||
|
image.imageFormat = "jpg"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
|||||||
@ -43,4 +43,21 @@ public protocol PageScrolledBehavior: PageBehaviorProtocol {
|
|||||||
public protocol PageBehaviorsTemplateProtocol {
|
public protocol PageBehaviorsTemplateProtocol {
|
||||||
|
|
||||||
var behaviors: [PageBehaviorProtocol]? { get set }
|
var behaviors: [PageBehaviorProtocol]? { get set }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension PageBehaviorsTemplateProtocol {
|
||||||
|
mutating func add(behavior: PageBehaviorProtocol) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
MVMCoreUI/Behaviors/PageScrolledClosureBehavior.swift
Normal file
33
MVMCoreUI/Behaviors/PageScrolledClosureBehavior.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
40
MVMCoreUI/Behaviors/PageVisibilityClosureBehavior.swift
Normal file
40
MVMCoreUI/Behaviors/PageVisibilityClosureBehavior.swift
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ import UIKit
|
|||||||
public var globalTopAlertDelegate: MVMCoreGlobalTopAlertDelegateProtocol?
|
public var globalTopAlertDelegate: MVMCoreGlobalTopAlertDelegateProtocol?
|
||||||
|
|
||||||
open override func defaultInitialSetup() {
|
open override func defaultInitialSetup() {
|
||||||
|
loadHandler = MVMCoreLoadHandler()
|
||||||
cache = MVMCoreCache()
|
cache = MVMCoreCache()
|
||||||
sessionHandler = MVMCoreSessionTimeHandler()
|
sessionHandler = MVMCoreSessionTimeHandler()
|
||||||
actionHandler = MVMCoreActionHandler()
|
actionHandler = MVMCoreActionHandler()
|
||||||
@ -21,5 +22,6 @@ import UIKit
|
|||||||
loggingDelegate = MVMCoreUILoggingHandler()
|
loggingDelegate = MVMCoreUILoggingHandler()
|
||||||
moleculeMap = MoleculeObjectMapping()
|
moleculeMap = MoleculeObjectMapping()
|
||||||
MoleculeObjectMapping.registerObjects()
|
MoleculeObjectMapping.registerObjects()
|
||||||
|
clientParameterRegistry = ClientParameterRegistry()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,6 +37,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
/// Checks if the view or any descendents of the view is currently focused for voice over.
|
/// Checks if the view or any descendents of the view is currently focused for voice over.
|
||||||
+ (BOOL)viewContainsAccessiblityFocus:(nonnull UIView *)view;
|
+ (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
|
#pragma mark - Setters
|
||||||
|
|
||||||
+ (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom;
|
+ (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom;
|
||||||
|
|||||||
@ -96,6 +96,51 @@
|
|||||||
return containsFocus;
|
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
|
#pragma mark - Setters
|
||||||
|
|
||||||
+ (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom {
|
+ (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user