├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Demo ├── Application │ ├── AppDelegate.swift │ └── SceneDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── TabBar │ │ ├── Contents.json │ │ ├── home.imageset │ │ │ ├── Contents.json │ │ │ └── home.png │ │ ├── notification.imageset │ │ │ ├── Contents.json │ │ │ └── notification.png │ │ ├── report.imageset │ │ │ ├── Contents.json │ │ │ └── report.png │ │ └── search.imageset │ │ │ ├── Contents.json │ │ │ └── search.png │ ├── account.imageset │ │ ├── Contents.json │ │ └── account.png │ ├── alert.imageset │ │ ├── Contents.json │ │ └── alert.png │ ├── appointment.imageset │ │ ├── Contents.json │ │ └── appointment.png │ ├── clock.imageset │ │ ├── Contents.json │ │ └── clock 1.png │ ├── cookie.imageset │ │ ├── Contents.json │ │ └── cookie.png │ ├── eaten.imageset │ │ ├── Contents.json │ │ └── eaten.png │ ├── ic_close.imageset │ │ ├── Contents.json │ │ ├── ic_close.png │ │ ├── ic_close@2x.png │ │ └── ic_close@3x.png │ ├── notify.imageset │ │ ├── Contents.json │ │ └── notify.png │ └── write.imageset │ │ ├── Contents.json │ │ └── kisspng-clip-art-vector-graphics-openclipart-computer-icon-notebook-paper-pencil-computer-icons-drawing-free-5c02671584d171.540751221543661333544.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── Controllers │ ├── AViewController.swift │ ├── BViewController.swift │ ├── DetailViewController.swift │ ├── HomeViewController.swift │ ├── JDTableViewController.swift │ ├── PinkViewController.swift │ ├── SearchViewController.swift │ └── ViewController.swift ├── IViewController.swift ├── Info.plist ├── Main.storyboard ├── Shared │ └── Settings.swift └── Switches │ ├── BaseControl.swift │ ├── CALayer+Extension.swift │ ├── CGPath+Extension.swift │ ├── JDSwitch.swift │ ├── SDSwitch.swift │ ├── Switcher.swift │ ├── UIImage+Extension.swift │ ├── YapBaseSwitch.swift │ ├── YapLiquidSwitch.swift │ ├── YapSmileSwitch.swift │ └── YapSwitch.swift ├── DemoTests ├── DemoTests.swift └── Info.plist ├── DemoUITests ├── DemoUITests.swift └── Info.plist ├── JDAlertController.podspec ├── JDAlertController.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── Demo.xcscheme │ └── JDAlertController.xcscheme ├── JDAlertController.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── JDAlertControllerTests ├── Info.plist └── JDAlertControllerTests.swift ├── Package.swift ├── Podfile ├── README.md ├── Sources └── JDAlertController │ ├── CustomViews │ ├── AnimatedView │ │ ├── AnimatedView.swift │ │ ├── ErrorAnimationLayer.swift │ │ ├── InformationAnimationLayer.swift │ │ ├── PhysicsPanHandler.swift │ │ ├── PopupType.swift │ │ └── SuccessAnimationLayer.swift │ ├── PopupAction │ │ └── PopupAction.swift │ └── TextField │ │ └── AppTextField.swift │ ├── Extension │ ├── CALayer+Extension.swift │ ├── CGRect+Extension.swift │ ├── UIColor+Extenstions.swift │ ├── UIFont+Extension.swift │ └── UIView+Extensions.swift │ ├── Factories │ ├── UIButtonFactory.swift │ ├── UIImageViewFactory.swift │ ├── UILabelFactory.swift │ └── UIStackViewFactory.swift │ ├── Info.plist │ ├── JDAlertController.h │ ├── Layouts │ ├── LayoutAttributes.swift │ └── Layouts.swift │ ├── PresentationManager │ ├── DimmedPopupPresentationController.swift │ ├── DragingPresentationAnimator.swift │ ├── OpacityPresentationAnimator.swift │ ├── PopupPresentationManager.swift │ ├── PopupStyle.swift │ └── SlideInPresentationAnimator.swift │ └── Scenes │ ├── AlertController.swift │ └── ViewController.swift ├── Tests ├── JDAlertControllerTests │ ├── JDAlertControllerTests.swift │ └── XCTestManifests.swift └── LinuxMain.swift └── images └── JDAlertController.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/xcode,swift,cocoapods 2 | # Edit at https://www.gitignore.io/?templates=xcode,swift,cocoapods 3 | 4 | ### CocoaPods ### 5 | ## CocoaPods GitIgnore Template 6 | 7 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 8 | # - Also handy if you have a large number of dependant pods 9 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 10 | Pods/ 11 | 12 | ### Swift ### 13 | # Xcode 14 | # 15 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 16 | 17 | ## Build generated 18 | build/ 19 | DerivedData/ 20 | 21 | ## Various settings 22 | *.pbxuser 23 | !default.pbxuser 24 | *.mode1v3 25 | !default.mode1v3 26 | *.mode2v3 27 | !default.mode2v3 28 | *.perspectivev3 29 | !default.perspectivev3 30 | xcuserdata/ 31 | 32 | ## Other 33 | *.moved-aside 34 | *.xccheckout 35 | *.xcscmblueprint 36 | 37 | ## Obj-C/Swift specific 38 | *.hmap 39 | *.ipa 40 | *.dSYM.zip 41 | *.dSYM 42 | 43 | ## Playgrounds 44 | timeline.xctimeline 45 | playground.xcworkspace 46 | 47 | # Swift Package Manager 48 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 49 | # Packages/ 50 | # Package.pins 51 | # Package.resolved 52 | .build/ 53 | # Add this line if you want to avoid checking in Xcode SPM integration. 54 | # .swiftpm/xcode 55 | 56 | # CocoaPods 57 | # We recommend against adding the Pods directory to your .gitignore. However 58 | # you should judge for yourself, the pros and cons are mentioned at: 59 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 60 | # Pods/ 61 | # Add this line if you want to avoid checking in source code from the Xcode workspace 62 | # *.xcworkspace 63 | 64 | # Carthage 65 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 66 | # Carthage/Checkouts 67 | 68 | Carthage/Build 69 | 70 | # Accio dependency management 71 | Dependencies/ 72 | .accio/ 73 | 74 | # fastlane 75 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 76 | # screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # After new code Injection tools there's a generated folder /iOSInjectionProject 87 | # https://github.com/johnno1962/injectionforxcode 88 | 89 | iOSInjectionProject/ 90 | 91 | ### Xcode ### 92 | # Xcode 93 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 94 | 95 | ## User settings 96 | 97 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 98 | 99 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 100 | 101 | ## Xcode Patch 102 | 103 | 104 | Podfile.lock 105 | 106 | # End of https://www.gitignore.io/api/xcode,swift,cocoapods 107 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | force_cast: warning 2 | force_try: warning 3 | 4 | line_length: 5 | warning: 150 6 | error: 200 7 | 8 | file_length: 9 | warning: 600 10 | error: 1000 11 | 12 | function_body_length: 13 | warning: 40 14 | error: 120 15 | 16 | opt_in_rules: 17 | - empty_count 18 | - force_unwrapping 19 | - legacy_constant 20 | - legacy_constructor 21 | - private_action 22 | - private_outlet 23 | 24 | excluded: 25 | - Pods 26 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Demo 4 | // 5 | // Created by Jawad Ali on 03/03/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 14 | UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .normal) 15 | UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: #colorLiteral(red: 0.1244207397, green: 0.3299400806, blue: 0.4999417067, alpha: 1) ], for: .selected) 16 | 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, 24 | configurationForConnecting connectingSceneSession: UISceneSession, 25 | options: UIScene.ConnectionOptions) -> UISceneConfiguration { 26 | // Called when a new scene session is being created. 27 | // Use this method to select a configuration to create the new scene with. 28 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 29 | } 30 | 31 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 32 | // Called when the user discards a scene session. 33 | // If any sessions were discarded while 34 | // the application was not running, this will be called shortly after 35 | // application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Demo/Application/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Demo 4 | // 5 | // Created by Jawad Ali on 03/03/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 15 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 16 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 17 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 18 | // guard let _ = (scene as? UIWindowScene) else { return } 19 | } 20 | 21 | func sceneDidDisconnect(_ scene: UIScene) { 22 | // Called as the scene is being released by the system. 23 | // This occurs shortly after the scene enters the background, or when its session is discarded. 24 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 25 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 26 | } 27 | 28 | func sceneDidBecomeActive(_ scene: UIScene) { 29 | // Called when the scene has moved from an inactive state to an active state. 30 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 31 | } 32 | 33 | func sceneWillResignActive(_ scene: UIScene) { 34 | // Called when the scene will move from an active state to an inactive state. 35 | // This may occur due to temporary interruptions (ex. an incoming phone call). 36 | } 37 | 38 | func sceneWillEnterForeground(_ scene: UIScene) { 39 | // Called as the scene transitions from the background to the foreground. 40 | // Use this method to undo the changes made on entering the background. 41 | } 42 | 43 | func sceneDidEnterBackground(_ scene: UIScene) { 44 | // Called as the scene transitions from the foreground to the background. 45 | // Use this method to save data, release shared resources, and store enough scene-specific state information 46 | // to restore the scene back to its current state. 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/TabBar/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/TabBar/home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "home.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/TabBar/home.imageset/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/TabBar/home.imageset/home.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/TabBar/notification.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "notification.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/TabBar/notification.imageset/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/TabBar/notification.imageset/notification.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/TabBar/report.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "report.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/TabBar/report.imageset/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/TabBar/report.imageset/report.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/TabBar/search.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "search.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/TabBar/search.imageset/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/TabBar/search.imageset/search.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/account.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "account.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/account.imageset/account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/account.imageset/account.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/alert.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "alert.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/alert.imageset/alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/alert.imageset/alert.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/appointment.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "appointment.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/appointment.imageset/appointment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/appointment.imageset/appointment.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/clock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "clock 1.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/clock.imageset/clock 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/clock.imageset/clock 1.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/cookie.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cookie.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/cookie.imageset/cookie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/cookie.imageset/cookie.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/eaten.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "eaten.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/eaten.imageset/eaten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/eaten.imageset/eaten.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/ic_close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ic_close.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ic_close@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ic_close@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/ic_close.imageset/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/ic_close.imageset/ic_close.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/ic_close.imageset/ic_close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/ic_close.imageset/ic_close@2x.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/ic_close.imageset/ic_close@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/ic_close.imageset/ic_close@3x.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/notify.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "notify.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/notify.imageset/notify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/notify.imageset/notify.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/write.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "kisspng-clip-art-vector-graphics-openclipart-computer-icon-notebook-paper-pencil-computer-icons-drawing-free-5c02671584d171.540751221543661333544.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/write.imageset/kisspng-clip-art-vector-graphics-openclipart-computer-icon-notebook-paper-pencil-computer-icons-drawing-free-5c02671584d171.540751221543661333544.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/Demo/Assets.xcassets/write.imageset/kisspng-clip-art-vector-graphics-openclipart-computer-icon-notebook-paper-pencil-computer-icons-drawing-free-5c02671584d171.540751221543661333544.png -------------------------------------------------------------------------------- /Demo/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 | -------------------------------------------------------------------------------- /Demo/Controllers/AViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AViewController.swift 3 | // SOTabBar_Example 4 | // 5 | // Created by Jawad Ali on 04/09/2020. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AViewController: UIViewController { 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /Demo/Controllers/BViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BViewController.swift 3 | // SOTabBar_Example 4 | // 5 | // Created by Jawad Ali on 04/09/2020. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | } 18 | override func viewWillAppear(_ animated: Bool) { 19 | super.viewWillAppear(animated) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Demo/Controllers/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // Demo 4 | // 5 | // Created by Jawad Ali on 11/09/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DetailViewController: UIViewController { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Demo/Controllers/HomeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.swift 3 | // Demo 4 | // 5 | // Created by Jawad Ali on 10/09/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JDTabBarController 11 | import JDAlertController 12 | class HomeViewController: UIViewController { 13 | 14 | @IBOutlet weak private var offsetField: UITextField! 15 | @IBOutlet weak private var isDragSwitch: SwitcherFullStrtech! 16 | @IBOutlet weak private var segmentControl: UISegmentedControl! 17 | @IBOutlet weak private var switcher: Switcher! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | self.title = "JDAlertController" 22 | 23 | } 24 | @IBAction func yyyyy(_ sender: SwitcherFullStrtech) { 25 | Settings.shared.isDragEnabled = sender.isOn 26 | } 27 | @IBAction private func offsetChanged(_ sender: UITextField) { 28 | Settings.shared.offSet = Double(sender.text ?? "") ?? 0 29 | } 30 | @IBAction private func isDragableOn(_ sender: Switcher) { 31 | Settings.shared.isDragEnabled = sender.isOn 32 | } 33 | @IBAction private func touchOutsideAllowed(_ sender: Switcher) { 34 | Settings.shared.dismissOuside = sender.isOn 35 | } 36 | @IBAction private func segmentChanged(_ sender: UISegmentedControl) { 37 | switch sender.selectedSegmentIndex { 38 | case 0: 39 | Settings.shared.preferedStyle = .alert 40 | case 1: 41 | Settings.shared.preferedStyle = .topSheet(offset: Settings.shared.offSet) 42 | case 2: 43 | Settings.shared.preferedStyle = .actionSheet(offset: Settings.shared.offSet) 44 | case 3: 45 | Settings.shared.preferedStyle = .dragIn 46 | default: 47 | break 48 | } 49 | 50 | } 51 | 52 | override func viewDidAppear(_ animated: Bool) { 53 | super.viewDidAppear(animated) 54 | // jDNavigationController?.jDnavigationBar?.addStyle() 55 | } 56 | 57 | @IBAction func WidthRatioChanged(_ sender: UITextField) { 58 | Settings.shared.widthRatio = CGFloat(Double(sender.text ?? "") ?? 0) 59 | } 60 | @IBAction func setStyle(_ sender: UIButton) { 61 | self.jDTabBarController?.tabBar.shapeType = Shape(rawValue: sender.tag) ?? Shape.upperRound 62 | } 63 | } 64 | extension HomeViewController: UITextFieldDelegate { 65 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 66 | textField.resignFirstResponder() 67 | return true 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Demo/Controllers/PinkViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PinkViewController.swift 3 | // SOTabBar_Example 4 | // 5 | // Created by Jawad Ali on 02/09/2020. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PinkViewController: UIViewController { 12 | 13 | @IBOutlet weak private var stackView: UIStackView! 14 | override func viewDidLoad() { 15 | 16 | self.title = "Reports" 17 | super.viewDidLoad() 18 | 19 | // Do any additional setup after loading the view. 20 | } 21 | 22 | override func viewDidAppear(_ animated: Bool) { 23 | super.viewDidAppear(animated) 24 | // self.sOTabBarController?.tabBar.isHidden = true 25 | } 26 | 27 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 28 | 29 | } 30 | 31 | @IBAction private func goTo(_ sender: UIButton) { 32 | self.jDTabBarController?.selectedIndex = sender.tag 33 | } 34 | 35 | } 36 | 37 | @IBDesignable public class ShadowButton: UIButton { 38 | 39 | override init(frame: CGRect) { 40 | super.init(frame: frame) 41 | commonInit() 42 | } 43 | 44 | required init?(coder: NSCoder) { 45 | super.init(coder: coder) 46 | commonInit() 47 | } 48 | 49 | private func commonInit() { 50 | self.addShadow() 51 | } 52 | 53 | public override func layoutSubviews() { 54 | super.layoutSubviews() 55 | self.addShadow() 56 | } 57 | 58 | } 59 | 60 | public extension ShadowButton { 61 | func addShadow() { 62 | layer.cornerRadius = bounds.midY 63 | layer.shadowColor = UIColor.black.cgColor 64 | layer.shadowOpacity = 0.1 65 | layer.shadowRadius = 2 66 | layer.shadowOffset = CGSize(width: 0, height: 5) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Demo/Controllers/SearchViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchViewController.swift 3 | // Demo 4 | // 5 | // Created by Jawad Ali on 10/09/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JDAlertController 11 | class SearchViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | self.title = "Search" 15 | 16 | super.viewDidLoad() 17 | 18 | // Do any additional setup after loading the view. 19 | } 20 | override func viewWillAppear(_ animated: Bool) { 21 | super.viewWillAppear(animated) 22 | 23 | } 24 | 25 | @IBAction private func changeTintColor(_ sender: UIButton) { 26 | // jDTabBarController?.tabBar.tabBarTintColor = sender.backgroundColor ?? .white 27 | 28 | showSuccessAnimated() 29 | } 30 | private func showSuccessAnimated() { 31 | let alert = AlertController(type: .success, 32 | title: "Awaiting your payment!", 33 | message: "Leo Walton will receive \n 62,500.00 PKR ", 34 | preferredStyle: .alert) 35 | 36 | alert.isAnimated = true 37 | 38 | let continueButton = PopupAction(title: "Continue", style: .round, propotionalWidth: .custom(ratio: 0.5), handler: nil) 39 | continueButton.setTitleColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), for: .normal) 40 | continueButton.backgroundColor = #colorLiteral(red: 0.1994869411, green: 0.857052505, blue: 0.9589684606, alpha: 1) 41 | 42 | alert.addAction(continueButton) 43 | 44 | self.present(alert, animated: true, completion: nil) 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Demo/Controllers/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Demo 4 | // 5 | // Created by Jawad Ali on 09/09/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JDTabBarController 11 | class ViewController: JDTabBarController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | self.delegate = self 16 | let homeStoryboard = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HOME_ID") 17 | let chatStoryboard = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "CHAT_ID") 18 | let sleepStoryboard = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SLEEP_ID") 19 | let musicStoryboard = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MUSIC_ID") 20 | 21 | homeStoryboard.tabBarItem = UITabBarItem(title: "Home", image: UIImage(named: "home"), tag: 0) 22 | chatStoryboard.tabBarItem = UITabBarItem(title: "Search", image: UIImage(named: "search"), tag: 1) 23 | sleepStoryboard.tabBarItem = UITabBarItem(title: "Report", image: UIImage(named: "report"), tag: 2) 24 | musicStoryboard.tabBarItem = UITabBarItem(title: "Alerts", image: UIImage(named: "notification"), tag: 3) 25 | 26 | viewControllers = [homeStoryboard, chatStoryboard, sleepStoryboard, musicStoryboard] 27 | 28 | } 29 | 30 | } 31 | 32 | extension ViewController: JDTabBarControllerDelegate { 33 | func tabBarController(_ tabBarController: JDTabBarController, didSelect viewController: UIViewController) { 34 | print(viewController.tabBarItem.title ?? "") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Demo/Shared/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // Demo 4 | // 5 | // Created by Jawad Ali on 04/03/2021. 6 | // 7 | import UIKit 8 | import JDAlertController 9 | class Settings { 10 | static let shared = Settings() 11 | var isDragEnabled: Bool = false 12 | var preferedStyle: PopupStyle = .alert 13 | var offSet: Double = 0 14 | var dismissOuside: Bool = true 15 | var widthRatio: CGFloat = 0.65 16 | private init() {} 17 | } 18 | -------------------------------------------------------------------------------- /Demo/Switches/BaseControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseControl.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 28/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public typealias SDSwitchValueChange = (_ value: Bool) -> Void 12 | open class BaseControl: UIControl { 13 | 14 | // MARK: - Property 15 | open var valueChange: SDSwitchValueChange? 16 | 17 | open var isOn: Bool = false { 18 | didSet { 19 | layoutIfNeeded() 20 | layoutSublayers(of: self.layer) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Demo/Switches/CALayer+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Extension.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 20/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public extension CALayer { 11 | 12 | var areAnimationsEnabled: Bool { 13 | get { delegate == nil } 14 | set { delegate = newValue ? nil : CALayerAnimationsDisablingDelegate.shared } 15 | } 16 | 17 | private class CALayerAnimationsDisablingDelegate: NSObject, CALayerDelegate { 18 | static let shared = CALayerAnimationsDisablingDelegate() 19 | private let null = NSNull() 20 | 21 | func action(for layer: CALayer, forKey event: String) -> CAAction? { 22 | null 23 | } 24 | } 25 | 26 | func bringToFront() { 27 | guard let sLayer = superlayer else { 28 | return 29 | } 30 | removeFromSuperlayer() 31 | sLayer.insertSublayer(self, at: UInt32(sLayer.sublayers?.count ?? 0)) 32 | } 33 | 34 | func sendToBack() { 35 | guard let sLayer = superlayer else { 36 | return 37 | } 38 | removeFromSuperlayer() 39 | sLayer.insertSublayer(self, at: 0) 40 | } 41 | 42 | func animateGradientColors(from: [CGColor], toColor: [CGColor], duration: Double) { 43 | let animation = CABasicAnimation(keyPath: "colors") 44 | animation.fromValue = from 45 | animation.toValue = toColor 46 | animation.duration = duration 47 | animation.fillMode = .forwards 48 | animation.isRemovedOnCompletion = false 49 | 50 | // add the animation to the gradient 51 | self.add(animation, forKey: nil) 52 | 53 | } 54 | 55 | func strokeAnimation(duration: Double) { 56 | let animation = CABasicAnimation(keyPath: "strokeEnd") 57 | animation.fromValue = 0 58 | animation.toValue = 1 59 | animation.duration = duration 60 | animation.fillMode = .forwards 61 | animation.isRemovedOnCompletion = false 62 | self.add(animation, forKey: "line") 63 | } 64 | 65 | func rotateAnimation(angal: CGFloat, duration: Double, repeatAnimation: Bool = false) { 66 | 67 | let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") 68 | rotationAnimation.fromValue = 0 69 | rotationAnimation.toValue = angal 70 | rotationAnimation.duration = duration 71 | rotationAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 72 | rotationAnimation.fillMode = .forwards 73 | rotationAnimation.isRemovedOnCompletion = false 74 | rotationAnimation.repeatCount = repeatAnimation ? .infinity : 0 75 | self.add(rotationAnimation, forKey: "rotation") 76 | } 77 | 78 | func removeRotationAnimation() { 79 | self.removeAnimation(forKey: "rotation") 80 | } 81 | 82 | func animateShape(path: CGPath, duration: Double) { 83 | 84 | let animation = CABasicAnimation(keyPath: "path") 85 | animation.duration = duration 86 | animation.toValue = path 87 | animation.timingFunction = CAMediaTimingFunction(name: .linear) 88 | animation.isRemovedOnCompletion = false 89 | animation.fillMode = .forwards 90 | 91 | self.add(animation, forKey: nil) 92 | 93 | } 94 | 95 | func doMask(by imageMask: UIImage) { 96 | let maskLayer = CAShapeLayer() 97 | maskLayer.bounds = CGRect(x: 0, y: 0, width: imageMask.size.width, height: imageMask.size.height) 98 | bounds = maskLayer.bounds 99 | maskLayer.contents = imageMask.cgImage 100 | maskLayer.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height) 101 | mask = maskLayer 102 | } 103 | 104 | func toImage() -> UIImage? { 105 | UIGraphicsBeginImageContextWithOptions(bounds.size, 106 | isOpaque, 107 | UIScreen.main.scale) 108 | guard let context = UIGraphicsGetCurrentContext() else { 109 | UIGraphicsEndImageContext() 110 | return nil 111 | } 112 | render(in: context) 113 | let image = UIGraphicsGetImageFromCurrentImageContext() 114 | UIGraphicsEndImageContext() 115 | return image 116 | } 117 | 118 | } 119 | extension BinaryInteger { 120 | var degreesToRadians: CGFloat { CGFloat(self) * .pi / 180 } 121 | } 122 | -------------------------------------------------------------------------------- /Demo/Switches/CGPath+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPath+Extension.swift 3 | // Switches 4 | // 5 | // Created by Jawad Ali on 06/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public extension CGRect { 11 | func drawCheckmarkPath() -> CGPath { 12 | let bezierPath = UIBezierPath() 13 | 14 | let radius = self.maxX/5 15 | let center = CGPoint(x: self.midX, y: self.midY ) 16 | 17 | let x11 = center.x + (radius ) * cos(-45.degreesToRadians) 18 | let y11 = center.y + (radius ) * sin(-45.degreesToRadians) 19 | 20 | let x21 = center.x + (radius ) * cos(135.degreesToRadians) 21 | let y21 = center.y + (radius ) * sin(135.degreesToRadians) 22 | 23 | bezierPath.move(to: CGPoint(x: x11, y: y11)) 24 | bezierPath.addLine(to: CGPoint(x: x21, y: y21)) 25 | 26 | let x31 = center.x + (radius + radius * 0.5 ) * cos(-175.degreesToRadians) 27 | let y31 = center.y + (radius + radius * 0.5 ) * sin(-175.degreesToRadians) 28 | 29 | bezierPath.move(to: CGPoint(x: x31, y: y31)) 30 | bezierPath.addLine(to: CGPoint(x: x21, y: y21 )) 31 | 32 | return bezierPath.cgPath 33 | } 34 | 35 | func drawCrossPath() -> CGPath { 36 | let bezierPath = UIBezierPath() 37 | 38 | let radius = self.maxX/5 39 | let center = CGPoint(x: self.midX, y: self.midY ) 40 | 41 | let x11 = center.x + (radius ) * cos(-45.degreesToRadians) 42 | let y11 = center.y + (radius ) * sin(-45.degreesToRadians) 43 | 44 | let x21 = center.x + (radius ) * cos(135.degreesToRadians) 45 | let y21 = center.y + (radius ) * sin(135.degreesToRadians) 46 | 47 | let x31 = center.x + (radius ) * cos(-135.degreesToRadians) 48 | let y31 = center.y + (radius ) * sin(-135.degreesToRadians) 49 | 50 | let x41 = center.x + (radius ) * cos(45.degreesToRadians) 51 | let y41 = center.y + (radius ) * sin(45.degreesToRadians) 52 | 53 | bezierPath.move(to: CGPoint(x: x41, y: y41)) 54 | bezierPath.addLine(to: CGPoint(x: x31, y: y31)) 55 | 56 | bezierPath.move(to: CGPoint(x: x11, y: y11)) 57 | bezierPath.addLine(to: CGPoint(x: x21, y: y21)) 58 | 59 | return bezierPath.cgPath 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Demo/Switches/JDSwitch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JDSwitch.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 22/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | // `Design by` : Oleg Frolov 10 | // https://dribbble.com/shots/2346044-Switch-on-off 11 | 12 | import UIKit 13 | @IBDesignable public class JDSwitch: BaseControl { 14 | 15 | // MARK: - Views 16 | private lazy var thumbLayer: CALayer = { 17 | let layer = CALayer() 18 | layer.shadowColor = UIColor.black.cgColor 19 | layer.shadowRadius = 2 20 | layer.shadowOpacity = 0.4 21 | layer.shadowOffset = CGSize(width: 0.75, height: 2) 22 | layer.contentsGravity = .resizeAspect 23 | layer.backgroundColor = UIColor.white.cgColor 24 | return layer 25 | }() 26 | 27 | private lazy var trackLayer: CAShapeLayer = { 28 | let shape = CAShapeLayer() 29 | 30 | shape.borderWidth = borderWidth 31 | shape.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) 32 | return shape 33 | }() 34 | 35 | var changeThumbColor: Bool = false { 36 | didSet { 37 | setThumbColor() 38 | } 39 | } 40 | 41 | private var isTouchDown: Bool = false { 42 | didSet { 43 | layoutSublayers(of: layer) 44 | changeThumbColor = true 45 | } 46 | } 47 | // MARK: - Public Properties 48 | 49 | public var shape: ShapeType = .rounded { 50 | didSet { 51 | layoutSublayers(of: layer) 52 | } 53 | } 54 | public var borderWidth: CGFloat = 0 { 55 | didSet { 56 | layoutSublayers(of: layer) 57 | } 58 | } 59 | public var thumbRadiusPadding: CGFloat = 0 { 60 | didSet { 61 | layoutThumbLayer(for: layer.bounds) 62 | } 63 | } 64 | public var thumbCornerRadius: CGFloat = 0 { 65 | didSet { 66 | layoutThumbLayer(for: layer.bounds) 67 | } 68 | } 69 | public var thumbTintColor: UIColor? = nil { 70 | didSet { setThumbColor() } 71 | } 72 | 73 | public var onThumbTintColor: UIColor = #colorLiteral(red: 0.5438016653, green: 0.7640405893, blue: 0.291983664, alpha: 1) { 74 | didSet { setThumbColor() } 75 | } 76 | 77 | public var offThumbTintColor: UIColor = #colorLiteral(red: 0.864574194, green: 0.8753482103, blue: 0.848641932, alpha: 1) { 78 | didSet { setThumbColor() } 79 | } 80 | 81 | // MARK: - initializers 82 | convenience init() { 83 | self.init(frame: .zero) 84 | } 85 | 86 | override init(frame: CGRect) { 87 | super.init(frame: frame) 88 | controlDidLoad() 89 | } 90 | 91 | required init?(coder aDecoder: NSCoder) { 92 | super.init(coder: aDecoder) 93 | controlDidLoad() 94 | } 95 | 96 | // MARK: - Common Init 97 | 98 | private func controlDidLoad() { 99 | layer.addSublayer(trackLayer) 100 | layer.addSublayer(thumbLayer) 101 | 102 | layer.shadowOffset = .zero 103 | layer.shadowOpacity = 0.3 104 | layer.shadowRadius = 5 105 | backgroundColor = .clear 106 | 107 | setThumbColor() 108 | layoutSublayers(of: layer) 109 | addTouchHandlers() 110 | } 111 | 112 | final func getThumbSize() -> CGSize { 113 | let height = bounds.height - 2 * (borderWidth + thumbRadiusPadding) 114 | return CGSize(width: height, height: height) 115 | } 116 | 117 | final func getThumbOrigin(for width: CGFloat) -> CGPoint { 118 | 119 | let inset = borderWidth + thumbRadiusPadding 120 | if isTouchDown { 121 | let x11 = (!isOn) ? bounds.width - width - inset : inset 122 | return CGPoint(x: x11, y: inset) 123 | } else { 124 | 125 | let x11 = (isOn) ? bounds.width - width - inset : inset 126 | return CGPoint(x: x11, y: inset) 127 | } 128 | } 129 | 130 | private func getTackSize() -> CGSize { 131 | isTouchDown ? CGSize(width: bounds.height, height: bounds.height) : bounds.size 132 | } 133 | 134 | final func getTrackOrigin(for width: CGFloat) -> CGPoint { 135 | let inset: CGFloat = 0.0 136 | let x11 = (!isOn) ? bounds.width - width : inset 137 | return CGPoint(x: x11, y: inset) 138 | } 139 | 140 | public override func layoutSublayers(of layer: CALayer) { 141 | super.layoutSublayers(of: layer) 142 | 143 | layoutTrackLayer(for: layer.bounds) 144 | layoutThumbLayer(for: layer.bounds) 145 | } 146 | } 147 | 148 | // MARK: - layout Layers 149 | private extension JDSwitch { 150 | 151 | func layoutTrackLayer(for bounds: CGRect) { 152 | 153 | let size = getTackSize() 154 | let origin = getTrackOrigin(for: size.width) 155 | trackLayer.frame = CGRect(origin: origin, size: size) 156 | 157 | shape == .rounded ? (trackLayer.cornerRadius = trackLayer.bounds.height / 2) : (trackLayer.cornerRadius = 5) 158 | } 159 | 160 | func layoutThumbLayer(for bounds: CGRect) { 161 | let size = getThumbSize() 162 | let origin = getThumbOrigin(for: size.width) 163 | thumbLayer.frame = CGRect(origin: origin, size: size) 164 | 165 | shape == .rounded ? (thumbLayer.cornerRadius = size.height / 2) : (thumbLayer.cornerRadius = thumbCornerRadius) 166 | } 167 | } 168 | // MARK: - Touches 169 | private extension JDSwitch { 170 | 171 | private func addTouchHandlers() { 172 | addTarget(self, action: #selector(touchDown), for: [.touchDown, .touchDragEnter]) 173 | addTarget(self, action: #selector(touchUp), for: [.touchUpInside]) 174 | addTarget(self, action: #selector(touchEnded), for: [.touchDragExit, .touchCancel]) 175 | 176 | let leftSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeLeftRight(_:))) 177 | leftSwipeGesture.direction = [.left] 178 | addGestureRecognizer(leftSwipeGesture) 179 | 180 | let rightSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeLeftRight(_:))) 181 | rightSwipeGesture.direction = [.right] 182 | addGestureRecognizer(rightSwipeGesture) 183 | } 184 | 185 | @objc 186 | func swipeLeftRight(_ gesture: UISwipeGestureRecognizer) { 187 | let canLeftSwipe = isOn && gesture.direction == .left 188 | let canRightSwipe = !isOn && gesture.direction == .right 189 | guard canLeftSwipe || canRightSwipe else { return } 190 | touchUp() 191 | } 192 | 193 | @objc 194 | func touchDown() { 195 | isTouchDown = true 196 | } 197 | 198 | @objc 199 | func touchUp() { 200 | isOn.toggle() 201 | touchEnded() 202 | } 203 | 204 | @objc 205 | func touchEnded() { 206 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { 207 | self.isTouchDown = false 208 | } 209 | 210 | } 211 | } 212 | 213 | // MARK: - color setting 214 | extension JDSwitch { 215 | private func setThumbColor() { 216 | if let thumbColor = thumbTintColor { 217 | thumbLayer.backgroundColor = thumbColor.cgColor 218 | } else { 219 | thumbLayer.backgroundColor = (isOn ? onThumbTintColor : offThumbTintColor).cgColor 220 | } 221 | } 222 | 223 | } 224 | -------------------------------------------------------------------------------- /Demo/Switches/SDSwitch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SDSwitch.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 22/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | // Crdit design by 10 | // Mauricio Bucardo 11 | //https://dribbble.com/shots/9841408-Loading-switch-interaction 12 | 13 | // Alexis Doreau 14 | //https://dribbble.com/shots/3545882-Switch-with-server-calls 15 | 16 | // George Assan 17 | //https://dribbble.com/shots/5192899-Simple-toggle 18 | 19 | import UIKit 20 | enum LoadingResult { 21 | case success 22 | case failled 23 | } 24 | 25 | public typealias SDSwitchLoadingStarted = () -> Void 26 | 27 | @IBDesignable public class SDSwitch: BaseControl { 28 | 29 | open var loadingStarted: SDSwitchLoadingStarted? 30 | 31 | // MARK: - Views 32 | fileprivate lazy var thumbLayer: CALayer = { 33 | let layer = CALayer() 34 | layer.shadowColor = UIColor.black.cgColor 35 | layer.shadowRadius = 2 36 | layer.shadowOpacity = 0.4 37 | layer.shadowOffset = CGSize(width: 0.75, height: 2) 38 | layer.contentsGravity = .resizeAspect 39 | layer.backgroundColor = UIColor.white.cgColor 40 | return layer 41 | }() 42 | 43 | fileprivate lazy var trackLayer: CALayer = { 44 | let shape = CALayer() 45 | shape.borderWidth = borderWidth 46 | shape.borderColor = UIColor.blue.cgColor 47 | 48 | return shape 49 | }() 50 | private lazy var thumbLoadingCompleteLayer: CAShapeLayer = { 51 | let shape = CAShapeLayer() 52 | shape.lineWidth = thumbRadiusPadding 53 | shape.strokeColor = loadingColor.cgColor 54 | shape.fillColor = UIColor.clear.cgColor 55 | shape.backgroundColor = UIColor.clear.cgColor 56 | shape.lineCap = .round 57 | return shape 58 | }() 59 | 60 | private lazy var loadingLayer: CAShapeLayer = { 61 | let shape = CAShapeLayer() 62 | shape.lineWidth = thumbRadiusPadding 63 | shape.strokeColor = loadingColor.cgColor 64 | shape.fillColor = UIColor.clear.cgColor 65 | shape.backgroundColor = UIColor.clear.cgColor 66 | shape.lineCap = .round 67 | return shape 68 | }() 69 | 70 | var changeThumbColor: Bool = false { 71 | didSet { 72 | 73 | setThumbColor() 74 | } 75 | } 76 | 77 | private var isLoadingCompleted = false 78 | 79 | private var loadingResult: LoadingResult = .success 80 | 81 | fileprivate var isTouchDown: Bool = false { 82 | didSet { 83 | layoutSublayers(of: layer) 84 | changeThumbColor = true 85 | stateDidChange() 86 | } 87 | } 88 | // MARK: - Public Properties 89 | 90 | public var borderColor: UIColor? = nil { 91 | didSet { setBorderColor() } 92 | } 93 | 94 | public var onBorderColor: UIColor = .white { 95 | didSet { setBorderColor() } 96 | } 97 | public var offBorderColor: UIColor = .white { 98 | didSet { setBorderColor() } 99 | } 100 | 101 | public var isLoadingEnabled: Bool = false 102 | 103 | public var shape: ShapeType = .rounded { 104 | didSet { 105 | layoutSublayers(of: layer) 106 | } 107 | } 108 | public var borderWidth: CGFloat = 2 { 109 | didSet { 110 | trackLayer.borderWidth = borderWidth 111 | layoutSublayers(of: layer) 112 | } 113 | } 114 | public var thumbRadiusPadding: CGFloat = 4 { 115 | didSet { 116 | layoutSublayers(of: layer) 117 | } 118 | } 119 | public var thumbCornerRadius: CGFloat = 0 { 120 | didSet { 121 | layoutThumbLayer(for: layer.bounds) 122 | } 123 | } 124 | public var thumbTintColor: UIColor? = nil { 125 | didSet { setThumbColor() } 126 | } 127 | 128 | public var onThumbTintColor: UIColor = #colorLiteral(red: 0.9999018312, green: 1, blue: 0.9998798966, alpha: 1) { 129 | didSet { setThumbColor() } 130 | } 131 | 132 | public var offThumbTintColor: UIColor = #colorLiteral(red: 0.9999018312, green: 1, blue: 0.9998798966, alpha: 1) { 133 | didSet { setThumbColor() } 134 | } 135 | 136 | public var loadingColor: UIColor = #colorLiteral(red: 0.2855720818, green: 0.8943144679, blue: 0.6083024144, alpha: 1) { 137 | didSet { loadingLayer.strokeColor = loadingColor.cgColor } 138 | } 139 | public var onTintColor: UIColor = #colorLiteral(red: 0.2855720818, green: 0.8943144679, blue: 0.6083024144, alpha: 1) { 140 | didSet { 141 | trackLayer.backgroundColor = getBackgroundColor() 142 | 143 | setNeedsLayout() 144 | } 145 | } 146 | 147 | public var offTintColor: UIColor = #colorLiteral(red: 0.823615551, green: 0.8911703229, blue: 0.9554550052, alpha: 1) { 148 | didSet { 149 | trackLayer.backgroundColor = getBackgroundColor() 150 | setNeedsLayout() 151 | } 152 | } 153 | 154 | // MARK: - initializers 155 | convenience init() { 156 | self.init(frame: .zero) 157 | } 158 | 159 | override init(frame: CGRect) { 160 | super.init(frame: frame) 161 | controlDidLoad() 162 | } 163 | 164 | required init?(coder aDecoder: NSCoder) { 165 | super.init(coder: aDecoder) 166 | controlDidLoad() 167 | } 168 | 169 | // MARK: - Common Init 170 | 171 | fileprivate func controlDidLoad() { 172 | layer.addSublayer(trackLayer) 173 | layer.addSublayer(thumbLayer) 174 | 175 | layer.shadowColor = UIColor.black.cgColor 176 | layer.shadowOffset = .zero 177 | layer.shadowOpacity = 0.3 178 | layer.shadowRadius = 10 179 | backgroundColor = .clear 180 | 181 | trackLayer.backgroundColor = getBackgroundColor() 182 | setThumbColor() 183 | layoutSublayers(of: layer) 184 | addTouchHandlers() 185 | } 186 | 187 | fileprivate func getThumbSize() -> CGSize { 188 | let height = bounds.height - 2 * (borderWidth + thumbRadiusPadding) 189 | return CGSize(width: height, height: height) 190 | } 191 | 192 | final func getThumbOrigin(for width: CGFloat) -> CGPoint { 193 | 194 | let inset = borderWidth + thumbRadiusPadding 195 | if isTouchDown { 196 | 197 | return CGPoint(x: bounds.midX - width/2, y: inset) 198 | } else { 199 | 200 | let xPoint = (isOn) ? bounds.width - width - inset : inset 201 | return CGPoint(x: xPoint, y: inset) 202 | } 203 | } 204 | 205 | fileprivate func getTackSize() -> CGSize { 206 | isTouchDown ? CGSize(width: bounds.height, height: bounds.height) : bounds.size 207 | } 208 | 209 | final func getTrackOrigin(for width: CGFloat) -> CGPoint { 210 | 211 | if isTouchDown { 212 | 213 | return CGPoint(x: bounds.midX - width/2, y: 0) 214 | } 215 | 216 | let inset: CGFloat = 0.0 217 | let xPoint = (!isOn) ? bounds.width - width : inset 218 | return CGPoint(x: xPoint, y: inset) 219 | } 220 | 221 | private func stateDidChange() { 222 | 223 | trackLayer.backgroundColor = getBackgroundColor() 224 | } 225 | 226 | public override func layoutSublayers(of layer: CALayer) { 227 | super.layoutSublayers(of: layer) 228 | 229 | layoutTrackLayer(for: layer.bounds) 230 | layoutThumbLayer(for: layer.bounds) 231 | layoutLoadingLayer(for: layer.bounds) 232 | 233 | setBorderColor() 234 | } 235 | 236 | @objc final public func loadingCompleted(isSuccessFull: Bool) { 237 | isLoadingCompleted = true 238 | isSuccessFull ? (loadingResult = .success) : (loadingResult = .failled) 239 | if !isSuccessFull { 240 | isOn.toggle() 241 | } 242 | 243 | loadingLayer.removeRotationAnimation() 244 | loadingLayer.removeFromSuperlayer() 245 | 246 | thumbLoadingCompleteLayer.path = (loadingResult == LoadingResult.success ) ? drawOnPath(thumbLayer.bounds) : drawOffPath(thumbLayer.bounds) 247 | thumbLoadingCompleteLayer.lineWidth = thumbRadiusPadding/3 248 | thumbLoadingCompleteLayer.strokeColor = (isSuccessFull ? loadingColor : UIColor.red).cgColor 249 | thumbLoadingCompleteLayer.strokeEnd = 0 250 | thumbLayer.addSublayer(thumbLoadingCompleteLayer) 251 | 252 | CATransaction.setCompletionBlock { 253 | print("completed") 254 | self.isTouchDown = false 255 | self.sendActions(for: .valueChanged) 256 | self.valueChange?(self.isOn) 257 | self.isUserInteractionEnabled = true 258 | } 259 | 260 | CATransaction.begin() 261 | thumbLoadingCompleteLayer.strokeAnimation(duration: 1) 262 | CATransaction.commit() 263 | } 264 | 265 | } 266 | 267 | // MARK: - layout Layers 268 | private extension SDSwitch { 269 | 270 | func layoutTrackLayer(for bounds: CGRect) { 271 | 272 | let size = getTackSize() 273 | let origin = getTrackOrigin(for: size.width) 274 | trackLayer.frame = CGRect(origin: origin, size: size) 275 | 276 | shape == .rounded ? (trackLayer.cornerRadius = trackLayer.bounds.height / 2) : (trackLayer.cornerRadius = 5) 277 | } 278 | 279 | func layoutThumbLayer(for bounds: CGRect) { 280 | let size = getThumbSize() 281 | let origin = getThumbOrigin(for: size.width) 282 | thumbLayer.frame = CGRect(origin: origin, size: size) 283 | 284 | shape == .rounded ? (thumbLayer.cornerRadius = size.height / 2) : (thumbLayer.cornerRadius = thumbCornerRadius) 285 | } 286 | 287 | func layoutLoadingLayer(for bounds: CGRect) { 288 | loadingLayer.lineWidth = thumbRadiusPadding 289 | 290 | let loadingBounds = bounds.insetBy(dx: 0, dy: borderWidth + loadingLayer.lineWidth/2) 291 | let center = CGPoint(x: bounds.midX, y: bounds.midY) 292 | let bezierPath = UIBezierPath(arcCenter: center, 293 | radius: bounds.midY, 294 | startAngle: -110.degreesToRadians, 295 | endAngle: -60.degreesToRadians, 296 | clockwise: true) 297 | loadingLayer.frame = loadingBounds 298 | loadingLayer.path = bezierPath.cgPath 299 | } 300 | 301 | } 302 | // MARK: - Touches 303 | private extension SDSwitch { 304 | 305 | private func addTouchHandlers() { 306 | addTarget(self, action: #selector(touchDown), for: [.touchDown, .touchDragEnter]) 307 | addTarget(self, action: #selector(touchUp), for: [.touchUpInside]) 308 | addTarget(self, action: #selector(touchEnded), for: [.touchDragExit, .touchCancel]) 309 | 310 | let leftSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeLeftRight(_:))) 311 | leftSwipeGesture.direction = [.left] 312 | addGestureRecognizer(leftSwipeGesture) 313 | 314 | let rightSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeLeftRight(_:))) 315 | rightSwipeGesture.direction = [.right] 316 | addGestureRecognizer(rightSwipeGesture) 317 | } 318 | 319 | @objc 320 | func swipeLeftRight(_ gesture: UISwipeGestureRecognizer) { 321 | let canLeftSwipe = isOn && gesture.direction == .left 322 | let canRightSwipe = !isOn && gesture.direction == .right 323 | guard canLeftSwipe || canRightSwipe else { return } 324 | touchUp() 325 | } 326 | 327 | @objc 328 | func touchDown() { 329 | isLoadingCompleted = false 330 | if thumbLayer.sublayers?.contains(thumbLoadingCompleteLayer) ?? false { 331 | thumbLoadingCompleteLayer.removeFromSuperlayer() 332 | } 333 | 334 | isTouchDown = true 335 | 336 | } 337 | 338 | @objc 339 | func touchUp() { 340 | isOn.toggle() 341 | touchEnded() 342 | } 343 | 344 | @objc 345 | func touchEnded() { 346 | if self.isLoadingEnabled && isOn { 347 | performLoading() 348 | 349 | } else { 350 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { 351 | self.isTouchDown = false 352 | self.valueChange?(self.isOn) 353 | } 354 | } 355 | 356 | } 357 | 358 | } 359 | 360 | // MARK: - color setting 361 | private extension SDSwitch { 362 | func setThumbColor() { 363 | if let thumbColor = thumbTintColor { 364 | thumbLayer.backgroundColor = thumbColor.cgColor 365 | } else { 366 | thumbLayer.backgroundColor = (isOn ? onThumbTintColor : offThumbTintColor).cgColor 367 | } 368 | } 369 | func getBackgroundColor() -> CGColor { 370 | return ((isOn && !isTouchDown) ? onTintColor : offTintColor).cgColor 371 | } 372 | func setBorderColor() { 373 | if let borderClor = borderColor { 374 | trackLayer.borderColor = borderClor.cgColor 375 | } else { 376 | trackLayer.borderColor = (isOn ? onBorderColor : offBorderColor).cgColor 377 | } 378 | } 379 | 380 | } 381 | 382 | // MARK: - Loading Animation 383 | private extension SDSwitch { 384 | func performLoading() { 385 | loadingStarted?() 386 | layer.addSublayer(loadingLayer) 387 | isUserInteractionEnabled = false 388 | loadingLayer.rotateAnimation(angal: 360.degreesToRadians, duration: 1, repeatAnimation: true) 389 | } 390 | } 391 | 392 | private extension SDSwitch { 393 | func drawOnPath(_ group: CGRect) -> CGPath { 394 | group.drawCheckmarkPath() 395 | } 396 | private func drawOffPath(_ group: CGRect) -> CGPath { 397 | group.drawCrossPath() 398 | } 399 | } 400 | 401 | // `Crdit` :-Morph Switch 402 | // https://dribbble.com/shots/2330566-Morph-Switch 403 | @IBDesignable public class YapFullTextSwitch: SDSwitch { 404 | 405 | private lazy var contentsLayer: CATextLayer = CATextLayer() 406 | 407 | public var onText: String? { 408 | didSet { 409 | layoutTextLayerIfNeeded() 410 | } 411 | } 412 | 413 | public var offText: String? { 414 | didSet { 415 | layoutTextLayerIfNeeded() 416 | } 417 | } 418 | public var onTextColor: UIColor = .white 419 | 420 | public var offTextColor: UIColor = .white 421 | 422 | override func controlDidLoad() { 423 | super.controlDidLoad() 424 | 425 | thumbLayer.addSublayer(contentsLayer) 426 | thumbRadiusPadding = 0 427 | } 428 | 429 | override func getThumbSize() -> CGSize { 430 | let height = bounds.height - 2 * (borderWidth ) 431 | let width = (isTouchDown) ? bounds.width - 2 * (borderWidth ) : height 432 | return CGSize(width: width, height: height) 433 | } 434 | 435 | override func getTackSize() -> CGSize { 436 | bounds.size 437 | } 438 | 439 | override func touchEnded() { 440 | if isOn { 441 | contentsLayer.string = onText 442 | contentsLayer.foregroundColor = onTextColor.cgColor 443 | 444 | } else { 445 | contentsLayer.string = offText 446 | contentsLayer.foregroundColor = offTextColor.cgColor 447 | } 448 | 449 | setBorderColor() 450 | trackLayer.backgroundColor = getBackgroundColor() 451 | 452 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 453 | self.contentsLayer.string = nil 454 | self.isTouchDown = false 455 | self.valueChange?(self.isOn) 456 | } 457 | } 458 | 459 | public override func layoutSublayers(of layer: CALayer) { 460 | super.layoutSublayers(of: layer) 461 | layoutContentLayer(for: bounds) 462 | } 463 | 464 | private func layoutContentLayer(for bounds: CGRect) { 465 | let inset = 2 * (borderWidth ) 466 | let yValue = bounds.maxY - inset 467 | let leading = (borderWidth )/2 468 | let xValue = bounds.maxX - inset 469 | let origin = CGPoint(x: leading, y: leading + bounds.midY/4) 470 | 471 | contentsLayer.frame = CGRect(origin: origin, size: CGSize(width: xValue, height: yValue)) 472 | contentsLayer.contentsGravity = .center 473 | contentsLayer.fontSize = bounds.height * 0.4 474 | 475 | } 476 | 477 | // MARK: - Content Layers 478 | private func layoutTextLayerIfNeeded() { 479 | contentsLayer.alignmentMode = .center 480 | contentsLayer.fontSize = bounds.height * 0.4 481 | contentsLayer.font = UIFont.systemFont(ofSize: 10, weight: .bold) 482 | contentsLayer.contentsScale = UIScreen.main.scale 483 | } 484 | 485 | } 486 | -------------------------------------------------------------------------------- /Demo/Switches/Switcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Switcher.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 24/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | // `Design by` : Oleg Frolov 11 | // https://dribbble.com/shots/3844909-On-Off 12 | //https://dribbble.com/shots/4148855-Switcher-XXXIII 13 | 14 | @IBDesignable public class Switcher: BaseControl { 15 | 16 | // MARK: - Properties 17 | @IBInspectable public var onTintColor: UIColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) { 18 | didSet { 19 | onThumbLayerColorChange() 20 | } 21 | } 22 | 23 | @IBInspectable public var offTintColor: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) { 24 | didSet { 25 | offThumbLayerColorChange() 26 | } 27 | } 28 | 29 | // MARK: - Views 30 | fileprivate lazy var thumbOnLayer: CALayer = { 31 | let layer = CALayer() 32 | layer.contentsGravity = .resizeAspect 33 | layer.backgroundColor = onTintColor.cgColor 34 | return layer 35 | }() 36 | 37 | fileprivate lazy var thumbOffLayer: CALayer = { 38 | let layer = CALayer() 39 | 40 | layer.contentsGravity = .resizeAspect 41 | layer.backgroundColor = offTintColor.cgColor 42 | return layer 43 | }() 44 | 45 | private lazy var trackLayer: CAShapeLayer = { 46 | let shape = CAShapeLayer() 47 | shape.backgroundColor = UIColor.white.cgColor 48 | return shape 49 | }() 50 | 51 | fileprivate var thumbRadiusPadding: CGFloat = 5 52 | fileprivate var duration: Double = 1 53 | fileprivate var isTouchDown: Bool = false { 54 | didSet { 55 | layoutSublayers(of: layer) 56 | } 57 | } 58 | 59 | // MARK: - initializers 60 | convenience init() { 61 | self.init(frame: .zero) 62 | } 63 | 64 | override init(frame: CGRect) { 65 | super.init(frame: frame) 66 | controlDidLoad() 67 | } 68 | 69 | required init?(coder aDecoder: NSCoder) { 70 | super.init(coder: aDecoder) 71 | controlDidLoad() 72 | } 73 | 74 | fileprivate func controlDidLoad() { 75 | layer.addSublayer(trackLayer) 76 | layer.addSublayer(thumbOffLayer) 77 | layer.addSublayer(thumbOnLayer) 78 | 79 | layer.shadowOffset = .zero 80 | layer.shadowOpacity = 0.3 81 | layer.shadowRadius = 5 82 | layer.masksToBounds = true 83 | 84 | self.isOn ? (self.trackLayer.backgroundColor = self.offTintColor.cgColor) : (self.trackLayer.backgroundColor = self.onTintColor.cgColor) 85 | addTouchHandlers() 86 | backgroundColor = .clear 87 | } 88 | 89 | public override func layoutSublayers(of layer: CALayer) { 90 | super.layoutSublayers(of: layer) 91 | layer.cornerRadius = bounds.midY 92 | CATransaction.setAnimationDuration(duration) 93 | CATransaction.begin() 94 | 95 | layoutLayers() 96 | 97 | CATransaction.commit() 98 | } 99 | 100 | fileprivate func layoutLayers() { 101 | layoutTrackLayer(for: layer.bounds) 102 | layoutOnThumbLayer(for: layer.bounds) 103 | layoutOffThumbLayer(for: layer.bounds) 104 | } 105 | 106 | fileprivate func onThumbLayerColorChange() { 107 | thumbOnLayer.backgroundColor = onTintColor.cgColor 108 | self.isOn ? (self.trackLayer.backgroundColor = self.offTintColor.cgColor) : (self.trackLayer.backgroundColor = self.onTintColor.cgColor) 109 | } 110 | fileprivate func offThumbLayerColorChange() { 111 | thumbOffLayer.backgroundColor = offTintColor.cgColor 112 | self.isOn ? (self.trackLayer.backgroundColor = self.offTintColor.cgColor) : (self.trackLayer.backgroundColor = self.onTintColor.cgColor) 113 | } 114 | } 115 | // MARK: - layout Layers 116 | fileprivate extension Switcher { 117 | 118 | func layoutTrackLayer(for bounds: CGRect) { 119 | 120 | trackLayer.frame = bounds 121 | trackLayer.cornerRadius = trackLayer.bounds.midY 122 | } 123 | 124 | @objc func layoutOnThumbLayer(for bounds: CGRect) { 125 | let size = getOnThumbSize() 126 | let origin = getOnThumbOrigin(for: size.width) 127 | thumbOnLayer.frame = CGRect(origin: origin, size: size) 128 | thumbOnLayer.cornerRadius = size.height/2 129 | } 130 | 131 | @objc func layoutOffThumbLayer(for bounds: CGRect) { 132 | let size = getOffThumbSize() 133 | let origin = getOffThumbOrigin(for: size.width) 134 | thumbOffLayer.frame = CGRect(origin: origin, size: size) 135 | thumbOffLayer.cornerRadius = size.height/2 136 | } 137 | 138 | final func getOnThumbSize() -> CGSize { 139 | let height = bounds.height - 2 * ( thumbRadiusPadding) 140 | return ( isTouchDown && !isOn ) ? CGSize(width: bounds.maxX * 1.5, height: bounds.maxY * 2) : CGSize(width: height, height: height) 141 | } 142 | 143 | final func getOffThumbSize() -> CGSize { 144 | let height = bounds.height - 2 * ( thumbRadiusPadding) 145 | return ( isTouchDown && isOn ) ? CGSize(width: bounds.maxX * 1.5, height: bounds.maxY * 2) : CGSize(width: height, height: height) 146 | } 147 | 148 | final func getOnThumbOrigin(for width: CGFloat) -> CGPoint { 149 | let inset = thumbRadiusPadding 150 | return (isTouchDown && !isOn) ? CGPoint(x: -bounds.maxX/3, y: -bounds.midY * 0.8) : CGPoint( x: (bounds.width - width - inset), y: inset) 151 | } 152 | 153 | final func getOffThumbOrigin(for width: CGFloat) -> CGPoint { 154 | let inset = thumbRadiusPadding 155 | return (isTouchDown && isOn) ? CGPoint(x: -bounds.maxX/3, y: -bounds.midY * 0.8) : CGPoint( x: inset, y: inset) 156 | 157 | } 158 | } 159 | fileprivate extension Switcher { 160 | 161 | private func addTouchHandlers() { 162 | addTarget(self, action: #selector(touchDown), for: [.touchDown, .touchDragEnter]) 163 | addTarget(self, action: #selector(touchUp), for: [.touchUpInside]) 164 | addTarget(self, action: #selector(touchEnded), for: [.touchDragExit, .touchCancel]) 165 | 166 | let leftSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeLeftRight(_:))) 167 | leftSwipeGesture.direction = [.left] 168 | addGestureRecognizer(leftSwipeGesture) 169 | 170 | let rightSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeLeftRight(_:))) 171 | rightSwipeGesture.direction = [.right] 172 | addGestureRecognizer(rightSwipeGesture) 173 | } 174 | 175 | @objc 176 | func swipeLeftRight(_ gesture: UISwipeGestureRecognizer) { 177 | let canLeftSwipe = isOn && gesture.direction == .left 178 | let canRightSwipe = !isOn && gesture.direction == .right 179 | guard canLeftSwipe || canRightSwipe else { return } 180 | touchUp() 181 | } 182 | 183 | @objc 184 | func touchDown() { 185 | 186 | isUserInteractionEnabled = false 187 | 188 | UIView.animate(withDuration: 0.2, 189 | animations: { 190 | self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95) 191 | }) 192 | } 193 | 194 | @objc 195 | func touchUp() { 196 | isOn.toggle() 197 | touchEnded() 198 | } 199 | 200 | @objc 201 | func touchEnded() { 202 | isTouchDown = true 203 | 204 | UIView.animate(withDuration: duration/3, 205 | animations: { 206 | self.transform = CGAffineTransform(scaleX: 1.05, y: 1.05) 207 | }, 208 | completion: { _ in 209 | UIView.animate(withDuration: self.duration/3) { 210 | self.transform = CGAffineTransform.identity 211 | } 212 | }) 213 | 214 | DispatchQueue.main.asyncAfter(deadline: .now() + (duration)) { 215 | self.isOn ? (self.trackLayer.backgroundColor = self.offTintColor.cgColor) : (self.trackLayer.backgroundColor = self.onTintColor.cgColor) 216 | 217 | self.isOn ? (self.thumbOffLayer.areAnimationsEnabled = false) : (self.thumbOnLayer.areAnimationsEnabled = false) 218 | self.sendActions(for: .valueChanged) 219 | 220 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { 221 | 222 | self.isUserInteractionEnabled = true 223 | 224 | self.isTouchDown = false 225 | self.isOn ? self.thumbOffLayer.bringToFront() : self.thumbOnLayer.bringToFront() 226 | self.isOn ? (self.thumbOffLayer.areAnimationsEnabled = true) : (self.thumbOnLayer.areAnimationsEnabled = true) 227 | 228 | } 229 | } 230 | } 231 | 232 | } 233 | @IBDesignable public class SwitcherFullStrtech: Switcher { 234 | 235 | @IBInspectable public var thumbTintColor: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) { 236 | didSet { 237 | thumbLayerColorChange() 238 | } 239 | } 240 | 241 | private lazy var interactionLayer: CALayer = { 242 | let shape = CALayer() 243 | shape.backgroundColor = thumbTintColor.cgColor 244 | return shape 245 | 246 | }() 247 | 248 | override func controlDidLoad() { 249 | super.controlDidLoad() 250 | layer.addSublayer(interactionLayer) 251 | 252 | thumbOnLayer.bringToFront() 253 | thumbOffLayer.bringToFront() 254 | duration = 0.5 255 | thumbRadiusPadding = 0 256 | 257 | layer.masksToBounds = false 258 | } 259 | 260 | override func layoutLayers() { 261 | super.layoutLayers() 262 | layoutInteractionLayerLayer(for: bounds) 263 | } 264 | 265 | override func layoutOnThumbLayer(for bounds: CGRect) { 266 | 267 | let percent: CGFloat = bounds.maxY * 0.2 268 | 269 | thumbOnLayer.frame = CGRect(x: bounds.midY - percent/2, y: bounds.midY - percent/2, width: percent, height: percent) 270 | thumbOnLayer.cornerRadius = thumbOnLayer.bounds.midY 271 | } 272 | override func layoutOffThumbLayer(for bounds: CGRect) { 273 | 274 | let heightPercent: CGFloat = bounds.maxY * 0.4 275 | let widthPercent: CGFloat = bounds.maxY * 0.12 276 | 277 | thumbOffLayer.frame = CGRect(x: bounds.maxX - bounds.midY - widthPercent/2, 278 | y: bounds.midY - heightPercent/2, 279 | width: widthPercent, 280 | height: heightPercent) 281 | thumbOffLayer.cornerRadius = thumbOffLayer.bounds.midX 282 | } 283 | 284 | fileprivate override func onThumbLayerColorChange() { 285 | thumbOnLayer.backgroundColor = onTintColor.cgColor 286 | } 287 | 288 | fileprivate override func offThumbLayerColorChange() { 289 | thumbOffLayer.backgroundColor = offTintColor.cgColor 290 | } 291 | 292 | override func touchEnded() { 293 | isTouchDown = true 294 | DispatchQueue.main.asyncAfter(deadline: .now() + (duration)) { 295 | self.isTouchDown = false 296 | self.isUserInteractionEnabled = true 297 | self.sendActions(for: .valueChanged) 298 | let onTint = self.onTintColor 299 | self.onTintColor = self.offTintColor 300 | self.offTintColor = onTint 301 | 302 | } 303 | } 304 | 305 | func thumbLayerColorChange() { 306 | interactionLayer.backgroundColor = thumbTintColor.cgColor 307 | } 308 | } 309 | 310 | extension SwitcherFullStrtech { 311 | func layoutInteractionLayerLayer(for bounds: CGRect) { 312 | interactionLayer.cornerRadius = bounds.midY 313 | 314 | isTouchDown ? ( interactionLayer.frame = bounds ) : 315 | (isOn ? (interactionLayer.frame = CGRect(x: bounds.maxX - bounds.maxY, 316 | y: 0, 317 | width: bounds.maxY, 318 | height: bounds.maxY)) : 319 | (interactionLayer.frame = CGRect(x: 0, 320 | y: 0, 321 | width: bounds.maxY, 322 | height: bounds.maxY))) 323 | 324 | } 325 | 326 | } 327 | -------------------------------------------------------------------------------- /Demo/Switches/UIImage+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Extension.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 29/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public extension UIImage { 11 | 12 | func maskWithColor(color: UIColor) -> UIImage? { 13 | 14 | let maskLayer = CALayer() 15 | maskLayer.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height) 16 | maskLayer.backgroundColor = color.cgColor 17 | maskLayer.doMask(by: self) 18 | let maskImage = maskLayer.toImage() 19 | return maskImage 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Demo/Switches/YapBaseSwitch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YapBaseSwitch.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 13/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | // `Design by`: Oleg Frolov 10 | // https://dribbble.com/shots/2028065-Switcher-lll 11 | 12 | import UIKit 13 | 14 | @IBDesignable 15 | open class YapBaseSwitch: BaseControl { 16 | 17 | // MARK: - Property 18 | 19 | @IBInspectable open var animateDuration: Double = 0.4 20 | 21 | // internal var ison: Bool = false 22 | internal var sizeScale: CGFloat { 23 | return min(self.bounds.width, self.bounds.height)/100.0 24 | } 25 | 26 | open override var frame: CGRect { 27 | didSet { 28 | guard frame.size != oldValue.size else { return } 29 | resetView() 30 | } 31 | } 32 | 33 | open override var bounds: CGRect { 34 | didSet { 35 | guard frame.size != oldValue.size else { return } 36 | resetView() 37 | } 38 | } 39 | 40 | // MARK: - Getter 41 | 42 | final public func setOn(_ isOn: Bool, animate: Bool = true) { 43 | guard isOn != isOn else { return } 44 | toggleValue() 45 | } 46 | 47 | // MARK: - Init 48 | convenience public init() { 49 | self.init(frame: CGRect(x: 0, y: 0, width: 80, height: 40)) 50 | } 51 | 52 | override public init(frame: CGRect) { 53 | super.init(frame: frame) 54 | setUpView() 55 | } 56 | 57 | required public init?(coder aDecoder: NSCoder) { 58 | super.init(coder: aDecoder) 59 | setUpView() 60 | } 61 | 62 | // MARK: - Internal 63 | 64 | internal func resetView() { 65 | gestureRecognizers?.forEach(self.removeGestureRecognizer) 66 | layer.sublayers?.forEach({ $0.removeFromSuperlayer()}) 67 | setUpView() 68 | } 69 | 70 | internal func setUpView() { 71 | let tap = UITapGestureRecognizer(target: self, action: #selector(YapBaseSwitch.toggleValue)) 72 | self.addGestureRecognizer(tap) 73 | 74 | for view in self.subviews { 75 | view.removeFromSuperview() 76 | } 77 | } 78 | 79 | @objc internal func toggleValue() { 80 | 81 | self.isOn.toggle() 82 | self.isOn.toggle() 83 | valueChange?(isOn) 84 | sendActions(for: .valueChanged) 85 | changeValueAnimate(isOn, duration: animateDuration) 86 | } 87 | 88 | internal func changeValueAnimate(_ value: Bool, duration: Double) { 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /Demo/Switches/YapLiquidSwitch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TKLiquidSwitch.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 13/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // Dedign by Oleg Frolov 12 | //https://dribbble.com/shots/2028065-Switcher-lll 13 | 14 | @IBDesignable 15 | open class YapLiquidSwitch: YapBaseSwitch { 16 | 17 | private var bubbleLayer = CAShapeLayer() 18 | private var lineLayer = CAShapeLayer() 19 | @IBInspectable open var onColor: UIColor = UIColor(red: 0.373, green: 0.843, blue: 0.596, alpha: 1) { 20 | didSet { 21 | resetView() 22 | } 23 | } 24 | 25 | @IBInspectable open var offColor: UIColor = UIColor(red: 0.871, green: 0.871, blue: 0.871, alpha: 1) { 26 | didSet { 27 | resetView() 28 | } 29 | } 30 | 31 | override internal func setUpView() { 32 | super.setUpView() 33 | 34 | self.clipsToBounds = true 35 | 36 | lineLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, 37 | y: (self.bounds.height - 20 * sizeScale) / 2, 38 | width: self.bounds.width, 39 | height: 20 * sizeScale), cornerRadius: 10 * sizeScale).cgPath 40 | lineLayer.fillColor = switchColor(isOn).cgColor 41 | self.layer.addSublayer(lineLayer) 42 | 43 | bubbleLayer.frame = self.bounds 44 | bubbleLayer.position = bubblePosition(isOn) 45 | bubbleLayer.path = bubbleShapePath 46 | bubbleLayer.fillColor = switchColor(isOn).cgColor 47 | self.layer.addSublayer(bubbleLayer) 48 | 49 | } 50 | 51 | override func changeValueAnimate(_ value: Bool, duration: Double) { 52 | let bubbleTransformAnim = CAKeyframeAnimation(keyPath: "transform") 53 | bubbleTransformAnim.values = [NSValue(caTransform3D: CATransform3DIdentity), 54 | NSValue(caTransform3D: CATransform3DMakeScale(1, 0.8, 1)), 55 | NSValue(caTransform3D: CATransform3DMakeScale(0.8, 1, 1)), 56 | NSValue(caTransform3D: CATransform3DIdentity)] 57 | bubbleTransformAnim.keyTimes = [NSNumber(value: 0), NSNumber(value: 1.0 / 3.0), NSNumber(value: 2.0 / 3.0), NSNumber(value: 1)] 58 | bubbleTransformAnim.duration = duration 59 | 60 | let bubblePositionAnim = CABasicAnimation(keyPath: "position") 61 | bubblePositionAnim.fromValue = NSValue(cgPoint: bubblePosition(!isOn)) 62 | bubblePositionAnim.toValue = NSValue(cgPoint: bubblePosition(isOn)) 63 | bubblePositionAnim.duration = duration 64 | 65 | let bubbleGroupAnim = CAAnimationGroup() 66 | bubbleGroupAnim.animations = [bubbleTransformAnim, bubblePositionAnim] 67 | bubbleGroupAnim.isRemovedOnCompletion = false 68 | bubbleGroupAnim.fillMode = CAMediaTimingFillMode.forwards 69 | bubbleGroupAnim.duration = duration 70 | 71 | bubbleLayer.add(bubbleGroupAnim, forKey: "Bubble") 72 | 73 | let color = switchColor(value).cgColor 74 | UIView.animate(withDuration: duration, animations: { () -> Void in 75 | self.lineLayer.fillColor = color 76 | self.bubbleLayer.fillColor = color 77 | }) 78 | 79 | } 80 | } 81 | 82 | // Getter 83 | extension YapLiquidSwitch { 84 | 85 | var bubbleSize: CGSize { 86 | let lineH = 20 * sizeScale 87 | let width = lineH * 2 + bounds.height 88 | let height = bounds.height 89 | return CGSize(width: width, height: height) 90 | } 91 | 92 | var bubbleShapePath: CGPath { 93 | let bubblePath = UIBezierPath() 94 | let size = bubbleSize 95 | let ssR = (size.width - size.height)/4 96 | let llR = size.height/2 97 | 98 | let ll1 = CGPoint(x: ssR, y: llR - ssR) 99 | let ll2 = CGPoint(x: ssR, y: llR + ssR) 100 | 101 | let cc1 = CGPoint(x: ssR * 2 + llR, y: 0) 102 | let cc2 = CGPoint(x: ssR * 2 + llR, y: llR * 2) 103 | 104 | let rr1 = CGPoint(x: ssR * 3 + llR * 2, y: llR - ssR) 105 | let rr2 = CGPoint(x: ssR * 3 + llR * 2, y: llR + ssR) 106 | 107 | let oo1 = CGPoint(x: (llR + ssR * 2)/4, y: llR - ssR) 108 | let oo2 = CGPoint(x: (llR + ssR * 2)/4, y: llR + ssR) 109 | let oo3 = CGPoint(x: (llR * 2 + ssR * 4) - (llR + ssR * 2)/4, y: llR - ssR) 110 | let oo4 = CGPoint(x: (llR * 2 + ssR * 4) - (llR + ssR * 2)/4, y: llR + ssR) 111 | 112 | // let cL = CGPoint(x: sR, y: lR) 113 | let ccC = CGPoint(x: ssR * 2 + llR, y: llR) 114 | // let cR = CGPoint(x: sR * 3 + lR * 2, y: lR) 115 | 116 | bubblePath.move(to: ll1) 117 | bubblePath.addQuadCurve(to: cc1, controlPoint: oo1) 118 | bubblePath.addArc(withCenter: ccC, radius: llR, startAngle: -CGFloat.pi/2, endAngle: CGFloat.pi*3/2, clockwise: true) 119 | bubblePath.addQuadCurve(to: rr1, controlPoint: oo3) 120 | bubblePath.addLine(to: rr2) 121 | 122 | bubblePath.addQuadCurve(to: cc2, controlPoint: oo4) 123 | bubblePath.addQuadCurve(to: ll2, controlPoint: oo2) 124 | bubblePath.addLine(to: ll1) 125 | bubblePath.close() 126 | 127 | return bubblePath.cgPath 128 | } 129 | 130 | func switchColor(_ isOn: Bool) -> UIColor { 131 | if isOn { 132 | return onColor 133 | } else { 134 | return offColor 135 | } 136 | } 137 | 138 | func bubblePosition(_ isOn: Bool) -> CGPoint { 139 | let height = self.bounds.height 140 | let width = self.bounds.width 141 | let bbW = bubbleSize.width 142 | 143 | if isOn { 144 | return CGPoint(x: bbW * 0.8, y: height/2) 145 | } else { 146 | return CGPoint(x: width - bbW*0.2, y: height/2) 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Demo/Switches/YapSmileSwitch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmileSwitch.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 13/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // Dedign by Oleg Frolov 12 | //https://dribbble.com/shots/2011284-Switcher-ll 13 | 14 | open class YapSmileSwitch: YapBaseSwitch { 15 | 16 | // MARK: - Poperty 17 | private var smileFace: TKSmileFaceView? 18 | 19 | // MARK: - Init 20 | override internal func setUpView() { 21 | super.setUpView() 22 | backgroundColor = .clear 23 | let frame = CGRect(x: self.bounds.width - self.bounds.height, 24 | y: 0, 25 | width: self.bounds.height, 26 | height: self.bounds.height) 27 | let smileFace = TKSmileFaceView(frame: frame) 28 | 29 | addSubview(smileFace) 30 | 31 | 32 | } 33 | 34 | override open func draw(_ rect: CGRect) { 35 | let ctx = UIGraphicsGetCurrentContext() 36 | let lineWidth = 10 * sizeScale 37 | let path = UIBezierPath(roundedRect: rect.insetBy(dx: lineWidth, dy: lineWidth), cornerRadius: rect.width/2) 38 | ctx?.setLineWidth(lineWidth*2) 39 | ctx?.addPath(path.cgPath) 40 | UIColor(white: 0.9, alpha: 1).setStroke() 41 | ctx?.strokePath() 42 | } 43 | 44 | override func changeValueAnimate(_ value: Bool, duration: Double) { 45 | let xValue = value ? 0 : (bounds.width - bounds.height) 46 | let frame = CGRect(x: xValue, y: 0, width: bounds.height, height: bounds.height) 47 | self.smileFace!.faceType = value ? TKSmileFaceView.FaceType.happy : 48 | TKSmileFaceView.FaceType.sad 49 | self.smileFace?.rotation(animateDuration, count: 2, clockwise: !isOn) 50 | UIView.animate(withDuration: duration, animations: { () -> Void in 51 | self.smileFace?.frame = frame 52 | }, completion: { (complete) -> Void in 53 | if complete { 54 | self.smileFace?.eyeWinkAnimate(duration/2) 55 | } 56 | }) 57 | 58 | } 59 | } 60 | 61 | // 脸 62 | private class TKSmileFaceView: UIView { 63 | 64 | enum FaceType { 65 | case happy 66 | case sad 67 | func toColor() -> UIColor { 68 | switch self { 69 | case .happy: 70 | return UIColor(red: 0.388, green: 0.839, blue: 0.608, alpha: 1.000) 71 | case .sad: 72 | return UIColor(red: 0.843, green: 0.369, blue: 0.373, alpha: 1) 73 | } 74 | } 75 | } 76 | 77 | // MARK: - Property 78 | let rightEye: CAShapeLayer = CAShapeLayer() 79 | let leftEye: CAShapeLayer = CAShapeLayer() 80 | let mouth: CAShapeLayer = CAShapeLayer() 81 | let face: CAShapeLayer = CAShapeLayer() 82 | 83 | var faceType: FaceType = .sad { 84 | didSet { 85 | let position: CGFloat = isHappy ? 20 * sizeScale : 35 * sizeScale 86 | mouth.path = mouthPath.cgPath 87 | mouth.frame = CGRect(x: 0, y: position, width: 60 * sizeScale, height: 20 * sizeScale) 88 | face.fillColor = faceType.toColor().cgColor 89 | } 90 | } 91 | 92 | // MARK: - Getter 93 | var isHappy: Bool { 94 | return (faceType == .happy) 95 | } 96 | 97 | var sizeScale: CGFloat { 98 | return min(self.bounds.width, self.bounds.height)/100.0 99 | } 100 | 101 | var mouthPath: UIBezierPath { 102 | get { 103 | let point: CGFloat = isHappy ? 70 * sizeScale : 10 104 | let path = UIBezierPath() 105 | path.move(to: CGPoint(x: 30 * sizeScale, y: 40 * sizeScale)) 106 | path.addCurve(to: CGPoint(x: 70 * sizeScale, y: 40 * sizeScale), 107 | controlPoint1: CGPoint(x: 30 * sizeScale, y: 40 * sizeScale), 108 | controlPoint2: CGPoint(x: 50 * sizeScale, y: point)) 109 | path.lineCapStyle = .round 110 | return path 111 | } 112 | } 113 | 114 | // MARK: - Init 115 | override init(frame: CGRect) { 116 | super.init(frame: frame) 117 | self.setupLayers() 118 | } 119 | 120 | required init?(coder aDecoder: NSCoder) { 121 | super.init(coder: aDecoder) 122 | self.setupLayers() 123 | } 124 | 125 | // MARK: - Private Func 126 | private func setupLayers() { 127 | 128 | let facePath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 100 * sizeScale, height: 100 * sizeScale)) 129 | let eyePath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 20 * sizeScale, height: 20 * sizeScale)) 130 | 131 | face.frame = CGRect(x: 0, y: 0, width: 100 * sizeScale, height: 100 * sizeScale) 132 | face.path = facePath.cgPath 133 | face.fillColor = faceType.toColor().cgColor 134 | layer.addSublayer(face) 135 | 136 | leftEye.frame = CGRect(x: 20 * sizeScale, y: 28 * sizeScale, width: 10 * sizeScale, height: 10 * sizeScale) 137 | leftEye.path = eyePath.cgPath 138 | leftEye.fillColor = UIColor.white.cgColor 139 | layer.addSublayer(leftEye) 140 | 141 | rightEye.frame = CGRect(x: 60 * sizeScale, y: 28 * sizeScale, width: 10 * sizeScale, height: 10 * sizeScale) 142 | rightEye.path = eyePath.cgPath 143 | rightEye.fillColor = UIColor.white.cgColor 144 | layer.addSublayer(rightEye) 145 | 146 | mouth.path = mouthPath.cgPath 147 | mouth.strokeColor = UIColor.white.cgColor 148 | mouth.fillColor = UIColor.clear.cgColor 149 | mouth.lineCap = .round 150 | mouth.lineWidth = 10 * sizeScale 151 | layer.addSublayer(mouth) 152 | 153 | faceType = .sad 154 | 155 | } 156 | 157 | // MARK: - Animate 158 | func eyeWinkAnimate(_ duration: Double) { 159 | let eyeleftTransformAnim = CAKeyframeAnimation(keyPath: "transform") 160 | eyeleftTransformAnim.values = [NSValue(caTransform3D: CATransform3DIdentity), 161 | NSValue(caTransform3D: CATransform3DMakeScale(1, 0.1, 1)), 162 | NSValue(caTransform3D: CATransform3DIdentity) 163 | ] 164 | eyeleftTransformAnim.keyTimes = [0, 0.5, 1] 165 | eyeleftTransformAnim.duration = duration 166 | leftEye.add(eyeleftTransformAnim, forKey: "Wink") 167 | rightEye.add(eyeleftTransformAnim, forKey: "Wink") 168 | } 169 | 170 | func rotation(_ duration: Double, count: Int, clockwise: Bool) { 171 | let rotationTransformAnim = CAKeyframeAnimation(keyPath: "transform.rotation.z") 172 | rotationTransformAnim.values = [0, 180 * CGFloat.pi/180 * CGFloat(count) * (clockwise ? 1 : -1)] 173 | rotationTransformAnim.keyTimes = [0, 1] 174 | rotationTransformAnim.isRemovedOnCompletion = false 175 | rotationTransformAnim.duration = duration 176 | layer.add(rotationTransformAnim, forKey: "Rotation") 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /DemoTests/DemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoTests.swift 3 | // DemoTests 4 | // 5 | // Created by Jawad Ali on 03/03/2021. 6 | // 7 | 8 | import XCTest 9 | @testable import Demo 10 | 11 | class DemoTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() throws { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /DemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /DemoUITests/DemoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoUITests.swift 3 | // DemoUITests 4 | // 5 | // Created by Jawad Ali on 03/03/2021. 6 | // 7 | 8 | import XCTest 9 | 10 | class DemoUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - 19 | // required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDownWithError() throws { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() throws { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | func testLaunchPerformance() throws { 36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 37 | // This measures how long it takes to launch your application. 38 | measure(metrics: [XCTApplicationLaunchMetric()]) { 39 | XCUIApplication().launch() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /JDAlertController.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = "JDAlertController" 4 | s.version = "1.0.1" 5 | s.summary = "JDAlertController framework" 6 | s.description = <<-DESC 7 | You can add elegant JDAlertController animation in your view with just 2 line of code 8 | DESC 9 | s.homepage = "https://github.com/jwd-ali/IOS-Portfolio" 10 | s.license = "MIT" 11 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 12 | s.authors = { "Jawad Ali" => "L060214@gmail.com" } 13 | s.platforms = { :ios => "11.0" } 14 | s.source = { :git => "https://github.com/jwd-ali/JDAlertController.git", :tag => "#{s.version}" } 15 | 16 | s.source_files = "Sources/**/*.{h,m,swift}" 17 | s.requires_arc = true 18 | s.swift_version = "5.0" 19 | 20 | end 21 | -------------------------------------------------------------------------------- /JDAlertController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JDAlertController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JDAlertController.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /JDAlertController.xcodeproj/xcshareddata/xcschemes/JDAlertController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /JDAlertController.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /JDAlertController.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JDAlertControllerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /JDAlertControllerTests/JDAlertControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JDAlertControllerTests.swift 3 | // JDAlertControllerTests 4 | // 5 | // Created by Jawad Ali on 03/03/2021. 6 | // 7 | 8 | import XCTest 9 | @testable import JDAlertController 10 | 11 | class JDAlertControllerTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() throws { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "JDAlertController", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "JDAlertController", 12 | targets: ["JDAlertController"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "JDAlertController", 23 | dependencies: []), 24 | .testTarget( 25 | name: "JDAlertControllerTests", 26 | dependencies: ["JDAlertController"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'Demo' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for Demo 9 | pod 'JDTabBarController' 10 | pod 'SwiftLint' 11 | 12 | 13 | target 'DemoTests' do 14 | inherit! :search_paths 15 | # Pods for testing 16 | end 17 | 18 | target 'DemoUITests' do 19 | # Pods for testing 20 | end 21 | 22 | end 23 | 24 | target 'JDAlertController' do 25 | # Comment the next line if you don't want to use dynamic frameworks 26 | use_frameworks! 27 | 28 | # Pods for JDAlertController 29 | 30 | target 'JDAlertControllerTests' do 31 | # Pods for testing 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

