5 // Copyright (c) 2015-2017 Charles Scalesse.
7 // Permission is hereby granted, free of charge, to any person obtaining a
8 // copy of this software and associated documentation files (the
9 // "Software"), to deal in the Software without restriction, including
10 // without limitation the rights to use, copy, modify, merge, publish,
11 // distribute, sublicense, and/or sell copies of the Software, and to
12 // permit persons to whom the Software is furnished to do so, subject to
13 // the following conditions:
15 // The above copyright notice and this permission notice shall be included
16 // in all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 Toast is a Swift extension that adds toast notifications to the `UIView` object class.
31 It is intended to be simple, lightweight, and easy to use. Most toast notifications
32 can be triggered with a single line of code.
34 The `makeToast` methods create a new view and then display it as toast.
36 The `showToast` methods display any view as toast.
39 public extension UIView {
42 Keys used for associated objects.
44 private struct ToastKeys {
45 static var timer = "com.toast-swift.timer"
46 static var duration = "com.toast-swift.duration"
47 static var point = "com.toast-swift.point"
48 static var completion = "com.toast-swift.completion"
49 static var activeToasts = "com.toast-swift.activeToasts"
50 static var activityView = "com.toast-swift.activityView"
51 static var queue = "com.toast-swift.queue"
55 Swift closures can't be directly associated with objects via the
56 Objective-C runtime, so the (ugly) solution is to wrap them in a
57 class that can be used with associated objects.
59 private class ToastCompletionWrapper {
60 let completion: ((Bool) -> Void)?
62 init(_ completion: ((Bool) -> Void)?) {
63 self.completion = completion
67 private enum ToastError: Error {
68 case missingParameters
71 private var activeToasts: NSMutableArray {
73 if let activeToasts = objc_getAssociatedObject(self, &ToastKeys.activeToasts) as? NSMutableArray {
76 let activeToasts = NSMutableArray()
77 objc_setAssociatedObject(self, &ToastKeys.activeToasts, activeToasts, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
83 private var queue: NSMutableArray {
85 if let queue = objc_getAssociatedObject(self, &ToastKeys.queue) as? NSMutableArray {
88 let queue = NSMutableArray()
89 objc_setAssociatedObject(self, &ToastKeys.queue, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
95 // MARK: - Make Toast Methods
98 Creates and presents a new toast view.
100 @param message The message to be displayed
101 @param duration The toast duration
102 @param position The toast's position
103 @param title The title
104 @param image The image
105 @param style The style. The shared style will be used when nil
106 @param completion The completion closure, executed after the toast view disappears.
107 didTap will be `true` if the toast view was dismissed from a tap.
109 public func makeToast(_ message: String?, duration: TimeInterval = ToastManager.shared.duration, position: ToastPosition = ToastManager.shared.position, title: String? = nil, image: UIImage? = nil, style: ToastStyle = ToastManager.shared.style, completion: ((_ didTap: Bool) -> Void)? = nil) {
111 let toast = try toastViewForMessage(message, title: title, image: image, style: style)
112 showToast(toast, duration: duration, position: position, completion: completion)
113 } catch ToastError.missingParameters {
114 print("Error: message, title, and image are all nil")
119 Creates a new toast view and presents it at a given center point.
121 @param message The message to be displayed
122 @param duration The toast duration
123 @param point The toast's center point
124 @param title The title
125 @param image The image
126 @param style The style. The shared style will be used when nil
127 @param completion The completion closure, executed after the toast view disappears.
128 didTap will be `true` if the toast view was dismissed from a tap.
130 public func makeToast(_ message: String?, duration: TimeInterval = ToastManager.shared.duration, point: CGPoint, title: String?, image: UIImage?, style: ToastStyle = ToastManager.shared.style, completion: ((_ didTap: Bool) -> Void)?) {
132 let toast = try toastViewForMessage(message, title: title, image: image, style: style)
133 showToast(toast, duration: duration, point: point, completion: completion)
134 } catch ToastError.missingParameters {
135 print("Error: message, title, and image cannot all be nil")
139 // MARK: - Show Toast Methods
142 Displays any view as toast at a provided position and duration. The completion closure
143 executes when the toast view completes. `didTap` will be `true` if the toast view was
144 dismissed from a tap.
146 @param toast The view to be displayed as toast
147 @param duration The notification duration
148 @param position The toast's position
149 @param completion The completion block, executed after the toast view disappears.
150 didTap will be `true` if the toast view was dismissed from a tap.
152 public func showToast(_ toast: UIView, duration: TimeInterval = ToastManager.shared.duration, position: ToastPosition = ToastManager.shared.position, completion: ((_ didTap: Bool) -> Void)? = nil) {
153 let point = position.centerPoint(forToast: toast, inSuperview: self)
154 showToast(toast, duration: duration, point: point, completion: completion)
158 Displays any view as toast at a provided center point and duration. The completion closure
159 executes when the toast view completes. `didTap` will be `true` if the toast view was
160 dismissed from a tap.
162 @param toast The view to be displayed as toast
163 @param duration The notification duration
164 @param point The toast's center point
165 @param completion The completion block, executed after the toast view disappears.
166 didTap will be `true` if the toast view was dismissed from a tap.
168 public func showToast(_ toast: UIView, duration: TimeInterval = ToastManager.shared.duration, point: CGPoint, completion: ((_ didTap: Bool) -> Void)? = nil) {
169 objc_setAssociatedObject(toast, &ToastKeys.completion, ToastCompletionWrapper(completion), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
171 if ToastManager.shared.isQueueEnabled, activeToasts.count > 0 {
172 objc_setAssociatedObject(toast, &ToastKeys.duration, NSNumber(value: duration), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
173 objc_setAssociatedObject(toast, &ToastKeys.point, NSValue(cgPoint: point), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
177 showToast(toast, duration: duration, point: point)
181 // MARK: - Hide Toast Methods
184 Hides the active toast. If there are multiple toasts active in a view, this method
185 hides the oldest toast (the first of the toasts to have been presented).
187 @see `hideAllToasts()` to remove all active toasts from a view.
189 @warning This method has no effect on activity toasts. Use `hideToastActivity` to
190 hide activity toasts.
193 public func hideToast() {
194 guard let activeToast = activeToasts.firstObject as? UIView else { return }
195 hideToast(activeToast)
199 Hides an active toast.
201 @param toast The active toast view to dismiss. Any toast that is currently being displayed
202 on the screen is considered active.
204 @warning this does not clear a toast view that is currently waiting in the queue.
206 public func hideToast(_ toast: UIView) {
207 guard activeToasts.contains(toast) else { return }
208 hideToast(toast, fromTap: false)
212 Hides all toast views.
214 @param includeActivity If `true`, toast activity will also be hidden. Default is `false`.
215 @param clearQueue If `true`, removes all toast views from the queue. Default is `true`.
217 public func hideAllToasts(includeActivity: Bool = false, clearQueue: Bool = true) {
222 activeToasts.flatMap { $0 as? UIView }
223 .forEach { hideToast($0) }
231 Removes all toast views from the queue. This has no effect on toast views that are
232 active. Use `hideAllToasts(clearQueue:)` to hide the active toasts views and clear
235 public func clearToastQueue() {
236 queue.removeAllObjects()
239 // MARK: - Activity Methods
242 Creates and displays a new toast activity indicator view at a specified position.
244 @warning Only one toast activity indicator view can be presented per superview. Subsequent
245 calls to `makeToastActivity(position:)` will be ignored until `hideToastActivity()` is called.
247 @warning `makeToastActivity(position:)` works independently of the `showToast` methods. Toast
248 activity views can be presented and dismissed while toast views are being displayed.
249 `makeToastActivity(position:)` has no effect on the queueing behavior of the `showToast` methods.
251 @param position The toast's position
253 public func makeToastActivity(_ position: ToastPosition) {
255 guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return }
257 let toast = createToastActivityView()
258 let point = position.centerPoint(forToast: toast, inSuperview: self)
259 makeToastActivity(toast, point: point)
263 Creates and displays a new toast activity indicator view at a specified position.
265 @warning Only one toast activity indicator view can be presented per superview. Subsequent
266 calls to `makeToastActivity(position:)` will be ignored until `hideToastActivity()` is called.
268 @warning `makeToastActivity(position:)` works independently of the `showToast` methods. Toast
269 activity views can be presented and dismissed while toast views are being displayed.
270 `makeToastActivity(position:)` has no effect on the queueing behavior of the `showToast` methods.
272 @param point The toast's center point
274 public func makeToastActivity(_ point: CGPoint) {
276 guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return }
278 let toast = createToastActivityView()
279 makeToastActivity(toast, point: point)
283 Dismisses the active toast activity indicator view.
285 public func hideToastActivity() {
286 if let toast = objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView {
287 UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: {
290 toast.removeFromSuperview()
291 objc_setAssociatedObject(self, &ToastKeys.activityView, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
296 // MARK: - Private Activity Methods
298 private func makeToastActivity(_ toast: UIView, point: CGPoint) {
302 objc_setAssociatedObject(self, &ToastKeys.activityView, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
304 self.addSubview(toast)
306 UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: .curveEaseOut, animations: {
311 private func createToastActivityView() -> UIView {
312 let style = ToastManager.shared.style
314 let activityView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: style.activitySize.width, height: style.activitySize.height))
315 activityView.backgroundColor = style.activityBackgroundColor
316 activityView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
317 activityView.layer.cornerRadius = style.cornerRadius
319 if style.displayShadow {
320 activityView.layer.shadowColor = style.shadowColor.cgColor
321 activityView.layer.shadowOpacity = style.shadowOpacity
322 activityView.layer.shadowRadius = style.shadowRadius
323 activityView.layer.shadowOffset = style.shadowOffset
326 let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
327 activityIndicatorView.center = CGPoint(x: activityView.bounds.size.width / 2.0, y: activityView.bounds.size.height / 2.0)
328 activityView.addSubview(activityIndicatorView)
329 activityIndicatorView.color = style.activityIndicatorColor
330 activityIndicatorView.startAnimating()
335 // MARK: - Private Show/Hide Methods
337 private func showToast(_ toast: UIView, duration: TimeInterval, point: CGPoint) {
341 if ToastManager.shared.isTapToDismissEnabled {
342 let recognizer = UITapGestureRecognizer(target: self, action: #selector(UIView.handleToastTapped(_:)))
343 toast.addGestureRecognizer(recognizer)
344 toast.isUserInteractionEnabled = true
345 toast.isExclusiveTouch = true
348 activeToasts.add(toast)
349 self.addSubview(toast)
351 UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseOut, .allowUserInteraction], animations: {
354 let timer = Timer(timeInterval: duration, target: self, selector: #selector(UIView.toastTimerDidFinish(_:)), userInfo: toast, repeats: false)
355 RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
356 objc_setAssociatedObject(toast, &ToastKeys.timer, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
360 private func hideToast(_ toast: UIView, fromTap: Bool) {
361 if let timer = objc_getAssociatedObject(toast, &ToastKeys.timer) as? Timer {
365 UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: {
368 toast.removeFromSuperview()
369 self.activeToasts.remove(toast)
371 if let wrapper = objc_getAssociatedObject(toast, &ToastKeys.completion) as? ToastCompletionWrapper, let completion = wrapper.completion {
375 if let nextToast = self.queue.firstObject as? UIView, let duration = objc_getAssociatedObject(nextToast, &ToastKeys.duration) as? NSNumber, let point = objc_getAssociatedObject(nextToast, &ToastKeys.point) as? NSValue {
376 self.queue.removeObject(at: 0)
377 self.showToast(nextToast, duration: duration.doubleValue, point: point.cgPointValue)
385 private func handleToastTapped(_ recognizer: UITapGestureRecognizer) {
386 guard let toast = recognizer.view else { return }
387 hideToast(toast, fromTap: true)
391 private func toastTimerDidFinish(_ timer: Timer) {
392 guard let toast = timer.userInfo as? UIView else { return }
396 // MARK: - Toast Construction
399 Creates a new toast view with any combination of message, title, and image.
400 The look and feel is configured via the style. Unlike the `makeToast` methods,
401 this method does not present the toast view automatically. One of the `showToast`
402 methods must be used to present the resulting view.
404 @warning if message, title, and image are all nil, this method will throw
405 `ToastError.missingParameters`
407 @param message The message to be displayed
408 @param title The title
409 @param image The image
410 @param style The style. The shared style will be used when nil
411 @throws `ToastError.missingParameters` when message, title, and image are all nil
412 @return The newly created toast view
414 public func toastViewForMessage(_ message: String?, title: String?, image: UIImage?, style: ToastStyle) throws -> UIView {
416 guard message != nil || title != nil || image != nil else {
417 throw ToastError.missingParameters
420 var messageLabel: UILabel?
421 var titleLabel: UILabel?
422 var imageView: UIImageView?
424 let wrapperView = UIView()
425 wrapperView.backgroundColor = style.backgroundColor
426 wrapperView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
427 wrapperView.layer.cornerRadius = style.cornerRadius
429 if style.displayShadow {
430 wrapperView.layer.shadowColor = UIColor.black.cgColor
431 wrapperView.layer.shadowOpacity = style.shadowOpacity
432 wrapperView.layer.shadowRadius = style.shadowRadius
433 wrapperView.layer.shadowOffset = style.shadowOffset
436 if let image = image {
437 imageView = UIImageView(image: image)
438 imageView?.contentMode = .scaleAspectFit
439 imageView?.frame = CGRect(x: style.horizontalPadding, y: style.verticalPadding, width: style.imageSize.width, height: style.imageSize.height)
442 var imageRect = CGRect.zero
444 if let imageView = imageView {
445 imageRect.origin.x = style.horizontalPadding
446 imageRect.origin.y = style.verticalPadding
447 imageRect.size.width = imageView.bounds.size.width
448 imageRect.size.height = imageView.bounds.size.height
451 if let title = title {
452 titleLabel = UILabel()
453 titleLabel?.numberOfLines = style.titleNumberOfLines
454 titleLabel?.font = style.titleFont
455 titleLabel?.textAlignment = style.titleAlignment
456 titleLabel?.lineBreakMode = .byTruncatingTail
457 titleLabel?.textColor = style.titleColor
458 titleLabel?.backgroundColor = UIColor.clear
459 titleLabel?.text = title;
461 let maxTitleSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage)
462 let titleSize = titleLabel?.sizeThatFits(maxTitleSize)
463 if let titleSize = titleSize {
464 titleLabel?.frame = CGRect(x: 0.0, y: 0.0, width: titleSize.width, height: titleSize.height)
468 if let message = message {
469 messageLabel = UILabel()
470 messageLabel?.text = message
471 messageLabel?.numberOfLines = style.messageNumberOfLines
472 messageLabel?.font = style.messageFont
473 messageLabel?.textAlignment = style.messageAlignment
474 messageLabel?.lineBreakMode = .byTruncatingTail;
475 messageLabel?.textColor = style.messageColor
476 messageLabel?.backgroundColor = UIColor.clear
478 let maxMessageSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage)
479 let messageSize = messageLabel?.sizeThatFits(maxMessageSize)
480 if let messageSize = messageSize {
481 let actualWidth = min(messageSize.width, maxMessageSize.width)
482 let actualHeight = min(messageSize.height, maxMessageSize.height)
483 messageLabel?.frame = CGRect(x: 0.0, y: 0.0, width: actualWidth, height: actualHeight)
487 var titleRect = CGRect.zero
489 if let titleLabel = titleLabel {
490 titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding
491 titleRect.origin.y = style.verticalPadding
492 titleRect.size.width = titleLabel.bounds.size.width
493 titleRect.size.height = titleLabel.bounds.size.height
496 var messageRect = CGRect.zero
498 if let messageLabel = messageLabel {
499 messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding
500 messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding
501 messageRect.size.width = messageLabel.bounds.size.width
502 messageRect.size.height = messageLabel.bounds.size.height
505 let longerWidth = max(titleRect.size.width, messageRect.size.width)
506 let longerX = max(titleRect.origin.x, messageRect.origin.x)
507 let wrapperWidth = max((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding))
508 let wrapperHeight = max((messageRect.origin.y + messageRect.size.height + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0)))
510 wrapperView.frame = CGRect(x: 0.0, y: 0.0, width: wrapperWidth, height: wrapperHeight)
512 if let titleLabel = titleLabel {
513 titleRect.size.width = longerWidth
514 titleLabel.frame = titleRect
515 wrapperView.addSubview(titleLabel)
518 if let messageLabel = messageLabel {
519 messageRect.size.width = longerWidth
520 messageLabel.frame = messageRect
521 wrapperView.addSubview(messageLabel)
524 if let imageView = imageView {
525 wrapperView.addSubview(imageView)
533 // MARK: - Toast Style
536 `ToastStyle` instances define the look and feel for toast views created via the
537 `makeToast` methods as well for toast views created directly with
538 `toastViewForMessage(message:title:image:style:)`.
540 @warning `ToastStyle` offers relatively simple styling options for the default
541 toast view. If you require a toast view with more complex UI, it probably makes more
542 sense to create your own custom UIView subclass and present it with the `showToast`
545 public struct ToastStyle {
550 The background color. Default is `.black` at 80% opacity.
552 public var backgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8)
555 The title color. Default is `UIColor.whiteColor()`.
557 public var titleColor: UIColor = .white
560 The message color. Default is `.white`.
562 public var messageColor: UIColor = .white
565 A percentage value from 0.0 to 1.0, representing the maximum width of the toast
566 view relative to it's superview. Default is 0.8 (80% of the superview's width).
568 public var maxWidthPercentage: CGFloat = 0.8 {
570 maxWidthPercentage = max(min(maxWidthPercentage, 1.0), 0.0)
575 A percentage value from 0.0 to 1.0, representing the maximum height of the toast
576 view relative to it's superview. Default is 0.8 (80% of the superview's height).
578 public var maxHeightPercentage: CGFloat = 0.8 {
580 maxHeightPercentage = max(min(maxHeightPercentage, 1.0), 0.0)
585 The spacing from the horizontal edge of the toast view to the content. When an image
586 is present, this is also used as the padding between the image and the text.
590 public var horizontalPadding: CGFloat = 10.0
593 The spacing from the vertical edge of the toast view to the content. When a title
594 is present, this is also used as the padding between the title and the message.
595 Default is 10.0. On iOS11+, this value is added added to the `safeAreaInset.top`
596 and `safeAreaInsets.bottom`.
598 public var verticalPadding: CGFloat = 10.0
601 The corner radius. Default is 10.0.
603 public var cornerRadius: CGFloat = 10.0;
606 The title font. Default is `.boldSystemFont(16.0)`.
608 public var titleFont: UIFont = .boldSystemFont(ofSize: 16.0)
611 The message font. Default is `.systemFont(ofSize: 16.0)`.
613 public var messageFont: UIFont = .systemFont(ofSize: 16.0)
616 The title text alignment. Default is `NSTextAlignment.Left`.
618 public var titleAlignment: NSTextAlignment = .left
621 The message text alignment. Default is `NSTextAlignment.Left`.
623 public var messageAlignment: NSTextAlignment = .left
626 The maximum number of lines for the title. The default is 0 (no limit).
628 public var titleNumberOfLines = 0
631 The maximum number of lines for the message. The default is 0 (no limit).
633 public var messageNumberOfLines = 0
636 Enable or disable a shadow on the toast view. Default is `false`.
638 public var displayShadow = false
641 The shadow color. Default is `.black`.
643 public var shadowColor: UIColor = .black
646 A value from 0.0 to 1.0, representing the opacity of the shadow.
647 Default is 0.8 (80% opacity).
649 public var shadowOpacity: Float = 0.8 {
651 shadowOpacity = max(min(shadowOpacity, 1.0), 0.0)
656 The shadow radius. Default is 6.0.
658 public var shadowRadius: CGFloat = 6.0
661 The shadow offset. The default is 4 x 4.
663 public var shadowOffset = CGSize(width: 4.0, height: 4.0)
666 The image size. The default is 80 x 80.
668 public var imageSize = CGSize(width: 80.0, height: 80.0)
671 The size of the toast activity view when `makeToastActivity(position:)` is called.
672 Default is 100 x 100.
674 public var activitySize = CGSize(width: 100.0, height: 100.0)
677 The fade in/out animation duration. Default is 0.2.
679 public var fadeDuration: TimeInterval = 0.2
682 Activity indicator color. Default is `.white`.
684 public var activityIndicatorColor: UIColor = .white
687 Activity background color. Default is `.black` at 80% opacity.
689 public var activityBackgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8)
693 // MARK: - Toast Manager
696 `ToastManager` provides general configuration options for all toast
697 notifications. Backed by a singleton instance.
699 public class ToastManager {
702 The `ToastManager` singleton instance.
705 public static let shared = ToastManager()
708 The shared style. Used whenever toastViewForMessage(message:title:image:style:) is called
709 with with a nil style.
712 public var style = ToastStyle()
715 Enables or disables tap to dismiss on toast views. Default is `true`.
718 public var isTapToDismissEnabled = true
721 Enables or disables queueing behavior for toast views. When `true`,
722 toast views will appear one after the other. When `false`, multiple toast
723 views will appear at the same time (potentially overlapping depending
724 on their positions). This has no effect on the toast activity view,
725 which operates independently of normal toast views. Default is `false`.
728 public var isQueueEnabled = false
731 The default duration. Used for the `makeToast` and
732 `showToast` methods that don't require an explicit duration.
736 public var duration: TimeInterval = 3.0
739 Sets the default position. Used for the `makeToast` and
740 `showToast` methods that don't require an explicit position.
741 Default is `ToastPosition.Bottom`.
744 public var position: ToastPosition = .bottom
748 // MARK: - ToastPosition
750 public enum ToastPosition {
755 fileprivate func centerPoint(forToast toast: UIView, inSuperview superview: UIView) -> CGPoint {
756 let topPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.top
757 let bottomPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.bottom
761 return CGPoint(x: superview.bounds.size.width / 2.0, y: (toast.frame.size.height / 2.0) + topPadding)
763 return CGPoint(x: superview.bounds.size.width / 2.0, y: superview.bounds.size.height / 2.0)
765 return CGPoint(x: superview.bounds.size.width / 2.0, y: (superview.bounds.size.height - (toast.frame.size.height / 2.0)) - bottomPadding)
770 fileprivate extension UIView {
772 fileprivate var csSafeAreaInsets: UIEdgeInsets {
773 if #available(iOS 11.0, *) {
774 return self.safeAreaInsets