diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/PresentationDisplayUseCase.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/PresentationDisplayUseCase.swift index 62301b96..165416fd 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/PresentationDisplayUseCase.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/PresentationDisplayUseCase.swift @@ -16,7 +16,7 @@ final class PresentationDisplayUseCase { private var model: InAppFormData? private var factory: ViewFactoryProtocol? private var tracker: InAppMessagesTrackerProtocol - + init(tracker: InAppMessagesTrackerProtocol) { self.tracker = tracker } @@ -26,10 +26,12 @@ final class PresentationDisplayUseCase { changeType(model: model.content) guard let window = presentationStrategy?.getWindow() else { - Logger.common(message: "In-app modal window creating failed") + Logger.common(message: "In-app window creating failed") return } + Logger.common(message: "PresentationDisplayUseCase window: \(window)") + guard let factory = self.factory else { Logger.common(message: "Factory does not exists.", level: .error, category: .general) return @@ -46,6 +48,11 @@ final class PresentationDisplayUseCase { } presentedVC = viewController + + if let image = model.imagesDict[model.firstImageValue] { + presentationStrategy?.setupWindowFrame(model: model.content, imageSize: image.size) + } + presentationStrategy?.present(id: model.inAppId, in: window, using: viewController) } diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/ModalPresentationStrategy.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/ModalPresentationStrategy.swift index c92fa2be..eb3a38c4 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/ModalPresentationStrategy.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/ModalPresentationStrategy.swift @@ -10,7 +10,7 @@ import UIKit import MindboxLogger final class ModalPresentationStrategy: PresentationStrategyProtocol { - var inappWindow: UIWindow? + var window: UIWindow? func getWindow() -> UIWindow? { return makeInAppMessageWindow() @@ -28,6 +28,10 @@ final class ModalPresentationStrategy: PresentationStrategyProtocol { Logger.common(message: "In-app modal presentation dismissed", level: .debug, category: .inAppMessages) } + func setupWindowFrame(model: MindboxFormVariant, imageSize: CGSize) { + // Not need to setup. + } + private func makeInAppMessageWindow() -> UIWindow? { let window: UIWindow? if #available(iOS 13.0, *) { @@ -35,8 +39,8 @@ final class ModalPresentationStrategy: PresentationStrategyProtocol { } else { window = UIWindow(frame: UIScreen.main.bounds) } - self.inappWindow = window - window?.windowLevel = UIWindow.Level.normal + self.window = window + window?.windowLevel = .normal + 3 window?.isHidden = false return window } diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/PresentationStrategyProtocol.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/PresentationStrategyProtocol.swift index b7621250..806b8424 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/PresentationStrategyProtocol.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/PresentationStrategyProtocol.swift @@ -9,7 +9,10 @@ import UIKit protocol PresentationStrategyProtocol { + var window: UIWindow? { get set } + func getWindow() -> UIWindow? func present(id: String, in window: UIWindow, using viewController: UIViewController) func dismiss(viewController: UIViewController) + func setupWindowFrame(model: MindboxFormVariant, imageSize: CGSize) } diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift index 2360e009..97388213 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift @@ -10,19 +10,112 @@ import UIKit import MindboxLogger final class SnackbarPresentationStrategy: PresentationStrategyProtocol { - func getWindow() -> UIWindow? { - return UIApplication.shared.windows.first(where: { $0.isKeyWindow }) + + enum Constants { + static let oneThirdScreenHeight: CGFloat = UIScreen.main.bounds.height / 3.0 } + var window: UIWindow? + + func getWindow() -> UIWindow? { + let window: UIWindow + let screenBounds = UIScreen.main.bounds + let windowFrame = CGRect(x: 0, y: 0, width: screenBounds.width, height: screenBounds.height) + Logger.common(message: "SnackbarPresentationStrategy getWindow started.") + if #available(iOS 13, *) { + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { + window = UIWindow(windowScene: windowScene) + } else { + window = UIWindow(frame: UIScreen.main.bounds) + } + } else { + window = UIWindow(frame: UIScreen.main.bounds) + } + + window.frame = windowFrame + window.backgroundColor = .clear + window.windowLevel = .normal + 3 + let viewController = UIViewController() + window.rootViewController = viewController + viewController.view.frame = windowFrame + window.isHidden = false + self.window = window + return window + } + func present(id: String, in window: UIWindow, using viewController: UIViewController) { - window.rootViewController?.addChild(viewController) - window.rootViewController?.view.addSubview(viewController.view) - Logger.common(message: "In-app with id \(id) presented", level: .info, category: .inAppMessages) + if var topController = window.rootViewController { + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } + + viewController.view.frame = topController.view.frame + topController.addChild(viewController) + topController.view.addSubview(viewController.view) + + viewController.view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + viewController.view.topAnchor.constraint(equalTo: topController.view.topAnchor), + viewController.view.bottomAnchor.constraint(equalTo: topController.view.bottomAnchor), + viewController.view.leadingAnchor.constraint(equalTo: topController.view.leadingAnchor), + viewController.view.trailingAnchor.constraint(equalTo: topController.view.trailingAnchor) + ]) + + viewController.didMove(toParent: topController) + Logger.common(message: "In-app snackbar with id \(id) presented", level: .info, category: .inAppMessages) + } else { + Logger.common(message: "Unable to get top controller. Abort.", level: .error, category: .inAppMessages) + } } func dismiss(viewController: UIViewController) { viewController.view.removeFromSuperview() viewController.removeFromParent() - Logger.common(message: "InApp presentation dismissed", level: .debug, category: .inAppMessages) + Logger.common(message: "In-app snackbar presentation dismissed", level: .debug, category: .inAppMessages) + } + + func setupWindowFrame(model: MindboxFormVariant, imageSize: CGSize) { + switch model { + case .snackbar(let snackbarFormVariant): + if let gravity = snackbarFormVariant.content.position.gravity?.vertical { + let leftOffset = snackbarFormVariant.content.position.margin.left + let rightOffset = snackbarFormVariant.content.position.margin.right + let width = UIScreen.main.bounds.width - leftOffset - rightOffset + let heightMultiplier = width / imageSize.width + let imageHeight = imageSize.height * heightMultiplier + let finalHeight = (imageHeight < Constants.oneThirdScreenHeight) ? imageHeight : Constants.oneThirdScreenHeight + let safeAreaInset = getSafeAreaInset(gravity: gravity) + let y = getYPosition(gravity: gravity, finalHeight: finalHeight, safeAreaInset: safeAreaInset) + self.window?.frame = CGRect(x: leftOffset, y: y, width: width, height: finalHeight) + } + default: + break + } + } +} + +private extension SnackbarPresentationStrategy { + func getSafeAreaInset(gravity: GravityVerticalType) -> CGFloat { + var safeAreaInset: CGFloat = 0 + if #available(iOS 11, *) { + if gravity == .bottom { + safeAreaInset = window?.safeAreaInsets.bottom ?? 0 + } else if gravity == .top { + safeAreaInset = window?.safeAreaInsets.top ?? 0 + } + } + + return safeAreaInset + } + + func getYPosition(gravity: GravityVerticalType, finalHeight: CGFloat, safeAreaInset: CGFloat) -> CGFloat { + var y = UIScreen.main.bounds.height - finalHeight + if gravity == .bottom { + y = UIScreen.main.bounds.height - finalHeight - safeAreaInset + } else if gravity == .top { + y = safeAreaInset + } + + return y } } diff --git a/Mindbox/InAppMessages/Presentation/Views/CommonViews/InAppImageOnlyView.swift b/Mindbox/InAppMessages/Presentation/Views/CommonViews/InAppImageOnlyView.swift index f8e008af..d991fa97 100644 --- a/Mindbox/InAppMessages/Presentation/Views/CommonViews/InAppImageOnlyView.swift +++ b/Mindbox/InAppMessages/Presentation/Views/CommonViews/InAppImageOnlyView.swift @@ -6,6 +6,7 @@ // import UIKit +import MindboxLogger final class InAppImageOnlyView: UIView { var onClose: (() -> Void)? @@ -29,9 +30,12 @@ final class InAppImageOnlyView: UIView { func customInit() { guard let image = image else { + Logger.common(message: "[Error]: \(#function) at line \(#line) of \(#file)", level: .error) return } + Logger.common(message: "InAppImageOnlyView custom init started.") + imageView.contentMode = .scaleAspectFill imageView.image = image diff --git a/Mindbox/InAppMessages/Presentation/Views/CommonViews/LayersFactory/ImageLayer/ImageLayerFactory.swift b/Mindbox/InAppMessages/Presentation/Views/CommonViews/LayersFactory/ImageLayer/ImageLayerFactory.swift index e185019a..eda52d9d 100644 --- a/Mindbox/InAppMessages/Presentation/Views/CommonViews/LayersFactory/ImageLayer/ImageLayerFactory.swift +++ b/Mindbox/InAppMessages/Presentation/Views/CommonViews/LayersFactory/ImageLayer/ImageLayerFactory.swift @@ -7,6 +7,7 @@ // import UIKit +import MindboxLogger class ImageLayerFactory: LayerFactory { func create(from image: UIImage, layer: ContentBackgroundLayer, in view: UIView, with controller: GestureHandler) -> UIView? { @@ -14,10 +15,11 @@ class ImageLayerFactory: LayerFactory { let inAppView = InAppImageOnlyView(image: image, action: imageContentBackgroundLayer.action) let imageTapGestureRecognizer = UITapGestureRecognizer(target: controller, action: #selector(controller.imageTapped(_:))) inAppView.addGestureRecognizer(imageTapGestureRecognizer) - + Logger.common(message: "ImageLayerFactory return uiView.") return inAppView } - + + Logger.common(message: "ImageLayerFactory return nil.") return nil } @@ -39,5 +41,6 @@ class ImageLayerFactory: LayerFactory { view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor), view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor) ]) + Logger.common(message: "ImageLayerFactory setupConstraintsSnackbar finished.") } } diff --git a/Mindbox/InAppMessages/Presentation/Views/ModalView/ModalViewController.swift b/Mindbox/InAppMessages/Presentation/Views/ModalView/ModalViewController.swift index 40702691..969b3a78 100644 --- a/Mindbox/InAppMessages/Presentation/Views/ModalView/ModalViewController.swift +++ b/Mindbox/InAppMessages/Presentation/Views/ModalView/ModalViewController.swift @@ -71,6 +71,10 @@ final class ModalViewController: UIViewController, InappViewControllerProtocol, override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() + elements.forEach({ + $0.removeFromSuperview() + }) + setupElements() } diff --git a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift index 486f7b32..93bd7aab 100644 --- a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift +++ b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift @@ -6,6 +6,7 @@ // import UIKit +import MindboxLogger class SnackbarView: UIView { @@ -39,7 +40,7 @@ class SnackbarView: UIView { self.onClose = onClose self.animationTime = animationTime super.init(frame: .zero) - + Logger.common(message: "SnackbarView inited.") setupPanGesture() } @@ -72,6 +73,7 @@ class SnackbarView: UIView { if ((swipeDirection == .up && translation.y < 0) || (swipeDirection == .down && translation.y > 0)) && abs(translation.y) > threshold { animateHide(completion: onClose, animated: true) + } else { UIView.animate(withDuration: animationTime) { self.transform = .identity @@ -113,6 +115,7 @@ class SnackbarView: UIView { } required init?(coder: NSCoder) { + Logger.common(message: "SnackbarView init(coder:) has not been implemented.") fatalError("init(coder:) has not been implemented") } } diff --git a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift index 9e2e189d..a3c45664 100644 --- a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift +++ b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift @@ -10,8 +10,7 @@ import UIKit import MindboxLogger class SnackbarViewController: UIViewController, InappViewControllerProtocol { - - var snackbarView: SnackbarView? + var edgeConstraint: NSLayoutConstraint? let model: SnackbarFormVariant @@ -35,9 +34,9 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { } private let imagesDict: [String: UIImage] + let snackbarView: SnackbarView private let firstImageValue: String private let onPresented: () -> Void - private let onClose: () -> Void private let onTapAction: (ContentBackgroundLayerAction?) -> Void private var hasSetupLayers = false @@ -52,33 +51,32 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { init( model: SnackbarFormVariant, imagesDict: [String: UIImage], + snackbarView: SnackbarView, firstImageValue: String, onPresented: @escaping () -> Void, - onTapAction: @escaping (ContentBackgroundLayerAction?) -> Void, - onClose: @escaping () -> Void + onTapAction: @escaping (ContentBackgroundLayerAction?) -> Void ) { self.model = model self.imagesDict = imagesDict + self.snackbarView = snackbarView self.firstImageValue = firstImageValue self.onPresented = onPresented - self.onClose = onClose self.onTapAction = onTapAction super.init(nibName: nil, bundle: nil) + Logger.common(message: "SnackbarViewController inited.") } required init?(coder: NSCoder) { + Logger.common(message: "SnackbarViewController init(coder:) has not been implemented.") fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() + view.addSubview(snackbarView) view.isUserInteractionEnabled = true - snackbarView = SnackbarView(onClose: onClose) - if let snackbarView = snackbarView { - snackbarView.translatesAutoresizingMaskIntoConstraints = false - snackbarView.isUserInteractionEnabled = true - view.addSubview(snackbarView) - } + snackbarView.translatesAutoresizingMaskIntoConstraints = false + snackbarView.isUserInteractionEnabled = true } override func viewDidLayoutSubviews() { @@ -88,11 +86,11 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { setupConstraints() setupLayers() - if snackbarView?.bounds.size != .zero { + if snackbarView.bounds.size != .zero { setupElements() hasSetupElements = true } - } else if !hasSetupElements && snackbarView?.bounds.size != .zero { + } else if !hasSetupElements && snackbarView.bounds.size != .zero { UIView.performWithoutAnimation { setupElements() hasSetupElements = true @@ -109,10 +107,7 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { private func setupLayers() { let layers = model.content.background.layers - guard let snackbarView = snackbarView else { - return - } - + for layer in layers { if let factory = layersFactories[layer.layerType] { if case .image(let imageContentBackgroundLayer) = layer { @@ -129,11 +124,7 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { } } - private func setupElements() { - guard let snackbarView = snackbarView else { - return - } - + private func setupElements() { for element in model.content.elements { if let factory = elementFactories[element.elementType] { let elementView = factory.create(from: element, with: self) @@ -161,18 +152,16 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { func setupConstraints() { guard let image = imagesDict[firstImageValue] else { + Logger.common(message: "[Error]: \(#function) at line \(#line) of \(#file)", level: .error) return } - let width = view.layer.frame.width - leftOffset - rightOffset + let width = view.layer.frame.width let heightMultiplier = width / image.size.width let imageHeight = image.size.height * heightMultiplier let finalHeight = (imageHeight < Constants.oneThirdScreenHeight) ? imageHeight : Constants.oneThirdScreenHeight setViewFrame(with: finalHeight) - guard let snackbarView = snackbarView else { - return - } snackbarView.swipeDirection = swipeDirection snackbarView.translatesAutoresizingMaskIntoConstraints = false @@ -186,17 +175,15 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { } func setupLayoutConstraints(with height: CGFloat) { - guard let snackbarView = snackbarView else { - return - } - if #available(iOS 11.0, *) { + Logger.common(message: "SnackbarViewController setupLayoutConstraints iOS 11+.") NSLayoutConstraint.activate([ snackbarView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), snackbarView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), snackbarView.heightAnchor.constraint(equalToConstant: height), ]) } else { + Logger.common(message: "SnackbarViewController setupLayoutConstraints iOS 10.") NSLayoutConstraint.activate([ snackbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), snackbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), @@ -213,6 +200,7 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { extension SnackbarViewController: GestureHandler { @objc func imageTapped(_ sender: UITapGestureRecognizer) { guard let imageView = sender.view as? InAppImageOnlyView else { + Logger.common(message: "[Error]: \(#function) at line \(#line) of \(#file)", level: .error) return } @@ -222,13 +210,14 @@ extension SnackbarViewController: GestureHandler { @objc func onCloseButton(_ gesture: UILongPressGestureRecognizer) { guard let crossView = gesture.view else { + Logger.common(message: "[Error]: \(#function) at line \(#line) of \(#file)", level: .error) return } let location = gesture.location(in: crossView) let isInsideCrossView = crossView.bounds.contains(location) if gesture.state == .ended && isInsideCrossView { - snackbarView?.hide() + snackbarView.hide() } } } @@ -253,9 +242,9 @@ class TopSnackbarViewController: SnackbarViewController { override func setupEdgeConstraint(with height: CGFloat) { if #available(iOS 11.0, *) { - edgeConstraint = snackbarView?.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: -height) + edgeConstraint = snackbarView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: -height) } else { - edgeConstraint = snackbarView?.topAnchor.constraint(equalTo: view.topAnchor, constant: -height) + edgeConstraint = snackbarView.topAnchor.constraint(equalTo: view.topAnchor, constant: -height) } edgeConstraint?.isActive = true @@ -280,13 +269,16 @@ class BottomSnackbarViewController: SnackbarViewController { self.view.frame = CGRect(x: leftOffset, y: screenHeight - finalHeight, width: UIScreen.main.bounds.width - leftOffset - rightOffset, height: finalHeight) + Logger.common(message: "SnackbarViewController setViewFrame function finished.") } override func setupEdgeConstraint(with height: CGFloat) { if #available(iOS 11.0, *) { - edgeConstraint = snackbarView?.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: height) + Logger.common(message: "SnackbarViewController setupEdgeConstraint iOS 11+.") + edgeConstraint = snackbarView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: height) } else { - edgeConstraint = snackbarView?.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: height) + Logger.common(message: "SnackbarViewController setupEdgeConstraint iOS 10.") + edgeConstraint = snackbarView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: height) } edgeConstraint?.isActive = true diff --git a/Mindbox/InAppMessages/Presentation/Views/ViewFactory/SnackbarViewFactory.swift b/Mindbox/InAppMessages/Presentation/Views/ViewFactory/SnackbarViewFactory.swift index 2750ca6c..b009d91f 100644 --- a/Mindbox/InAppMessages/Presentation/Views/ViewFactory/SnackbarViewFactory.swift +++ b/Mindbox/InAppMessages/Presentation/Views/ViewFactory/SnackbarViewFactory.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +import MindboxLogger class SnackbarViewFactory: ViewFactoryProtocol { @@ -17,20 +18,24 @@ class SnackbarViewFactory: ViewFactoryProtocol { if case .snackbar(let snackbarFormVariant) = model { if let gravity = snackbarFormVariant.content.position.gravity?.vertical { var snackbarViewController: UIViewController? + let snackbarView = SnackbarView(onClose: onClose) switch gravity { case .top: - snackbarViewController = TopSnackbarViewController(model: snackbarFormVariant, imagesDict: imagesDict, firstImageValue: firstImageValue, onPresented: onPresented, onTapAction: onTapAction, onClose: onClose) + snackbarViewController = TopSnackbarViewController(model: snackbarFormVariant, imagesDict: imagesDict, snackbarView: snackbarView, firstImageValue: firstImageValue, onPresented: onPresented, onTapAction: onTapAction) case .bottom: - snackbarViewController = BottomSnackbarViewController(model: snackbarFormVariant, imagesDict: imagesDict, firstImageValue: firstImageValue, onPresented: onPresented, onTapAction: onTapAction, onClose: onClose) + snackbarViewController = BottomSnackbarViewController(model: snackbarFormVariant, imagesDict: imagesDict, snackbarView: snackbarView, firstImageValue: firstImageValue, onPresented: onPresented, onTapAction: onTapAction) default: + Logger.common(message: "SnackbarViewFactory controller is nil.") return nil } self.viewController = snackbarViewController + return viewController } } - + + Logger.common(message: "SnackbarViewFactory create returns nil.") return nil } } diff --git a/Mindbox/Info.plist b/Mindbox/Info.plist index ff4d699a..c6cff490 100644 --- a/Mindbox/Info.plist +++ b/Mindbox/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 4895 + 4897