I think the magic you are looking for is
func getTopViewController(_ viewController: UIViewController? = UIApplication.shared.delegate?.window??.rootViewController) -> UIViewController? {
if let tabBarViewController = viewController as? UITabBarController {
return getTopViewController(tabBarViewController.selectedViewController)
} else if let navigationController = viewController as? UINavigationController {
return getTopViewController(navigationController.visibleViewController)
} else if let presentedViewController = viewController?.presentedViewController {
return getTopViewController(presentedViewController)
} else {
return viewController
}
}
I hate using it because it does not adhere to Apple best practices, but in a pinch it will work for simpler storyboards. You can use this view controllers view to add a message to
if let viewController = getTopViewController() {
let toastLabel = UILabel(frame: frame)
// set up labels background color and other properties...
toastLabel.text = "your message"
// add the subview to the rootViewController
viewController.view.addSubview(toastLabel)
// easy animation
UIView.animate(withDuration: 0.4, delay: duration, options: .curveEaseOut, animations: {
toastLabel.alpha = 0.0
}, completion: {(isCompleted) in
toastLabel.removeFromSuperview()
})
} else {
print("Unable to get top view controller.")
}
Make sure to use this only in the main dispatch queue
DispatchQueue.main.async {
// ...
}
Below are a couple of techniques to make this more module and object orientated.
One technique is to use a static Toast class.
import UIKit
public class Toast {
private init() { }
public static var frame = CGRect(
x: UIScreen.main.bounds.size.width/2 - (UIScreen.main.bounds.size.width/2 - 16),
y: UIScreen.main.bounds.size.height - 100,
width: UIScreen.main.bounds.size.width - 32,
height: 35
)
public static var backgroundColor = UIColor.black.withAlphaComponent(0.6)
public static var textColor = UIColor.white
public static var textAlignment = NSTextAlignment.center
public static var font = UIFont.systemFont(ofSize: 12.0)
public static var alpha:CGFloat = 1.0
public static var cornerRadius:CGFloat = 10.0;
public static func makeToast(
message: String,
duration: TimeInterval = 4.0,
completion: ((_ complete:Bool)->Void)? = nil
) {
if let viewController = getTopViewController() {
let toastLabel = UILabel(frame: Toast.frame)
toastLabel.backgroundColor = Toast.backgroundColor
toastLabel.textColor = Toast.textColor
toastLabel.textAlignment = Toast.textAlignment;
toastLabel.font = Toast.font
toastLabel.alpha = Toast.alpha
toastLabel.layer.cornerRadius = Toast.cornerRadius
toastLabel.clipsToBounds = true
toastLabel.text = message
viewController.view.addSubview(toastLabel)
UIView.animate(withDuration: 0.4, delay: duration, options: .curveEaseOut, animations: {
toastLabel.alpha = 0.0
}, completion: {(isCompleted) in
toastLabel.removeFromSuperview()
completion?(isCompleted)
})
} else {
print("Unable to get top view controller.")
}
}
private static func getTopViewController(_ viewController: UIViewController? = UIApplication.shared.delegate?.window??.rootViewController) -> UIViewController? {
if let tabBarViewController = viewController as? UITabBarController {
return getTopViewController(tabBarViewController.selectedViewController)
} else if let navigationController = viewController as? UINavigationController {
return getTopViewController(navigationController.visibleViewController)
} else if let presentedViewController = viewController?.presentedViewController {
return getTopViewController(presentedViewController)
} else {
return viewController
}
}
}
Usage:
Toast.makeToast(message: "This is a test", duration: 4.0) { (isCompleted) in
print("completed: \(isCompleted)")
}
// or
Toast.makeToast(message: "This is a test", duration: 4.0)
// or just
Toast.makeToast(message: "This is a test")
You can set the frame, backgroundColor, textColor, textAlignment, font, alpha, and cornerRadius using the static variables like so:
Toast.frame = CGRect(
x: UIScreen.main.bounds.size.width/2 - (UIScreen.main.bounds.size.width/2 - 16),
y: UIScreen.main.bounds.size.height - 100,
width: UIScreen.main.bounds.size.width - 32,
height: 35
)
Toast.backgroundColor = UIColor.blue
Toast.textColor = UIColor.green
Toast.textAlignment = .left
Toast.font = UIFont.systemFont(ofSize: 14.0)
Toast.alpha = 0.8
Toast.cornerRadius = 8.0
Another technique is to extend the UIApplication.
import UIKit
extension UIApplication {
public func makeToast(
message: String,
duration: TimeInterval = 4.0,
frame:CGRect = CGRect(
x: UIScreen.main.bounds.size.width/2 - (UIScreen.main.bounds.size.width/2 - 16),
y: UIScreen.main.bounds.size.height - 100,
width: UIScreen.main.bounds.size.width - 32,
height: 35
),
backgroundColor:UIColor = UIColor.black.withAlphaComponent(0.6),
textColor:UIColor = UIColor.white,
textAlignment:NSTextAlignment = .center,
font:UIFont = UIFont.systemFont(ofSize: 12.0),
alpha:CGFloat = 1.0,
cornerRadius:CGFloat = 10,
completion: ((_ complete:Bool)->Void)? = nil
) {
if let viewController = self.getTopViewController(self.delegate?.window??.rootViewController) {
let toastLabel = UILabel(frame: frame)
toastLabel.backgroundColor = backgroundColor
toastLabel.textColor = textColor
toastLabel.textAlignment = textAlignment;
toastLabel.font = font
toastLabel.alpha = alpha
toastLabel.layer.cornerRadius = cornerRadius
toastLabel.clipsToBounds = true
toastLabel.text = message
viewController.view.addSubview(toastLabel)
UIView.animate(withDuration: 0.4, delay: duration, options: .curveEaseOut, animations: {
toastLabel.alpha = 0.0
}, completion: {(isCompleted) in
toastLabel.removeFromSuperview()
completion?(isCompleted)
})
} else {
print("Unable to get top view controller.")
}
}
private func getTopViewController(_ viewController: UIViewController?) -> UIViewController? {
if let tabBarViewController = viewController as? UITabBarController {
return getTopViewController(tabBarViewController.selectedViewController)
} else if let navigationController = viewController as? UINavigationController {
return getTopViewController(navigationController.visibleViewController)
} else if let presentedViewController = viewController?.presentedViewController {
return getTopViewController(presentedViewController)
} else {
return viewController
}
}
}
Usage:
UIApplication.shared.makeToast(message: "This is another test", duration: 4.0) { (isCompleted) in
print("completed: \(isCompleted)")
}
You can set the frame, backgroundColor, textColor, textAlignment, font, alpha, and cornerRadius by passing them in to the function with their tags:
UIApplication.shared.makeToast(
message: "This is another test",
duration: 4.0,
frame: CGRect(
x: UIScreen.main.bounds.size.width/2 - (UIScreen.main.bounds.size.width/2 - 16),
y: UIScreen.main.bounds.size.height - 100,
width: UIScreen.main.bounds.size.width - 32,
height: 35
),
backgroundColor: UIColor.blue.withAlphaComponent(0.6),
textColor: UIColor.red,
textAlignment: NSTextAlignment.left,
font: UIFont.systemFont(ofSize: 16.0),
alpha: 1.0,
cornerRadius: 10
) { (isCompleted) in
print("completed: \(isCompleted)")
}
Download the example
Quick gist