Skip to content

Commit

Permalink
iOS 10309 crouton on screen (#382)
Browse files Browse the repository at this point in the history
* IOS-10309: Force crouton on exact screen

* IOS-10309: Unify show functions

* Run swiftformat

* Remove comments and added assert

* Fix autoclosure on tests

---------

Co-authored-by: WanaldinoTelefonica <WanaldinoTelefonica@users.noreply.github.com>
  • Loading branch information
WanaldinoTelefonica and WanaldinoTelefonica authored Jul 26, 2024
1 parent bee78df commit 8e1bd80
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
import UIKit

public class CroutonController: NSObject {
public enum RootViewController {
public typealias Closure = () -> UIViewController?
public static let `default`: Closure = { UIApplication.shared.windows.filter(\.isKeyWindow).first?.rootViewController }
}

public typealias Token = UUID
public typealias ActionConfig = (text: String, accessibilityLabel: String?, handler: DidTapActionBlock)
fileprivate typealias OngoingCrouton = (token: Token, croutonView: CroutonView, rootViewController: () -> UIViewController?)

public typealias DismissHandlerBlock = (SnackbarDismissReason) -> Void
public typealias DidTapActionBlock = () -> Void
Expand All @@ -27,27 +31,6 @@ public class CroutonController: NSObject {
// MARK: Public functions

public extension CroutonController {
/// Show a crouton (or enqueue one if there is already a crouton shown)
/// - Parameters:
/// - text: The text to display in the crouton
/// - action: An optional action which will show a button with the given title and invoke the handler when the button is pressed
/// - style: The style of the crouton, `.info` by default
/// - dismissHandler: A handler which is called when the handler is removed from the screen
@available(iOSApplicationExtension, unavailable)
@discardableResult
func showCrouton(
config: SnackbarConfig,
style: CroutonStyle = .info,
dismissHandler: DismissHandlerBlock? = nil
) -> Token {
showCrouton(
config: config,
style: style,
dismissHandler: dismissHandler,
rootViewController: UIApplication.shared.windows.filter(\.isKeyWindow).first?.rootViewController
)
}

/// Show a crouton (or enqueue one if there is already a crouton shown)
/// - Parameters:
/// - text: The text to display in the crouton
Expand All @@ -60,7 +43,8 @@ public extension CroutonController {
config: SnackbarConfig,
style: CroutonStyle = .info,
dismissHandler: DismissHandlerBlock? = nil,
rootViewController: @escaping @autoclosure () -> UIViewController?
exactViewController: UIViewController? = nil,
rootViewController: RootViewController.Closure? = nil
) -> Token {
assertMainThread()

Expand All @@ -84,15 +68,21 @@ public extension CroutonController {
}

let token = Token()
let crouton = CroutonView(
let croutonView = CroutonView(
text: config.title,
action: overwrittenAction,
config: styleConfig,
dismissHandler: dismissHandler,
forceDismiss: config.forceDismiss
)

show(crouton, token: token, rootViewController: rootViewController)
let ongoingCrouton = OngoingCrouton(
token: token,
croutonView: croutonView,
exactViewController: exactViewController,
rootViewController: rootViewController
)
show(ongoingCrouton)

return token
}
Expand Down Expand Up @@ -125,9 +115,8 @@ public extension CroutonController {
// MARK: Private methods

private extension CroutonController {
func show(_ crouton: CroutonView, token: Token, rootViewController: @escaping () -> UIViewController?) {
enqueue(OngoingCrouton(token: token, croutonView: crouton, rootViewController: rootViewController))

func show(_ crouton: OngoingCrouton) {
enqueue(crouton)
showEnqueuedCrouton()
}

Expand Down Expand Up @@ -165,62 +154,10 @@ private extension CroutonController {
func showEnqueuedCrouton() {
guard showingToken == nil else { return }
guard let ongoingCrouton = croutonViewList.first else { return }
guard let containerView = visibleContainerView(ongoingCrouton.rootViewController) else { return }
guard let containerView = ongoingCrouton.view() else { return }

showingToken = ongoingCrouton.token

ongoingCrouton.croutonView.show(in: containerView)
}

func visibleContainerView(_ rootViewController: () -> UIViewController?) -> UIView? {
guard let viewController = visibleViewController(rootViewController) else { return nil }

if let viewController = viewController as? CustomCroutonContainer {
return viewController.customCroutonContainerView
} else {
return viewController.view
}
}

func visibleViewController(_ rootViewController: () -> UIViewController?) -> UIViewController? {
guard let rootViewController = rootViewController() else {
assertionFailure("Root view controller not found!")
return nil
}

let visibleVC = visibleViewController(from: rootViewController)

if let rootVC = visibleVC as? UINavigationController {
return visibleViewController(from: rootVC.topViewController!)
} else if let homeVC = visibleVC.parent?.tabBarController,
let selectedNavigationController = homeVC.selectedViewController as? UINavigationController {
return visibleViewController(from: selectedNavigationController.topViewController!)
} else {
return visibleVC
}
}

func visibleViewController(from viewController: UIViewController) -> UIViewController {
if let presentedViewController = viewController.presentedViewController {
if presentedViewController is UIAlertController {
return viewController
} else {
return visibleViewController(from: presentedViewController)
}
} else if let viewController = viewController as? UINavigationController {
if let topViewController = viewController.topViewController {
return visibleViewController(from: topViewController)
} else {
return viewController
}
} else if let tabViewController = viewController as? UITabBarController {
if let selectedTab = tabViewController.selectedViewController {
return selectedTab
} else {
return viewController
}
}

return viewController
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// OngoingCrouton.swift
//
// Made with ❤️ by Novum
//
// Copyright © Telefonica. All rights reserved.
//

import UIKit

extension CroutonController {
struct OngoingCrouton {
let token: Token
let croutonView: CroutonView
private weak var exactViewController: UIViewController?
private let rootViewController: RootViewController.Closure

init(
token: Token,
croutonView: CroutonView,
exactViewController: UIViewController? = nil,
rootViewController: RootViewController.Closure? = nil
) {
self.token = token
self.croutonView = croutonView
self.exactViewController = exactViewController
self.rootViewController = rootViewController ?? RootViewController.default
}

func view() -> UIView? {
guard let viewController = viewController() else { return nil }

if let viewController = viewController as? CustomCroutonContainer {
return viewController.customCroutonContainerView
} else {
return viewController.view
}
}

private func viewController() -> UIViewController? {
if let exactViewController {
return exactViewController
} else if let rootViewController = rootViewController() {
return visibleViewController(from: rootViewController)
} else {
assertionFailure("Root view controller not found!")
return nil
}
}

private func visibleViewController(from viewController: UIViewController) -> UIViewController {
if let presentedViewController = viewController.presentedViewController {
if presentedViewController is UIAlertController {
return viewController
} else {
return visibleViewController(from: presentedViewController)
}
} else if let viewController = viewController as? UINavigationController {
if let topViewController = viewController.topViewController {
return visibleViewController(from: topViewController)
} else {
return viewController
}
} else if let tabViewController = viewController as? UITabBarController {
if let selectedTab = tabViewController.selectedViewController {
return selectedTab
} else {
return viewController
}
}

return viewController
}
}
}
2 changes: 1 addition & 1 deletion Tests/MisticaTests/UI/CroutonTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private class CroutonTestViewController: UIViewController {
CroutonController().showCrouton(
config: config,
style: style,
rootViewController: self
rootViewController: { self }
)
}
}
Expand Down

0 comments on commit 8e1bd80

Please sign in to comment.