JDAlertController

4 | 5 | [![CI Status](https://travis-ci.org/jwd-ali/RingPieChart.svg)](https://travis-ci.org/jwd-ali/RingPieChart) 6 | [![CocoaPods Version](https://img.shields.io/cocoapods/v/RingPieChart.svg?style=flat)](https://cocoapods.org/pods/RingPieChart) 7 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-0473B3.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | [![License](https://img.shields.io/cocoapods/l/RingPieChart.svg?style=flat)](https://cocoapods.org/pods/RingPieChart) 9 | [![Platform](https://img.shields.io/cocoapods/p/RingPieChart.svg?style=flat)](https://cocoapods.org/pods/RingPieChart) 10 | [![Swift 5.1](https://img.shields.io/badge/swift-5.1-orange)](https://swift.org) 11 | 12 |

JDAlertController is AlertController replica that can significantly enhance your users’ experiences and set your app apart from the rest of the pack.

13 | 14 | ___ 15 | You don’t need to do much to integrate it. Its build using UIViewControllerTransitioningDelegate and UIPresentationController having four types of UIViewControllerAnimatedTransitioning animations and many more customisations with different buttons styles and UITextField support as well 16 | 17 | Error and Success Animations are build using CABasicAnimation and CAShapeLayer through UIBezierpath. Its fun to play with CoreGraphics.It starts slow and By the end, you’ll be able to create stunning graphics for your apps. 18 |

19 | 20 | 21 | 22 | 23 |

24 | ## Requirements 25 | 26 | - iOS 11.0+ / Mac OS X 10.9+ / watchOS 2.0+ / tvOS 9.0+ 27 | - Xcode 8.0+ 28 | 29 | ## Installation 30 | 31 | ### [CocoaPods](http://cocoapods.org) 32 | 33 | To integrate **JDAlertController** into your Xcode project using CocoaPods, specify it in your `Podfile`: 34 | 35 | ```ruby 36 | use_frameworks! 37 | 38 | pod 'JDAlertController' 39 | ``` 40 | 41 | Then, run the following command: 42 | 43 | ```bash 44 | $ pod install 45 | ``` 46 | ### [Carthage](http://github.com/Carthage/Carthage) 47 | 48 | To integrate `JDAlertController` into your Xcode project using Carthage, specify it in your `Cartfile`: 49 | 50 | ```ogdl 51 | github "jwd-ali/JDAlertController" 52 | ``` 53 | 54 | ### [Swift Package Manager (SPM)](https://swift.org/package-manager) 55 | 56 | #### Prerequisites 57 | - OSX 58 | 59 | 60 | #### Update `Package.swift` 61 | To integrate `JDAlertController` in your project, add the proper description to your `Package.swift` file: 62 | ```swift 63 | // swift-tools-version:5.0 64 | import PackageDescription 65 | 66 | let package = Package( 67 | name: "YOUR_PROJECT_NAME", 68 | dependencies: [ 69 | .package(url: "https://github.com/jwd-ali/JDAlertController.git") 70 | ], 71 | targets: [ 72 | .target( 73 | name: "YOUR_TARGET_NAME", 74 | dependencies: ["JDAlertController"] 75 | ), 76 | ... 77 | ] 78 | ) 79 | ``` 80 | 81 | ### Manually 82 | 83 | If you prefer not to use a dependency manager, you can integrate JDAlertController into your project manually. 84 | 85 | - Add sources into your project: 86 | - Drag `Sources` 87 | 88 | ## Usage 89 | 90 | > If you are using any dependency manager (pods , carthage , package manager)to integrate JDAlertController. Import JDAlertController first: 91 | > ```swift 92 | > import JDAlertController 93 | > ``` 94 | 95 | > And for Manuall install you dont need to import anything 96 | 97 | - Init your ring with `Alert` same as you initialize default `UIAlertController`: 98 | ```swift 99 | let alert = AlertController(icon: UIImage(named: "iconName"), 100 | title: "Can We Help?", 101 | message: "Any questions or feedback? We are here to help you! ", 102 | preferredStyle: .alert) 103 | ``` 104 | We can show alert with 4 different styles 105 | 106 | ```swift 107 | public enum PopupStyle { 108 | case alert 109 | case actionSheet(offset: Double) 110 | case topSheet(offset: Double) 111 | case dragIn 112 | } 113 | ``` 114 | 115 | Action sheet and top sheet support offset as well if you want to position it some point rather then attaching to bottom or top 116 | You can also set the icon size in the initializer like this 117 | 118 | ```swift 119 | let alert = AlertController(icon: UIImage(named: "write"), 120 | title: "Can We Help?", 121 | message: "Any questions or feedback? We are here to help you! ", 122 | preferredStyle: Settings.shared.preferedStyle, 123 | iconSize: 80) 124 | ``` 125 | 126 | Here you are giving top icon size to 80 points 127 | 128 | You can enable touch anywhere outside the alert to dismiss it on 129 | 130 | ```swift 131 | alert.tapToClose = true 132 | 133 | ``` 134 | If its On user can tap anywhere outside popup to dismiss it. 135 | 136 | You can also enable draging and fast drag dismiss by setting the key `isDragEnable` 137 | 138 | ```swift 139 | alert.isDragEnable = true 140 | 141 | ``` 142 | You can control the width of the popup using key `widthRatio` ita ratio of total screen width. its value should be in range 0...1 143 | 144 | ```swift 145 | alert.widthRatio = 0.8 146 | 147 | ``` 148 | 149 | For Animated success or failure you need to set PopupType in inializer like this 150 | 151 | ```swift 152 | public enum PopupType { 153 | case success 154 | case error 155 | } 156 | 157 | let alert = AlertController(type: .success, 158 | title: "Awaiting your payment!", 159 | message: "Leo Walton will receive \n 62,500.00 PKR ", 160 | preferredStyle: Settings.shared.preferedStyle) 161 | 162 | ``` 163 | 164 | And set the `isAnimated` property to true if you want Top to bottom animation like unfolding 165 | 166 | 167 | ```swift 168 | alert.isAnimated = true 169 | ``` 170 | 171 | 172 | Adding `buttons` and `TextFields` in alert is as simple as adding them in Alert controller 173 | 174 | ```swift 175 | let homeAction = PopupAction(title: "No, Thanks", 176 | style: .classic(cornerRadius: 5), 177 | propotionalWidth: .margin) { 178 | 179 | } 180 | 181 | alert.addAction(homeAction) 182 | ``` 183 | 184 | Same as AlertController we get closure to do actions while declaring buttons and dismissal of popup is auto so you dont need to dismiss the alert on taping of the button 185 | We have different style buttons available to inject into the Action 186 | 187 | ```swift 188 | public enum Style { 189 | 190 | case `default` 191 | case bold 192 | case destructive 193 | case round 194 | case classic(cornerRadius: CGFloat) 195 | case justText 196 | } 197 | ``` 198 | I think they are self explainatory as their name. Also you can have sizes of buttons as well 199 | 200 | ```swift 201 | public enum ButtonWidth { 202 | case full 203 | case margin 204 | case normal 205 | case custom(ratio: CGFloat) 206 | } 207 | ``` 208 | Where full are full width and you can also have custom width and marginal width as well 209 | here is the propotinal width graph 210 | 211 | ```swift 212 | var buttonSize: CGFloat { 213 | switch propotionalWidth { 214 | case .full: 215 | return 1.0 216 | case .margin: 217 | return 0.9 218 | case .normal: 219 | return 0.75 220 | case .custom(let width): 221 | return width 222 | } 223 | } 224 | ``` 225 | 226 | It explain the width of the button 227 | 228 | Adding Text fields is also as easy as its native control 229 | 230 | ```swift 231 | let emailField = AppTextField(with: "User Name or Email") 232 | let passwordField = AppTextField(with: "Password") 233 | passwordField.isSecureTextEntry = true 234 | 235 | alert.addTextField(emailField) 236 | alert.addTextField(passwordField) 237 | 238 | ``` 239 | 240 | And then you can access them like this 241 | 242 | ```swift 243 | let firstTextField = alert.textFields.first 244 | ``` 245 | 246 | Congratulations! You're done. 247 | 248 | 249 | ## Contributing 250 | 251 | I’d love to have help on this project. For small changes please [open a pull request](https://github.com/jwd-ali/JDAlertController/pulls), for larger changes please [open an issue](https://github.com/jwd-ali/JDAlertController/issues) first to discuss what you’d like to see. 252 | 253 | 254 | License 255 | ------- 256 | 257 | JDAlertController is under [MIT](https://opensource.org/licenses/MIT). See [LICENSE](LICENSE) file for more info. 258 | -------------------------------------------------------------------------------- /Sources/JDAlertController/CustomViews/AnimatedView/AnimatedView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatedView.swift 3 | // DialogueView 4 | // 5 | // Created by Jawad Ali on 06/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public protocol AnimateLogo { 11 | var logoLayer: CAShapeLayer { get } 12 | func animateLayer(duration: Double, delay: Double, completion: AnimationCompletion) 13 | } 14 | 15 | @IBDesignable 16 | public class AnimatedView: UIView { 17 | 18 | private var animateLogo: AnimateLogo 19 | 20 | @IBInspectable public var animatioColor: UIColor = .red { 21 | didSet { 22 | outMostView.fillColor = animatioColor.withAlphaComponent(0.2).cgColor 23 | middleView.fillColor = animatioColor.withAlphaComponent(0.4).cgColor 24 | topMostView.fillColor = animatioColor.cgColor 25 | } 26 | } 27 | 28 | @IBInspectable public var lineWidth: CGFloat = 10.0 { 29 | didSet { 30 | animateLogo.logoLayer.lineWidth = lineWidth 31 | } 32 | } 33 | @IBInspectable public var strokeColor: UIColor = .white { 34 | didSet { 35 | animateLogo.logoLayer.strokeColor = strokeColor.cgColor 36 | } 37 | } 38 | 39 | private lazy var viewCenter = CGPoint(x: bounds.midX, y: bounds.midY) 40 | private lazy var outMostView: CAShapeLayer = { 41 | let view = CAShapeLayer() 42 | view.fillColor = animatioColor.withAlphaComponent(0.2).cgColor 43 | return view 44 | }() 45 | 46 | private lazy var middleView: CAShapeLayer = { 47 | let view = CAShapeLayer() 48 | view.fillColor = animatioColor.withAlphaComponent(0.4).cgColor 49 | return view 50 | }() 51 | 52 | private lazy var topMostView: CAShapeLayer = { 53 | let view = CAShapeLayer() 54 | view.fillColor = animatioColor.cgColor 55 | return view 56 | }() 57 | 58 | public init(logoLayer: AnimateLogo, color: UIColor) { 59 | animatioColor = color 60 | animateLogo = logoLayer 61 | super.init(frame: .zero) 62 | commonInit() 63 | } 64 | 65 | required init?(coder: NSCoder) { 66 | fatalError("init(coder:) has not been implemented") 67 | } 68 | 69 | private func commonInit() { 70 | updatePaths() 71 | setupView() 72 | 73 | animateLogo.logoLayer.lineWidth = lineWidth 74 | animateLogo.logoLayer.strokeColor = strokeColor.cgColor 75 | } 76 | 77 | public override func layoutSublayers(of layer: CALayer) { 78 | super.layoutSublayers(of: layer) 79 | viewCenter = CGPoint(x: bounds.midX, y: bounds.midY) 80 | updatePaths() 81 | 82 | } 83 | 84 | } 85 | private extension AnimatedView { 86 | func setupView() { 87 | layer.addSublayer(outMostView) 88 | layer.addSublayer(middleView) 89 | layer.addSublayer(topMostView) 90 | 91 | // animateLogo.wholeLayer.frame = bounds 92 | layer.addSublayer(animateLogo.logoLayer) 93 | } 94 | func updatePaths() { 95 | animateLogo.logoLayer.frame = bounds 96 | let path = UIBezierPath(arcCenter: viewCenter, radius: 1, startAngle: 0, endAngle: 360.degreesToRadians, clockwise: true).cgPath 97 | 98 | outMostView.path = path 99 | middleView.path = path 100 | topMostView.path = path 101 | 102 | } 103 | } 104 | 105 | public extension AnimatedView { 106 | 107 | func startAnimation(completion: AnimationCompletion) { 108 | let radius = min(bounds.maxX, bounds.maxY)/2 109 | 110 | let outMostViewPath = UIBezierPath(arcCenter: viewCenter, 111 | radius: radius, 112 | startAngle: 0, 113 | endAngle: 360.degreesToRadians, 114 | clockwise: true).cgPath 115 | 116 | let middleViewPath = UIBezierPath(arcCenter: viewCenter, 117 | radius: radius*0.8, 118 | startAngle: 0, 119 | endAngle: 360.degreesToRadians, 120 | clockwise: true).cgPath 121 | 122 | let topViewPath = UIBezierPath(arcCenter: viewCenter, 123 | radius: radius*0.6, 124 | startAngle: 0, 125 | endAngle: 360.degreesToRadians, 126 | clockwise: true).cgPath 127 | 128 | outMostView.animateShape(path: outMostViewPath, duration: 0.6, delay: 0) 129 | middleView.animateShape(path: middleViewPath, duration: 0.5, delay: 0.2) 130 | topMostView.animateShape(path: topViewPath, duration: 0.4, delay: 0.3) 131 | 132 | animateLogo.animateLayer(duration: 0.6, delay: 0.6, completion: completion) 133 | 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /Sources/JDAlertController/CustomViews/AnimatedView/ErrorAnimationLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorAnimationLayer.swift 3 | // DialogueView 4 | // 5 | // Created by Jawad Ali on 07/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public class ErrorAnimationLayer: CAShapeLayer, AnimateLogo { 11 | 12 | public override var strokeColor: CGColor? { 13 | didSet { 14 | crossLayerA.strokeColor = strokeColor 15 | crossLayerB.strokeColor = strokeColor 16 | } 17 | } 18 | 19 | public override var lineWidth: CGFloat { 20 | didSet { 21 | crossLayerA.lineWidth = lineWidth 22 | crossLayerB.lineWidth = lineWidth 23 | } 24 | } 25 | 26 | public override func removeAllAnimations() { 27 | super.removeAllAnimations() 28 | crossLayerA.removeAllAnimations() 29 | crossLayerB.removeAllAnimations() 30 | } 31 | 32 | private lazy var crossLayerA: CAShapeLayer = { 33 | let view = CAShapeLayer() 34 | view.fillColor = UIColor.clear.cgColor 35 | view.backgroundColor = UIColor.clear.cgColor 36 | view.strokeColor = UIColor.purple.cgColor 37 | view.lineWidth = lineWidth 38 | view.lineCap = .round 39 | return view 40 | }() 41 | 42 | private lazy var crossLayerB: CAShapeLayer = { 43 | let view = CAShapeLayer() 44 | view.fillColor = UIColor.clear.cgColor 45 | view.backgroundColor = UIColor.clear.cgColor 46 | view.strokeColor = UIColor.purple.cgColor 47 | view.lineWidth = lineWidth 48 | view.lineCap = .round 49 | return view 50 | }() 51 | 52 | public var logoLayer: CAShapeLayer { 53 | return self 54 | } 55 | 56 | public override var frame: CGRect { 57 | didSet { 58 | commonInit() 59 | } 60 | } 61 | 62 | public override init() { 63 | super.init() 64 | commonInit() 65 | } 66 | 67 | override init(layer: Any) { 68 | super.init(layer: layer) 69 | commonInit() 70 | } 71 | 72 | required init?(coder: NSCoder) { 73 | super.init(coder: coder) 74 | commonInit() 75 | } 76 | 77 | public override func layoutSublayers() { 78 | super.layoutSublayers() 79 | commonInit() 80 | } 81 | 82 | private func commonInit() { 83 | 84 | fillColor = UIColor.clear.cgColor 85 | backgroundColor = UIColor.clear.cgColor 86 | lineCap = .round 87 | 88 | addSublayer(crossLayerA) 89 | addSublayer(crossLayerB) 90 | 91 | crossLayerA.frame = bounds 92 | crossLayerB.frame = bounds 93 | 94 | crossLayerB.path = linePath() 95 | crossLayerA.path = linePath() 96 | } 97 | 98 | private func linePath() -> CGPath { 99 | let viewCenter = CGPoint(x: bounds.midX, y: bounds.midY) 100 | let path = UIBezierPath() 101 | path.move(to: viewCenter) 102 | path.addLine(to: viewCenter) 103 | return path.cgPath 104 | } 105 | 106 | } 107 | public extension ErrorAnimationLayer { 108 | func animateLayer(duration: Double, delay: Double, completion: AnimationCompletion) { 109 | let path = UIBezierPath() 110 | let minimumRadius = bounds.midX - bounds.maxX/6 111 | let maximumRadius = bounds.midX + bounds.maxX/6 112 | 113 | path.move(to: CGPoint(x: minimumRadius, y: bounds.midY)) 114 | path.addLine(to: CGPoint(x: maximumRadius, y: bounds.midY)) 115 | 116 | crossLayerB.animateShape(path: path.cgPath, duration: duration, delay: delay) 117 | crossLayerA.animateShape(path: path.cgPath, duration: duration, delay: delay) 118 | 119 | crossLayerB.rotateAnimationFrames(angels: [0, -165.degreesToRadians, -135.degreesToRadians], 120 | times: [0, 0.7, 1], 121 | duration: duration, 122 | delay: duration+delay, 123 | completion: nil) 124 | 125 | crossLayerA.rotateAnimationFrames(angels: [0, -75.degreesToRadians, -45.degreesToRadians], 126 | times: [0, 0.7, 1], 127 | duration: duration, 128 | delay: duration+delay, 129 | completion: completion) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Sources/JDAlertController/CustomViews/AnimatedView/InformationAnimationLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InformationAnimationLayer.swift 3 | // DialogueView 4 | // 5 | // Created by Jawad Ali on 07/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public class InformationAnimationLayer: CAShapeLayer, AnimateLogo { 11 | 12 | public override var strokeColor: CGColor? { 13 | didSet { 14 | crossLayerA.strokeColor = strokeColor 15 | crossLayerB.strokeColor = strokeColor 16 | } 17 | } 18 | 19 | public override var lineWidth: CGFloat { 20 | didSet { 21 | crossLayerA.lineWidth = lineWidth 22 | crossLayerB.lineWidth = lineWidth 23 | } 24 | } 25 | 26 | public override func removeAllAnimations() { 27 | super.removeAllAnimations() 28 | crossLayerA.removeAllAnimations() 29 | crossLayerB.removeAllAnimations() 30 | } 31 | 32 | private lazy var crossLayerA: CAShapeLayer = { 33 | let view = CAShapeLayer() 34 | view.fillColor = UIColor.white.cgColor 35 | view.backgroundColor = UIColor.clear.cgColor 36 | view.strokeColor = UIColor.clear.cgColor 37 | view.lineWidth = 5 38 | view.lineCap = .round 39 | return view 40 | }() 41 | 42 | private lazy var crossLayerB: CAShapeLayer = { 43 | let view = CAShapeLayer() 44 | view.fillColor = UIColor.white.cgColor 45 | view.backgroundColor = UIColor.clear.cgColor 46 | view.strokeColor = UIColor.clear.cgColor 47 | // view.lineWidth = 5 48 | view.lineCap = .round 49 | return view 50 | }() 51 | 52 | public var logoLayer: CAShapeLayer { 53 | return self 54 | } 55 | 56 | public override var frame: CGRect { 57 | didSet { 58 | commonInit() 59 | } 60 | } 61 | 62 | public override init() { 63 | super.init() 64 | commonInit() 65 | } 66 | 67 | override init(layer: Any) { 68 | super.init(layer: layer) 69 | commonInit() 70 | } 71 | 72 | required init?(coder: NSCoder) { 73 | super.init(coder: coder) 74 | commonInit() 75 | } 76 | 77 | public override func layoutSublayers() { 78 | super.layoutSublayers() 79 | commonInit() 80 | } 81 | 82 | private func commonInit() { 83 | 84 | fillColor = UIColor.clear.cgColor 85 | backgroundColor = UIColor.clear.cgColor 86 | lineCap = .round 87 | 88 | addSublayer(crossLayerA) 89 | // addSublayer(crossLayerB) 90 | 91 | let viewCenter = CGPoint(x: bounds.midX-5, y: bounds.midY-5) 92 | crossLayerA.frame = CGRect(origin: viewCenter, size: CGSize(width: 10, height: 10)) 93 | // crossLayerB.frame = .zero 94 | 95 | // crossLayerB.path = linePath() 96 | crossLayerA.path = linePath(layerBounds: crossLayerA.frame) 97 | } 98 | 99 | private func linePath(layerBounds: CGRect) -> CGPath { 100 | 101 | let radius = layerBounds.size.height/18 102 | 103 | let verticalControlOffset = radius 104 | let horizontalControllOffset = radius / 2 105 | let height = radius * 5 106 | 107 | let path = UIBezierPath() 108 | path.addArc(withCenter: CGPoint(x: layerBounds.midX, y: layerBounds.size.height/4 + radius), 109 | radius: radius, 110 | startAngle: .pi, 111 | endAngle: 2 * .pi, 112 | clockwise: true) 113 | 114 | var startPoint = path.currentPoint 115 | var endPoint = CGPoint(x: layerBounds.midX, y: layerBounds.size.height/4 + height) 116 | var cp1 = CGPoint(x: startPoint.x, y: startPoint.y + verticalControlOffset) 117 | var cp2 = CGPoint(x: endPoint.x + horizontalControllOffset, y: endPoint.y) 118 | 119 | path.addCurve(to: endPoint, 120 | controlPoint1: cp1, 121 | controlPoint2: cp2) 122 | 123 | startPoint = path.currentPoint 124 | endPoint = CGPoint(x: layerBounds.midX - radius, y: layerBounds.size.height/4 + radius) 125 | cp1 = CGPoint(x: startPoint.x - horizontalControllOffset, y: startPoint.y) 126 | cp2 = CGPoint(x: endPoint.x, y: endPoint.y + verticalControlOffset) 127 | 128 | path.addCurve(to: endPoint, 129 | controlPoint1: cp1, 130 | controlPoint2: cp2) 131 | 132 | return path.cgPath 133 | } 134 | 135 | } 136 | public extension InformationAnimationLayer { 137 | func animateLayer(duration: Double, delay: Double, completion: AnimationCompletion) { 138 | // let path = UIBezierPath() 139 | // let minimumRadius = bounds.midX - bounds.maxX/6 140 | // let maximumRadius = bounds.midX + bounds.maxX/6 141 | 142 | crossLayerA.boundsAnimationFrames(bounds: [crossLayerA.bounds, bounds], times: [0, 1], duration: 4, delay: 0) 143 | 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Sources/JDAlertController/CustomViews/AnimatedView/PhysicsPanHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhysicsPanHandler.swift 3 | // DialogueView 4 | // 5 | // Created by Jawad Ali on 14/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class PhysicsPanHandler { 12 | // MARK: - Animator 13 | private let animator: UIDynamicAnimator 14 | private var attachmentBehavior: UIAttachmentBehavior! 15 | private var gravityBehaviour: UIGravityBehavior! 16 | private var snapBehavior: UISnapBehavior! 17 | private var restingCenter: CGPoint? 18 | private var dismisscontroller: AnimationCompletion 19 | 20 | weak var messageView: UIView? 21 | weak var containerView: UIView? 22 | 23 | private var areaCenter: CGPoint { 24 | return CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY) 25 | } 26 | 27 | public init(messageView: UIView?, containerView: UIView?, dismiss: AnimationCompletion) { 28 | self.messageView = messageView 29 | self.containerView = containerView 30 | self.dismisscontroller = dismiss 31 | animator = UIDynamicAnimator(referenceView: containerView ?? UIView()) 32 | messageView?.addGestureRecognizer(pan) 33 | } 34 | 35 | public private(set) lazy var pan: UIPanGestureRecognizer = { 36 | let pan = UIPanGestureRecognizer() 37 | pan.addTarget(self, action: #selector(pan(_:))) 38 | return pan 39 | }() 40 | 41 | public func stop() { 42 | animator.removeAllBehaviors() 43 | } 44 | } 45 | 46 | extension PhysicsPanHandler { 47 | // MARK: - Handle Gesture 48 | @objc func pan(_ sender: UIPanGestureRecognizer) { 49 | guard let messageView = messageView, let containerView = containerView else { return } 50 | switch sender.state { 51 | case .began: 52 | restingCenter = messageView.center 53 | animator.removeAllBehaviors() 54 | let centerOffset = UIOffset(horizontal: sender.location(in: messageView).x - messageView.bounds.midX, 55 | vertical: sender.location(in: messageView).y - messageView.bounds.midY) 56 | attachmentBehavior = UIAttachmentBehavior(item: messageView, 57 | offsetFromCenter: centerOffset, attachedToAnchor: sender.location(in: containerView)) 58 | attachmentBehavior.frequency = 0 59 | animator.addBehavior(attachmentBehavior) 60 | 61 | case .changed: 62 | self.attachmentBehavior.anchorPoint = sender.location(in: containerView) 63 | 64 | case .ended, .cancelled: 65 | 66 | let translation = sender.translation(in: messageView) 67 | let velocity = sender.velocity(in: containerView) 68 | let speed = sqrt(pow(velocity.x, 2) + pow(velocity.y, 2)) 69 | 70 | if speed > 500 || translation.y > 250 { 71 | self.animator.removeBehavior(self.attachmentBehavior) 72 | animator.removeAllBehaviors() 73 | gravityBehaviour = UIGravityBehavior(items: [messageView]) 74 | gravityBehaviour.gravityDirection = CGVector.init(dx: 0, dy: 10) 75 | animator.addBehavior(gravityBehaviour) 76 | 77 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [unowned self] in 78 | if let completion = self.dismisscontroller { 79 | messageView.alpha = 0 80 | self.animator.removeAllBehaviors() 81 | completion() 82 | } 83 | } 84 | 85 | } else { 86 | UIView.animate(withDuration: 0.5, 87 | delay: 0, 88 | usingSpringWithDamping: 0.65, 89 | initialSpringVelocity: 0, 90 | options: .beginFromCurrentState, 91 | animations: { 92 | self.attachmentBehavior.anchorPoint = self.restingCenter ?? self.areaCenter 93 | self.animator.removeBehavior(self.attachmentBehavior) 94 | }) 95 | self.snapBehavior = UISnapBehavior(item: messageView, snapTo: restingCenter ?? areaCenter) 96 | self.animator.addBehavior(snapBehavior) 97 | } 98 | default: 99 | break 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /Sources/JDAlertController/CustomViews/AnimatedView/PopupType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopupType.swift 3 | // DialogueView 4 | // 5 | // Created by Jawad Ali on 14/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public enum PopupType { 11 | case success 12 | case error 13 | } 14 | extension PopupType { 15 | var color: UIColor { 16 | switch self { 17 | case .success: 18 | return #colorLiteral(red: 0.1944677234, green: 0.861061275, blue: 0.9629308581, alpha: 1) 19 | case .error: 20 | return #colorLiteral(red: 0.9998564124, green: 0.4897186756, blue: 0.5090405345, alpha: 1) 21 | } 22 | } 23 | 24 | var animationLayer: AnimateLogo { 25 | switch self { 26 | case .success: 27 | return SuccessAnimationLayer() 28 | case .error: 29 | return ErrorAnimationLayer() 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/JDAlertController/CustomViews/AnimatedView/SuccessAnimationLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SuccessAnimation.swift 3 | // DialogueView 4 | // 5 | // Created by Jawad Ali on 07/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public class SuccessAnimationLayer: CAShapeLayer, AnimateLogo { 11 | 12 | public func animateLayer(duration: Double, delay: Double, completion: AnimationCompletion) { 13 | logoLayer.strokeAnimation(duration: duration, delay: delay, completion: completion) 14 | } 15 | 16 | public var logoLayer: CAShapeLayer { 17 | return self 18 | } 19 | 20 | public override var frame: CGRect { 21 | didSet { 22 | commonInit() 23 | } 24 | } 25 | 26 | public override init() { 27 | super.init() 28 | commonInit() 29 | } 30 | 31 | override init(layer: Any) { 32 | super.init(layer: layer) 33 | commonInit() 34 | } 35 | 36 | required init?(coder: NSCoder) { 37 | super.init(coder: coder) 38 | commonInit() 39 | } 40 | 41 | public override func layoutSublayers() { 42 | super.layoutSublayers() 43 | commonInit() 44 | } 45 | 46 | private func commonInit() { 47 | 48 | fillColor = UIColor.clear.cgColor 49 | backgroundColor = UIColor.clear.cgColor 50 | strokeEnd = 0 51 | lineCap = .round 52 | 53 | let viewCenter = CGPoint(x: bounds.midX, y: bounds.midY) 54 | let minimum = min(bounds.midX, bounds.midY) 55 | path = CGRect(origin: viewCenter, size: CGSize(width: minimum, height: minimum)).drawCheckmarkPath() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/JDAlertController/CustomViews/PopupAction/PopupAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopupButton.swift 3 | // Vowpay 4 | // 5 | // Created by Jawad Ali on 28/01/2021. 6 | // Copyright © 2021 Vowpay. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public typealias Action = () -> Void 12 | 13 | extension PopupAction { 14 | 15 | public enum ButtonWidth { 16 | case full 17 | case margin 18 | case normal 19 | case custom(ratio: CGFloat) 20 | } 21 | 22 | var buttonSize: CGFloat { 23 | switch propotionalWidth { 24 | case .full: 25 | return 1.0 26 | case .margin: 27 | return 0.9 28 | case .normal: 29 | return 0.75 30 | case .custom(let width): 31 | return width 32 | } 33 | } 34 | 35 | public enum Style { 36 | 37 | case `default` 38 | case bold 39 | case destructive 40 | case round 41 | case classic(cornerRadius: CGFloat) 42 | case justText 43 | } 44 | 45 | private func applyStyle() { 46 | switch style { 47 | 48 | case .default: 49 | print("default 000") 50 | case .bold: 51 | titleLabel?.font = UIFont.boldSystemFont(ofSize: titleLabel?.font.pointSize ?? 16) 52 | case .destructive: 53 | self.setTitleColor(.red, for: .normal) 54 | case .round: 55 | borderView.isHidden = true 56 | self.layer.cornerRadius = bounds.midY 57 | self.layer.borderWidth = 2 58 | self.layer.borderColor = self.titleLabel?.textColor.cgColor 59 | titleLabel?.font = UIFont.boldSystemFont(ofSize: titleLabel?.font.pointSize ?? 16) 60 | addShadow() 61 | case .classic(let corner): 62 | addShadow() 63 | borderView.isHidden = true 64 | layer.cornerRadius = corner 65 | titleLabel?.font = UIFont.boldSystemFont(ofSize: titleLabel?.font.pointSize ?? 16) 66 | case .justText: 67 | borderView.isHidden = true 68 | backgroundColor = .clear 69 | } 70 | } 71 | } 72 | 73 | open class PopupAction: UIButton { 74 | // MARK: - Views 75 | private lazy var borderView: UIView = { 76 | let view = UIView() 77 | view.backgroundColor = .border 78 | view.translatesAutoresizingMaskIntoConstraints = false 79 | return view 80 | }() 81 | 82 | // MARK: - Properties 83 | private var action: Action? 84 | private var titleText: String? 85 | private(set) var style: Style 86 | private(set) var propotionalWidth: ButtonWidth 87 | 88 | open override func setTitleColor(_ color: UIColor?, for state: UIControl.State) { 89 | super.setTitleColor(color, for: state) 90 | 91 | layer.borderColor = color?.cgColor 92 | } 93 | open override func willMove(toSuperview newSuperview: UIView?) { 94 | borderView 95 | .alignEdgesWithSuperview([.left, .right, .top]) 96 | .height(constant: 1.5) 97 | } 98 | // MARK: - Initializer 99 | public init(title: String, style: Style = .default, propotionalWidth: ButtonWidth = .full, handler: Action? = nil) { 100 | self.style = style 101 | self.propotionalWidth = propotionalWidth 102 | super.init(frame: .zero) 103 | 104 | self.action = handler 105 | self.titleText = title 106 | translatesAutoresizingMaskIntoConstraints = false 107 | setupView() 108 | setupConstraints() 109 | } 110 | 111 | required public init?(coder: NSCoder) { 112 | fatalError("init(coder:) has not been implemented") 113 | } 114 | 115 | // MARK: Shadow 116 | func addShadow() { 117 | layer.shadowColor = UIColor.textDark.cgColor 118 | layer.shadowOffset = .zero 119 | layer.shadowRadius = 20 120 | layer.shadowOpacity = 0.2 121 | layer.masksToBounds = false 122 | backgroundColor = self.backgroundColor == .clear ? .white : backgroundColor 123 | } 124 | 125 | open override func layoutSubviews() { 126 | super.layoutSubviews() 127 | applyStyle() 128 | } 129 | } 130 | 131 | // MARK: - Setup & bind 132 | private extension PopupAction { 133 | func setupView() { 134 | self.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) 135 | 136 | setTitleColor(.primary, for: .normal) 137 | setTitle(titleText, for: .normal) 138 | addSubview(borderView) 139 | 140 | applyStyle() 141 | } 142 | 143 | @objc func buttonTapped() { 144 | if let action = action { 145 | action() 146 | } 147 | } 148 | 149 | func setupConstraints() { 150 | 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /Sources/JDAlertController/CustomViews/TextField/AppTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppTextField.swift 3 | // YAPKit 4 | // 5 | // Created by Zain on 23/07/2019. 6 | // Copyright © 2019 YAP. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | open class AppTextField: UITextField { 13 | // MARK: - SubViews 14 | 15 | fileprivate lazy var title: UILabel = { 16 | let label = UILabel() 17 | label.font = UIFont.systemFont(ofSize: 16) 18 | label.textAlignment = .left 19 | label.textColor = .black 20 | label.backgroundColor = .clear 21 | label.translatesAutoresizingMaskIntoConstraints = false 22 | return label 23 | }() 24 | 25 | private lazy var bottomBar: UIView = { 26 | let view = UIView() 27 | view.backgroundColor = .clear 28 | view.translatesAutoresizingMaskIntoConstraints = false 29 | return view 30 | }() 31 | 32 | fileprivate lazy var icon: UIImageView = { 33 | let imageView = UIImageView() 34 | imageView.isHidden = true 35 | imageView.contentMode = .scaleAspectFit 36 | imageView.translatesAutoresizingMaskIntoConstraints = false 37 | return imageView 38 | }() 39 | 40 | private lazy var stateImage: UIImageView = { 41 | let imageView = UIImageView() 42 | imageView.isHidden = true 43 | imageView.contentMode = .scaleAspectFit 44 | imageView.translatesAutoresizingMaskIntoConstraints = false 45 | return imageView 46 | }() 47 | 48 | private lazy var tempPlaceholder: UILabel = { 49 | let label = UILabel() 50 | label.textColor = placeholderColor 51 | label.text = placeholder 52 | label.textAlignment = textAlignment 53 | label.font = font 54 | label.alpha = 0 55 | return label 56 | }() 57 | 58 | // MAKR: - Control properties 59 | private let padding = UIEdgeInsets(top: 15, left: 25, bottom: 15, right: 25) 60 | 61 | public var showsIcon: Bool = false { 62 | didSet { 63 | icon.isHidden = !showsIcon 64 | } 65 | } 66 | 67 | public var titleText: String? { 68 | get { return title.text } 69 | set (newValue) { title.text = newValue } 70 | } 71 | 72 | public var iconImage: UIImage? { 73 | get { return icon.image } 74 | set (newValue) { 75 | icon.image = newValue 76 | showsIcon = newValue != nil 77 | } 78 | } 79 | 80 | open override var text: String? { 81 | didSet { 82 | guard text?.count ?? 0 > 0, !isFirstResponder else { return } 83 | title.textColor = .black 84 | tempPlaceholder.text = nil 85 | } 86 | } 87 | 88 | open override var attributedText: NSAttributedString? { 89 | didSet { 90 | guard text?.count ?? 0 > 0, !isFirstResponder else { return } 91 | title.textColor = .gray 92 | } 93 | } 94 | 95 | public var placeholderColor: UIColor = UIColor.gray { 96 | didSet { 97 | let placeholder = self.placeholder 98 | self.placeholder = placeholder 99 | } 100 | } 101 | 102 | public override var placeholder: String? { 103 | didSet { 104 | guard let `placeholder` = placeholder else { return } 105 | let attributedPlaceholder = NSMutableAttributedString(string: placeholder) 106 | attributedPlaceholder.addAttributes([.foregroundColor: self.placeholderColor], range: NSRange(location: 0, length: placeholder.count)) 107 | self.attributedPlaceholder = attributedPlaceholder 108 | } 109 | } 110 | 111 | public var animatesTitleOnEditingBegin: Bool = true 112 | 113 | // MARK: Initialization 114 | 115 | public override init(frame: CGRect) { 116 | super.init(frame: frame) 117 | commonInit() 118 | } 119 | 120 | public required init?(coder aDecoder: NSCoder) { 121 | super.init(coder: aDecoder) 122 | commonInit() 123 | } 124 | 125 | convenience public init(with placeholderText: String) { 126 | self.init(frame: .zero) 127 | placeholder = placeholderText 128 | } 129 | 130 | private func commonInit() { 131 | translatesAutoresizingMaskIntoConstraints = false 132 | setupViews() 133 | setupConstraints() 134 | tempPlaceholder.removeFromSuperview() 135 | } 136 | } 137 | 138 | // MARK: - Responder 139 | 140 | extension AppTextField { 141 | @discardableResult 142 | open override func becomeFirstResponder() -> Bool { 143 | return super.becomeFirstResponder() 144 | } 145 | 146 | @discardableResult 147 | open override func resignFirstResponder() -> Bool { 148 | return super.resignFirstResponder() 149 | } 150 | } 151 | 152 | // MARK: Drawing 153 | 154 | extension AppTextField { 155 | 156 | open override func textRect(forBounds bounds: CGRect) -> CGRect { 157 | return bounds.inset(by: padding) 158 | } 159 | 160 | open override func placeholderRect(forBounds bounds: CGRect) -> CGRect { 161 | return bounds.inset(by: padding) 162 | } 163 | 164 | open override func editingRect(forBounds bounds: CGRect) -> CGRect { 165 | return bounds.inset(by: padding) 166 | } 167 | 168 | private func rect(forBounds bounds: CGRect) -> CGRect { 169 | let originY = title.frame.origin.y + title.frame.size.height 170 | let height: CGFloat = 70 171 | return showsIcon ? CGRect(x: bounds.origin.x+40, 172 | y: originY, 173 | width: bounds.size.width - 70, 174 | height: height) : 175 | CGRect(x: bounds.origin.x + 30, 176 | y: originY, 177 | width: bounds.size.width - 30, 178 | height: height) 179 | } 180 | } 181 | 182 | // MARK: - View setup 183 | 184 | private extension AppTextField { 185 | func setupViews() { 186 | self.layer.cornerRadius = 10 187 | self.layer.borderWidth = 1.5 188 | 189 | self.layer.borderColor = UIColor.lightGray.cgColor 190 | 191 | addSubview(icon) 192 | addSubview(stateImage) 193 | addSubview(bottomBar) 194 | } 195 | 196 | func setupConstraints() { 197 | 198 | let iconConstraints = [ 199 | icon.leadingAnchor.constraint(equalTo: leadingAnchor), 200 | icon.topAnchor.constraint(equalTo: topAnchor, constant: 4), 201 | bottomBar.topAnchor.constraint(equalTo: icon.bottomAnchor, constant: 4), 202 | icon.widthAnchor.constraint(equalToConstant: 30) 203 | ] 204 | 205 | let stateImageConstraints = [ 206 | trailingAnchor.constraint(equalTo: stateImage.trailingAnchor), 207 | stateImage.centerYAnchor.constraint(equalTo: icon.centerYAnchor), 208 | stateImage.heightAnchor.constraint(equalToConstant: 20), 209 | stateImage.widthAnchor.constraint(equalToConstant: 20) 210 | ] 211 | 212 | let bottomBarConstraints = [ 213 | bottomBar.leadingAnchor.constraint(equalTo: leadingAnchor), 214 | trailingAnchor.constraint(equalTo: bottomBar.trailingAnchor), 215 | bottomAnchor.constraint(equalTo: bottomBar.bottomAnchor), 216 | bottomBar.heightAnchor.constraint(equalToConstant: 1) 217 | ] 218 | 219 | NSLayoutConstraint.activate(iconConstraints + stateImageConstraints + bottomBarConstraints) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Extension/CALayer+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Extension.swift 3 | // testi 4 | // 5 | // Created by Jawad Ali on 20/08/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public typealias AnimationCompletion = (() -> Void)? 11 | 12 | public extension CALayer { 13 | 14 | var areAnimationsEnabled: Bool { 15 | get { delegate == nil } 16 | set { delegate = newValue ? nil : CALayerAnimationsDisablingDelegate.shared } 17 | } 18 | 19 | private class CALayerAnimationsDisablingDelegate: NSObject, CALayerDelegate { 20 | static let shared = CALayerAnimationsDisablingDelegate() 21 | private let null = NSNull() 22 | 23 | func action(for layer: CALayer, forKey event: String) -> CAAction? { 24 | null 25 | } 26 | } 27 | 28 | func bringToFront() { 29 | guard let sLayer = superlayer else { 30 | return 31 | } 32 | removeFromSuperlayer() 33 | sLayer.insertSublayer(self, at: UInt32(sLayer.sublayers?.count ?? 0)) 34 | } 35 | 36 | func sendToBack() { 37 | guard let sLayer = superlayer else { 38 | return 39 | } 40 | removeFromSuperlayer() 41 | sLayer.insertSublayer(self, at: 0) 42 | } 43 | 44 | func animateGradientColors(from: [CGColor], toColor: [CGColor], duration: Double) { 45 | let animation = CABasicAnimation(keyPath: "colors") 46 | animation.fromValue = from 47 | animation.toValue = toColor 48 | animation.duration = duration 49 | animation.fillMode = .forwards 50 | animation.isRemovedOnCompletion = false 51 | 52 | // add the animation to the gradient 53 | self.add(animation, forKey: nil) 54 | 55 | } 56 | 57 | func strokeAnimation(duration: Double, delay: Double, completion: AnimationCompletion) { 58 | CATransaction.begin() 59 | let animation = CABasicAnimation(keyPath: "strokeEnd") 60 | animation.fromValue = 0 61 | animation.beginTime = CACurrentMediaTime() + delay 62 | animation.toValue = 1 63 | animation.duration = duration 64 | animation.fillMode = .forwards 65 | animation.isRemovedOnCompletion = false 66 | CATransaction.setCompletionBlock(completion) 67 | self.add(animation, forKey: "line") 68 | CATransaction.commit() 69 | } 70 | 71 | func boundsAnimationFrames(bounds: [CGRect], times: [Double], duration: Double, delay: Double ) { 72 | let rotationAnimation = CAKeyframeAnimation(keyPath: "bounds") 73 | rotationAnimation.beginTime = CACurrentMediaTime() + delay 74 | rotationAnimation.duration = duration 75 | rotationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) 76 | rotationAnimation.fillMode = .forwards 77 | rotationAnimation.isRemovedOnCompletion = false 78 | rotationAnimation.values = bounds 79 | rotationAnimation.keyTimes = times.map { NSNumber(value: $0) } 80 | self.add(rotationAnimation, forKey: nil) 81 | } 82 | 83 | func rotateAnimationFrames(angels: [CGFloat], times: [Double], duration: Double, delay: Double, completion: AnimationCompletion) { 84 | CATransaction.begin() 85 | let rotationAnimation = CAKeyframeAnimation(keyPath: "transform.rotation.z") 86 | rotationAnimation.beginTime = CACurrentMediaTime() + delay 87 | rotationAnimation.duration = duration 88 | rotationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) 89 | rotationAnimation.fillMode = .forwards 90 | rotationAnimation.isRemovedOnCompletion = false 91 | rotationAnimation.values = angels 92 | CATransaction.setCompletionBlock(completion) 93 | rotationAnimation.keyTimes = times.map { NSNumber(value: $0) } 94 | self.add(rotationAnimation, forKey: nil) 95 | CATransaction.commit() 96 | } 97 | 98 | func rotateAnimation(angal: CGFloat, duration: Double, repeatAnimation: Bool = false, delay: Double) { 99 | 100 | let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") 101 | rotationAnimation.fromValue = 0 102 | rotationAnimation.toValue = angal 103 | rotationAnimation.beginTime = CACurrentMediaTime() + delay 104 | rotationAnimation.duration = duration 105 | rotationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) 106 | rotationAnimation.fillMode = .forwards 107 | rotationAnimation.isRemovedOnCompletion = false 108 | rotationAnimation.repeatCount = repeatAnimation ? .infinity : 0 109 | self.add(rotationAnimation, forKey: "rotation") 110 | } 111 | 112 | func removeRotationAnimation() { 113 | self.removeAnimation(forKey: "rotation") 114 | } 115 | 116 | func animateShape(path: CGPath, duration: Double, delay: Double) { 117 | 118 | let animation = CABasicAnimation(keyPath: "path") 119 | animation.duration = duration 120 | animation.beginTime = CACurrentMediaTime() + delay 121 | animation.toValue = path 122 | animation.timingFunction = CAMediaTimingFunction(name: .easeOut) 123 | animation.isRemovedOnCompletion = false 124 | animation.fillMode = .forwards 125 | 126 | self.add(animation, forKey: nil) 127 | 128 | } 129 | 130 | func doMask(by imageMask: UIImage) { 131 | let maskLayer = CAShapeLayer() 132 | maskLayer.bounds = CGRect(x: 0, y: 0, width: imageMask.size.width, height: imageMask.size.height) 133 | bounds = maskLayer.bounds 134 | maskLayer.contents = imageMask.cgImage 135 | maskLayer.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height) 136 | mask = maskLayer 137 | } 138 | 139 | func toImage() -> UIImage? { 140 | UIGraphicsBeginImageContextWithOptions(bounds.size, 141 | isOpaque, 142 | UIScreen.main.scale) 143 | guard let context = UIGraphicsGetCurrentContext() else { 144 | UIGraphicsEndImageContext() 145 | return nil 146 | } 147 | render(in: context) 148 | let image = UIGraphicsGetImageFromCurrentImageContext() 149 | UIGraphicsEndImageContext() 150 | return image 151 | } 152 | 153 | } 154 | extension BinaryInteger { 155 | var degreesToRadians: CGFloat { CGFloat(self) * .pi / 180 } 156 | } 157 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Extension/CGRect+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPath+Extension.swift 3 | // Switches 4 | // 5 | // Created by Jawad Ali on 06/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public extension CGRect { 11 | func drawCheckmarkPath() -> CGPath { 12 | let bezierPath = UIBezierPath() 13 | 14 | let radius = self.maxX/10 15 | let center = CGPoint(x: self.midX, y: self.midY ) 16 | 17 | let x11 = center.x - radius*2 + (radius ) * cos(-45.degreesToRadians) 18 | let y11 = center.y - radius*2.3 + (radius ) * sin(-45.degreesToRadians) 19 | 20 | let x21 = center.x - radius*2 + (radius ) * cos(135.degreesToRadians) 21 | let y21 = center.y - radius*2.3 + (radius ) * sin(135.degreesToRadians) 22 | 23 | // bezierPath.move(to: CGPoint(x: x1, y: y1)) 24 | 25 | let x31 = center.x - radius*2 + (radius + radius * 0.5 ) * cos(-175.degreesToRadians) 26 | let y31 = center.y - radius*2.3 + (radius + radius * 0.5 ) * sin(-175.degreesToRadians) 27 | 28 | bezierPath.move(to: CGPoint(x: x31, y: y31)) 29 | bezierPath.addLine(to: CGPoint(x: x21, y: y21 )) 30 | bezierPath.addLine(to: CGPoint(x: x11, y: y11)) 31 | 32 | bezierPath.lineJoinStyle = .round 33 | 34 | return bezierPath.cgPath 35 | } 36 | 37 | func drawCrossPath() -> CGPath { 38 | let bezierPath = UIBezierPath() 39 | 40 | let radius = self.maxX/5 41 | let center = CGPoint(x: self.midX, y: self.midY ) 42 | 43 | let x11 = center.x + (radius ) * cos(-45.degreesToRadians) 44 | let y11 = center.y + (radius ) * sin(-45.degreesToRadians) 45 | 46 | let x21 = center.x + (radius ) * cos(135.degreesToRadians) 47 | let y21 = center.y + (radius ) * sin(135.degreesToRadians) 48 | 49 | let x31 = center.x + (radius ) * cos(-135.degreesToRadians) 50 | let y31 = center.y + (radius ) * sin(-135.degreesToRadians) 51 | 52 | let x41 = center.x + (radius ) * cos(45.degreesToRadians) 53 | let y41 = center.y + (radius ) * sin(45.degreesToRadians) 54 | 55 | bezierPath.move(to: CGPoint(x: x41, y: y41)) 56 | bezierPath.addLine(to: CGPoint(x: x31, y: y31)) 57 | 58 | bezierPath.move(to: CGPoint(x: x11, y: y11)) 59 | bezierPath.addLine(to: CGPoint(x: x21, y: y21)) 60 | 61 | return bezierPath.cgPath 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Extension/UIColor+Extenstions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Extenstions.swift 3 | // VowpayCore 4 | // 5 | // Created by Zain on 07/08/2020. 6 | // Copyright © 2020 Vowpay. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UIColor { 13 | static var primary: UIColor { #colorLiteral(red: 0.3665855527, green: 0.2125249207, blue: 0.9989208579, alpha: 1) } 14 | static var primaryBlue: UIColor { #colorLiteral(red: 0.368627451, green: 0.2156862745, blue: 1, alpha: 1) } 15 | 16 | static var background: UIColor { #colorLiteral(red: 0.9647058824, green: 0.9725490196, blue: 0.9960784314, alpha: 1) } 17 | static var border: UIColor { #colorLiteral(red: 0.9098039216, green: 0.9294117647, blue: 0.9921568627, alpha: 1) } 18 | static var icon: UIColor { #colorLiteral(red: 0.662745098, green: 0.6666666667, blue: 0.8745098039, alpha: 1) } 19 | 20 | static var textDark: UIColor { #colorLiteral(red: 0.137254902, green: 0.1568627451, blue: 0.2666666667, alpha: 1) } 21 | static var textLight: UIColor { #colorLiteral(red: 0.4862745098, green: 0.4823529412, blue: 0.6941176471, alpha: 1) } 22 | 23 | static var success: UIColor { #colorLiteral(red: 0.2470588235, green: 0.8588235294, blue: 0.4666666667, alpha: 1) } 24 | static var warning: UIColor { #colorLiteral(red: 1, green: 0.5647058824, blue: 0.2509803922, alpha: 1) } 25 | static var error: UIColor { #colorLiteral(red: 1, green: 0.2156862745, blue: 0.3568627451, alpha: 1) } 26 | 27 | static var placeHolder: UIColor { #colorLiteral(red: 0.4039215686, green: 0.4078431373, blue: 0.6039215686, alpha: 1) } 28 | static var borderDark: UIColor { #colorLiteral(red: 0.831372549, green: 0.8549019608, blue: 0.9176470588, alpha: 1) } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Extension/UIFont+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+Extension.swift 3 | // VowpayCore 4 | // 5 | // Created by Zain on 07/08/2020. 6 | // Copyright © 2020 Vowpay. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UIFont { 13 | static var titleSmall: UIFont { .systemFont(ofSize: 16, weight: .semibold) } 14 | static var titleLarge: UIFont { .systemFont(ofSize: 36, weight: .bold) } 15 | 16 | static var highlighted: UIFont { .systemFont(ofSize: 24, weight: .medium) } 17 | static var subtitle: UIFont { .systemFont(ofSize: 20, weight: .semibold) } 18 | 19 | static var bodyRegular: UIFont { .systemFont(ofSize: 16, weight: .regular) } 20 | static var bodyMedium: UIFont { .systemFont(ofSize: 16, weight: .medium) } 21 | static var bodySemibold: UIFont { .systemFont(ofSize: 16, weight: .semibold) } 22 | static var bodyBold: UIFont { .systemFont(ofSize: 16, weight: .bold) } 23 | 24 | static var buttonText: UIFont { .systemFont(ofSize: 16, weight: .medium) } 25 | static var headline: UIFont { .systemFont(ofSize: 14, weight: .medium) } 26 | 27 | static var captionLarge: UIFont { .systemFont(ofSize: 12, weight: .medium) } 28 | static var captionSmall: UIFont { .systemFont(ofSize: 11, weight: .medium) } 29 | 30 | static var tabBar: UIFont { .systemFont(ofSize: 10, weight: .medium) } 31 | 32 | static var initialFont: UIFont {. systemFont(ofSize: 50, weight: .medium)} 33 | } 34 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Extension/UIView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Extensions.swift 3 | // VowpayCore 4 | // 5 | // Created by Zain on 05/08/2020. 6 | // Copyright © 2020 Vowpay. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | // MARK: Inspectables 13 | public extension UIView { 14 | 15 | @IBInspectable 16 | var cornerRadius: CGFloat { 17 | get { 18 | return layer.cornerRadius 19 | } 20 | set { 21 | layer.cornerRadius = newValue 22 | } 23 | } 24 | 25 | // @IBInspectable 26 | // var borderWidth: CGFloat { 27 | // get { 28 | // return layer.borderWidth 29 | // } 30 | // set { 31 | // layer.borderWidth = newValue 32 | // } 33 | // } 34 | 35 | // @IBInspectable 36 | // var borderColor: UIColor? { 37 | // get { 38 | // if let color = layer.borderColor { 39 | // return UIColor(cgColor: color) 40 | // } 41 | // return nil 42 | // } 43 | // set { 44 | // if let color = newValue { 45 | // layer.borderColor = color.cgColor 46 | // } else { 47 | // layer.borderColor = nil 48 | // } 49 | // } 50 | // } 51 | 52 | @IBInspectable 53 | var shadowRadius: CGFloat { 54 | get { 55 | return layer.shadowRadius 56 | } 57 | set { 58 | layer.shadowRadius = newValue 59 | } 60 | } 61 | 62 | @IBInspectable 63 | var shadowOpacity: Float { 64 | get { 65 | return layer.shadowOpacity 66 | } 67 | set { 68 | layer.shadowOpacity = newValue 69 | } 70 | } 71 | 72 | @IBInspectable 73 | var shadowOffset: CGSize { 74 | get { 75 | return layer.shadowOffset 76 | } 77 | set { 78 | layer.shadowOffset = newValue 79 | } 80 | } 81 | 82 | @IBInspectable 83 | var shadowColor: UIColor? { 84 | get { 85 | if let color = layer.shadowColor { 86 | return UIColor(cgColor: color) 87 | } 88 | return nil 89 | } 90 | set { 91 | if let color = newValue { 92 | layer.shadowColor = color.cgColor 93 | } else { 94 | layer.shadowColor = nil 95 | } 96 | } 97 | } 98 | } 99 | 100 | // MARK: Redering 101 | 102 | public extension UIView { 103 | 104 | /// Must be called after layouting 105 | func roundView() { 106 | layer.cornerRadius = bounds.width/2 107 | layer.masksToBounds = true 108 | } 109 | } 110 | 111 | public extension UIView { 112 | @discardableResult 113 | func takeScreenshot(rect: CGRect, shouldSave: Bool) -> UIImage? { 114 | // creates new image context with same size as view 115 | // UIGraphicsBeginImageContextWithOptions (scale=0.0) for high res capture 116 | UIGraphicsBeginImageContextWithOptions(rect.size, true, 0.0) 117 | 118 | // renders the view's layer into the current graphics context 119 | if let context = UIGraphicsGetCurrentContext() { layer.render(in: context) } 120 | 121 | // creates UIImage from what was drawn into graphics context 122 | let screenshot: UIImage? = UIGraphicsGetImageFromCurrentImageContext() 123 | 124 | // clean up newly created context and return screenshot 125 | UIGraphicsEndImageContext() 126 | 127 | if let image = screenshot, shouldSave { 128 | UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) 129 | } 130 | 131 | return screenshot 132 | } 133 | } 134 | public extension UIView { 135 | 136 | func removeAllConstraints() { 137 | var isuperview = self.superview 138 | 139 | while let superview = isuperview { 140 | for constraint in superview.constraints { 141 | 142 | if let first = constraint.firstItem as? UIView, first == self { 143 | superview.removeConstraint(constraint) 144 | } 145 | 146 | if let second = constraint.secondItem as? UIView, second == self { 147 | superview.removeConstraint(constraint) 148 | } 149 | } 150 | 151 | isuperview = superview.superview 152 | } 153 | 154 | self.removeConstraints(self.constraints) 155 | self.translatesAutoresizingMaskIntoConstraints = true 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Factories/UIButtonFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButtonFactory.swift 3 | // YAPKit 4 | // 5 | // Created by Wajahat Hassan on 31/07/2019. 6 | // Copyright © 2019 YAP. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public class UIButtonFactory { 11 | 12 | public class func createButton(title: String = String(), backgroundColor: UIColor = .clear, textColor: UIColor = .textDark) -> UIButton { 13 | let button = UIButton() 14 | button.setTitle(title, for: .normal) 15 | button.setTitleColor(textColor, for: .normal) 16 | button.backgroundColor = backgroundColor 17 | button.translatesAutoresizingMaskIntoConstraints = false 18 | return button 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Factories/UIImageViewFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageViewFactory.swift 3 | // MobileExercise 4 | // 5 | // Created by Jawad Ali on 23/09/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public class UIImageViewFactory { 11 | public class func createBackgroundImageView(mode: UIImageView.ContentMode = .scaleAspectFill, 12 | image: UIImage) -> UIImageView { 13 | let imageView = UIImageView() 14 | imageView.contentMode = mode 15 | imageView.translatesAutoresizingMaskIntoConstraints = false 16 | imageView.image = image 17 | return imageView 18 | } 19 | public class func createImageView(mode: UIImageView.ContentMode = .scaleAspectFill, 20 | image: UIImage? = nil, 21 | tintColor: UIColor? = .clear) -> UIImageView { 22 | let imageView = UIImageView() 23 | imageView.contentMode = mode 24 | imageView.tintColor = tintColor 25 | imageView.translatesAutoresizingMaskIntoConstraints = false 26 | imageView.image = image 27 | return imageView 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Factories/UILabelFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabelFactory.swift 3 | // MobileExercise 4 | // 5 | // Created by Jawad Ali on 23/09/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public class UILabelFactory { 11 | 12 | public class func createUILabel(with color: UIColor = .black, 13 | font: UIFont = UIFont.systemFont(ofSize: 17), 14 | alignment: NSTextAlignment = .left, 15 | numberOfLines: Int = 1, 16 | lineBreakMode: NSLineBreakMode = .byTruncatingTail, 17 | text: String? = nil, 18 | alpha: CGFloat = 1.0) -> T { 19 | let label = T() 20 | label.font = font 21 | label.textColor = color 22 | label.textAlignment = alignment 23 | label.numberOfLines = numberOfLines 24 | label.lineBreakMode = lineBreakMode 25 | label.text = text 26 | label.alpha = alpha 27 | label.translatesAutoresizingMaskIntoConstraints = false 28 | return label 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Factories/UIStackViewFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIStackViewFactory.swift 3 | // MobileExercise 4 | // 5 | // Created by Jawad Ali on 23/09/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class UIStackViewFactory { 12 | public class func createStackView(with axis: NSLayoutConstraint.Axis, 13 | alignment: UIStackView.Alignment = .leading, 14 | distribution: UIStackView.Distribution = .fillProportionally, 15 | spacing: CGFloat = 0, 16 | arrangedSubviews: [UIView]? = nil) -> UIStackView { 17 | let arrangedSubviews = arrangedSubviews ?? [] 18 | let stackView = UIStackView(arrangedSubviews: arrangedSubviews) 19 | stackView.axis = axis 20 | stackView.alignment = alignment 21 | stackView.distribution = distribution 22 | stackView.spacing = spacing 23 | stackView.translatesAutoresizingMaskIntoConstraints = false 24 | return stackView 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/JDAlertController/JDAlertController.h: -------------------------------------------------------------------------------- 1 | // 2 | // JDAlertController.h 3 | // JDAlertController 4 | // 5 | // Created by Jawad Ali on 03/03/2021. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for JDAlertController. 11 | FOUNDATION_EXPORT double JDAlertControllerVersionNumber; 12 | 13 | //! Project version string for JDAlertController. 14 | FOUNDATION_EXPORT const unsigned char JDAlertControllerVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Layouts/LayoutAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutAttributes.swift 3 | // MobileExercise 4 | // 5 | // Created by Jawad Ali on 23/09/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | enum LayoutAxis { 13 | case vertical 14 | case horizontal 15 | case dimensions 16 | } 17 | 18 | public enum LayoutEdge { 19 | case left 20 | case right 21 | case top 22 | case bottom 23 | case bottomAvoidingKeyboard 24 | case safeAreaLeft 25 | case safeAreaRight 26 | case safeAreaTop 27 | case safeAreaBottom 28 | case safeAreaBottomAvoidingKeyboard 29 | case centerX 30 | case centerY 31 | case height 32 | case width 33 | } 34 | 35 | extension LayoutEdge { 36 | var axis: LayoutAxis { 37 | switch self { 38 | case .left, .right, .centerX, .safeAreaLeft, .safeAreaRight: 39 | return .horizontal 40 | case .bottom, .top, .centerY, .safeAreaTop, .safeAreaBottom, .bottomAvoidingKeyboard, .safeAreaBottomAvoidingKeyboard: 41 | return .vertical 42 | case .height, .width: 43 | return .dimensions 44 | 45 | } 46 | } 47 | 48 | var safeAreaEdge: LayoutEdge { 49 | switch self { 50 | case .left, .safeAreaLeft: 51 | return .safeAreaLeft 52 | case .top, .safeAreaTop: 53 | return .safeAreaTop 54 | case .right, .safeAreaRight: 55 | return .safeAreaRight 56 | case .bottom, .safeAreaBottom, .bottomAvoidingKeyboard, .safeAreaBottomAvoidingKeyboard: 57 | return .safeAreaBottom 58 | default: 59 | return .safeAreaLeft 60 | } 61 | } 62 | } 63 | 64 | public enum LayoutConstantModifier { 65 | case equalTo 66 | case lessThanOrEqualTo 67 | case greaterThanOrEqualTo 68 | } 69 | 70 | internal extension UIView { 71 | func horizontalAnchor(_ edge: LayoutEdge) -> NSLayoutXAxisAnchor { 72 | switch edge { 73 | case .left: 74 | return leadingAnchor 75 | case .right: 76 | return trailingAnchor 77 | case .centerX: 78 | return centerXAnchor 79 | case .safeAreaLeft: 80 | return safeAreaLayoutGuide.leadingAnchor 81 | case .safeAreaRight: 82 | return safeAreaLayoutGuide.trailingAnchor 83 | default: 84 | return leadingAnchor 85 | } 86 | } 87 | 88 | func verticalAnchor(_ edge: LayoutEdge) -> NSLayoutYAxisAnchor { 89 | switch edge { 90 | case .top: 91 | return topAnchor 92 | case .bottom, .bottomAvoidingKeyboard: 93 | return bottomAnchor 94 | case .centerY: 95 | return centerYAnchor 96 | case .safeAreaTop: 97 | return safeAreaLayoutGuide.topAnchor 98 | case .safeAreaBottom, .safeAreaBottomAvoidingKeyboard: 99 | return safeAreaLayoutGuide.bottomAnchor 100 | default: 101 | return topAnchor 102 | } 103 | } 104 | 105 | func dimensionAnchor(_ edge: LayoutEdge) -> NSLayoutDimension { 106 | switch edge { 107 | case .width: 108 | return widthAnchor 109 | case .height: 110 | return heightAnchor 111 | default: 112 | return widthAnchor 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Sources/JDAlertController/PresentationManager/DimmedPopupPresentationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DimmedPopupPresentationController.swift 3 | // DropDown 4 | // 5 | // Created by Jawad Ali on 27/11/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public final class DimmedPopupPresentationController: UIPresentationController { 11 | fileprivate var dimmingView: UIView = { 12 | let view = UIView() 13 | view.translatesAutoresizingMaskIntoConstraints = false 14 | view.backgroundColor = UIColor(white: 0.0, alpha: 0.3) 15 | view.alpha = 0.0 16 | view.isUserInteractionEnabled = true 17 | return view 18 | }() 19 | 20 | public override var frameOfPresentedViewInContainerView: CGRect { 21 | var frame: CGRect = .zero 22 | 23 | guard let containerView = self.containerView else {return frame} 24 | 25 | frame.size = size(forChildContentContainer: presentedViewController, 26 | withParentContainerSize: containerView.bounds.size) 27 | 28 | frame.origin = .zero 29 | return frame 30 | } 31 | 32 | override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) { 33 | 34 | super.init(presentedViewController: presentedViewController, presenting: presentingViewController) 35 | setupDimmingView() 36 | } 37 | 38 | public override func presentationTransitionWillBegin() { 39 | containerView?.insertSubview(dimmingView, at: 0) 40 | dimmingView 41 | .alignAllEdgesWithSuperview() 42 | 43 | guard let coordinator = presentedViewController.transitionCoordinator else { 44 | dimmingView.alpha = 1.0 45 | return 46 | } 47 | 48 | coordinator.animate(alongsideTransition: { _ in 49 | self.dimmingView.alpha = 1.0 50 | }) 51 | } 52 | 53 | public override func dismissalTransitionWillBegin() { 54 | guard let coordinator = presentedViewController.transitionCoordinator else { 55 | dimmingView.alpha = 0.0 56 | return 57 | } 58 | 59 | coordinator.animate(alongsideTransition: { _ in 60 | self.dimmingView.alpha = 0.0 61 | }) 62 | } 63 | 64 | public override func containerViewWillLayoutSubviews() { 65 | presentedView?.frame = frameOfPresentedViewInContainerView 66 | } 67 | 68 | public override func size(forChildContentContainer container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize { 69 | return presentedViewController.preferredContentSize 70 | } 71 | 72 | public override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) { 73 | super.preferredContentSizeDidChange(forChildContentContainer: container) 74 | 75 | guard let containerView = containerView else { 76 | return 77 | } 78 | 79 | UIView.animate(withDuration: 1.0, animations: { 80 | containerView.setNeedsLayout() 81 | containerView.layoutIfNeeded() 82 | }) 83 | } 84 | 85 | } 86 | 87 | // MARK: - dimming view 88 | private extension DimmedPopupPresentationController { 89 | func setupDimmingView() { 90 | dimmingView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingViewTapped(_:)))) 91 | } 92 | 93 | @objc func dimmingViewTapped(_ gestureRecognizer: UITapGestureRecognizer) { 94 | // if vc conforms to DimmingViewTappedProtocol, it can receive tap events 95 | if let vcontroller = presentedViewController as? DimmingViewTappedProtocol { 96 | vcontroller.dimmingViewTapped() 97 | } 98 | } 99 | 100 | } 101 | 102 | /// conform to this protocol to receive dimming view tap events 103 | public protocol DimmingViewTappedProtocol: class { 104 | func dimmingViewTapped() 105 | } 106 | extension DimmingViewTappedProtocol where Self: UIViewController { 107 | func dimmingViewTapped() { 108 | dismiss(animated: true, completion: nil) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/JDAlertController/PresentationManager/DragingPresentationAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DragingPresentationAnimator.swift 3 | // DialogueView 4 | // 5 | // Created by Jawad Ali on 21/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | final class DragingPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning { 12 | //MARK:- properties 13 | let isPresentation: Bool 14 | 15 | //MARK: - Animator 16 | var animator = UIDynamicAnimator() 17 | var attachmentBehavior : UIAttachmentBehavior! 18 | var gravityBehaviour : UIGravityBehavior! 19 | var snapBehavior : UISnapBehavior! 20 | 21 | //MARK:- initializer 22 | init(isPresentation: Bool) { 23 | self.isPresentation = isPresentation 24 | super.init() 25 | } 26 | 27 | private var areaCenter: CGPoint { 28 | return CGPoint(x:UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY) 29 | } 30 | 31 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 32 | return 0.6 33 | } 34 | 35 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 36 | let key = isPresentation ? UITransitionContextViewControllerKey.to : UITransitionContextViewControllerKey.from 37 | let controller = transitionContext.viewController(forKey: key)! 38 | let animationDuration = transitionDuration(using: transitionContext) 39 | 40 | 41 | 42 | 43 | if isPresentation { 44 | controller.view.transform = CGAffineTransform(scaleX: 0.4, y: 0.4) 45 | controller.view.alpha = 0.1 46 | transitionContext.containerView.addSubview(controller.view) 47 | 48 | controller.view.center = CGPoint( x: areaCenter.x, y: areaCenter.y*0.2 ) 49 | 50 | // DispatchQueue.main.asyncAfter(deadline: .now() + 0.21) { 51 | UIView.animate(withDuration:animationDuration){ 52 | controller.view.alpha = 1 53 | } 54 | 55 | 56 | UIView.animate(withDuration:animationDuration , animations: { 57 | 58 | controller.view.translatesAutoresizingMaskIntoConstraints = false 59 | controller.view 60 | .centerInSuperView() 61 | .width(constant: controller.preferredContentSize.width) 62 | 63 | self.snapBehavior = UISnapBehavior(item: controller.view, snapTo: self.areaCenter) 64 | self.animator.addBehavior(self.snapBehavior) 65 | controller.view.transform = .identity 66 | 67 | 68 | }) { (finished) in 69 | transitionContext.completeTransition(true) 70 | } 71 | // } 72 | } else { 73 | UIView.animate( 74 | withDuration: animationDuration/2, 75 | delay: 0, 76 | usingSpringWithDamping: 1, 77 | initialSpringVelocity: 1, 78 | 79 | animations: { 80 | self.gravityBehaviour = UIGravityBehavior(items: [controller.view]) 81 | self.gravityBehaviour.gravityDirection = CGVector.init(dx: 0, dy: 10) 82 | controller.view.alpha = 0.6 83 | self.animator.addBehavior(self.gravityBehaviour) 84 | 85 | // controller.view.alpha = 0 86 | // self.snapBehavior = UISnapBehavior(item: controller.view, snapTo: CGPoint( x: self.areaCenter.x, y: self.areaCenter.y*2 )) 87 | // self.animator.addBehavior(self.snapBehavior) 88 | // controller.view.transform = .init(rotationAngle: CGFloat(self.rad2deg(60))) 89 | 90 | 91 | }, completion: { finished in 92 | transitionContext.completeTransition(finished) 93 | }) 94 | } 95 | } 96 | } 97 | extension DragingPresentationAnimator { 98 | func rad2deg(_ number: Double) -> Double { 99 | return number * 180 / .pi 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/JDAlertController/PresentationManager/OpacityPresentationAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpacityPresentationAnimator.swift 3 | // DropDown 4 | // 5 | // Created by Jawad Ali on 08/01/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | final class OpacityPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning { 11 | // MARK: - properties 12 | let isPresentation: Bool 13 | 14 | // MARK: - initializer 15 | init(isPresentation: Bool) { 16 | self.isPresentation = isPresentation 17 | super.init() 18 | } 19 | 20 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 21 | return 0.5 22 | } 23 | 24 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 25 | let key = isPresentation ? UITransitionContextViewControllerKey.to : UITransitionContextViewControllerKey.from 26 | guard let controller = transitionContext.viewController(forKey: key) else {return assertionFailure("No Controller")} 27 | let animationDuration = transitionDuration(using: transitionContext) 28 | 29 | controller.view.translatesAutoresizingMaskIntoConstraints = false 30 | if isPresentation { 31 | transitionContext.containerView.addSubview(controller.view) 32 | controller.view 33 | .centerInSuperView() 34 | .width(constant: controller.preferredContentSize.width) 35 | } 36 | 37 | let view = controller.view 38 | view?.alpha = isPresentation ? 0.25 : view?.alpha ?? 1 39 | view?.transform = isPresentation ? CGAffineTransform(scaleX: 0.6, y: 0.6) : view?.transform ?? .identity 40 | CATransaction.begin() 41 | CATransaction.setCompletionBlock { 42 | transitionContext.completeTransition(true) 43 | } 44 | UIView.animate(withDuration: animationDuration, 45 | delay: 0, 46 | usingSpringWithDamping: 0.6, 47 | initialSpringVelocity: 0, 48 | options: [.beginFromCurrentState, 49 | .curveLinear, 50 | .allowUserInteraction], 51 | animations: { 52 | view?.transform = self.isPresentation ? .identity : CGAffineTransform(scaleX: 0.6, y: 0.6) 53 | }, 54 | completion: nil) 55 | UIView.animate(withDuration: 0.3 * animationDuration, 56 | delay: 0, 57 | options: [.beginFromCurrentState, 58 | .curveLinear, 59 | .allowUserInteraction], 60 | animations: { 61 | view?.alpha = self.isPresentation ? 1 : 0 62 | }, 63 | completion: nil) 64 | CATransaction.commit() 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/JDAlertController/PresentationManager/PopupPresentationManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopupPresentationManager.swift 3 | // DropDown 4 | // 5 | // Created by Jawad Ali on 27/11/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | open class PopupPresentationManager: NSObject, UIViewControllerTransitioningDelegate { 11 | 12 | // MARK: - properties 13 | public var type: PopupStyle = .alert 14 | 15 | public var presentationController: DimmedPopupPresentationController! 16 | 17 | public func presentationController(forPresented presented: UIViewController, 18 | presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { 19 | presentationController = DimmedPopupPresentationController(presentedViewController: presented, 20 | presenting: presenting) 21 | 22 | return presentationController 23 | } 24 | 25 | public func animationController(forPresented presented: UIViewController, 26 | presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 27 | return type.animationControllerPresent 28 | } 29 | 30 | public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 31 | return type.animationControllerDismiss 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/JDAlertController/PresentationManager/PopupStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopupStyle.swift 3 | // DialogueView 4 | // 5 | // Created by Jawad Ali on 14/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | public enum PopupStyle { 11 | case alert 12 | case actionSheet(offset: Double) 13 | case topSheet(offset: Double) 14 | case dragIn 15 | } 16 | 17 | extension PopupStyle { 18 | 19 | var animationControllerPresent: UIViewControllerAnimatedTransitioning { 20 | switch self { 21 | case .alert: 22 | return OpacityPresentationAnimator(isPresentation: true) 23 | case .actionSheet(let offset): 24 | return SlideInPresentationAnimator(isPresentation: true, slide: Slide(side: .bottom, offset: offset)) 25 | case .topSheet(let offset): 26 | return SlideInPresentationAnimator(isPresentation: true, slide: Slide(side: .top, offset: offset)) 27 | case .dragIn: 28 | return DragingPresentationAnimator(isPresentation: true) 29 | } 30 | } 31 | 32 | var animationControllerDismiss: UIViewControllerAnimatedTransitioning { 33 | switch self { 34 | case .alert: 35 | return OpacityPresentationAnimator(isPresentation: false) 36 | case .actionSheet(let offset): 37 | return SlideInPresentationAnimator(isPresentation: false, slide: Slide(side: .bottom, offset: offset)) 38 | case .topSheet(let offset): 39 | return SlideInPresentationAnimator(isPresentation: false, slide: Slide(side: .top, offset: offset)) 40 | case .dragIn: 41 | return DragingPresentationAnimator(isPresentation: false) 42 | } 43 | } 44 | 45 | } 46 | 47 | public struct Slide { 48 | let side: SlideSide 49 | let offset: Double 50 | } 51 | 52 | public enum DisplayStyle { 53 | case opaccity 54 | case slide (Slide) 55 | } 56 | 57 | public enum SlideSide { 58 | case bottom 59 | case top 60 | } 61 | -------------------------------------------------------------------------------- /Sources/JDAlertController/PresentationManager/SlideInPresentationAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SlideInPresentationAnimator.swift 3 | // DropDown 4 | // 5 | // Created by Jawad Ali on 27/11/2020. 6 | // Copyright © 2020 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | final class SlideInPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning { 11 | let isPresentation: Bool 12 | let slide: Slide 13 | init(isPresentation: Bool, slide: Slide) { 14 | self.isPresentation = isPresentation 15 | self.slide = slide 16 | super.init() 17 | } 18 | 19 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 20 | return 0.5 21 | } 22 | 23 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 24 | let key = isPresentation ? UITransitionContextViewControllerKey.to : UITransitionContextViewControllerKey.from 25 | guard let controller = transitionContext.viewController(forKey: key) else {return} 26 | let animationDuration = transitionDuration(using: transitionContext) 27 | 28 | controller.view.translatesAutoresizingMaskIntoConstraints = false 29 | transitionContext.containerView.addSubview(controller.view) 30 | 31 | controller.view 32 | .centerHorizontallyInSuperview() 33 | .width(constant: controller.preferredContentSize.width) 34 | 35 | if slide.side == .top { 36 | controller.view 37 | .alignEdgeWithSuperview(.top, constant: CGFloat(slide.offset), priority: .defaultLow) 38 | } else { 39 | controller.view 40 | .alignEdgeWithSuperview(.bottom, constant: CGFloat(slide.offset), priority: .defaultLow) 41 | } 42 | 43 | transitionContext.containerView.layoutIfNeeded() 44 | 45 | let animationDistance = controller.view.frame.height + CGFloat(slide.offset) + 20 46 | 47 | controller.view.transform = slide.side == .top ? 48 | (isPresentation ? CGAffineTransform(translationX: 0, y: -animationDistance) : 49 | .identity) : 50 | (isPresentation ? CGAffineTransform(translationX: 0, y: animationDistance) : 51 | .identity) 52 | 53 | UIView.animate(withDuration: animationDuration, 54 | delay: 0.0, 55 | usingSpringWithDamping: 0.7, 56 | initialSpringVelocity: 0, 57 | options: [.beginFromCurrentState, 58 | .curveLinear, 59 | .allowUserInteraction], 60 | animations: { [unowned self] in 61 | controller.view.transform = self.slide.side == .top ? 62 | (self.isPresentation ? .identity : CGAffineTransform(translationX: 0, y: -animationDistance)) : 63 | (self.isPresentation ? .identity : 64 | CGAffineTransform(translationX: 0, y: animationDistance)) 65 | }, completion: { completed in 66 | transitionContext.completeTransition(completed) 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/JDAlertController/Scenes/AlertController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DialogueView 4 | // 5 | // Created by Jawad Ali on 06/02/2021. 6 | // Copyright © 2021 Jawad Ali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class AlertController: UIViewController { 12 | 13 | // MARK: - Views 14 | fileprivate lazy var mainStack = UIStackViewFactory.createStackView(with: .vertical, 15 | alignment: .center, 16 | distribution: .fill, 17 | spacing: 20) 18 | 19 | private lazy var animationView: AnimatedView? = { 20 | guard let type = type else {return nil} 21 | let animation = AnimatedView(logoLayer: type.animationLayer, color: type.color) 22 | animation.translatesAutoresizingMaskIntoConstraints = false 23 | return animation 24 | }() 25 | 26 | private lazy var icon: UIImageView? = UIImageViewFactory.createImageView(mode: .scaleAspectFit) 27 | 28 | private lazy var titleLabel = UILabelFactory.createUILabel(with: titleColor, 29 | font: .bodySemibold, 30 | alignment: .center, numberOfLines: 0, 31 | lineBreakMode: .byWordWrapping) 32 | 33 | private lazy var descriptionLabel = UILabelFactory.createUILabel(with: .textLight, font: .headline, alignment: .center, numberOfLines: 0) 34 | 35 | fileprivate lazy var buttonStack = UIStackViewFactory.createStackView(with: .vertical, alignment: .fill, distribution: .fillEqually, spacing: 5) 36 | 37 | fileprivate lazy var textFieldsStack = UIStackViewFactory.createStackView(with: .vertical, 38 | alignment: .fill, 39 | distribution: .fillEqually, 40 | spacing: 5) 41 | 42 | fileprivate lazy var stack = UIStackViewFactory.createStackView(with: .vertical, 43 | alignment: .fill, distribution: .fill, 44 | spacing: 5, arrangedSubviews: 45 | [titleLabel, 46 | descriptionLabel]) 47 | 48 | // MARK: Properties 49 | private var type: PopupType? 50 | fileprivate var horizantalButtons = false 51 | /// alertSize set `preferredContentSize` for the controller 52 | fileprivate var alertSize: CGSize = .zero { 53 | didSet { 54 | preferredContentSize = alertSize 55 | } 56 | } 57 | private var physicsPan: PhysicsPanHandler? 58 | private let iconSize: CGFloat 59 | fileprivate var buttonHeight: CGFloat = 46 60 | private let textFieldHeight: CGFloat = 40 61 | 62 | open private(set) var actions: [PopupAction] = [] { 63 | didSet { 64 | setupButtons() 65 | } 66 | } 67 | 68 | open private(set) var textFields: [AppTextField] = [] { 69 | didSet { 70 | setupTextFields() 71 | } 72 | } 73 | 74 | // MARK: - Public Properties 75 | open var widthRatio: CGFloat = 0.65 76 | open var cornerRadius: CGFloat = 10.0 77 | 78 | open var titleColor: UIColor = .textDark { 79 | didSet { 80 | titleLabel.textColor = titleColor 81 | } 82 | } 83 | 84 | open var isAnimated: Bool = false 85 | open var isDragEnable = false 86 | open var tapToClose = true 87 | 88 | open lazy var presentationManager: PopupPresentationManager = { 89 | let manager = PopupPresentationManager() 90 | return manager 91 | }() 92 | 93 | // MARK: initializer 94 | 95 | public init(type: PopupType? = nil, 96 | icon: UIImage? = nil, 97 | title: String? = nil, 98 | message: String? = nil, 99 | preferredStyle: PopupStyle = .actionSheet(offset: 0), 100 | iconSize: CGFloat = 40) { 101 | self.iconSize = iconSize 102 | super.init( nibName: nil, bundle: nil) 103 | 104 | self.type = type 105 | self.icon?.image = icon 106 | self.titleLabel.text = title 107 | self.descriptionLabel.text = message 108 | 109 | presentationManager.type = preferredStyle 110 | modalPresentationStyle = .custom 111 | transitioningDelegate = presentationManager 112 | } 113 | 114 | required public init?(coder: NSCoder) { 115 | fatalError("init(coder:) has not been implemented") 116 | } 117 | 118 | // MARK:- Lifecycle 119 | 120 | public override func viewDidLoad() { 121 | super.viewDidLoad() 122 | setupView() 123 | setupConstraints() 124 | 125 | if isAnimated { 126 | mainStack.arrangedSubviews.filter { $0 != animationView }.forEach { 127 | $0.isHidden = true 128 | $0.alpha = 0 129 | } 130 | } 131 | 132 | view.layoutIfNeeded() 133 | } 134 | 135 | public override func viewDidAppear(_ animated: Bool) { 136 | super.viewDidAppear(animated) 137 | addDragGestureIfEnabled() 138 | startAnimationIfEnabled() 139 | } 140 | 141 | public override func viewDidLayoutSubviews() { 142 | super.viewDidLayoutSubviews() 143 | alertSize = CGSize(width: UIScreen.main.bounds.width*widthRatio, height: mainStack.frame.size.height + 40) 144 | } 145 | 146 | open func addAction(_ action: PopupAction) { 147 | actions.append(action) 148 | } 149 | 150 | open func addTextField(_ textField: AppTextField) { 151 | textFields.append(textField) 152 | } 153 | 154 | fileprivate func setupMainStack() { 155 | 156 | switch presentationManager.type { 157 | case .actionSheet, .topSheet: 158 | mainStack 159 | .alignEdgesWithSuperview([.left, .right], constants: [0, 0]) 160 | .alignEdgeWithSuperviewSafeArea(.top, constant: 20) 161 | .alignEdgeWithSuperviewSafeArea(.bottom, .greaterThanOrEqualTo, constant: 20) 162 | default: 163 | mainStack 164 | .alignEdgesWithSuperview([.left, .right], constants: [0, 0]) 165 | .alignEdgeWithSuperview(.top, constant: 20) 166 | .alignEdgeWithSuperview(.bottom, .greaterThanOrEqualTo, constant: 20) 167 | } 168 | 169 | } 170 | } 171 | 172 | // MARK: - Setup 173 | 174 | private extension AlertController { 175 | func setupView() { 176 | view.layer.cornerRadius = cornerRadius 177 | view.layer.shadowRadius = 10 178 | view.layer.shadowOpacity = 0.2 179 | view.layer.shadowOffset = .zero 180 | view.layer.masksToBounds = false 181 | view.backgroundColor = .white 182 | 183 | view.addSubview(mainStack) 184 | 185 | if let icon = icon, icon.image != nil { 186 | mainStack.addArrangedSubview(icon) 187 | } else if let animationView = animationView { 188 | mainStack.addArrangedSubview(animationView) 189 | } 190 | 191 | mainStack.addArrangedSubview(stack) 192 | mainStack.addArrangedSubview(textFieldsStack) 193 | mainStack.addArrangedSubview(buttonStack) 194 | } 195 | 196 | func setupConstraints() { 197 | 198 | setupMainStack() 199 | 200 | stack 201 | .width(constant: UIScreen.main.bounds.maxX * widthRatio * 0.8 ) 202 | 203 | if let icon = icon, icon.image != nil { 204 | icon 205 | .width( constant: iconSize) 206 | .height( constant: iconSize) 207 | } else if let animationView = animationView { 208 | animationView 209 | .width( constant: 150) 210 | .height(constant: 150) 211 | } 212 | } 213 | 214 | func setupTextFields() { 215 | textFieldsStack.arrangedSubviews.forEach { $0.removeFromSuperview() } 216 | 217 | textFields.forEach { 218 | $0.removeAllConstraints() 219 | $0.translatesAutoresizingMaskIntoConstraints = false 220 | // $0.height(constant: textFieldHeight) 221 | $0.width(.greaterThanOrEqualTo, constant: UIScreen.main.bounds.width*widthRatio-40) 222 | 223 | $0.alpha = isAnimated ? 0 : 1 224 | textFieldsStack.addArrangedSubview($0) 225 | } 226 | 227 | view.layoutIfNeeded() 228 | } 229 | 230 | func setupButtons() { 231 | buttonStack.arrangedSubviews.forEach { $0.removeFromSuperview() } 232 | 233 | actions.forEach { 234 | $0.removeAllConstraints() 235 | $0.translatesAutoresizingMaskIntoConstraints = false 236 | $0.height(constant: buttonHeight) 237 | if horizantalButtons { 238 | $0.width(.greaterThanOrEqualTo, constant: $0.buttonSize*UIScreen.main.bounds.width*widthRatio/CGFloat(actions.count)) } else { 239 | $0.width(.greaterThanOrEqualTo, constant: $0.buttonSize*UIScreen.main.bounds.width*widthRatio) } 240 | $0.addTarget(self, action: #selector(dismissController), for: .touchUpInside) 241 | $0.alpha = isAnimated ? 0 : 1 242 | buttonStack.addArrangedSubview($0) 243 | } 244 | 245 | view.layoutIfNeeded() 246 | } 247 | 248 | @objc func dismissController() { 249 | physicsPan?.stop() 250 | self.dismiss(animated: true, completion: nil) 251 | } 252 | } 253 | private extension AlertController { 254 | func addDragGestureIfEnabled() { 255 | if isDragEnable { 256 | physicsPan = PhysicsPanHandler(messageView: view, containerView: view.superview) { [weak self] in self?.dismissController() } 257 | } 258 | } 259 | 260 | func startAnimationIfEnabled() { 261 | animationView?.startAnimation {[weak self] in 262 | 263 | let duration = 0.8 264 | 265 | UIView.animate(withDuration: duration, animations: { 266 | self?.mainStack.arrangedSubviews.forEach { $0.isHidden = false } 267 | self?.preferredContentSize = CGSize(width: UIScreen.main.bounds.width*(self?.widthRatio ?? 0), height: 600) 268 | }, completion: { (_) in 269 | self?.mainStack.arrangedSubviews.forEach { $0.alpha = 1 } 270 | 271 | DispatchQueue.main.asyncAfter(deadline: .now() + duration) { 272 | self?.buttonStack.arrangedSubviews.forEach { $0.alpha = 1 } 273 | self?.textFieldsStack.arrangedSubviews.forEach { $0.alpha = 1 } 274 | } 275 | }) 276 | } 277 | } 278 | } 279 | extension AlertController: DimmingViewTappedProtocol { 280 | public func dimmingViewTapped() { 281 | if tapToClose { 282 | dismissController() 283 | } 284 | } 285 | } 286 | open class BAlertController: AlertController { 287 | 288 | override var horizantalButtons: Bool { 289 | get { return true } 290 | set {} 291 | } 292 | 293 | override var buttonHeight: CGFloat { 294 | get { return 55 } 295 | set {} 296 | } 297 | 298 | override func setupMainStack() { 299 | 300 | mainStack 301 | .alignEdgesWithSuperview([.left, .right, .top], constants: [0, 0, 20]) 302 | .alignEdgeWithSuperview(.bottom, .greaterThanOrEqualTo, constant: 0) 303 | 304 | mainStack.spacing = 45 305 | stack.spacing = 20 306 | buttonStack.axis = .horizontal 307 | buttonStack.spacing = 0 308 | buttonStack.distribution = .fillEqually 309 | 310 | } 311 | 312 | public override func viewDidLayoutSubviews() { 313 | alertSize = CGSize(width: UIScreen.main.bounds.width*widthRatio, height: mainStack.frame.size.height + 20) 314 | } 315 | 316 | public override func viewWillLayoutSubviews() { 317 | alertSize = CGSize(width: UIScreen.main.bounds.width*widthRatio, height: mainStack.frame.size.height + 20) 318 | } 319 | 320 | } 321 | 322 | open class CAlertController: AlertController { 323 | private lazy var crossButton = UIButtonFactory.createButton() 324 | 325 | public override func viewDidLoad() { 326 | super.viewDidLoad() 327 | 328 | setupCView() 329 | setupCConstraints() 330 | } 331 | } 332 | private extension CAlertController { 333 | func setupCView() { 334 | crossButton.setImage(UIImage(named: "ic_close"), for: .normal) 335 | crossButton.addTarget(self, action: #selector(crossButtonTapped), for: .touchUpInside) 336 | view.addSubview(crossButton) 337 | } 338 | 339 | func setupCConstraints() { 340 | crossButton 341 | .width(constant: 25) 342 | .height(constant: 25) 343 | .alignEdgesWithSuperview([.right, .top], constants: [15, 15]) 344 | } 345 | 346 | @objc private func crossButtonTapped() { 347 | dismissController() 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /Tests/JDAlertControllerTests/JDAlertControllerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import JDAlertController 3 | 4 | final class JDAlertControllerTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(JDAlertController().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/JDAlertControllerTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(JDAlertControllerTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import JDAlertControllerTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += JDAlertControllerTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /images/JDAlertController.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwd-ali/JDAlertController/0375d35a60b16a43f4da70a25b063eeef6149a04/images/JDAlertController.gif --------------------------------------------------------------------------------