├── .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 | [](https://travis-ci.org/jwd-ali/RingPieChart)
6 | [](https://cocoapods.org/pods/RingPieChart)
7 | [](https://github.com/Carthage/Carthage)
8 | [](https://cocoapods.org/pods/RingPieChart)
9 | [](https://cocoapods.org/pods/RingPieChart)
10 | [](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
--------------------------------------------------------------------------------