123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782 |
- //
- // Toast.swift
- // Toast-Swift
- //
- // Copyright (c) 2015-2019 Charles Scalesse.
- //
- // Permission is hereby granted, free of charge, to any person obtaining a
- // copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to
- // permit persons to whom the Software is furnished to do so, subject to
- // the following conditions:
- //
- // The above copyright notice and this permission notice shall be included
- // in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- import UIKit
- import ObjectiveC
- /**
- Toast is a Swift extension that adds toast notifications to the `UIView` object class.
- It is intended to be simple, lightweight, and easy to use. Most toast notifications
- can be triggered with a single line of code.
-
- The `makeToast` methods create a new view and then display it as toast.
-
- The `showToast` methods display any view as toast.
-
- */
- public extension UIView {
-
- /**
- Keys used for associated objects.
- */
- private struct ToastKeys {
- static var timer = "com.toast-swift.timer"
- static var duration = "com.toast-swift.duration"
- static var point = "com.toast-swift.point"
- static var completion = "com.toast-swift.completion"
- static var activeToasts = "com.toast-swift.activeToasts"
- static var activityView = "com.toast-swift.activityView"
- static var queue = "com.toast-swift.queue"
- }
-
- /**
- Swift closures can't be directly associated with objects via the
- Objective-C runtime, so the (ugly) solution is to wrap them in a
- class that can be used with associated objects.
- */
- private class ToastCompletionWrapper {
- let completion: ((Bool) -> Void)?
-
- init(_ completion: ((Bool) -> Void)?) {
- self.completion = completion
- }
- }
-
- private enum ToastError: Error {
- case missingParameters
- }
-
- private var activeToasts: NSMutableArray {
- get {
- if let activeToasts = objc_getAssociatedObject(self, &ToastKeys.activeToasts) as? NSMutableArray {
- return activeToasts
- } else {
- let activeToasts = NSMutableArray()
- objc_setAssociatedObject(self, &ToastKeys.activeToasts, activeToasts, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
- return activeToasts
- }
- }
- }
-
- private var queue: NSMutableArray {
- get {
- if let queue = objc_getAssociatedObject(self, &ToastKeys.queue) as? NSMutableArray {
- return queue
- } else {
- let queue = NSMutableArray()
- objc_setAssociatedObject(self, &ToastKeys.queue, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
- return queue
- }
- }
- }
-
- // MARK: - Make Toast Methods
-
- /**
- Creates and presents a new toast view.
-
- @param message The message to be displayed
- @param duration The toast duration
- @param position The toast's position
- @param title The title
- @param image The image
- @param style The style. The shared style will be used when nil
- @param completion The completion closure, executed after the toast view disappears.
- didTap will be `true` if the toast view was dismissed from a tap.
- */
- 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) {
- do {
- let toast = try toastViewForMessage(message, title: title, image: image, style: style)
- showToast(toast, duration: duration, position: position, completion: completion)
- } catch ToastError.missingParameters {
- print("Error: message, title, and image are all nil")
- } catch {}
- }
-
- /**
- Creates a new toast view and presents it at a given center point.
-
- @param message The message to be displayed
- @param duration The toast duration
- @param point The toast's center point
- @param title The title
- @param image The image
- @param style The style. The shared style will be used when nil
- @param completion The completion closure, executed after the toast view disappears.
- didTap will be `true` if the toast view was dismissed from a tap.
- */
- func makeToast(_ message: String?, duration: TimeInterval = ToastManager.shared.duration, point: CGPoint, title: String?, image: UIImage?, style: ToastStyle = ToastManager.shared.style, completion: ((_ didTap: Bool) -> Void)?) {
- do {
- let toast = try toastViewForMessage(message, title: title, image: image, style: style)
- showToast(toast, duration: duration, point: point, completion: completion)
- } catch ToastError.missingParameters {
- print("Error: message, title, and image cannot all be nil")
- } catch {}
- }
-
- // MARK: - Show Toast Methods
-
- /**
- Displays any view as toast at a provided position and duration. The completion closure
- executes when the toast view completes. `didTap` will be `true` if the toast view was
- dismissed from a tap.
-
- @param toast The view to be displayed as toast
- @param duration The notification duration
- @param position The toast's position
- @param completion The completion block, executed after the toast view disappears.
- didTap will be `true` if the toast view was dismissed from a tap.
- */
- func showToast(_ toast: UIView, duration: TimeInterval = ToastManager.shared.duration, position: ToastPosition = ToastManager.shared.position, completion: ((_ didTap: Bool) -> Void)? = nil) {
- let point = position.centerPoint(forToast: toast, inSuperview: self)
- showToast(toast, duration: duration, point: point, completion: completion)
- }
-
- /**
- Displays any view as toast at a provided center point and duration. The completion closure
- executes when the toast view completes. `didTap` will be `true` if the toast view was
- dismissed from a tap.
-
- @param toast The view to be displayed as toast
- @param duration The notification duration
- @param point The toast's center point
- @param completion The completion block, executed after the toast view disappears.
- didTap will be `true` if the toast view was dismissed from a tap.
- */
- func showToast(_ toast: UIView, duration: TimeInterval = ToastManager.shared.duration, point: CGPoint, completion: ((_ didTap: Bool) -> Void)? = nil) {
- objc_setAssociatedObject(toast, &ToastKeys.completion, ToastCompletionWrapper(completion), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
-
- if ToastManager.shared.isQueueEnabled, activeToasts.count > 0 {
- objc_setAssociatedObject(toast, &ToastKeys.duration, NSNumber(value: duration), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- objc_setAssociatedObject(toast, &ToastKeys.point, NSValue(cgPoint: point), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
-
- queue.add(toast)
- } else {
- showToast(toast, duration: duration, point: point)
- }
- }
-
- // MARK: - Hide Toast Methods
-
- /**
- Hides the active toast. If there are multiple toasts active in a view, this method
- hides the oldest toast (the first of the toasts to have been presented).
-
- @see `hideAllToasts()` to remove all active toasts from a view.
-
- @warning This method has no effect on activity toasts. Use `hideToastActivity` to
- hide activity toasts.
-
- */
- func hideToast() {
- guard let activeToast = activeToasts.firstObject as? UIView else { return }
- hideToast(activeToast)
- }
-
- /**
- Hides an active toast.
-
- @param toast The active toast view to dismiss. Any toast that is currently being displayed
- on the screen is considered active.
-
- @warning this does not clear a toast view that is currently waiting in the queue.
- */
- func hideToast(_ toast: UIView) {
- guard activeToasts.contains(toast) else { return }
- hideToast(toast, fromTap: false)
- }
-
- /**
- Hides all toast views.
-
- @param includeActivity If `true`, toast activity will also be hidden. Default is `false`.
- @param clearQueue If `true`, removes all toast views from the queue. Default is `true`.
- */
- func hideAllToasts(includeActivity: Bool = false, clearQueue: Bool = true) {
- if clearQueue {
- clearToastQueue()
- }
-
- activeToasts.compactMap { $0 as? UIView }
- .forEach { hideToast($0) }
-
- if includeActivity {
- hideToastActivity()
- }
- }
-
- /**
- Removes all toast views from the queue. This has no effect on toast views that are
- active. Use `hideAllToasts(clearQueue:)` to hide the active toasts views and clear
- the queue.
- */
- func clearToastQueue() {
- queue.removeAllObjects()
- }
-
- // MARK: - Activity Methods
-
- /**
- Creates and displays a new toast activity indicator view at a specified position.
-
- @warning Only one toast activity indicator view can be presented per superview. Subsequent
- calls to `makeToastActivity(position:)` will be ignored until `hideToastActivity()` is called.
-
- @warning `makeToastActivity(position:)` works independently of the `showToast` methods. Toast
- activity views can be presented and dismissed while toast views are being displayed.
- `makeToastActivity(position:)` has no effect on the queueing behavior of the `showToast` methods.
-
- @param position The toast's position
- */
- func makeToastActivity(_ position: ToastPosition) {
- // sanity
- guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return }
-
- let toast = createToastActivityView()
- let point = position.centerPoint(forToast: toast, inSuperview: self)
- makeToastActivity(toast, point: point)
- }
-
- /**
- Creates and displays a new toast activity indicator view at a specified position.
-
- @warning Only one toast activity indicator view can be presented per superview. Subsequent
- calls to `makeToastActivity(position:)` will be ignored until `hideToastActivity()` is called.
-
- @warning `makeToastActivity(position:)` works independently of the `showToast` methods. Toast
- activity views can be presented and dismissed while toast views are being displayed.
- `makeToastActivity(position:)` has no effect on the queueing behavior of the `showToast` methods.
-
- @param point The toast's center point
- */
- func makeToastActivity(_ point: CGPoint) {
- // sanity
- guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return }
-
- let toast = createToastActivityView()
- makeToastActivity(toast, point: point)
- }
-
- /**
- Dismisses the active toast activity indicator view.
- */
- func hideToastActivity() {
- if let toast = objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView {
- UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: {
- toast.alpha = 0.0
- }) { _ in
- toast.removeFromSuperview()
- objc_setAssociatedObject(self, &ToastKeys.activityView, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
- }
- }
- }
-
- // MARK: - Private Activity Methods
-
- private func makeToastActivity(_ toast: UIView, point: CGPoint) {
- toast.alpha = 0.0
- toast.center = point
-
- objc_setAssociatedObject(self, &ToastKeys.activityView, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
-
- self.addSubview(toast)
-
- UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: .curveEaseOut, animations: {
- toast.alpha = 1.0
- })
- }
-
- private func createToastActivityView() -> UIView {
- let style = ToastManager.shared.style
-
- let activityView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: style.activitySize.width, height: style.activitySize.height))
- activityView.backgroundColor = style.activityBackgroundColor
- activityView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
- activityView.layer.cornerRadius = style.cornerRadius
-
- if style.displayShadow {
- activityView.layer.shadowColor = style.shadowColor.cgColor
- activityView.layer.shadowOpacity = style.shadowOpacity
- activityView.layer.shadowRadius = style.shadowRadius
- activityView.layer.shadowOffset = style.shadowOffset
- }
-
- let activityIndicatorView = UIActivityIndicatorView(style: .whiteLarge)
- activityIndicatorView.center = CGPoint(x: activityView.bounds.size.width / 2.0, y: activityView.bounds.size.height / 2.0)
- activityView.addSubview(activityIndicatorView)
- activityIndicatorView.color = style.activityIndicatorColor
- activityIndicatorView.startAnimating()
-
- return activityView
- }
-
- // MARK: - Private Show/Hide Methods
-
- private func showToast(_ toast: UIView, duration: TimeInterval, point: CGPoint) {
- toast.center = point
- toast.alpha = 0.0
-
- if ToastManager.shared.isTapToDismissEnabled {
- let recognizer = UITapGestureRecognizer(target: self, action: #selector(UIView.handleToastTapped(_:)))
- toast.addGestureRecognizer(recognizer)
- toast.isUserInteractionEnabled = true
- toast.isExclusiveTouch = true
- }
-
- activeToasts.add(toast)
- self.addSubview(toast)
-
- UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseOut, .allowUserInteraction], animations: {
- toast.alpha = 1.0
- }) { _ in
- let timer = Timer(timeInterval: duration, target: self, selector: #selector(UIView.toastTimerDidFinish(_:)), userInfo: toast, repeats: false)
- RunLoop.main.add(timer, forMode: .common)
- objc_setAssociatedObject(toast, &ToastKeys.timer, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
- }
- }
-
- private func hideToast(_ toast: UIView, fromTap: Bool) {
- if let timer = objc_getAssociatedObject(toast, &ToastKeys.timer) as? Timer {
- timer.invalidate()
- }
-
- UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: {
- toast.alpha = 0.0
- }) { _ in
- toast.removeFromSuperview()
- self.activeToasts.remove(toast)
-
- if let wrapper = objc_getAssociatedObject(toast, &ToastKeys.completion) as? ToastCompletionWrapper, let completion = wrapper.completion {
- completion(fromTap)
- }
-
- 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 {
- self.queue.removeObject(at: 0)
- self.showToast(nextToast, duration: duration.doubleValue, point: point.cgPointValue)
- }
- }
- }
-
- // MARK: - Events
-
- @objc
- private func handleToastTapped(_ recognizer: UITapGestureRecognizer) {
- guard let toast = recognizer.view else { return }
- hideToast(toast, fromTap: true)
- }
-
- @objc
- private func toastTimerDidFinish(_ timer: Timer) {
- guard let toast = timer.userInfo as? UIView else { return }
- hideToast(toast)
- }
-
- // MARK: - Toast Construction
-
- /**
- Creates a new toast view with any combination of message, title, and image.
- The look and feel is configured via the style. Unlike the `makeToast` methods,
- this method does not present the toast view automatically. One of the `showToast`
- methods must be used to present the resulting view.
-
- @warning if message, title, and image are all nil, this method will throw
- `ToastError.missingParameters`
-
- @param message The message to be displayed
- @param title The title
- @param image The image
- @param style The style. The shared style will be used when nil
- @throws `ToastError.missingParameters` when message, title, and image are all nil
- @return The newly created toast view
- */
- func toastViewForMessage(_ message: String?, title: String?, image: UIImage?, style: ToastStyle) throws -> UIView {
- // sanity
- guard message != nil || title != nil || image != nil else {
- throw ToastError.missingParameters
- }
-
- var messageLabel: UILabel?
- var titleLabel: UILabel?
- var imageView: UIImageView?
-
- let wrapperView = UIView()
- wrapperView.backgroundColor = style.backgroundColor
- wrapperView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
- wrapperView.layer.cornerRadius = style.cornerRadius
-
- if style.displayShadow {
- wrapperView.layer.shadowColor = UIColor.black.cgColor
- wrapperView.layer.shadowOpacity = style.shadowOpacity
- wrapperView.layer.shadowRadius = style.shadowRadius
- wrapperView.layer.shadowOffset = style.shadowOffset
- }
-
- if let image = image {
- imageView = UIImageView(image: image)
- imageView?.contentMode = .scaleAspectFit
- imageView?.frame = CGRect(x: style.horizontalPadding, y: style.verticalPadding, width: style.imageSize.width, height: style.imageSize.height)
- }
-
- var imageRect = CGRect.zero
-
- if let imageView = imageView {
- imageRect.origin.x = style.horizontalPadding
- imageRect.origin.y = style.verticalPadding
- imageRect.size.width = imageView.bounds.size.width
- imageRect.size.height = imageView.bounds.size.height
- }
- if let title = title {
- titleLabel = UILabel()
- titleLabel?.numberOfLines = style.titleNumberOfLines
- titleLabel?.font = style.titleFont
- titleLabel?.textAlignment = style.titleAlignment
- titleLabel?.lineBreakMode = .byTruncatingTail
- titleLabel?.textColor = style.titleColor
- titleLabel?.backgroundColor = UIColor.clear
- titleLabel?.text = title;
-
- let maxTitleSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage)
- let titleSize = titleLabel?.sizeThatFits(maxTitleSize)
- if let titleSize = titleSize {
- titleLabel?.frame = CGRect(x: 0.0, y: 0.0, width: titleSize.width, height: titleSize.height)
- }
- }
-
- if let message = message {
- messageLabel = UILabel()
- messageLabel?.text = message
- messageLabel?.numberOfLines = style.messageNumberOfLines
- messageLabel?.font = style.messageFont
- messageLabel?.textAlignment = style.messageAlignment
- messageLabel?.lineBreakMode = .byTruncatingTail;
- messageLabel?.textColor = style.messageColor
- messageLabel?.backgroundColor = UIColor.clear
-
- let maxMessageSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage)
- let messageSize = messageLabel?.sizeThatFits(maxMessageSize)
- if let messageSize = messageSize {
- let actualWidth = min(messageSize.width, maxMessageSize.width)
- let actualHeight = min(messageSize.height, maxMessageSize.height)
- messageLabel?.frame = CGRect(x: 0.0, y: 0.0, width: actualWidth, height: actualHeight)
- }
- }
-
- var titleRect = CGRect.zero
-
- if let titleLabel = titleLabel {
- titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding
- titleRect.origin.y = style.verticalPadding
- titleRect.size.width = titleLabel.bounds.size.width
- titleRect.size.height = titleLabel.bounds.size.height
- }
-
- var messageRect = CGRect.zero
-
- if let messageLabel = messageLabel {
- messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding
- messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding
- messageRect.size.width = messageLabel.bounds.size.width
- messageRect.size.height = messageLabel.bounds.size.height
- }
-
- let longerWidth = max(titleRect.size.width, messageRect.size.width)
- let longerX = max(titleRect.origin.x, messageRect.origin.x)
- let wrapperWidth = max((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding))
- let wrapperHeight = max((messageRect.origin.y + messageRect.size.height + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0)))
-
- wrapperView.frame = CGRect(x: 0.0, y: 0.0, width: wrapperWidth, height: wrapperHeight)
-
- if let titleLabel = titleLabel {
- titleRect.size.width = longerWidth
- titleLabel.frame = titleRect
- wrapperView.addSubview(titleLabel)
- }
-
- if let messageLabel = messageLabel {
- messageRect.size.width = longerWidth
- messageLabel.frame = messageRect
- wrapperView.addSubview(messageLabel)
- }
-
- if let imageView = imageView {
- wrapperView.addSubview(imageView)
- }
-
- return wrapperView
- }
-
- }
- // MARK: - Toast Style
- /**
- `ToastStyle` instances define the look and feel for toast views created via the
- `makeToast` methods as well for toast views created directly with
- `toastViewForMessage(message:title:image:style:)`.
- @warning `ToastStyle` offers relatively simple styling options for the default
- toast view. If you require a toast view with more complex UI, it probably makes more
- sense to create your own custom UIView subclass and present it with the `showToast`
- methods.
- */
- public struct ToastStyle {
- public init() {}
-
- /**
- The background color. Default is `.black` at 80% opacity.
- */
- public var backgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8)
-
- /**
- The title color. Default is `UIColor.whiteColor()`.
- */
- public var titleColor: UIColor = .white
-
- /**
- The message color. Default is `.white`.
- */
- public var messageColor: UIColor = .white
-
- /**
- A percentage value from 0.0 to 1.0, representing the maximum width of the toast
- view relative to it's superview. Default is 0.8 (80% of the superview's width).
- */
- public var maxWidthPercentage: CGFloat = 0.8 {
- didSet {
- maxWidthPercentage = max(min(maxWidthPercentage, 1.0), 0.0)
- }
- }
-
- /**
- A percentage value from 0.0 to 1.0, representing the maximum height of the toast
- view relative to it's superview. Default is 0.8 (80% of the superview's height).
- */
- public var maxHeightPercentage: CGFloat = 0.8 {
- didSet {
- maxHeightPercentage = max(min(maxHeightPercentage, 1.0), 0.0)
- }
- }
-
- /**
- The spacing from the horizontal edge of the toast view to the content. When an image
- is present, this is also used as the padding between the image and the text.
- Default is 10.0.
-
- */
- public var horizontalPadding: CGFloat = 10.0
-
- /**
- The spacing from the vertical edge of the toast view to the content. When a title
- is present, this is also used as the padding between the title and the message.
- Default is 10.0. On iOS11+, this value is added added to the `safeAreaInset.top`
- and `safeAreaInsets.bottom`.
- */
- public var verticalPadding: CGFloat = 10.0
-
- /**
- The corner radius. Default is 10.0.
- */
- public var cornerRadius: CGFloat = 10.0;
-
- /**
- The title font. Default is `.boldSystemFont(16.0)`.
- */
- public var titleFont: UIFont = .boldSystemFont(ofSize: 16.0)
-
- /**
- The message font. Default is `.systemFont(ofSize: 16.0)`.
- */
- public var messageFont: UIFont = .systemFont(ofSize: 16.0)
-
- /**
- The title text alignment. Default is `NSTextAlignment.Left`.
- */
- public var titleAlignment: NSTextAlignment = .left
-
- /**
- The message text alignment. Default is `NSTextAlignment.Left`.
- */
- public var messageAlignment: NSTextAlignment = .left
-
- /**
- The maximum number of lines for the title. The default is 0 (no limit).
- */
- public var titleNumberOfLines = 0
-
- /**
- The maximum number of lines for the message. The default is 0 (no limit).
- */
- public var messageNumberOfLines = 0
-
- /**
- Enable or disable a shadow on the toast view. Default is `false`.
- */
- public var displayShadow = false
-
- /**
- The shadow color. Default is `.black`.
- */
- public var shadowColor: UIColor = .black
-
- /**
- A value from 0.0 to 1.0, representing the opacity of the shadow.
- Default is 0.8 (80% opacity).
- */
- public var shadowOpacity: Float = 0.8 {
- didSet {
- shadowOpacity = max(min(shadowOpacity, 1.0), 0.0)
- }
- }
- /**
- The shadow radius. Default is 6.0.
- */
- public var shadowRadius: CGFloat = 6.0
-
- /**
- The shadow offset. The default is 4 x 4.
- */
- public var shadowOffset = CGSize(width: 4.0, height: 4.0)
-
- /**
- The image size. The default is 80 x 80.
- */
- public var imageSize = CGSize(width: 80.0, height: 80.0)
-
- /**
- The size of the toast activity view when `makeToastActivity(position:)` is called.
- Default is 100 x 100.
- */
- public var activitySize = CGSize(width: 100.0, height: 100.0)
-
- /**
- The fade in/out animation duration. Default is 0.2.
- */
- public var fadeDuration: TimeInterval = 0.2
-
- /**
- Activity indicator color. Default is `.white`.
- */
- public var activityIndicatorColor: UIColor = .white
-
- /**
- Activity background color. Default is `.black` at 80% opacity.
- */
- public var activityBackgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8)
-
- }
- // MARK: - Toast Manager
- /**
- `ToastManager` provides general configuration options for all toast
- notifications. Backed by a singleton instance.
- */
- public class ToastManager {
-
- /**
- The `ToastManager` singleton instance.
-
- */
- public static let shared = ToastManager()
-
- /**
- The shared style. Used whenever toastViewForMessage(message:title:image:style:) is called
- with with a nil style.
-
- */
- public var style = ToastStyle()
-
- /**
- Enables or disables tap to dismiss on toast views. Default is `true`.
-
- */
- public var isTapToDismissEnabled = true
-
- /**
- Enables or disables queueing behavior for toast views. When `true`,
- toast views will appear one after the other. When `false`, multiple toast
- views will appear at the same time (potentially overlapping depending
- on their positions). This has no effect on the toast activity view,
- which operates independently of normal toast views. Default is `false`.
-
- */
- public var isQueueEnabled = false
-
- /**
- The default duration. Used for the `makeToast` and
- `showToast` methods that don't require an explicit duration.
- Default is 3.0.
-
- */
- public var duration: TimeInterval = 3.0
-
- /**
- Sets the default position. Used for the `makeToast` and
- `showToast` methods that don't require an explicit position.
- Default is `ToastPosition.Bottom`.
-
- */
- public var position: ToastPosition = .bottom
-
- }
- // MARK: - ToastPosition
- public enum ToastPosition {
- case top
- case center
- case bottom
-
- fileprivate func centerPoint(forToast toast: UIView, inSuperview superview: UIView) -> CGPoint {
- let topPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.top
- let bottomPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.bottom
-
- switch self {
- case .top:
- return CGPoint(x: superview.bounds.size.width / 2.0, y: (toast.frame.size.height / 2.0) + topPadding)
- case .center:
- return CGPoint(x: superview.bounds.size.width / 2.0, y: superview.bounds.size.height / 2.0)
- case .bottom:
- return CGPoint(x: superview.bounds.size.width / 2.0, y: (superview.bounds.size.height - (toast.frame.size.height / 2.0)) - bottomPadding)
- }
- }
- }
- // MARK: - Private UIView Extensions
- private extension UIView {
-
- var csSafeAreaInsets: UIEdgeInsets {
- if #available(iOS 11.0, *) {
- return self.safeAreaInsets
- } else {
- return .zero
- }
- }
-
- }
|