├── .swift-version ├── toast_swift_screenshot.jpg ├── Example ├── Assets.xcassets │ ├── Contents.json │ ├── toast.imageset │ │ ├── toast.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── Base.lproj │ └── LaunchScreen.storyboard ├── AppDelegate.swift ├── LaunchScreen.storyboard └── ViewController.swift ├── Toast-Swift.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── Toast-Swift.xcscmblueprint ├── xcshareddata │ └── xcschemes │ │ ├── ToastSwiftFramework.xcscheme │ │ └── Toast-Swift.xcscheme └── project.pbxproj ├── Toast ├── Resources │ └── PrivacyInfo.xcprivacy └── Toast.swift ├── .gitignore ├── Package.swift ├── Toast-Swift-Framework ├── Toast-Swift-Framework.h └── Info.plist ├── Toast-Swift.podspec ├── LICENSE └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /toast_swift_screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalessec/Toast-Swift/HEAD/toast_swift_screenshot.jpg -------------------------------------------------------------------------------- /Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/toast.imageset/toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalessec/Toast-Swift/HEAD/Example/Assets.xcassets/toast.imageset/toast.png -------------------------------------------------------------------------------- /Toast-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Toast-Swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Assets.xcassets/toast.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "toast.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Toast/Resources/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTracking 6 | 7 | NSPrivacyCollectedDataTypes 8 | 9 | NSPrivacyTrackingDomains 10 | 11 | NSPrivacyAccessedAPITypes 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | 6 | ## Build generated 7 | build/ 8 | DerivedData 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata 20 | 21 | ## Other 22 | *.xccheckout 23 | *.moved-aside 24 | *.xcuserstate 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | .build/ 37 | 38 | # Carthage 39 | Carthage/Build 40 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Toast", 6 | platforms: [ 7 | .iOS(.v12), 8 | ], 9 | products: [ 10 | .library( 11 | name: "Toast", 12 | targets: ["Toast"]), 13 | ], 14 | dependencies: [], 15 | targets: [ 16 | .target( 17 | name: "Toast", 18 | dependencies: [], 19 | path: "Toast", 20 | resources: [ 21 | .copy("Resources/PrivacyInfo.xcprivacy") 22 | ]) 23 | ], 24 | swiftLanguageVersions: [.v5] 25 | ) -------------------------------------------------------------------------------- /Toast-Swift-Framework/Toast-Swift-Framework.h: -------------------------------------------------------------------------------- 1 | // 2 | // Toast-Swift-Framework.h 3 | // Toast-Swift-Framework 4 | // 5 | // Created by Sandro Machado on 14/04/16. 6 | // Copyright © 2016 Charles Scalesse. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Toast-Swift-Framework. 12 | FOUNDATION_EXPORT double Toast_Swift_FrameworkVersionNumber; 13 | 14 | //! Project version string for Toast-Swift-Framework. 15 | FOUNDATION_EXPORT const unsigned char Toast_Swift_FrameworkVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Toast-Swift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Toast-Swift" 3 | s.version = "5.1.1" 4 | s.summary = "A Swift extension that adds toast notifications to the UIView object class." 5 | s.homepage = "https://github.com/scalessec/Toast-Swift" 6 | s.license = 'MIT' 7 | s.author = { "Charles Scalesse" => "scalessec@gmail.com" } 8 | s.source = { :git => "https://github.com/scalessec/Toast-Swift.git", :tag => "5.1.1" } 9 | s.platform = :ios 10 | s.source_files = 'Toast/*.swift' 11 | s.resource_bundles = {'Toast-Swift': 'Toast/Resources/PrivacyInfo.xcprivacy'} 12 | s.framework = 'QuartzCore' 13 | s.requires_arc = true 14 | s.ios.deployment_target = '12.0' 15 | s.swift_version = '5.0' 16 | end 17 | -------------------------------------------------------------------------------- /Toast-Swift-Framework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2024 Charles Scalesse. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Toast-Swift.xcodeproj/project.xcworkspace/xcshareddata/Toast-Swift.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "1AE4A4A0564E38992EFB0E8F9258432F89681A15", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "1AE4A4A0564E38992EFB0E8F9258432F89681A15" : 0, 8 | "775E195363DA268E1E1924859088052B9B8BE981" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "332B5743-ED98-44EA-BC62-A34924B1B90B", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "1AE4A4A0564E38992EFB0E8F9258432F89681A15" : "Toast-Swift\/", 13 | "775E195363DA268E1E1924859088052B9B8BE981" : "Toast\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Toast-Swift", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Toast-Swift.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:scalessec\/Toast-Swift.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1AE4A4A0564E38992EFB0E8F9258432F89681A15" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/scalessec\/Toast.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "775E195363DA268E1E1924859088052B9B8BE981" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Toast-Swift.xcodeproj/xcshareddata/xcschemes/ToastSwiftFramework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Toast-Swift 4 | // 5 | // Copyright (c) 2015-2024 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 | 28 | @UIApplicationMain 29 | class AppDelegate: UIResponder, UIApplicationDelegate { 30 | 31 | lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds) 32 | 33 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 34 | let viewController = ViewController(style: .plain) 35 | let navigationController = UINavigationController(rootViewController: viewController) 36 | self.window?.rootViewController = navigationController 37 | self.window?.makeKeyAndVisible() 38 | 39 | configureAppearance() 40 | 41 | return true 42 | } 43 | 44 | private func configureAppearance() { 45 | let navigationBarColor: UIColor = .lightBlue 46 | let titleTextAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor: UIColor.white] 47 | 48 | let appearance = UINavigationBar.appearance() 49 | if #available(iOS 15, *) { 50 | let barAppearance = UINavigationBarAppearance() 51 | barAppearance.backgroundColor = navigationBarColor 52 | barAppearance.titleTextAttributes = titleTextAttributes 53 | appearance.standardAppearance = barAppearance 54 | appearance.scrollEdgeAppearance = barAppearance 55 | } else { 56 | appearance.barTintColor = navigationBarColor 57 | appearance.titleTextAttributes = titleTextAttributes 58 | } 59 | } 60 | } 61 | 62 | // MARK: - Theming 63 | 64 | extension UIColor { 65 | 66 | static var lightBlue: UIColor { 67 | return UIColor(red: 76.0 / 255.0, green: 152.0 / 255.0, blue: 213.0 / 255.0, alpha: 1.0) 68 | } 69 | 70 | static var darkBlue: UIColor { 71 | return UIColor(red: 62.0 / 255.0, green: 128.0 / 255.0, blue: 180.0 / 255.0, alpha: 1.0) 72 | } 73 | 74 | } 75 | 76 | -------------------------------------------------------------------------------- /Example/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Toast-Swift.xcodeproj/xcshareddata/xcschemes/Toast-Swift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Toast-Swift 2 | ============= 3 | 4 | [![CocoaPods Version](https://img.shields.io/cocoapods/v/Toast-Swift.svg)](http://cocoadocs.org/docsets/Toast-Swift) 5 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | 7 | Toast-Swift 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. 8 | 9 | **Toast-Swift is a native Swift port of [Toast for iOS](https://github.com/scalessec/Toast "Toast for iOS").** 10 | 11 | Screenshots 12 | --------- 13 | ![Toast-Swift Screenshots](toast_swift_screenshot.jpg) 14 | 15 | 16 | Basic Examples 17 | --------- 18 | ```swift 19 | // basic usage 20 | self.view.makeToast("This is a piece of toast") 21 | 22 | // toast with a specific duration and position 23 | self.view.makeToast("This is a piece of toast", duration: 3.0, position: .top) 24 | 25 | // toast presented with multiple options and with a completion closure 26 | self.view.makeToast("This is a piece of toast", duration: 2.0, point: CGPoint(x: 110.0, y: 110.0), title: "Toast Title", image: UIImage(named: "toast.png")) { didTap in 27 | if didTap { 28 | print("completion from tap") 29 | } else { 30 | print("completion without tap") 31 | } 32 | } 33 | 34 | // display toast with an activity spinner 35 | self.view.makeToastActivity(.center) 36 | 37 | // display any view as toast 38 | self.view.showToast(myView) 39 | 40 | // immediately hides all toast views in self.view 41 | self.view.hideAllToasts() 42 | ``` 43 | 44 | But wait, there's more! 45 | --------- 46 | ```swift 47 | // create a new style 48 | var style = ToastStyle() 49 | 50 | // this is just one of many style options 51 | style.messageColor = .blue 52 | 53 | // present the toast with the new style 54 | self.view.makeToast("This is a piece of toast", duration: 3.0, position: .bottom, style: style) 55 | 56 | // or perhaps you want to use this style for all toasts going forward? 57 | // just set the shared style and there's no need to provide the style again 58 | ToastManager.shared.style = style 59 | self.view.makeToast("This is a piece of toast") // now uses the shared style 60 | 61 | // toggle "tap to dismiss" functionality 62 | ToastManager.shared.isTapToDismissEnabled = true 63 | 64 | // toggle queueing behavior 65 | ToastManager.shared.isQueueEnabled = true 66 | ``` 67 | 68 | See the demo project for more examples. 69 | 70 | 71 | Setup Instructions 72 | ------------------ 73 | 74 | [CocoaPods](http://cocoapods.org) 75 | ------------------ 76 | 77 | To integrate Toast-Swift into your Xcode project using CocoaPods, specify it in your `Podfile`: 78 | 79 | ```ruby 80 | pod 'Toast-Swift', '~> 5.1.1' 81 | ``` 82 | 83 | and in your code add `import Toast_Swift`. 84 | 85 | [Carthage](https://github.com/Carthage/Carthage) 86 | ------------------ 87 | 88 | To integrate Toast-Swift into your Xcode project using Carthage, specify it in your `Cartfile`: 89 | 90 | ```ogdl 91 | github "scalessec/Toast-Swift" ~> 5.1.1 92 | ``` 93 | 94 | Run `carthage update` to build the framework and drag the built `ToastSwiftFramework.framework` into your Xcode project. 95 | 96 | and in your code add `import ToastSwiftFramework`. 97 | 98 | [Swift Package Manager](https://swift.org/package-manager/) 99 | ------------------ 100 | 101 | When using Xcode 11 or later, you can install `Toast` by going to your Project settings > `Swift Packages` and add the repository by providing the GitHub URL. Alternatively, you can go to `File` > `Swift Packages` > `Add Package Dependencies...` 102 | 103 | Manually 104 | ------------------ 105 | 106 | 1. Add `Toast.swift` to your project. 107 | 2. Grab yourself a cold 🍺. 108 | 109 | Compatibility 110 | ------------------ 111 | * Version `5.x.x` requires Swift 5 and Xcode 10.2 or later. 112 | * Version `4.x.x` requires Swift 4.2 and Xcode 10. 113 | * Version `3.x.x` requires Swift 4 and Xcode 9. 114 | * Version `2.x.x` requires Swift 3 and Xcode 8. 115 | * Version `1.4.x` requires Swift 2.2 and Xcode 7.3. 116 | * Version `1.0.0` can be used with Swift 2.1 and earlier versions of Xcode. 117 | 118 | Privacy 119 | ----------- 120 | Toast-Swift does not collect any data. A [privacy manifest](Toast/Resources/PrivacyInfo.xcprivacy) is provided with the library. See [Apple's documentation](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files) for related details. 121 | 122 | MIT License 123 | ----------- 124 | Copyright (c) 2015-2024 Charles Scalesse. 125 | 126 | Permission is hereby granted, free of charge, to any person obtaining a 127 | copy of this software and associated documentation files (the 128 | "Software"), to deal in the Software without restriction, including 129 | without limitation the rights to use, copy, modify, merge, publish, 130 | distribute, sublicense, and/or sell copies of the Software, and to 131 | permit persons to whom the Software is furnished to do so, subject to 132 | the following conditions: 133 | 134 | The above copyright notice and this permission notice shall be included 135 | in all copies or substantial portions of the Software. 136 | 137 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 138 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 139 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 140 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 141 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 142 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 143 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 144 | -------------------------------------------------------------------------------- /Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Toast-Swift 4 | // 5 | // Copyright (c) 2015-2024 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 | 28 | class ViewController: UITableViewController { 29 | 30 | fileprivate var showingActivity = false 31 | 32 | fileprivate struct ReuseIdentifiers { 33 | static let switchCellId = "switchCell" 34 | static let exampleCellId = "exampleCell" 35 | } 36 | 37 | // MARK: - Constructors 38 | 39 | override init(style: UITableView.Style) { 40 | super.init(style: style) 41 | self.title = "Toast-Swift" 42 | } 43 | 44 | required init?(coder aDecoder: NSCoder) { 45 | fatalError("not used") 46 | } 47 | 48 | // MARK: - View Lifecycle 49 | 50 | override func viewDidLoad() { 51 | super.viewDidLoad() 52 | self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: ReuseIdentifiers.exampleCellId) 53 | } 54 | 55 | // MARK: - Events 56 | 57 | @objc 58 | private func handleTapToDismissToggled() { 59 | ToastManager.shared.isTapToDismissEnabled = !ToastManager.shared.isTapToDismissEnabled 60 | } 61 | 62 | @objc 63 | private func handleQueueToggled() { 64 | ToastManager.shared.isQueueEnabled = !ToastManager.shared.isQueueEnabled 65 | } 66 | } 67 | 68 | // MARK: - UITableViewDelegate & DataSource Methods 69 | 70 | extension ViewController { 71 | 72 | override func numberOfSections(in tableView: UITableView) -> Int { 73 | return 2 74 | } 75 | 76 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 77 | if section == 0 { 78 | return 2 79 | } else { 80 | return 11 81 | } 82 | } 83 | 84 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 85 | return 60.0 86 | } 87 | 88 | override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 89 | return 40.0 90 | } 91 | 92 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 93 | if section == 0 { 94 | return "SETTINGS" 95 | } else { 96 | return "EXAMPLES" 97 | } 98 | } 99 | 100 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 101 | if indexPath.section == 0 { 102 | 103 | var cell = tableView.dequeueReusableCell(withIdentifier: ReuseIdentifiers.switchCellId) 104 | 105 | if indexPath.row == 0 { 106 | if cell == nil { 107 | cell = UITableViewCell(style: .default, reuseIdentifier: ReuseIdentifiers.switchCellId) 108 | let tapToDismissSwitch = UISwitch() 109 | tapToDismissSwitch.onTintColor = .darkBlue 110 | tapToDismissSwitch.isOn = ToastManager.shared.isTapToDismissEnabled 111 | tapToDismissSwitch.addTarget(self, action: #selector(ViewController.handleTapToDismissToggled), for: .valueChanged) 112 | cell?.accessoryView = tapToDismissSwitch 113 | cell?.selectionStyle = .none 114 | cell?.textLabel?.font = UIFont.systemFont(ofSize: 16.0) 115 | } 116 | cell?.textLabel?.text = "Tap to dismiss" 117 | } else { 118 | if cell == nil { 119 | cell = UITableViewCell(style: .default, reuseIdentifier: ReuseIdentifiers.switchCellId) 120 | let queueSwitch = UISwitch() 121 | queueSwitch.onTintColor = .darkBlue 122 | queueSwitch.isOn = ToastManager.shared.isQueueEnabled 123 | queueSwitch.addTarget(self, action: #selector(ViewController.handleQueueToggled), for: .valueChanged) 124 | cell?.accessoryView = queueSwitch 125 | cell?.selectionStyle = .none 126 | cell?.textLabel?.font = UIFont.systemFont(ofSize: 16.0) 127 | } 128 | cell?.textLabel?.text = "Queue toast" 129 | } 130 | 131 | return cell! 132 | 133 | } else { 134 | 135 | let cell = tableView.dequeueReusableCell(withIdentifier: ReuseIdentifiers.exampleCellId, for: indexPath) 136 | cell.textLabel?.numberOfLines = 2 137 | cell.textLabel?.font = UIFont.systemFont(ofSize: 16.0) 138 | cell.accessoryType = .disclosureIndicator 139 | 140 | switch indexPath.row { 141 | case 0: cell.textLabel?.text = "Make toast" 142 | case 1: cell.textLabel?.text = "Make toast on top for 3 seconds" 143 | case 2: cell.textLabel?.text = "Make toast with a title" 144 | case 3: cell.textLabel?.text = "Make toast with an image" 145 | case 4: cell.textLabel?.text = "Make toast with a title, image, and completion closure" 146 | case 5: cell.textLabel?.text = "Make toast with a custom style" 147 | case 6: cell.textLabel?.text = "Show a custom view as toast" 148 | case 7: cell.textLabel?.text = "Show an image as toast at point\n(110, 110)" 149 | case 8: cell.textLabel?.text = showingActivity ? "Hide toast activity" : "Show toast activity" 150 | case 9: cell.textLabel?.text = "Hide toast" 151 | case 10: cell.textLabel?.text = "Hide all toasts" 152 | default: cell.textLabel?.text = nil 153 | } 154 | 155 | return cell 156 | 157 | } 158 | } 159 | 160 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 161 | guard indexPath.section > 0 else { return } 162 | 163 | tableView.deselectRow(at: indexPath, animated: true) 164 | 165 | switch indexPath.row { 166 | case 0: 167 | // Make Toast 168 | self.navigationController?.view.makeToast("This is a piece of toast") 169 | case 1: 170 | // Make toast with a duration and position 171 | self.navigationController?.view.makeToast("This is a piece of toast on top for 3 seconds", duration: 3.0, position: .top) 172 | case 2: 173 | // Make toast with a title 174 | self.navigationController?.view.makeToast("This is a piece of toast with a title", duration: 2.0, position: .top, title: "Toast Title", image: nil) 175 | case 3: 176 | // Make toast with an image 177 | self.navigationController?.view.makeToast("This is a piece of toast with an image", duration: 2.0, position: .center, title: nil, image: UIImage(named: "toast.png")) 178 | case 4: 179 | // Make toast with an image, title, and completion closure 180 | self.navigationController?.view.makeToast("This is a piece of toast with a title, image, and completion closure", duration: 2.0, position: .bottom, title: "Toast Title", image: UIImage(named: "toast.png")) { didTap in 181 | if didTap { 182 | print("completion from tap") 183 | } else { 184 | print("completion without tap") 185 | } 186 | } 187 | case 5: 188 | // Make toast with a custom style 189 | var style = ToastStyle() 190 | style.messageFont = UIFont(name: "Zapfino", size: 14.0)! 191 | style.messageColor = UIColor.red 192 | style.messageAlignment = .center 193 | style.backgroundColor = UIColor.yellow 194 | self.navigationController?.view.makeToast("This is a piece of toast with a custom style", duration: 3.0, position: .bottom, style: style) 195 | case 6: 196 | // Show a custom view as toast 197 | let customView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 80.0, height: 400.0)) 198 | customView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] 199 | customView.backgroundColor = .lightBlue 200 | self.navigationController?.view.showToast(customView, duration: 2.0, position: .center) 201 | case 7: 202 | // Show an image view as toast, on center at point (110,110) 203 | let toastView = UIImageView(image: UIImage(named: "toast.png")) 204 | self.navigationController?.view.showToast(toastView, duration: 2.0, point: CGPoint(x: 110.0, y: 110.0)) 205 | case 8: 206 | // Make toast activity 207 | if !showingActivity { 208 | self.navigationController?.view.makeToastActivity(.center) 209 | } else { 210 | self.navigationController?.view.hideToastActivity() 211 | } 212 | 213 | showingActivity.toggle() 214 | 215 | tableView.reloadData() 216 | case 9: 217 | // Hide toast 218 | self.navigationController?.view.hideToast() 219 | case 10: 220 | // Hide all toasts 221 | self.navigationController?.view.hideAllToasts() 222 | default: 223 | break 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /Toast-Swift.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 151796EB1BE68E4B004BA6E5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 151796EA1BE68E4B004BA6E5 /* AppDelegate.swift */; }; 11 | 151796ED1BE68E4B004BA6E5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 151796EC1BE68E4B004BA6E5 /* ViewController.swift */; }; 12 | 151796F21BE68E4B004BA6E5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 151796F11BE68E4B004BA6E5 /* Assets.xcassets */; }; 13 | 151797001BE69026004BA6E5 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 151796FF1BE69026004BA6E5 /* Toast.swift */; }; 14 | 15FC18381FC0877300C57AFA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 15FC18371FC0877300C57AFA /* LaunchScreen.storyboard */; }; 15 | 2A3743511CBFAF4B006A3BAB /* Toast-Swift-Framework.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A3743501CBFAF4B006A3BAB /* Toast-Swift-Framework.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | 2A3743561CBFAF94006A3BAB /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 151796FF1BE69026004BA6E5 /* Toast.swift */; }; 17 | 56E0D0E72B4A084F00926644 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 56E0D0E62B4A084F00926644 /* PrivacyInfo.xcprivacy */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 151796E71BE68E4B004BA6E5 /* Toast-Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Toast-Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 151796EA1BE68E4B004BA6E5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Example/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 23 | 151796EC1BE68E4B004BA6E5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = Example/ViewController.swift; sourceTree = SOURCE_ROOT; }; 24 | 151796F11BE68E4B004BA6E5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Example/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 25 | 151796F61BE68E4B004BA6E5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Example/Info.plist; sourceTree = ""; }; 26 | 151796FF1BE69026004BA6E5 /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Toast.swift; path = Toast/Toast.swift; sourceTree = SOURCE_ROOT; }; 27 | 15FC18371FC0877300C57AFA /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = Example/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 2A37434E1CBFAF4B006A3BAB /* ToastSwiftFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ToastSwiftFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 2A3743501CBFAF4B006A3BAB /* Toast-Swift-Framework.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Toast-Swift-Framework.h"; sourceTree = ""; }; 30 | 2A3743521CBFAF4C006A3BAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 56E0D0E62B4A084F00926644 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Toast/Resources/PrivacyInfo.xcprivacy; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | 151796E41BE68E4B004BA6E5 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | 2A37434A1CBFAF4B006A3BAB /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | 151796DE1BE68E4B004BA6E5 = { 53 | isa = PBXGroup; 54 | children = ( 55 | 151796E91BE68E4B004BA6E5 /* Toast */, 56 | 151796FC1BE68E75004BA6E5 /* Example */, 57 | 2A37434F1CBFAF4B006A3BAB /* Toast-Swift-Framework */, 58 | 151796E81BE68E4B004BA6E5 /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | 151796E81BE68E4B004BA6E5 /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 151796E71BE68E4B004BA6E5 /* Toast-Swift.app */, 66 | 2A37434E1CBFAF4B006A3BAB /* ToastSwiftFramework.framework */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | 151796E91BE68E4B004BA6E5 /* Toast */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 151796FF1BE69026004BA6E5 /* Toast.swift */, 75 | 56E0D0E52B4A083500926644 /* Resources */, 76 | ); 77 | name = Toast; 78 | sourceTree = ""; 79 | }; 80 | 151796FC1BE68E75004BA6E5 /* Example */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 151796EA1BE68E4B004BA6E5 /* AppDelegate.swift */, 84 | 151796EC1BE68E4B004BA6E5 /* ViewController.swift */, 85 | 151796FD1BE68EAC004BA6E5 /* Supporting Files */, 86 | 151796FE1BE68EB5004BA6E5 /* Resources */, 87 | ); 88 | name = Example; 89 | sourceTree = ""; 90 | }; 91 | 151796FD1BE68EAC004BA6E5 /* Supporting Files */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 151796F61BE68E4B004BA6E5 /* Info.plist */, 95 | ); 96 | name = "Supporting Files"; 97 | sourceTree = ""; 98 | }; 99 | 151796FE1BE68EB5004BA6E5 /* Resources */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 151796F11BE68E4B004BA6E5 /* Assets.xcassets */, 103 | 15FC18371FC0877300C57AFA /* LaunchScreen.storyboard */, 104 | ); 105 | name = Resources; 106 | sourceTree = ""; 107 | }; 108 | 2A37434F1CBFAF4B006A3BAB /* Toast-Swift-Framework */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 2A3743501CBFAF4B006A3BAB /* Toast-Swift-Framework.h */, 112 | 2A3743521CBFAF4C006A3BAB /* Info.plist */, 113 | ); 114 | path = "Toast-Swift-Framework"; 115 | sourceTree = ""; 116 | }; 117 | 56E0D0E52B4A083500926644 /* Resources */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 56E0D0E62B4A084F00926644 /* PrivacyInfo.xcprivacy */, 121 | ); 122 | name = Resources; 123 | sourceTree = ""; 124 | }; 125 | /* End PBXGroup section */ 126 | 127 | /* Begin PBXHeadersBuildPhase section */ 128 | 2A37434B1CBFAF4B006A3BAB /* Headers */ = { 129 | isa = PBXHeadersBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | 2A3743511CBFAF4B006A3BAB /* Toast-Swift-Framework.h in Headers */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXHeadersBuildPhase section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | 151796E61BE68E4B004BA6E5 /* Toast-Swift */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 151796F91BE68E4B004BA6E5 /* Build configuration list for PBXNativeTarget "Toast-Swift" */; 142 | buildPhases = ( 143 | 151796E31BE68E4B004BA6E5 /* Sources */, 144 | 151796E41BE68E4B004BA6E5 /* Frameworks */, 145 | 151796E51BE68E4B004BA6E5 /* Resources */, 146 | ); 147 | buildRules = ( 148 | ); 149 | dependencies = ( 150 | ); 151 | name = "Toast-Swift"; 152 | productName = "Toast-Swift"; 153 | productReference = 151796E71BE68E4B004BA6E5 /* Toast-Swift.app */; 154 | productType = "com.apple.product-type.application"; 155 | }; 156 | 2A37434D1CBFAF4B006A3BAB /* ToastSwiftFramework */ = { 157 | isa = PBXNativeTarget; 158 | buildConfigurationList = 2A3743551CBFAF4C006A3BAB /* Build configuration list for PBXNativeTarget "ToastSwiftFramework" */; 159 | buildPhases = ( 160 | 2A3743491CBFAF4B006A3BAB /* Sources */, 161 | 2A37434A1CBFAF4B006A3BAB /* Frameworks */, 162 | 2A37434B1CBFAF4B006A3BAB /* Headers */, 163 | 2A37434C1CBFAF4B006A3BAB /* Resources */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = ToastSwiftFramework; 170 | productName = "Toast-Swift-Framework"; 171 | productReference = 2A37434E1CBFAF4B006A3BAB /* ToastSwiftFramework.framework */; 172 | productType = "com.apple.product-type.framework"; 173 | }; 174 | /* End PBXNativeTarget section */ 175 | 176 | /* Begin PBXProject section */ 177 | 151796DF1BE68E4B004BA6E5 /* Project object */ = { 178 | isa = PBXProject; 179 | attributes = { 180 | BuildIndependentTargetsInParallel = YES; 181 | LastSwiftUpdateCheck = 0710; 182 | LastUpgradeCheck = 1500; 183 | ORGANIZATIONNAME = "Charles Scalesse"; 184 | TargetAttributes = { 185 | 151796E61BE68E4B004BA6E5 = { 186 | CreatedOnToolsVersion = 7.1; 187 | LastSwiftMigration = 1020; 188 | ProvisioningStyle = Automatic; 189 | }; 190 | 2A37434D1CBFAF4B006A3BAB = { 191 | CreatedOnToolsVersion = 7.3; 192 | }; 193 | }; 194 | }; 195 | buildConfigurationList = 151796E21BE68E4B004BA6E5 /* Build configuration list for PBXProject "Toast-Swift" */; 196 | compatibilityVersion = "Xcode 3.2"; 197 | developmentRegion = en; 198 | hasScannedForEncodings = 0; 199 | knownRegions = ( 200 | en, 201 | Base, 202 | ); 203 | mainGroup = 151796DE1BE68E4B004BA6E5; 204 | productRefGroup = 151796E81BE68E4B004BA6E5 /* Products */; 205 | projectDirPath = ""; 206 | projectRoot = ""; 207 | targets = ( 208 | 151796E61BE68E4B004BA6E5 /* Toast-Swift */, 209 | 2A37434D1CBFAF4B006A3BAB /* ToastSwiftFramework */, 210 | ); 211 | }; 212 | /* End PBXProject section */ 213 | 214 | /* Begin PBXResourcesBuildPhase section */ 215 | 151796E51BE68E4B004BA6E5 /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | 151796F21BE68E4B004BA6E5 /* Assets.xcassets in Resources */, 220 | 15FC18381FC0877300C57AFA /* LaunchScreen.storyboard in Resources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | 2A37434C1CBFAF4B006A3BAB /* Resources */ = { 225 | isa = PBXResourcesBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | 56E0D0E72B4A084F00926644 /* PrivacyInfo.xcprivacy in Resources */, 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | /* End PBXResourcesBuildPhase section */ 233 | 234 | /* Begin PBXSourcesBuildPhase section */ 235 | 151796E31BE68E4B004BA6E5 /* Sources */ = { 236 | isa = PBXSourcesBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | 151796ED1BE68E4B004BA6E5 /* ViewController.swift in Sources */, 240 | 151797001BE69026004BA6E5 /* Toast.swift in Sources */, 241 | 151796EB1BE68E4B004BA6E5 /* AppDelegate.swift in Sources */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | 2A3743491CBFAF4B006A3BAB /* Sources */ = { 246 | isa = PBXSourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | 2A3743561CBFAF94006A3BAB /* Toast.swift in Sources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXSourcesBuildPhase section */ 254 | 255 | /* Begin XCBuildConfiguration section */ 256 | 151796F71BE68E4B004BA6E5 /* Debug */ = { 257 | isa = XCBuildConfiguration; 258 | buildSettings = { 259 | ALWAYS_SEARCH_USER_PATHS = NO; 260 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 261 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 262 | CLANG_CXX_LIBRARY = "libc++"; 263 | CLANG_ENABLE_MODULES = YES; 264 | CLANG_ENABLE_OBJC_ARC = YES; 265 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 266 | CLANG_WARN_BOOL_CONVERSION = YES; 267 | CLANG_WARN_COMMA = YES; 268 | CLANG_WARN_CONSTANT_CONVERSION = YES; 269 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 270 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INFINITE_RECURSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 277 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 279 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 280 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 281 | CLANG_WARN_STRICT_PROTOTYPES = YES; 282 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 283 | CLANG_WARN_UNREACHABLE_CODE = YES; 284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 285 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 286 | COPY_PHASE_STRIP = NO; 287 | CURRENT_PROJECT_VERSION = 5.1.0; 288 | DEBUG_INFORMATION_FORMAT = dwarf; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | ENABLE_TESTABILITY = YES; 291 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 292 | GCC_C_LANGUAGE_STANDARD = gnu99; 293 | GCC_DYNAMIC_NO_PIC = NO; 294 | GCC_NO_COMMON_BLOCKS = YES; 295 | GCC_OPTIMIZATION_LEVEL = 0; 296 | GCC_PREPROCESSOR_DEFINITIONS = ( 297 | "DEBUG=1", 298 | "$(inherited)", 299 | ); 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 307 | MARKETING_VERSION = 5.1.0; 308 | MTL_ENABLE_DEBUG_INFO = YES; 309 | ONLY_ACTIVE_ARCH = YES; 310 | SDKROOT = iphoneos; 311 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 312 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 313 | SWIFT_VERSION = 5.0; 314 | }; 315 | name = Debug; 316 | }; 317 | 151796F81BE68E4B004BA6E5 /* Release */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 327 | CLANG_WARN_BOOL_CONVERSION = YES; 328 | CLANG_WARN_COMMA = YES; 329 | CLANG_WARN_CONSTANT_CONVERSION = YES; 330 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 331 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 332 | CLANG_WARN_EMPTY_BODY = YES; 333 | CLANG_WARN_ENUM_CONVERSION = YES; 334 | CLANG_WARN_INFINITE_RECURSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 338 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 340 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 341 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 342 | CLANG_WARN_STRICT_PROTOTYPES = YES; 343 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 347 | COPY_PHASE_STRIP = NO; 348 | CURRENT_PROJECT_VERSION = 5.1.0; 349 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 350 | ENABLE_NS_ASSERTIONS = NO; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu99; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 362 | MARKETING_VERSION = 5.1.0; 363 | MTL_ENABLE_DEBUG_INFO = NO; 364 | SDKROOT = iphoneos; 365 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 366 | SWIFT_VERSION = 5.0; 367 | VALIDATE_PRODUCT = YES; 368 | }; 369 | name = Release; 370 | }; 371 | 151796FA1BE68E4B004BA6E5 /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 375 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 376 | CODE_SIGN_STYLE = Automatic; 377 | DEVELOPMENT_TEAM = ""; 378 | INFOPLIST_FILE = Example/Info.plist; 379 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 380 | LD_RUNPATH_SEARCH_PATHS = ( 381 | "$(inherited)", 382 | "@executable_path/Frameworks", 383 | ); 384 | PRODUCT_BUNDLE_IDENTIFIER = "com.scalessec.Toast-Swift"; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | PROVISIONING_PROFILE_SPECIFIER = ""; 387 | }; 388 | name = Debug; 389 | }; 390 | 151796FB1BE68E4B004BA6E5 /* Release */ = { 391 | isa = XCBuildConfiguration; 392 | buildSettings = { 393 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 394 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 395 | CODE_SIGN_STYLE = Automatic; 396 | DEVELOPMENT_TEAM = ""; 397 | INFOPLIST_FILE = Example/Info.plist; 398 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 399 | LD_RUNPATH_SEARCH_PATHS = ( 400 | "$(inherited)", 401 | "@executable_path/Frameworks", 402 | ); 403 | PRODUCT_BUNDLE_IDENTIFIER = "com.scalessec.Toast-Swift"; 404 | PRODUCT_NAME = "$(TARGET_NAME)"; 405 | PROVISIONING_PROFILE_SPECIFIER = ""; 406 | SWIFT_COMPILATION_MODE = wholemodule; 407 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 408 | }; 409 | name = Release; 410 | }; 411 | 2A3743531CBFAF4C006A3BAB /* Debug */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | CLANG_ANALYZER_NONNULL = YES; 415 | CODE_SIGN_IDENTITY = ""; 416 | CURRENT_PROJECT_VERSION = 1; 417 | DEFINES_MODULE = YES; 418 | DYLIB_COMPATIBILITY_VERSION = 1; 419 | DYLIB_CURRENT_VERSION = 1; 420 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 421 | ENABLE_MODULE_VERIFIER = YES; 422 | INFOPLIST_FILE = "Toast-Swift-Framework/Info.plist"; 423 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 424 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 425 | LD_RUNPATH_SEARCH_PATHS = ( 426 | "$(inherited)", 427 | "@executable_path/Frameworks", 428 | "@loader_path/Frameworks", 429 | ); 430 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 431 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; 432 | PRODUCT_BUNDLE_IDENTIFIER = "com.scalessec.Toast-Swift-Framework"; 433 | PRODUCT_NAME = "$(TARGET_NAME)"; 434 | SKIP_INSTALL = YES; 435 | TARGETED_DEVICE_FAMILY = "1,2"; 436 | VERSIONING_SYSTEM = "apple-generic"; 437 | VERSION_INFO_PREFIX = ""; 438 | }; 439 | name = Debug; 440 | }; 441 | 2A3743541CBFAF4C006A3BAB /* Release */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | CLANG_ANALYZER_NONNULL = YES; 445 | CODE_SIGN_IDENTITY = ""; 446 | CURRENT_PROJECT_VERSION = 1; 447 | DEFINES_MODULE = YES; 448 | DYLIB_COMPATIBILITY_VERSION = 1; 449 | DYLIB_CURRENT_VERSION = 1; 450 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 451 | ENABLE_MODULE_VERIFIER = YES; 452 | INFOPLIST_FILE = "Toast-Swift-Framework/Info.plist"; 453 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 454 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 455 | LD_RUNPATH_SEARCH_PATHS = ( 456 | "$(inherited)", 457 | "@executable_path/Frameworks", 458 | "@loader_path/Frameworks", 459 | ); 460 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 461 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; 462 | PRODUCT_BUNDLE_IDENTIFIER = "com.scalessec.Toast-Swift-Framework"; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | SKIP_INSTALL = YES; 465 | SWIFT_COMPILATION_MODE = wholemodule; 466 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 467 | TARGETED_DEVICE_FAMILY = "1,2"; 468 | VERSIONING_SYSTEM = "apple-generic"; 469 | VERSION_INFO_PREFIX = ""; 470 | }; 471 | name = Release; 472 | }; 473 | /* End XCBuildConfiguration section */ 474 | 475 | /* Begin XCConfigurationList section */ 476 | 151796E21BE68E4B004BA6E5 /* Build configuration list for PBXProject "Toast-Swift" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | 151796F71BE68E4B004BA6E5 /* Debug */, 480 | 151796F81BE68E4B004BA6E5 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | 151796F91BE68E4B004BA6E5 /* Build configuration list for PBXNativeTarget "Toast-Swift" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | 151796FA1BE68E4B004BA6E5 /* Debug */, 489 | 151796FB1BE68E4B004BA6E5 /* Release */, 490 | ); 491 | defaultConfigurationIsVisible = 0; 492 | defaultConfigurationName = Release; 493 | }; 494 | 2A3743551CBFAF4C006A3BAB /* Build configuration list for PBXNativeTarget "ToastSwiftFramework" */ = { 495 | isa = XCConfigurationList; 496 | buildConfigurations = ( 497 | 2A3743531CBFAF4C006A3BAB /* Debug */, 498 | 2A3743541CBFAF4C006A3BAB /* Release */, 499 | ); 500 | defaultConfigurationIsVisible = 0; 501 | defaultConfigurationName = Release; 502 | }; 503 | /* End XCConfigurationList section */ 504 | }; 505 | rootObject = 151796DF1BE68E4B004BA6E5 /* Project object */; 506 | } 507 | -------------------------------------------------------------------------------- /Toast/Toast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Toast.swift 3 | // Toast-Swift 4 | // 5 | // Copyright (c) 2015-2024 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 = malloc(1) 46 | static var duration = malloc(1) 47 | static var point = malloc(1) 48 | static var completion = malloc(1) 49 | static var activeToasts = malloc(1) 50 | static var activityView = malloc(1) 51 | static var queue = malloc(1) 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 | 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 | 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 | 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 | 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 | 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 | 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 | func hideAllToasts(includeActivity: Bool = false, clearQueue: Bool = true) { 218 | if clearQueue { 219 | clearToastQueue() 220 | } 221 | 222 | activeToasts.compactMap { $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 | 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 | 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 | 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 | 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: - Helper Methods 297 | 298 | /** 299 | Returns `true` if a toast view or toast activity view is actively being displayed. 300 | */ 301 | func isShowingToast() -> Bool { 302 | return activeToasts.count > 0 || objc_getAssociatedObject(self, &ToastKeys.activityView) != nil 303 | } 304 | 305 | // MARK: - Private Activity Methods 306 | 307 | private func makeToastActivity(_ toast: UIView, point: CGPoint) { 308 | toast.alpha = 0.0 309 | toast.center = point 310 | 311 | objc_setAssociatedObject(self, &ToastKeys.activityView, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 312 | 313 | self.addSubview(toast) 314 | 315 | UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: .curveEaseOut, animations: { 316 | toast.alpha = 1.0 317 | }) 318 | } 319 | 320 | private func createToastActivityView() -> UIView { 321 | let style = ToastManager.shared.style 322 | 323 | let activityView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: style.activitySize.width, height: style.activitySize.height)) 324 | activityView.backgroundColor = style.activityBackgroundColor 325 | activityView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] 326 | activityView.layer.cornerRadius = style.cornerRadius 327 | 328 | if style.displayShadow { 329 | activityView.layer.shadowColor = style.shadowColor.cgColor 330 | activityView.layer.shadowOpacity = style.shadowOpacity 331 | activityView.layer.shadowRadius = style.shadowRadius 332 | activityView.layer.shadowOffset = style.shadowOffset 333 | } 334 | 335 | let activityIndicatorView = UIActivityIndicatorView(style: .whiteLarge) 336 | activityIndicatorView.center = CGPoint(x: activityView.bounds.size.width / 2.0, y: activityView.bounds.size.height / 2.0) 337 | activityView.addSubview(activityIndicatorView) 338 | activityIndicatorView.color = style.activityIndicatorColor 339 | activityIndicatorView.startAnimating() 340 | 341 | return activityView 342 | } 343 | 344 | // MARK: - Private Show/Hide Methods 345 | 346 | private func showToast(_ toast: UIView, duration: TimeInterval, point: CGPoint) { 347 | toast.center = point 348 | toast.alpha = 0.0 349 | 350 | if ToastManager.shared.isTapToDismissEnabled { 351 | let recognizer = UITapGestureRecognizer(target: self, action: #selector(UIView.handleToastTapped(_:))) 352 | toast.addGestureRecognizer(recognizer) 353 | toast.isUserInteractionEnabled = true 354 | toast.isExclusiveTouch = true 355 | } 356 | 357 | activeToasts.add(toast) 358 | self.addSubview(toast) 359 | 360 | let timer = Timer(timeInterval: duration, target: self, selector: #selector(UIView.toastTimerDidFinish(_:)), userInfo: toast, repeats: false) 361 | objc_setAssociatedObject(toast, &ToastKeys.timer, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 362 | 363 | UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseOut, .allowUserInteraction], animations: { 364 | toast.alpha = 1.0 365 | }) { _ in 366 | guard let timer = objc_getAssociatedObject(toast, &ToastKeys.timer) as? Timer else { return } 367 | RunLoop.main.add(timer, forMode: .common) 368 | } 369 | 370 | UIAccessibility.post(notification: .screenChanged, argument: toast) 371 | } 372 | 373 | private func hideToast(_ toast: UIView, fromTap: Bool) { 374 | if let timer = objc_getAssociatedObject(toast, &ToastKeys.timer) as? Timer { 375 | timer.invalidate() 376 | } 377 | 378 | UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: { 379 | toast.alpha = 0.0 380 | }) { _ in 381 | toast.removeFromSuperview() 382 | self.activeToasts.remove(toast) 383 | 384 | if let wrapper = objc_getAssociatedObject(toast, &ToastKeys.completion) as? ToastCompletionWrapper, let completion = wrapper.completion { 385 | completion(fromTap) 386 | } 387 | 388 | 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 { 389 | self.queue.removeObject(at: 0) 390 | self.showToast(nextToast, duration: duration.doubleValue, point: point.cgPointValue) 391 | } 392 | } 393 | } 394 | 395 | // MARK: - Events 396 | 397 | @objc 398 | private func handleToastTapped(_ recognizer: UITapGestureRecognizer) { 399 | guard let toast = recognizer.view else { return } 400 | hideToast(toast, fromTap: true) 401 | } 402 | 403 | @objc 404 | private func toastTimerDidFinish(_ timer: Timer) { 405 | guard let toast = timer.userInfo as? UIView else { return } 406 | hideToast(toast) 407 | } 408 | 409 | // MARK: - Toast Construction 410 | 411 | /** 412 | Creates a new toast view with any combination of message, title, and image. 413 | The look and feel is configured via the style. Unlike the `makeToast` methods, 414 | this method does not present the toast view automatically. One of the `showToast` 415 | methods must be used to present the resulting view. 416 | 417 | @warning if message, title, and image are all nil, this method will throw 418 | `ToastError.missingParameters` 419 | 420 | @param message The message to be displayed 421 | @param title The title 422 | @param image The image 423 | @param style The style. The shared style will be used when nil 424 | @throws `ToastError.missingParameters` when message, title, and image are all nil 425 | @return The newly created toast view 426 | */ 427 | func toastViewForMessage(_ message: String?, title: String?, image: UIImage?, style: ToastStyle) throws -> UIView { 428 | // sanity 429 | guard message != nil || title != nil || image != nil else { 430 | throw ToastError.missingParameters 431 | } 432 | 433 | var messageLabel: UILabel? 434 | var titleLabel: UILabel? 435 | var imageView: UIImageView? 436 | 437 | let wrapperView = UIView() 438 | wrapperView.backgroundColor = style.backgroundColor 439 | wrapperView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] 440 | wrapperView.layer.cornerRadius = style.cornerRadius 441 | 442 | if style.displayShadow { 443 | wrapperView.layer.shadowColor = style.shadowColor.cgColor 444 | wrapperView.layer.shadowOpacity = style.shadowOpacity 445 | wrapperView.layer.shadowRadius = style.shadowRadius 446 | wrapperView.layer.shadowOffset = style.shadowOffset 447 | } 448 | 449 | if let image = image { 450 | imageView = UIImageView(image: image) 451 | imageView?.contentMode = .scaleAspectFit 452 | imageView?.frame = CGRect(x: style.horizontalPadding, y: style.verticalPadding, width: style.imageSize.width, height: style.imageSize.height) 453 | } 454 | 455 | var imageRect = CGRect.zero 456 | 457 | if let imageView = imageView { 458 | imageRect.origin.x = style.horizontalPadding 459 | imageRect.origin.y = style.verticalPadding 460 | imageRect.size.width = imageView.bounds.size.width 461 | imageRect.size.height = imageView.bounds.size.height 462 | } 463 | 464 | if let title = title { 465 | titleLabel = UILabel() 466 | titleLabel?.numberOfLines = style.titleNumberOfLines 467 | titleLabel?.font = style.titleFont 468 | titleLabel?.textAlignment = style.titleAlignment 469 | titleLabel?.lineBreakMode = .byTruncatingTail 470 | titleLabel?.textColor = style.titleColor 471 | titleLabel?.backgroundColor = UIColor.clear 472 | titleLabel?.text = title; 473 | 474 | let maxTitleSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage) 475 | let titleSize = titleLabel?.sizeThatFits(maxTitleSize) 476 | if let titleSize = titleSize { 477 | titleLabel?.frame = CGRect(x: 0.0, y: 0.0, width: titleSize.width, height: titleSize.height) 478 | } 479 | } 480 | 481 | if let message = message { 482 | messageLabel = UILabel() 483 | messageLabel?.text = message 484 | messageLabel?.numberOfLines = style.messageNumberOfLines 485 | messageLabel?.font = style.messageFont 486 | messageLabel?.textAlignment = style.messageAlignment 487 | messageLabel?.lineBreakMode = .byTruncatingTail; 488 | messageLabel?.textColor = style.messageColor 489 | messageLabel?.backgroundColor = UIColor.clear 490 | 491 | let maxMessageSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage) 492 | let messageSize = messageLabel?.sizeThatFits(maxMessageSize) 493 | if let messageSize = messageSize { 494 | let actualWidth = min(messageSize.width, maxMessageSize.width) 495 | let actualHeight = min(messageSize.height, maxMessageSize.height) 496 | messageLabel?.frame = CGRect(x: 0.0, y: 0.0, width: actualWidth, height: actualHeight) 497 | } 498 | } 499 | 500 | var titleRect = CGRect.zero 501 | 502 | if let titleLabel = titleLabel { 503 | titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding 504 | titleRect.origin.y = style.verticalPadding 505 | titleRect.size.width = titleLabel.bounds.size.width 506 | titleRect.size.height = titleLabel.bounds.size.height 507 | } 508 | 509 | var messageRect = CGRect.zero 510 | 511 | if let messageLabel = messageLabel { 512 | messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding 513 | messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding 514 | messageRect.size.width = messageLabel.bounds.size.width 515 | messageRect.size.height = messageLabel.bounds.size.height 516 | } 517 | 518 | let longerWidth = max(titleRect.size.width, messageRect.size.width) 519 | let longerX = max(titleRect.origin.x, messageRect.origin.x) 520 | let wrapperWidth = max((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding)) 521 | 522 | let textMaxY = messageRect.size.height <= 0.0 && titleRect.size.height > 0.0 ? titleRect.maxY : messageRect.maxY 523 | let wrapperHeight = max((textMaxY + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0))) 524 | 525 | wrapperView.frame = CGRect(x: 0.0, y: 0.0, width: wrapperWidth, height: wrapperHeight) 526 | 527 | if let titleLabel = titleLabel { 528 | titleRect.size.width = longerWidth 529 | titleLabel.frame = titleRect 530 | wrapperView.addSubview(titleLabel) 531 | } 532 | 533 | if let messageLabel = messageLabel { 534 | messageRect.size.width = longerWidth 535 | messageLabel.frame = messageRect 536 | wrapperView.addSubview(messageLabel) 537 | } 538 | 539 | if let imageView = imageView { 540 | wrapperView.addSubview(imageView) 541 | } 542 | 543 | return wrapperView 544 | } 545 | 546 | } 547 | 548 | // MARK: - Toast Style 549 | 550 | /** 551 | `ToastStyle` instances define the look and feel for toast views created via the 552 | `makeToast` methods as well for toast views created directly with 553 | `toastViewForMessage(message:title:image:style:)`. 554 | 555 | @warning `ToastStyle` offers relatively simple styling options for the default 556 | toast view. If you require a toast view with more complex UI, it probably makes more 557 | sense to create your own custom UIView subclass and present it with the `showToast` 558 | methods. 559 | */ 560 | public struct ToastStyle { 561 | 562 | public init() {} 563 | 564 | /** 565 | The background color. Default is `.black` at 80% opacity. 566 | */ 567 | public var backgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8) 568 | 569 | /** 570 | The title color. Default is `UIColor.whiteColor()`. 571 | */ 572 | public var titleColor: UIColor = .white 573 | 574 | /** 575 | The message color. Default is `.white`. 576 | */ 577 | public var messageColor: UIColor = .white 578 | 579 | /** 580 | A percentage value from 0.0 to 1.0, representing the maximum width of the toast 581 | view relative to it's superview. Default is 0.8 (80% of the superview's width). 582 | */ 583 | public var maxWidthPercentage: CGFloat = 0.8 { 584 | didSet { 585 | maxWidthPercentage = max(min(maxWidthPercentage, 1.0), 0.0) 586 | } 587 | } 588 | 589 | /** 590 | A percentage value from 0.0 to 1.0, representing the maximum height of the toast 591 | view relative to it's superview. Default is 0.8 (80% of the superview's height). 592 | */ 593 | public var maxHeightPercentage: CGFloat = 0.8 { 594 | didSet { 595 | maxHeightPercentage = max(min(maxHeightPercentage, 1.0), 0.0) 596 | } 597 | } 598 | 599 | /** 600 | The spacing from the horizontal edge of the toast view to the content. When an image 601 | is present, this is also used as the padding between the image and the text. 602 | Default is 10.0. 603 | 604 | */ 605 | public var horizontalPadding: CGFloat = 10.0 606 | 607 | /** 608 | The spacing from the vertical edge of the toast view to the content. When a title 609 | is present, this is also used as the padding between the title and the message. 610 | Default is 10.0. On iOS11+, this value is added added to the `safeAreaInset.top` 611 | and `safeAreaInsets.bottom`. 612 | */ 613 | public var verticalPadding: CGFloat = 10.0 614 | 615 | /** 616 | The corner radius. Default is 10.0. 617 | */ 618 | public var cornerRadius: CGFloat = 10.0; 619 | 620 | /** 621 | The title font. Default is `.boldSystemFont(16.0)`. 622 | */ 623 | public var titleFont: UIFont = .boldSystemFont(ofSize: 16.0) 624 | 625 | /** 626 | The message font. Default is `.systemFont(ofSize: 16.0)`. 627 | */ 628 | public var messageFont: UIFont = .systemFont(ofSize: 16.0) 629 | 630 | /** 631 | The title text alignment. Default is `NSTextAlignment.Left`. 632 | */ 633 | public var titleAlignment: NSTextAlignment = .left 634 | 635 | /** 636 | The message text alignment. Default is `NSTextAlignment.Left`. 637 | */ 638 | public var messageAlignment: NSTextAlignment = .left 639 | 640 | /** 641 | The maximum number of lines for the title. The default is 0 (no limit). 642 | */ 643 | public var titleNumberOfLines = 0 644 | 645 | /** 646 | The maximum number of lines for the message. The default is 0 (no limit). 647 | */ 648 | public var messageNumberOfLines = 0 649 | 650 | /** 651 | Enable or disable a shadow on the toast view. Default is `false`. 652 | */ 653 | public var displayShadow = false 654 | 655 | /** 656 | The shadow color. Default is `.black`. 657 | */ 658 | public var shadowColor: UIColor = .black 659 | 660 | /** 661 | A value from 0.0 to 1.0, representing the opacity of the shadow. 662 | Default is 0.8 (80% opacity). 663 | */ 664 | public var shadowOpacity: Float = 0.8 { 665 | didSet { 666 | shadowOpacity = max(min(shadowOpacity, 1.0), 0.0) 667 | } 668 | } 669 | 670 | /** 671 | The shadow radius. Default is 6.0. 672 | */ 673 | public var shadowRadius: CGFloat = 6.0 674 | 675 | /** 676 | The shadow offset. The default is 4 x 4. 677 | */ 678 | public var shadowOffset = CGSize(width: 4.0, height: 4.0) 679 | 680 | /** 681 | The image size. The default is 80 x 80. 682 | */ 683 | public var imageSize = CGSize(width: 80.0, height: 80.0) 684 | 685 | /** 686 | The size of the toast activity view when `makeToastActivity(position:)` is called. 687 | Default is 100 x 100. 688 | */ 689 | public var activitySize = CGSize(width: 100.0, height: 100.0) 690 | 691 | /** 692 | The fade in/out animation duration. Default is 0.2. 693 | */ 694 | public var fadeDuration: TimeInterval = 0.2 695 | 696 | /** 697 | Activity indicator color. Default is `.white`. 698 | */ 699 | public var activityIndicatorColor: UIColor = .white 700 | 701 | /** 702 | Activity background color. Default is `.black` at 80% opacity. 703 | */ 704 | public var activityBackgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8) 705 | 706 | } 707 | 708 | // MARK: - Toast Manager 709 | 710 | /** 711 | `ToastManager` provides general configuration options for all toast 712 | notifications. Backed by a singleton instance. 713 | */ 714 | public class ToastManager { 715 | 716 | /** 717 | The `ToastManager` singleton instance. 718 | 719 | */ 720 | public static let shared = ToastManager() 721 | 722 | /** 723 | The shared style. Used whenever toastViewForMessage(message:title:image:style:) is called 724 | with with a nil style. 725 | 726 | */ 727 | public var style = ToastStyle() 728 | 729 | /** 730 | Enables or disables tap to dismiss on toast views. Default is `true`. 731 | 732 | */ 733 | public var isTapToDismissEnabled = true 734 | 735 | /** 736 | Enables or disables queueing behavior for toast views. When `true`, 737 | toast views will appear one after the other. When `false`, multiple toast 738 | views will appear at the same time (potentially overlapping depending 739 | on their positions). This has no effect on the toast activity view, 740 | which operates independently of normal toast views. Default is `false`. 741 | 742 | */ 743 | public var isQueueEnabled = false 744 | 745 | /** 746 | The default duration. Used for the `makeToast` and 747 | `showToast` methods that don't require an explicit duration. 748 | Default is 3.0. 749 | 750 | */ 751 | public var duration: TimeInterval = 3.0 752 | 753 | /** 754 | Sets the default position. Used for the `makeToast` and 755 | `showToast` methods that don't require an explicit position. 756 | Default is `ToastPosition.Bottom`. 757 | 758 | */ 759 | public var position: ToastPosition = .bottom 760 | 761 | } 762 | 763 | // MARK: - ToastPosition 764 | 765 | public enum ToastPosition { 766 | case top 767 | case center 768 | case bottom 769 | 770 | fileprivate func centerPoint(forToast toast: UIView, inSuperview superview: UIView) -> CGPoint { 771 | let topPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.top 772 | let bottomPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.bottom 773 | 774 | switch self { 775 | case .top: 776 | return CGPoint(x: superview.bounds.size.width / 2.0, y: (toast.frame.size.height / 2.0) + topPadding) 777 | case .center: 778 | return CGPoint(x: superview.bounds.size.width / 2.0, y: superview.bounds.size.height / 2.0) 779 | case .bottom: 780 | return CGPoint(x: superview.bounds.size.width / 2.0, y: (superview.bounds.size.height - (toast.frame.size.height / 2.0)) - bottomPadding) 781 | } 782 | } 783 | } 784 | 785 | // MARK: - Private UIView Extensions 786 | 787 | private extension UIView { 788 | 789 | var csSafeAreaInsets: UIEdgeInsets { 790 | if #available(iOS 11.0, *) { 791 | return self.safeAreaInsets 792 | } else { 793 | return .zero 794 | } 795 | } 796 | 797 | } 798 | --------------------------------------------------------------------------------