added iOS source code
[wl-app.git] / iOS / Pods / Toast-Swift / Toast / Toast.swift
1 //
2 //  Toast.swift
3 //  Toast-Swift
4 //
5 //  Copyright (c) 2015-2017 Charles Scalesse.
6 //
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:
14 //
15 //  The above copyright notice and this permission notice shall be included
16 //  in all copies or substantial portions of the Software.
17 //
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.
25
26 import UIKit
27 import ObjectiveC
28
29 /**
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.
33  
34  The `makeToast` methods create a new view and then display it as toast.
35  
36  The `showToast` methods display any view as toast.
37  
38  */
39 public extension UIView {
40     
41     /**
42      Keys used for associated objects.
43      */
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"
52     }
53     
54     /**
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.
58      */
59     private class ToastCompletionWrapper {
60         let completion: ((Bool) -> Void)?
61         
62         init(_ completion: ((Bool) -> Void)?) {
63             self.completion = completion
64         }
65     }
66     
67     private enum ToastError: Error {
68         case missingParameters
69     }
70     
71     private var activeToasts: NSMutableArray {
72         get {
73             if let activeToasts = objc_getAssociatedObject(self, &ToastKeys.activeToasts) as? NSMutableArray {
74                 return activeToasts
75             } else {
76                 let activeToasts = NSMutableArray()
77                 objc_setAssociatedObject(self, &ToastKeys.activeToasts, activeToasts, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
78                 return activeToasts
79             }
80         }
81     }
82     
83     private var queue: NSMutableArray {
84         get {
85             if let queue = objc_getAssociatedObject(self, &ToastKeys.queue) as? NSMutableArray {
86                 return queue
87             } else {
88                 let queue = NSMutableArray()
89                 objc_setAssociatedObject(self, &ToastKeys.queue, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
90                 return queue
91             }
92         }
93     }
94     
95     // MARK: - Make Toast Methods
96     
97     /**
98      Creates and presents a new toast view.
99      
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.
108      */
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) {
110         do {
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")
115         } catch {}
116     }
117     
118     /**
119      Creates a new toast view and presents it at a given center point.
120      
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.
129      */
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)?) {
131         do {
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")
136         } catch {}
137     }
138     
139     // MARK: - Show Toast Methods
140     
141     /**
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.
145      
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.
151      */
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)
155     }
156     
157     /**
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.
161      
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.
167      */
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);
170         
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);
174             
175             queue.add(toast)
176         } else {
177             showToast(toast, duration: duration, point: point)
178         }
179     }
180     
181     // MARK: - Hide Toast Methods
182     
183     /**
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).
186      
187      @see `hideAllToasts()` to remove all active toasts from a view.
188      
189      @warning This method has no effect on activity toasts. Use `hideToastActivity` to
190      hide activity toasts.
191      
192     */
193     public func hideToast() {
194         guard let activeToast = activeToasts.firstObject as? UIView else { return }
195         hideToast(activeToast)
196     }
197     
198     /**
199      Hides an active toast.
200      
201      @param toast The active toast view to dismiss. Any toast that is currently being displayed
202      on the screen is considered active.
203      
204      @warning this does not clear a toast view that is currently waiting in the queue.
205      */
206     public func hideToast(_ toast: UIView) {
207         guard activeToasts.contains(toast) else { return }
208         hideToast(toast, fromTap: false)
209     }
210     
211     /**
212      Hides all toast views.
213      
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`.
216     */
217     public func hideAllToasts(includeActivity: Bool = false, clearQueue: Bool = true) {
218         if clearQueue {
219             clearToastQueue()
220         }
221         
222         activeToasts.flatMap { $0 as? UIView }
223                     .forEach { hideToast($0) }
224         
225         if includeActivity {
226             hideToastActivity()
227         }
228     }
229     
230     /**
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
233      the queue.
234      */
235     public func clearToastQueue() {
236         queue.removeAllObjects()
237     }
238     
239     // MARK: - Activity Methods
240     
241     /**
242      Creates and displays a new toast activity indicator view at a specified position.
243     
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.
246     
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.
250     
251      @param position The toast's position
252      */
253     public func makeToastActivity(_ position: ToastPosition) {
254         // sanity
255         guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return }
256         
257         let toast = createToastActivityView()
258         let point = position.centerPoint(forToast: toast, inSuperview: self)
259         makeToastActivity(toast, point: point)
260     }
261     
262     /**
263      Creates and displays a new toast activity indicator view at a specified position.
264      
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.
267      
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.
271      
272      @param point The toast's center point
273      */
274     public func makeToastActivity(_ point: CGPoint) {
275         // sanity
276         guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return }
277         
278         let toast = createToastActivityView()
279         makeToastActivity(toast, point: point)
280     }
281     
282     /**
283      Dismisses the active toast activity indicator view.
284      */
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: {
288                 toast.alpha = 0.0
289             }) { _ in
290                 toast.removeFromSuperview()
291                 objc_setAssociatedObject(self, &ToastKeys.activityView, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
292             }
293         }
294     }
295     
296     // MARK: - Private Activity Methods
297     
298     private func makeToastActivity(_ toast: UIView, point: CGPoint) {
299         toast.alpha = 0.0
300         toast.center = point
301         
302         objc_setAssociatedObject(self, &ToastKeys.activityView, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
303         
304         self.addSubview(toast)
305         
306         UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: .curveEaseOut, animations: {
307             toast.alpha = 1.0
308         })
309     }
310     
311     private func createToastActivityView() -> UIView {
312         let style = ToastManager.shared.style
313         
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
318         
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
324         }
325         
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()
331         
332         return activityView
333     }
334     
335     // MARK: - Private Show/Hide Methods
336     
337     private func showToast(_ toast: UIView, duration: TimeInterval, point: CGPoint) {
338         toast.center = point
339         toast.alpha = 0.0
340         
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
346         }
347         
348         activeToasts.add(toast)
349         self.addSubview(toast)
350         
351         UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseOut, .allowUserInteraction], animations: {
352             toast.alpha = 1.0
353         }) { _ in
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)
357         }
358     }
359     
360     private func hideToast(_ toast: UIView, fromTap: Bool) {
361         if let timer = objc_getAssociatedObject(toast, &ToastKeys.timer) as? Timer {
362             timer.invalidate()
363         }
364         
365         UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: {
366             toast.alpha = 0.0
367         }) { _ in
368             toast.removeFromSuperview()
369             self.activeToasts.remove(toast)
370             
371             if let wrapper = objc_getAssociatedObject(toast, &ToastKeys.completion) as? ToastCompletionWrapper, let completion = wrapper.completion {
372                 completion(fromTap)
373             }
374             
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)
378             }
379         }
380     }
381     
382     // MARK: - Events
383     
384     @objc
385     private func handleToastTapped(_ recognizer: UITapGestureRecognizer) {
386         guard let toast = recognizer.view else { return }
387         hideToast(toast, fromTap: true)
388     }
389     
390     @objc
391     private func toastTimerDidFinish(_ timer: Timer) {
392         guard let toast = timer.userInfo as? UIView else { return }
393         hideToast(toast)
394     }
395     
396     // MARK: - Toast Construction
397     
398     /**
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.
403     
404      @warning if message, title, and image are all nil, this method will throw
405      `ToastError.missingParameters`
406     
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
413     */
414     public func toastViewForMessage(_ message: String?, title: String?, image: UIImage?, style: ToastStyle) throws -> UIView {
415         // sanity
416         guard message != nil || title != nil || image != nil else {
417             throw ToastError.missingParameters
418         }
419         
420         var messageLabel: UILabel?
421         var titleLabel: UILabel?
422         var imageView: UIImageView?
423         
424         let wrapperView = UIView()
425         wrapperView.backgroundColor = style.backgroundColor
426         wrapperView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
427         wrapperView.layer.cornerRadius = style.cornerRadius
428         
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
434         }
435         
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)
440         }
441         
442         var imageRect = CGRect.zero
443         
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
449         }
450
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;
460             
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)
465             }
466         }
467         
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
477             
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)
484             }
485         }
486   
487         var titleRect = CGRect.zero
488         
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
494         }
495         
496         var messageRect = CGRect.zero
497         
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
503         }
504         
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)))
509         
510         wrapperView.frame = CGRect(x: 0.0, y: 0.0, width: wrapperWidth, height: wrapperHeight)
511         
512         if let titleLabel = titleLabel {
513             titleRect.size.width = longerWidth
514             titleLabel.frame = titleRect
515             wrapperView.addSubview(titleLabel)
516         }
517         
518         if let messageLabel = messageLabel {
519             messageRect.size.width = longerWidth
520             messageLabel.frame = messageRect
521             wrapperView.addSubview(messageLabel)
522         }
523         
524         if let imageView = imageView {
525             wrapperView.addSubview(imageView)
526         }
527         
528         return wrapperView
529     }
530     
531 }
532
533 // MARK: - Toast Style
534
535 /**
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:)`.
539
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`
543  methods.
544 */
545 public struct ToastStyle {
546
547     public init() {}
548     
549     /**
550      The background color. Default is `.black` at 80% opacity.
551     */
552     public var backgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8)
553     
554     /**
555      The title color. Default is `UIColor.whiteColor()`.
556     */
557     public var titleColor: UIColor = .white
558     
559     /**
560      The message color. Default is `.white`.
561     */
562     public var messageColor: UIColor = .white
563     
564     /**
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).
567     */
568     public var maxWidthPercentage: CGFloat = 0.8 {
569         didSet {
570             maxWidthPercentage = max(min(maxWidthPercentage, 1.0), 0.0)
571         }
572     }
573     
574     /**
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).
577     */
578     public var maxHeightPercentage: CGFloat = 0.8 {
579         didSet {
580             maxHeightPercentage = max(min(maxHeightPercentage, 1.0), 0.0)
581         }
582     }
583     
584     /**
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.
587      Default is 10.0.
588      
589     */
590     public var horizontalPadding: CGFloat = 10.0
591     
592     /**
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`.
597     */
598     public var verticalPadding: CGFloat = 10.0
599     
600     /**
601      The corner radius. Default is 10.0.
602     */
603     public var cornerRadius: CGFloat = 10.0;
604     
605     /**
606      The title font. Default is `.boldSystemFont(16.0)`.
607     */
608     public var titleFont: UIFont = .boldSystemFont(ofSize: 16.0)
609     
610     /**
611      The message font. Default is `.systemFont(ofSize: 16.0)`.
612     */
613     public var messageFont: UIFont = .systemFont(ofSize: 16.0)
614     
615     /**
616      The title text alignment. Default is `NSTextAlignment.Left`.
617     */
618     public var titleAlignment: NSTextAlignment = .left
619     
620     /**
621      The message text alignment. Default is `NSTextAlignment.Left`.
622     */
623     public var messageAlignment: NSTextAlignment = .left
624     
625     /**
626      The maximum number of lines for the title. The default is 0 (no limit).
627     */
628     public var titleNumberOfLines = 0
629     
630     /**
631      The maximum number of lines for the message. The default is 0 (no limit).
632     */
633     public var messageNumberOfLines = 0
634     
635     /**
636      Enable or disable a shadow on the toast view. Default is `false`.
637     */
638     public var displayShadow = false
639     
640     /**
641      The shadow color. Default is `.black`.
642      */
643     public var shadowColor: UIColor = .black
644     
645     /**
646      A value from 0.0 to 1.0, representing the opacity of the shadow.
647      Default is 0.8 (80% opacity).
648     */
649     public var shadowOpacity: Float = 0.8 {
650         didSet {
651             shadowOpacity = max(min(shadowOpacity, 1.0), 0.0)
652         }
653     }
654
655     /**
656      The shadow radius. Default is 6.0.
657     */
658     public var shadowRadius: CGFloat = 6.0
659     
660     /**
661      The shadow offset. The default is 4 x 4.
662     */
663     public var shadowOffset = CGSize(width: 4.0, height: 4.0)
664     
665     /**
666      The image size. The default is 80 x 80.
667     */
668     public var imageSize = CGSize(width: 80.0, height: 80.0)
669     
670     /**
671      The size of the toast activity view when `makeToastActivity(position:)` is called.
672      Default is 100 x 100.
673     */
674     public var activitySize = CGSize(width: 100.0, height: 100.0)
675     
676     /**
677      The fade in/out animation duration. Default is 0.2.
678      */
679     public var fadeDuration: TimeInterval = 0.2
680     
681     /**
682      Activity indicator color. Default is `.white`.
683      */
684     public var activityIndicatorColor: UIColor = .white
685     
686     /**
687      Activity background color. Default is `.black` at 80% opacity.
688      */
689     public var activityBackgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8)
690     
691 }
692
693 // MARK: - Toast Manager
694
695 /**
696  `ToastManager` provides general configuration options for all toast
697  notifications. Backed by a singleton instance.
698 */
699 public class ToastManager {
700     
701     /**
702      The `ToastManager` singleton instance.
703      
704      */
705     public static let shared = ToastManager()
706     
707     /**
708      The shared style. Used whenever toastViewForMessage(message:title:image:style:) is called
709      with with a nil style.
710      
711      */
712     public var style = ToastStyle()
713     
714     /**
715      Enables or disables tap to dismiss on toast views. Default is `true`.
716      
717      */
718     public var isTapToDismissEnabled = true
719     
720     /**
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`.
726      
727      */
728     public var isQueueEnabled = false
729     
730     /**
731      The default duration. Used for the `makeToast` and
732      `showToast` methods that don't require an explicit duration.
733      Default is 3.0.
734      
735      */
736     public var duration: TimeInterval = 3.0
737     
738     /**
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`.
742      
743      */
744     public var position: ToastPosition = .bottom
745     
746 }
747
748 // MARK: - ToastPosition
749
750 public enum ToastPosition {
751     case top
752     case center
753     case bottom
754     
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
758         
759         switch self {
760         case .top:
761             return CGPoint(x: superview.bounds.size.width / 2.0, y: (toast.frame.size.height / 2.0) + topPadding)
762         case .center:
763             return CGPoint(x: superview.bounds.size.width / 2.0, y: superview.bounds.size.height / 2.0)
764         case .bottom:
765             return CGPoint(x: superview.bounds.size.width / 2.0, y: (superview.bounds.size.height - (toast.frame.size.height / 2.0)) - bottomPadding)
766         }
767     }
768 }
769
770 fileprivate extension UIView {
771     
772     fileprivate var csSafeAreaInsets: UIEdgeInsets {
773         if #available(iOS 11.0, *) {
774             return self.safeAreaInsets
775         } else {
776             return .zero
777         }
778     }
779     
780 }