├── .gitignore ├── .swift-version ├── AZDialogView.podspec ├── AZDialogViewControllerExample ├── AZDialogViewController │ ├── AZDialogViewController.h │ └── Info.plist ├── AZDialogViewControllerExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── AZDialogViewController.xcscheme └── AZDialogViewControllerExample │ ├── .DS_Store │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── ic_bookmark.imageset │ │ ├── Contents.json │ │ ├── ic_bookmark.png │ │ ├── ic_bookmark_2x.png │ │ └── ic_bookmark_3x.png │ ├── ign.imageset │ │ ├── Contents.json │ │ └── ign.jpg │ ├── image.imageset │ │ ├── 5468515.jpg │ │ └── Contents.json │ └── share.imageset │ │ ├── Contents.json │ │ ├── appicon-Small.png │ │ ├── appicon-Small@2x.png │ │ └── appicon-Small@3x.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── LICENSE ├── README.md ├── Screenshots ├── demo.gif ├── sc_1.png ├── sc_2.png ├── sc_3.png └── sc_4.png ├── Sources ├── .DS_Store └── AZDialogViewController.swift └── _config.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | 67 | \.DS_Store 68 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 -------------------------------------------------------------------------------- /AZDialogView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "AZDialogView" 3 | s.version = "1.3.8" 4 | s.summary = "A highly customizable alert dialog controller that mimics Snapchat's alert dialog." 5 | s.homepage = "https://github.com/Minitour/AZDialogViewController" 6 | s.license = "MIT" 7 | s.author = { "Antonio Zaitoun" => "tony.z.1711@gmail.com" } 8 | s.platform = :ios, "9.0" 9 | s.source = { :git => "https://github.com/Minitour/AZDialogViewController.git", :tag => "#{s.version}" } 10 | s.source_files = "Sources/**/*.{swift}" 11 | end 12 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewController/AZDialogViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AZDialogViewController.h 3 | // AZDialogViewController 4 | // 5 | // Created by Eric Marchand on 07/09/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for AZDialogViewController. 12 | FOUNDATION_EXPORT double AZDialogViewControllerVersionNumber; 13 | 14 | //! Project version string for AZDialogViewController. 15 | FOUNDATION_EXPORT const unsigned char AZDialogViewControllerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4817E4FD1F61977600066F27 /* AZDialogViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4817E4FB1F61977600066F27 /* AZDialogViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 4817E5001F61977600066F27 /* AZDialogViewController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4817E4F91F61977600066F27 /* AZDialogViewController.framework */; }; 12 | 4817E5011F61977600066F27 /* AZDialogViewController.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4817E4F91F61977600066F27 /* AZDialogViewController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 13 | 4817E5061F61977A00066F27 /* AZDialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DDAB3751E639C6F005D257E /* AZDialogViewController.swift */; }; 14 | 9D6F3FE91E62E18000B70203 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6F3FE81E62E18000B70203 /* AppDelegate.swift */; }; 15 | 9D6F3FEB1E62E18000B70203 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6F3FEA1E62E18000B70203 /* ViewController.swift */; }; 16 | 9D6F3FEE1E62E18000B70203 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D6F3FEC1E62E18000B70203 /* Main.storyboard */; }; 17 | 9D6F3FF01E62E18000B70203 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D6F3FEF1E62E18000B70203 /* Assets.xcassets */; }; 18 | 9D6F3FF31E62E18000B70203 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D6F3FF11E62E18000B70203 /* LaunchScreen.storyboard */; }; 19 | 9DDAB3761E639C6F005D257E /* AZDialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DDAB3751E639C6F005D257E /* AZDialogViewController.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 4817E4FE1F61977600066F27 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 9D6F3FDD1E62E18000B70203 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 4817E4F81F61977600066F27; 28 | remoteInfo = AZDialogViewController; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXCopyFilesBuildPhase section */ 33 | 4817E5051F61977600066F27 /* Embed Frameworks */ = { 34 | isa = PBXCopyFilesBuildPhase; 35 | buildActionMask = 2147483647; 36 | dstPath = ""; 37 | dstSubfolderSpec = 10; 38 | files = ( 39 | 4817E5011F61977600066F27 /* AZDialogViewController.framework in Embed Frameworks */, 40 | ); 41 | name = "Embed Frameworks"; 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXCopyFilesBuildPhase section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 4817E4F91F61977600066F27 /* AZDialogViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AZDialogViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 4817E4FB1F61977600066F27 /* AZDialogViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AZDialogViewController.h; sourceTree = ""; }; 49 | 4817E4FC1F61977600066F27 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | 9D6F3FE51E62E18000B70203 /* AZDialogViewControllerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AZDialogViewControllerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 9D6F3FE81E62E18000B70203 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 52 | 9D6F3FEA1E62E18000B70203 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 53 | 9D6F3FED1E62E18000B70203 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | 9D6F3FEF1E62E18000B70203 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | 9D6F3FF21E62E18000B70203 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 56 | 9D6F3FF41E62E18000B70203 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 9DDAB3751E639C6F005D257E /* AZDialogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AZDialogViewController.swift; path = ../Sources/AZDialogViewController.swift; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 4817E4F51F61977600066F27 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | 9D6F3FE21E62E18000B70203 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | 4817E5001F61977600066F27 /* AZDialogViewController.framework in Frameworks */, 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | 4817E4FA1F61977600066F27 /* AZDialogViewController */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 4817E4FB1F61977600066F27 /* AZDialogViewController.h */, 83 | 4817E4FC1F61977600066F27 /* Info.plist */, 84 | ); 85 | path = AZDialogViewController; 86 | sourceTree = ""; 87 | }; 88 | 9D6F3FDC1E62E18000B70203 = { 89 | isa = PBXGroup; 90 | children = ( 91 | 9DDAB3771E639C8B005D257E /* Source */, 92 | 9D6F3FE71E62E18000B70203 /* AZDialogViewControllerExample */, 93 | 4817E4FA1F61977600066F27 /* AZDialogViewController */, 94 | 9D6F3FE61E62E18000B70203 /* Products */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | 9D6F3FE61E62E18000B70203 /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 9D6F3FE51E62E18000B70203 /* AZDialogViewControllerExample.app */, 102 | 4817E4F91F61977600066F27 /* AZDialogViewController.framework */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | 9D6F3FE71E62E18000B70203 /* AZDialogViewControllerExample */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 9D6F3FE81E62E18000B70203 /* AppDelegate.swift */, 111 | 9D6F3FEA1E62E18000B70203 /* ViewController.swift */, 112 | 9D6F3FEC1E62E18000B70203 /* Main.storyboard */, 113 | 9D6F3FEF1E62E18000B70203 /* Assets.xcassets */, 114 | 9D6F3FF11E62E18000B70203 /* LaunchScreen.storyboard */, 115 | 9D6F3FF41E62E18000B70203 /* Info.plist */, 116 | ); 117 | path = AZDialogViewControllerExample; 118 | sourceTree = ""; 119 | }; 120 | 9DDAB3771E639C8B005D257E /* Source */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 9DDAB3751E639C6F005D257E /* AZDialogViewController.swift */, 124 | ); 125 | name = Source; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXHeadersBuildPhase section */ 131 | 4817E4F61F61977600066F27 /* Headers */ = { 132 | isa = PBXHeadersBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 4817E4FD1F61977600066F27 /* AZDialogViewController.h in Headers */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | /* End PBXHeadersBuildPhase section */ 140 | 141 | /* Begin PBXNativeTarget section */ 142 | 4817E4F81F61977600066F27 /* AZDialogViewController */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = 4817E5041F61977600066F27 /* Build configuration list for PBXNativeTarget "AZDialogViewController" */; 145 | buildPhases = ( 146 | 4817E4F41F61977600066F27 /* Sources */, 147 | 4817E4F51F61977600066F27 /* Frameworks */, 148 | 4817E4F61F61977600066F27 /* Headers */, 149 | 4817E4F71F61977600066F27 /* Resources */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | ); 155 | name = AZDialogViewController; 156 | productName = AZDialogViewController; 157 | productReference = 4817E4F91F61977600066F27 /* AZDialogViewController.framework */; 158 | productType = "com.apple.product-type.framework"; 159 | }; 160 | 9D6F3FE41E62E18000B70203 /* AZDialogViewControllerExample */ = { 161 | isa = PBXNativeTarget; 162 | buildConfigurationList = 9D6F3FF71E62E18000B70203 /* Build configuration list for PBXNativeTarget "AZDialogViewControllerExample" */; 163 | buildPhases = ( 164 | 9D6F3FE11E62E18000B70203 /* Sources */, 165 | 9D6F3FE21E62E18000B70203 /* Frameworks */, 166 | 9D6F3FE31E62E18000B70203 /* Resources */, 167 | 4817E5051F61977600066F27 /* Embed Frameworks */, 168 | ); 169 | buildRules = ( 170 | ); 171 | dependencies = ( 172 | 4817E4FF1F61977600066F27 /* PBXTargetDependency */, 173 | ); 174 | name = AZDialogViewControllerExample; 175 | productName = AZDialogViewControllerExample; 176 | productReference = 9D6F3FE51E62E18000B70203 /* AZDialogViewControllerExample.app */; 177 | productType = "com.apple.product-type.application"; 178 | }; 179 | /* End PBXNativeTarget section */ 180 | 181 | /* Begin PBXProject section */ 182 | 9D6F3FDD1E62E18000B70203 /* Project object */ = { 183 | isa = PBXProject; 184 | attributes = { 185 | LastSwiftUpdateCheck = 0820; 186 | LastUpgradeCheck = 0900; 187 | ORGANIZATIONNAME = "Antonio Zaitoun"; 188 | TargetAttributes = { 189 | 4817E4F81F61977600066F27 = { 190 | CreatedOnToolsVersion = 8.3.3; 191 | DevelopmentTeam = 35DWKWL79J; 192 | ProvisioningStyle = Automatic; 193 | }; 194 | 9D6F3FE41E62E18000B70203 = { 195 | CreatedOnToolsVersion = 8.2.1; 196 | DevelopmentTeam = 35DWKWL79J; 197 | ProvisioningStyle = Automatic; 198 | }; 199 | }; 200 | }; 201 | buildConfigurationList = 9D6F3FE01E62E18000B70203 /* Build configuration list for PBXProject "AZDialogViewControllerExample" */; 202 | compatibilityVersion = "Xcode 3.2"; 203 | developmentRegion = English; 204 | hasScannedForEncodings = 0; 205 | knownRegions = ( 206 | en, 207 | Base, 208 | ); 209 | mainGroup = 9D6F3FDC1E62E18000B70203; 210 | productRefGroup = 9D6F3FE61E62E18000B70203 /* Products */; 211 | projectDirPath = ""; 212 | projectRoot = ""; 213 | targets = ( 214 | 9D6F3FE41E62E18000B70203 /* AZDialogViewControllerExample */, 215 | 4817E4F81F61977600066F27 /* AZDialogViewController */, 216 | ); 217 | }; 218 | /* End PBXProject section */ 219 | 220 | /* Begin PBXResourcesBuildPhase section */ 221 | 4817E4F71F61977600066F27 /* Resources */ = { 222 | isa = PBXResourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | 9D6F3FE31E62E18000B70203 /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 9D6F3FF31E62E18000B70203 /* LaunchScreen.storyboard in Resources */, 233 | 9D6F3FF01E62E18000B70203 /* Assets.xcassets in Resources */, 234 | 9D6F3FEE1E62E18000B70203 /* Main.storyboard in Resources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXResourcesBuildPhase section */ 239 | 240 | /* Begin PBXSourcesBuildPhase section */ 241 | 4817E4F41F61977600066F27 /* Sources */ = { 242 | isa = PBXSourcesBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | 4817E5061F61977A00066F27 /* AZDialogViewController.swift in Sources */, 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | 9D6F3FE11E62E18000B70203 /* Sources */ = { 250 | isa = PBXSourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | 9D6F3FEB1E62E18000B70203 /* ViewController.swift in Sources */, 254 | 9D6F3FE91E62E18000B70203 /* AppDelegate.swift in Sources */, 255 | 9DDAB3761E639C6F005D257E /* AZDialogViewController.swift in Sources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | /* End PBXSourcesBuildPhase section */ 260 | 261 | /* Begin PBXTargetDependency section */ 262 | 4817E4FF1F61977600066F27 /* PBXTargetDependency */ = { 263 | isa = PBXTargetDependency; 264 | target = 4817E4F81F61977600066F27 /* AZDialogViewController */; 265 | targetProxy = 4817E4FE1F61977600066F27 /* PBXContainerItemProxy */; 266 | }; 267 | /* End PBXTargetDependency section */ 268 | 269 | /* Begin PBXVariantGroup section */ 270 | 9D6F3FEC1E62E18000B70203 /* Main.storyboard */ = { 271 | isa = PBXVariantGroup; 272 | children = ( 273 | 9D6F3FED1E62E18000B70203 /* Base */, 274 | ); 275 | name = Main.storyboard; 276 | sourceTree = ""; 277 | }; 278 | 9D6F3FF11E62E18000B70203 /* LaunchScreen.storyboard */ = { 279 | isa = PBXVariantGroup; 280 | children = ( 281 | 9D6F3FF21E62E18000B70203 /* Base */, 282 | ); 283 | name = LaunchScreen.storyboard; 284 | sourceTree = ""; 285 | }; 286 | /* End PBXVariantGroup section */ 287 | 288 | /* Begin XCBuildConfiguration section */ 289 | 4817E5021F61977600066F27 /* Debug */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 293 | CODE_SIGN_IDENTITY = ""; 294 | CURRENT_PROJECT_VERSION = 1; 295 | DEFINES_MODULE = YES; 296 | DEVELOPMENT_TEAM = 35DWKWL79J; 297 | DYLIB_COMPATIBILITY_VERSION = 1; 298 | DYLIB_CURRENT_VERSION = 1; 299 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 300 | INFOPLIST_FILE = AZDialogViewController/Info.plist; 301 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 302 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 303 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 304 | PRODUCT_BUNDLE_IDENTIFIER = fr.phimage.AZDialogViewController; 305 | PRODUCT_NAME = "$(TARGET_NAME)"; 306 | SKIP_INSTALL = YES; 307 | SWIFT_VERSION = 4.2; 308 | TARGETED_DEVICE_FAMILY = "1,2"; 309 | VERSIONING_SYSTEM = "apple-generic"; 310 | VERSION_INFO_PREFIX = ""; 311 | }; 312 | name = Debug; 313 | }; 314 | 4817E5031F61977600066F27 /* Release */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 318 | CODE_SIGN_IDENTITY = ""; 319 | CURRENT_PROJECT_VERSION = 1; 320 | DEFINES_MODULE = YES; 321 | DEVELOPMENT_TEAM = 35DWKWL79J; 322 | DYLIB_COMPATIBILITY_VERSION = 1; 323 | DYLIB_CURRENT_VERSION = 1; 324 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 325 | INFOPLIST_FILE = AZDialogViewController/Info.plist; 326 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 327 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 328 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 329 | PRODUCT_BUNDLE_IDENTIFIER = fr.phimage.AZDialogViewController; 330 | PRODUCT_NAME = "$(TARGET_NAME)"; 331 | SKIP_INSTALL = YES; 332 | SWIFT_VERSION = 4.2; 333 | TARGETED_DEVICE_FAMILY = "1,2"; 334 | VERSIONING_SYSTEM = "apple-generic"; 335 | VERSION_INFO_PREFIX = ""; 336 | }; 337 | name = Release; 338 | }; 339 | 9D6F3FF51E62E18000B70203 /* Debug */ = { 340 | isa = XCBuildConfiguration; 341 | buildSettings = { 342 | ALWAYS_SEARCH_USER_PATHS = NO; 343 | CLANG_ANALYZER_NONNULL = YES; 344 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 345 | CLANG_CXX_LIBRARY = "libc++"; 346 | CLANG_ENABLE_MODULES = YES; 347 | CLANG_ENABLE_OBJC_ARC = YES; 348 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_COMMA = YES; 351 | CLANG_WARN_CONSTANT_CONVERSION = YES; 352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 353 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 354 | CLANG_WARN_EMPTY_BODY = YES; 355 | CLANG_WARN_ENUM_CONVERSION = YES; 356 | CLANG_WARN_INFINITE_RECURSION = YES; 357 | CLANG_WARN_INT_CONVERSION = YES; 358 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 359 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 361 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 362 | CLANG_WARN_STRICT_PROTOTYPES = YES; 363 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 364 | CLANG_WARN_UNREACHABLE_CODE = YES; 365 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 366 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 367 | COPY_PHASE_STRIP = NO; 368 | DEBUG_INFORMATION_FORMAT = dwarf; 369 | ENABLE_STRICT_OBJC_MSGSEND = YES; 370 | ENABLE_TESTABILITY = YES; 371 | GCC_C_LANGUAGE_STANDARD = gnu99; 372 | GCC_DYNAMIC_NO_PIC = NO; 373 | GCC_NO_COMMON_BLOCKS = YES; 374 | GCC_OPTIMIZATION_LEVEL = 0; 375 | GCC_PREPROCESSOR_DEFINITIONS = ( 376 | "DEBUG=1", 377 | "$(inherited)", 378 | ); 379 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 380 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 381 | GCC_WARN_UNDECLARED_SELECTOR = YES; 382 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 383 | GCC_WARN_UNUSED_FUNCTION = YES; 384 | GCC_WARN_UNUSED_VARIABLE = YES; 385 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 386 | MTL_ENABLE_DEBUG_INFO = YES; 387 | ONLY_ACTIVE_ARCH = YES; 388 | SDKROOT = iphoneos; 389 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 390 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 391 | SWIFT_VERSION = 4.2; 392 | }; 393 | name = Debug; 394 | }; 395 | 9D6F3FF61E62E18000B70203 /* Release */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | CLANG_ANALYZER_NONNULL = YES; 400 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 401 | CLANG_CXX_LIBRARY = "libc++"; 402 | CLANG_ENABLE_MODULES = YES; 403 | CLANG_ENABLE_OBJC_ARC = YES; 404 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 405 | CLANG_WARN_BOOL_CONVERSION = YES; 406 | CLANG_WARN_COMMA = YES; 407 | CLANG_WARN_CONSTANT_CONVERSION = YES; 408 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 409 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 410 | CLANG_WARN_EMPTY_BODY = YES; 411 | CLANG_WARN_ENUM_CONVERSION = YES; 412 | CLANG_WARN_INFINITE_RECURSION = YES; 413 | CLANG_WARN_INT_CONVERSION = YES; 414 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 416 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 418 | CLANG_WARN_STRICT_PROTOTYPES = YES; 419 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 420 | CLANG_WARN_UNREACHABLE_CODE = YES; 421 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 422 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 423 | COPY_PHASE_STRIP = NO; 424 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 425 | ENABLE_NS_ASSERTIONS = NO; 426 | ENABLE_STRICT_OBJC_MSGSEND = YES; 427 | GCC_C_LANGUAGE_STANDARD = gnu99; 428 | GCC_NO_COMMON_BLOCKS = YES; 429 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 430 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 431 | GCC_WARN_UNDECLARED_SELECTOR = YES; 432 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 433 | GCC_WARN_UNUSED_FUNCTION = YES; 434 | GCC_WARN_UNUSED_VARIABLE = YES; 435 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 436 | MTL_ENABLE_DEBUG_INFO = NO; 437 | SDKROOT = iphoneos; 438 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 439 | SWIFT_VERSION = 4.2; 440 | VALIDATE_PRODUCT = YES; 441 | }; 442 | name = Release; 443 | }; 444 | 9D6F3FF81E62E18000B70203 /* Debug */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 448 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 449 | DEVELOPMENT_TEAM = 35DWKWL79J; 450 | INFOPLIST_FILE = AZDialogViewControllerExample/Info.plist; 451 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 453 | PRODUCT_BUNDLE_IDENTIFIER = mobi.newsound.AZDialogViewControllerExample; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | SWIFT_VERSION = 4.2; 456 | TARGETED_DEVICE_FAMILY = "1,2"; 457 | }; 458 | name = Debug; 459 | }; 460 | 9D6F3FF91E62E18000B70203 /* Release */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 465 | DEVELOPMENT_TEAM = 35DWKWL79J; 466 | INFOPLIST_FILE = AZDialogViewControllerExample/Info.plist; 467 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 468 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 469 | PRODUCT_BUNDLE_IDENTIFIER = mobi.newsound.AZDialogViewControllerExample; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | SWIFT_VERSION = 4.2; 472 | TARGETED_DEVICE_FAMILY = "1,2"; 473 | }; 474 | name = Release; 475 | }; 476 | /* End XCBuildConfiguration section */ 477 | 478 | /* Begin XCConfigurationList section */ 479 | 4817E5041F61977600066F27 /* Build configuration list for PBXNativeTarget "AZDialogViewController" */ = { 480 | isa = XCConfigurationList; 481 | buildConfigurations = ( 482 | 4817E5021F61977600066F27 /* Debug */, 483 | 4817E5031F61977600066F27 /* Release */, 484 | ); 485 | defaultConfigurationIsVisible = 0; 486 | defaultConfigurationName = Release; 487 | }; 488 | 9D6F3FE01E62E18000B70203 /* Build configuration list for PBXProject "AZDialogViewControllerExample" */ = { 489 | isa = XCConfigurationList; 490 | buildConfigurations = ( 491 | 9D6F3FF51E62E18000B70203 /* Debug */, 492 | 9D6F3FF61E62E18000B70203 /* Release */, 493 | ); 494 | defaultConfigurationIsVisible = 0; 495 | defaultConfigurationName = Release; 496 | }; 497 | 9D6F3FF71E62E18000B70203 /* Build configuration list for PBXNativeTarget "AZDialogViewControllerExample" */ = { 498 | isa = XCConfigurationList; 499 | buildConfigurations = ( 500 | 9D6F3FF81E62E18000B70203 /* Debug */, 501 | 9D6F3FF91E62E18000B70203 /* Release */, 502 | ); 503 | defaultConfigurationIsVisible = 0; 504 | defaultConfigurationName = Release; 505 | }; 506 | /* End XCConfigurationList section */ 507 | }; 508 | rootObject = 9D6F3FDD1E62E18000B70203 /* Project object */; 509 | } 510 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample.xcodeproj/xcshareddata/xcschemes/AZDialogViewController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/AZDialogViewControllerExample/AZDialogViewControllerExample/.DS_Store -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AZDialogViewControllerExample 4 | // 5 | // Created by Antonio Zaitoun on 26/02/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ic_bookmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ic_bookmark.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ic_bookmark_2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ic_bookmark_3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ic_bookmark.imageset/ic_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ic_bookmark.imageset/ic_bookmark.png -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ic_bookmark.imageset/ic_bookmark_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ic_bookmark.imageset/ic_bookmark_2x.png -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ic_bookmark.imageset/ic_bookmark_3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ic_bookmark.imageset/ic_bookmark_3x.png -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ign.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ign.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ign.imageset/ign.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/ign.imageset/ign.jpg -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/image.imageset/5468515.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/image.imageset/5468515.jpg -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "5468515.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/share.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "appicon-Small.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "appicon-Small@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "appicon-Small@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/share.imageset/appicon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/share.imageset/appicon-Small.png -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/share.imageset/appicon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/share.imageset/appicon-Small@2x.png -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/share.imageset/appicon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/AZDialogViewControllerExample/AZDialogViewControllerExample/Assets.xcassets/share.imageset/appicon-Small@3x.png -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Base.lproj/Main.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 | 34 | 44 | 54 | 64 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /AZDialogViewControllerExample/AZDialogViewControllerExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AZDialogViewControllerExample 4 | // 5 | // Created by Antonio Zaitoun on 26/02/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | let primaryColor = #colorLiteral(red: 0.6271930337, green: 0.3653797209, blue: 0.8019730449, alpha: 1) 14 | 15 | let primaryColorDark = #colorLiteral(red: 0.5373370051, green: 0.2116269171, blue: 0.7118118405, alpha: 1) 16 | 17 | 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | // Do any additional setup after loading the view, typically from a nib. 22 | } 23 | 24 | override func didReceiveMemoryWarning() { 25 | super.didReceiveMemoryWarning() 26 | // Dispose of any resources that can be recreated. 27 | } 28 | 29 | @IBAction func click(_ sender: UIButton) { 30 | switch sender.tag{ 31 | case 0: 32 | ignDialog() 33 | //loadingIndicator() 34 | //imagePreviewDialog() 35 | //tableViewDialog() 36 | case 1: 37 | editUserDialog() 38 | case 2: 39 | reportUserDialog(controller: self) 40 | case 3: 41 | reportDialog() 42 | default: 43 | break 44 | } 45 | } 46 | 47 | 48 | func imagePreviewDialog(){ 49 | let dialog = AZDialogViewController(title: "Image",message: "Image Description") 50 | let container = dialog.container 51 | let imageView = UIImageView(image: #imageLiteral(resourceName: "image")) 52 | imageView.contentMode = .scaleAspectFit 53 | container.addSubview(imageView) 54 | imageView.translatesAutoresizingMaskIntoConstraints = false 55 | imageView.topAnchor.constraint(equalTo: container.topAnchor).isActive = true 56 | imageView.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true 57 | imageView.leftAnchor.constraint(equalTo: container.leftAnchor).isActive = true 58 | imageView.rightAnchor.constraint(equalTo: container.rightAnchor).isActive = true 59 | 60 | dialog.customViewSizeRatio = imageView.image!.size.height / imageView.image!.size.width 61 | 62 | dialog.addAction(AZDialogAction(title: "Done") { (dialog) -> (Void) in 63 | dialog.image = #imageLiteral(resourceName: "ign") 64 | }) 65 | 66 | 67 | dialog.show(in: self) 68 | } 69 | 70 | func loadingIndicator(){ 71 | let dialog = AZDialogViewController(title: "Loading...", message: "Logging you in, please wait") 72 | 73 | let container = dialog.container 74 | let indicator = UIActivityIndicatorView(style: .gray) 75 | dialog.container.addSubview(indicator) 76 | indicator.translatesAutoresizingMaskIntoConstraints = false 77 | indicator.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true 78 | indicator.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true 79 | indicator.startAnimating() 80 | 81 | 82 | dialog.buttonStyle = { (button,height,position) in 83 | button.setBackgroundImage(UIImage.imageWithColor(self.primaryColorDark), for: .highlighted) 84 | button.setTitleColor(UIColor.white, for: .highlighted) 85 | button.setTitleColor(self.primaryColor, for: .normal) 86 | button.layer.masksToBounds = true 87 | button.layer.borderColor = self.primaryColor.cgColor 88 | } 89 | 90 | //dialog.animationDuration = 5.0 91 | dialog.customViewSizeRatio = 0.2 92 | dialog.dismissDirection = .none 93 | dialog.allowDragGesture = false 94 | dialog.dismissWithOutsideTouch = true 95 | dialog.show(in: self) 96 | 97 | let when = DispatchTime.now() + 3 // change 2 to desired number of seconds 98 | DispatchQueue.main.asyncAfter(deadline: when) { 99 | dialog.message = "Preparing..." 100 | } 101 | 102 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 4) { 103 | dialog.message = "Syncing accounts..." 104 | } 105 | 106 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { 107 | dialog.title = "Ready" 108 | dialog.message = "Let's get started" 109 | dialog.image = #imageLiteral(resourceName: "image") 110 | dialog.customViewSizeRatio = 0 111 | dialog.addAction(AZDialogAction(title: "Go", handler: { (dialog) -> (Void) in 112 | dialog.cancelEnabled = !dialog.cancelEnabled 113 | })) 114 | dialog.dismissDirection = .bottom 115 | dialog.allowDragGesture = true 116 | } 117 | 118 | dialog.cancelButtonStyle = { (button,height) in 119 | button.tintColor = self.primaryColor 120 | button.setTitle("CANCEL", for: []) 121 | return false 122 | } 123 | 124 | 125 | 126 | 127 | } 128 | 129 | func ignDialog(){ 130 | let dialogController = AZDialogViewController(title: "IGN", 131 | message: "IGN is your destination for gaming, movies, comics and everything you're into. Find the latest reviews, news, videos, and more more.") 132 | 133 | dialogController.showSeparator = true 134 | 135 | dialogController.dismissDirection = .bottom 136 | 137 | dialogController.imageHandler = { (imageView) in 138 | imageView.image = UIImage(named: "ign") 139 | imageView.contentMode = .scaleAspectFill 140 | return true 141 | } 142 | 143 | dialogController.addAction(AZDialogAction(title: "Subscribe", handler: { (dialog) -> (Void) in 144 | //dialog.title = "title" 145 | //dialog.message = "new message" 146 | //dialog.image = dialog.image == nil ? #imageLiteral(resourceName: "ign") : nil 147 | //dialog.title = "" 148 | //dialog.message = "" 149 | //dialog.customViewSizeRatio = 0.2 150 | 151 | 152 | })) 153 | 154 | //let container = dialogController.container 155 | //let button = UIButton(type: .system) 156 | //button.setTitle("MY BUTTON", for: []) 157 | //dialogController.container.addSubview(button) 158 | //button.translatesAutoresizingMaskIntoConstraints = false 159 | //button.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true 160 | //button.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true 161 | 162 | 163 | dialogController.buttonStyle = { (button,height,position) in 164 | button.setBackgroundImage(UIImage.imageWithColor(self.primaryColor) , for: .normal) 165 | button.setBackgroundImage(UIImage.imageWithColor(self.primaryColorDark), for: .highlighted) 166 | button.setTitleColor(UIColor.white, for: []) 167 | button.layer.masksToBounds = true 168 | button.layer.borderColor = self.primaryColor.cgColor 169 | button.tintColor = .white 170 | if position == 0 { 171 | let image = #imageLiteral(resourceName: "ic_bookmark").withRenderingMode(.alwaysTemplate) 172 | button.setImage(image, for: []) 173 | button.imageView?.contentMode = .scaleAspectFit 174 | } 175 | } 176 | 177 | dialogController.blurBackground = true 178 | dialogController.blurEffectStyle = .dark 179 | 180 | dialogController.rightToolStyle = { (button) in 181 | button.setImage(#imageLiteral(resourceName: "share"), for: []) 182 | button.tintColor = .lightGray 183 | return true 184 | } 185 | 186 | dialogController.rightToolAction = { (button) in 187 | print("Share function") 188 | } 189 | 190 | dialogController.dismissWithOutsideTouch = true 191 | dialogController.show(in: self) 192 | } 193 | 194 | func editUserDialog(){ 195 | let dialogController = AZDialogViewController(title: "Antonio Zaitoun", message: "minitour") 196 | dialogController.showSeparator = true 197 | 198 | dialogController.addAction(AZDialogAction(title: "Edit Name", handler: { (dialog) -> (Void) in 199 | //dialog.removeAction(at: 0) 200 | dialog.addAction(AZDialogAction(title: "action") { (dialog) -> (Void) in 201 | dialog.dismiss() 202 | }) 203 | 204 | dialog.contentOffset = self.view.frame.height / 2.0 - dialog.estimatedHeight / 2.0 - 16 205 | 206 | })) 207 | 208 | dialogController.addAction(AZDialogAction(title: "Remove Friend", handler: { (dialog) -> (Void) in 209 | dialog.removeAction(at: 1) 210 | })) 211 | 212 | dialogController.addAction(AZDialogAction(title: "Block", handler: { (dialog) -> (Void) in 213 | //dialog.spacing = 20 214 | dialog.removeAction(at: 2) 215 | dialog.contentOffset = self.view.frame.height / 2.0 - dialog.estimatedHeight / 2.0 - 16 216 | })) 217 | 218 | dialogController.cancelButtonStyle = { (button,height) in 219 | button.tintColor = self.primaryColor 220 | button.setTitle("CANCEL", for: []) 221 | return true 222 | 223 | } 224 | 225 | dialogController.cancelEnabled = true 226 | 227 | dialogController.buttonStyle = { (button,height,position) in 228 | button.setBackgroundImage(UIImage.imageWithColor(self.primaryColorDark), for: .highlighted) 229 | button.setTitleColor(UIColor.white, for: .highlighted) 230 | button.setTitleColor(self.primaryColor, for: .normal) 231 | button.layer.masksToBounds = true 232 | button.layer.borderColor = self.primaryColor.cgColor 233 | } 234 | 235 | dialogController.dismissDirection = .bottom 236 | 237 | dialogController.dismissWithOutsideTouch = true 238 | 239 | 240 | dialogController.contentOffset = self.view.frame.height / 2.0 - dialogController.estimatedHeight / 2.0 - 16 241 | 242 | dialogController.show(in: self) 243 | 244 | } 245 | 246 | func reportUserDialog(controller: UIViewController){ 247 | let dialogController = AZDialogViewController(title: "Minitour has been blocked.", 248 | message: "Let us know your reason for blocking them?", 249 | widthRatio: 0.8) 250 | dialogController.dismissDirection = .none 251 | dialogController.dismissWithOutsideTouch = false 252 | 253 | dialogController.addAction(AZDialogAction(title: "Annoying", handler: { (dialog) -> (Void) in 254 | dialog.dismiss() 255 | })) 256 | 257 | dialogController.addAction(AZDialogAction(title: "I don't know them", handler: { (dialog) -> (Void) in 258 | dialog.dismiss() 259 | })) 260 | 261 | dialogController.addAction(AZDialogAction(title: "Inappropriate Snaps", handler: { (dialog) -> (Void) in 262 | dialog.dismiss() 263 | })) 264 | 265 | dialogController.addAction(AZDialogAction(title: "Harassing me", handler: { (dialog) -> (Void) in 266 | dialog.dismiss() 267 | })) 268 | 269 | dialogController.addAction(AZDialogAction(title: "Other", handler: { (dialog) -> (Void) in 270 | dialog.removeSection(0) 271 | //dialog.dismiss() 272 | })) 273 | 274 | dialogController.buttonStyle = { (button,height,position) in 275 | button.setBackgroundImage(UIImage.imageWithColor(self.primaryColorDark), for: .highlighted) 276 | button.setTitleColor(UIColor.white, for: .highlighted) 277 | button.setTitleColor(self.primaryColor, for: .normal) 278 | button.layer.masksToBounds = true 279 | button.layer.borderColor = self.primaryColor.cgColor 280 | } 281 | 282 | let imageView = UIImageView(image: #imageLiteral(resourceName: "image")) 283 | imageView.contentMode = .scaleAspectFill 284 | imageView.heightAnchor.constraint(equalToConstant: 100.0).isActive = true 285 | 286 | 287 | dialogController.show(in: controller) { 288 | $0.section(view: imageView) 289 | imageView.superview?.superview?.layer.masksToBounds = true 290 | } 291 | 292 | } 293 | 294 | func reportDialog(){ 295 | let dialogController = AZDialogViewController(title: nil, message: nil) 296 | dialogController.dismissDirection = .bottom 297 | 298 | dialogController.dismissWithOutsideTouch = true 299 | 300 | let primary = #colorLiteral(red: 0.1019607843, green: 0.737254902, blue: 0.6117647059, alpha: 1) 301 | let primaryDark = #colorLiteral(red: 0.0862745098, green: 0.6274509804, blue: 0.5215686275, alpha: 1) 302 | 303 | 304 | dialogController.buttonStyle = { (button,height,position) in 305 | button.tintColor = primary 306 | button.layer.masksToBounds = true 307 | button.setBackgroundImage(UIImage.imageWithColor(primary) , for: .normal) 308 | button.setBackgroundImage(UIImage.imageWithColor(primaryDark), for: .highlighted) 309 | button.setTitleColor(UIColor.white, for: []) 310 | button.layer.masksToBounds = true 311 | button.layer.borderColor = self.primaryColor.cgColor 312 | button.layer.borderColor = primary.cgColor 313 | 314 | } 315 | 316 | dialogController.buttonInit = { index in 317 | return HighlightableButton() 318 | } 319 | 320 | dialogController.cancelEnabled = true 321 | dialogController.cancelButtonStyle = { (button, height) in 322 | button.tintColor = primary 323 | button.setTitle("CANCEL", for: []) 324 | return true 325 | } 326 | 327 | 328 | dialogController.addAction(AZDialogAction(title: "Mute", handler: { (dialog) -> (Void) in 329 | dialog.dismiss() 330 | })) 331 | 332 | dialogController.addAction(AZDialogAction(title: "Group Info", handler: { (dialog) -> (Void) in 333 | dialog.dismiss() 334 | })) 335 | 336 | dialogController.addAction(AZDialogAction(title: "Export Chat", handler: { (dialog) -> (Void) in 337 | dialog.dismiss() 338 | })) 339 | 340 | dialogController.addAction(AZDialogAction(title: "Clear Chat", handler: { (dialog) -> (Void) in 341 | dialog.dismiss() 342 | })) 343 | 344 | dialogController.addAction(AZDialogAction(title: "Exit Chat", handler: { (dialog) -> (Void) in 345 | dialog.dismiss() 346 | })) 347 | 348 | 349 | dialogController.show(in: self) 350 | } 351 | 352 | var tableViewDialogController: AZDialogViewController? 353 | 354 | func tableViewDialog(){ 355 | let dialog = AZDialogViewController(title: "Switch Account", message: nil,widthRatio: 1.0) 356 | tableViewDialogController = dialog 357 | 358 | dialog.showSeparator = false 359 | 360 | let container = dialog.container 361 | 362 | dialog.customViewSizeRatio = ( view.bounds.height - 100) / view.bounds.width 363 | 364 | let tableView = UITableView(frame: .zero, style: .plain) 365 | 366 | container.addSubview(tableView) 367 | 368 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 369 | 370 | tableView.delegate = self 371 | tableView.dataSource = self 372 | tableView.separatorColor = .clear 373 | //tableView.bouncesZoom = false 374 | tableView.bounces = false 375 | 376 | tableView.translatesAutoresizingMaskIntoConstraints = false 377 | tableView.topAnchor.constraint(equalTo: container.topAnchor).isActive = true 378 | tableView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -32).isActive = true 379 | tableView.leftAnchor.constraint(equalTo: container.leftAnchor).isActive = true 380 | tableView.rightAnchor.constraint(equalTo: container.rightAnchor).isActive = true 381 | 382 | dialog.gestureRecognizer.delegate = self 383 | dialog.dismissDirection = .bottom 384 | 385 | dialog.show(in: self) { dialog in 386 | dialog.contentOffset = self.view.frame.height / 2.0 - dialog.estimatedHeight / 2.0 + 15 387 | } 388 | 389 | } 390 | 391 | @IBAction func fullScreenDialog(_ sender: UIButton) { 392 | tableViewDialog() 393 | } 394 | var items: [String] = { 395 | var list = [String]() 396 | for i in 0..<100 { 397 | list.append("Account \(i)") 398 | } 399 | return list 400 | }() 401 | 402 | func handlerForIndex(_ index: Int)->ActionHandler{ 403 | switch index{ 404 | case 0: 405 | return { dialog in 406 | print("action for index 0") 407 | } 408 | case 1: 409 | return { dialog in 410 | print("action for index 1") 411 | } 412 | default: 413 | return {dialog in 414 | print("default action") 415 | } 416 | } 417 | 418 | } 419 | 420 | var shouldDismiss: Bool = false 421 | var velocity: CGFloat = 0.0 422 | 423 | } 424 | 425 | 426 | 427 | extension ViewController: UITableViewDelegate{ 428 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 429 | tableView.deselectRow(at: indexPath, animated: true) 430 | tableViewDialogController?.dismiss() 431 | } 432 | 433 | 434 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 435 | let duration = Double(1.0/abs(velocity)) 436 | if scrollView.isAtTop { 437 | if shouldDismiss { 438 | tableViewDialogController?.animationDuration = duration 439 | tableViewDialogController?.dismiss() 440 | }else { 441 | tableViewDialogController?.applyAnimatedTranslation(-velocity * 35.0,duration: min(max(duration,0.2),0.4)) 442 | } 443 | 444 | } 445 | } 446 | 447 | func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 448 | shouldDismiss = velocity.y < -3.0 449 | self.velocity = velocity.y 450 | } 451 | 452 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 453 | scrollView.bounces = !scrollView.isAtTop 454 | 455 | } 456 | } 457 | 458 | extension ViewController: UIGestureRecognizerDelegate { 459 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 460 | 461 | let optionalTableView: UITableView? = otherGestureRecognizer.view as? UITableView 462 | 463 | guard let tableView = optionalTableView, 464 | let panGesture = gestureRecognizer as? UIPanGestureRecognizer, 465 | let direction = panGesture.direction 466 | else { return false } 467 | 468 | if tableView.isAtTop && direction == .down { 469 | return true 470 | } else { 471 | return false 472 | } 473 | } 474 | } 475 | 476 | 477 | 478 | extension ViewController: UITableViewDataSource{ 479 | func numberOfSections(in tableView: UITableView) -> Int { 480 | return 1 481 | } 482 | 483 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 484 | return items.count 485 | } 486 | 487 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 488 | if let cell = tableView.dequeueReusableCell(withIdentifier: "cell"){ 489 | cell.textLabel?.text = items[indexPath.row] 490 | return cell 491 | } 492 | return UITableViewCell() 493 | } 494 | } 495 | 496 | 497 | extension UIImage { 498 | class func imageWithColor(_ color: UIColor) -> UIImage { 499 | let rect: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1) 500 | UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 0) 501 | color.setFill() 502 | UIRectFill(rect) 503 | let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()! 504 | UIGraphicsEndImageContext() 505 | return image 506 | } 507 | } 508 | 509 | class HighlightableButton: UIButton{ 510 | 511 | var action: ((UIButton)->Void)? = nil 512 | 513 | convenience init(_ action: ((UIButton)->Void)? = nil ) { 514 | self.init() 515 | self.action = action 516 | addTarget(self, action: #selector(didClick(_:)), for: .touchUpInside) 517 | } 518 | 519 | override init(frame: CGRect) { 520 | super.init(frame: frame) 521 | setup() 522 | } 523 | 524 | required init?(coder aDecoder: NSCoder) { 525 | super.init(coder: aDecoder) 526 | setup() 527 | } 528 | 529 | func setup() { 530 | backgroundColor = #colorLiteral(red: 0.1019607843, green: 0.737254902, blue: 0.6117647059, alpha: 1) 531 | layer.cornerRadius = 5 532 | layer.masksToBounds = true 533 | } 534 | 535 | override var isHighlighted: Bool{ 536 | set{ 537 | UIView.animate(withDuration: 0.1) { [weak self] in 538 | self?.alpha = newValue ? 0.5 : 1 539 | self?.transform = newValue ? CGAffineTransform(scaleX: 0.95, y: 0.95) : .identity 540 | } 541 | super.isHighlighted = newValue 542 | }get{ 543 | return super.isHighlighted 544 | } 545 | } 546 | 547 | @objc func didClick(_ sender: UIButton) { 548 | self.action?(self) 549 | } 550 | } 551 | 552 | 553 | public extension UIScrollView { 554 | 555 | var isAtTop: Bool { 556 | return contentOffset.y <= verticalOffsetForTop 557 | } 558 | 559 | var isAtBottom: Bool { 560 | return contentOffset.y >= verticalOffsetForBottom 561 | } 562 | 563 | var verticalOffsetForTop: CGFloat { 564 | let topInset = contentInset.top 565 | return -topInset 566 | } 567 | 568 | var verticalOffsetForBottom: CGFloat { 569 | let scrollViewHeight = bounds.height 570 | let scrollContentSizeHeight = contentSize.height 571 | let bottomInset = contentInset.bottom 572 | let scrollViewBottomOffset = scrollContentSizeHeight + bottomInset - scrollViewHeight 573 | return scrollViewBottomOffset 574 | } 575 | 576 | } 577 | 578 | public enum PanDirection: Int { 579 | case up, down, left, right 580 | public var isVertical: Bool { return [.up, .down].contains(self) } 581 | public var isHorizontal: Bool { return !isVertical } 582 | } 583 | 584 | public extension UIPanGestureRecognizer { 585 | 586 | public var direction: PanDirection? { 587 | let velocity = self.velocity(in: view) 588 | let isVertical = abs(velocity.y) > abs(velocity.x) 589 | switch (isVertical, velocity.x, velocity.y) { 590 | case (true, _, let y) where y < 0: return .up 591 | case (true, _, let y) where y > 0: return .down 592 | case (false, let x, _) where x > 0: return .right 593 | case (false, let x, _) where x < 0: return .left 594 | default: return nil 595 | } 596 | } 597 | 598 | } 599 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Antonio Zaitoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AZDialogViewController 2 | A highly customizable alert dialog controller that mimics Snapchat's alert dialog. 3 | 4 | [![CocoaPods](https://img.shields.io/cocoapods/v/AZDialogView.svg)]() 5 | [![CocoaPods](https://img.shields.io/cocoapods/l/AZDialogView.svg)]() 6 | [![CocoaPods](https://img.shields.io/cocoapods/p/AZDialogView.svg)]() 7 | ## Screenshots 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## Installation 15 | 16 | 17 | ### CocoaPods: 18 | 19 | ```ruby 20 | pod 'AZDialogView' 21 | ``` 22 | 23 | ### Carthage: 24 | 25 | ```ruby 26 | github "Minitour/AZDialogViewController" 27 | ``` 28 | 29 | ### Manual: 30 | 31 | Simply drag and drop the ```Sources``` folder to your project. 32 | 33 | ## Usage 34 | 35 | Create an instance of AZDialogViewController: 36 | ```swift 37 | //init with optional parameters 38 | let dialog = AZDialogViewController(title: "Antonio Zaitoun", message: "minitour") 39 | ``` 40 | 41 | #### Customize: 42 | ```swift 43 | //set the title color 44 | dialog.titleColor = .black 45 | 46 | //set the message color 47 | dialog.messageColor = .black 48 | 49 | //set the dialog background color 50 | dialog.alertBackgroundColor = .white 51 | 52 | //set the gesture dismiss direction 53 | dialog.dismissDirection = .bottom 54 | 55 | //allow dismiss by touching the background 56 | dialog.dismissWithOutsideTouch = true 57 | 58 | //show seperator under the title 59 | dialog.showSeparator = false 60 | 61 | //set the seperator color 62 | dialog.separatorColor = UIColor.blue 63 | 64 | //enable/disable drag 65 | dialog.allowDragGesture = false 66 | 67 | //enable rubber (bounce) effect 68 | dialog.rubberEnabled = true 69 | 70 | //set dialog image 71 | dialog.image = UIImage(named: "icon") 72 | 73 | //enable/disable backgroud blur 74 | dialog.blurBackground = true 75 | 76 | //set the background blur style 77 | dialog.blurEffectStyle = .dark 78 | 79 | //set the dialog offset (from center) 80 | dialog.contentOffset = self.view.frame.height / 2.0 - dialog.estimatedHeight / 2.0 - 16.0 81 | 82 | ``` 83 | 84 | #### Add Actions: 85 | ```swift 86 | dialog.addAction(AZDialogAction(title: "Edit Name") { (dialog) -> (Void) in 87 | //add your actions here. 88 | dialog.dismiss() 89 | }) 90 | 91 | dialog.addAction(AZDialogAction(title: "Remove Friend") { (dialog) -> (Void) in 92 | //add your actions here. 93 | dialog.dismiss() 94 | }) 95 | 96 | dialog.addAction(AZDialogAction(title: "Block") { (dialog) -> (Void) in 97 | //add your actions here. 98 | dialog.dismiss() 99 | }) 100 | ``` 101 | 102 | #### Add Image: 103 | ```swift 104 | dialog.imageHandler = { (imageView) in 105 | imageView.image = UIImage(named: "your_image_here") 106 | imageView.contentMode = .scaleAspectFill 107 | return true //must return true, otherwise image won't show. 108 | } 109 | ``` 110 | 111 | ### Custom View 112 | ```swift 113 | /* 114 | customViewSizeRatio is the precentage of the height in respect to the width of the view. 115 | i.e. if the width is 100 and we set customViewSizeRatio to be 0.2 then the height will be 20. 116 | The default value is 0.0. 117 | */ 118 | dialog.customViewSizeRatio = 0.2 119 | 120 | //Add the subviews 121 | let container = dialog.container 122 | let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) 123 | dialog.container.addSubview(indicator) 124 | 125 | //add constraints 126 | indicator.translatesAutoresizingMaskIntoConstraints = false 127 | indicator.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true 128 | indicator.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true 129 | indicator.startAnimating() 130 | ``` 131 | 132 | #### Present The dialog: 133 | ```swift 134 | dialog.show(in: self) 135 | 136 | //or 137 | 138 | //Make sure to have animated set to false otherwise you'll see a delay. 139 | self.present(dialog, animated: false, completion: nil) 140 | ``` 141 | 142 | **Show with completion** 143 | ```swift 144 | dialog.show(in: self) { dialog in 145 | // show and then change the offset 146 | dialog.contentOffset = self.view.frame.height / 2.0 - dialog.estimatedHeight / 2.0 + 15 147 | } 148 | ``` 149 | 150 | ## Design 151 | 152 | #### Change Dialog Width 153 | 154 | This has been a requested feature and so I decided to add it. You can change the width of the dialog frame as a ratio in respect to the width of the main view. This can only be doing using the initalizer and the width cannot be modified afterwards. 155 | 156 | ```swift 157 | let dialog = AZDialogViewController(title: "Switch Account", message: "My Message", widthRatio: 1.0) 158 | ``` 159 | This will display a dialog which has the same width as the the controller it is presented in. 160 | 161 | The default value is `0.75` 162 | 163 | #### Customize Action Buttons Style: 164 | ```swift 165 | dialog.buttonStyle = { (button,height,position) in 166 | button.setBackgroundImage(UIImage.imageWithColor(self.primaryColorDark), for: .highlighted) 167 | button.setTitleColor(UIColor.white, for: .highlighted) 168 | button.setTitleColor(self.primaryColor, for: .normal) 169 | button.layer.masksToBounds = true 170 | button.layer.borderColor = self.primaryColor.cgColor 171 | } 172 | ``` 173 | 174 | #### Use custom UIButton sub-class: 175 | ```swift 176 | dialog.buttonInit = { index in 177 | //set a custom button only for the first index 178 | return index == 0 ? HighlightableButton() : nil 179 | } 180 | ``` 181 | 182 | #### Customize Tool Buttons: 183 | ```swift 184 | dialog.rightToolStyle = { (button) in 185 | button.setImage(UIImage(named: "ic_share"), for: []) 186 | button.tintColor = .lightGray 187 | return true 188 | } 189 | dialog.rightToolAction = { (button) in 190 | print("Share function") 191 | } 192 | 193 | dialog.leftToolStyle = { (button) in 194 | button.setImage(UIImage(named: "ic_share"), for: []) 195 | button.tintColor = .lightGray 196 | return true 197 | } 198 | dialog.leftToolAction = { (button) in 199 | print("Share function") 200 | } 201 | 202 | ``` 203 | 204 | #### Customize Cancel Button Style: 205 | ```swift 206 | dialog.cancelEnabled = true 207 | 208 | dialog.cancelButtonStyle = { (button,height) in 209 | button.tintColor = self.primaryColor 210 | button.setTitle("CANCEL", for: []) 211 | return true //must return true, otherwise cancel button won't show. 212 | } 213 | ``` 214 | 215 | 216 | -------------------------------------------------------------------------------- /Screenshots/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/Screenshots/demo.gif -------------------------------------------------------------------------------- /Screenshots/sc_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/Screenshots/sc_1.png -------------------------------------------------------------------------------- /Screenshots/sc_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/Screenshots/sc_2.png -------------------------------------------------------------------------------- /Screenshots/sc_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/Screenshots/sc_3.png -------------------------------------------------------------------------------- /Screenshots/sc_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/Screenshots/sc_4.png -------------------------------------------------------------------------------- /Sources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZDialogViewController/d7ed39d2e3cc6eda5d4d054ff9eb24eb5ada7be1/Sources/.DS_Store -------------------------------------------------------------------------------- /Sources/AZDialogViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DialogViewController.swift 3 | // test2 4 | // 5 | // Created by Tony Zaitoun on 2/24/17. 6 | // Copyright © 2017 Crofis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public typealias ActionHandler = ((AZDialogViewController)->(Void)) 12 | 13 | public struct DialogDefaults { 14 | 15 | /// The spaving ratio between the items in the stack (vertically) in respect to the height of the device. 16 | public static var spacingRatio: CGFloat = 0.012 17 | 18 | /// The font size of the title in respect to the device height. 19 | public static var titleFontSizeRatio: CGFloat = 0.0275 20 | 21 | /// The font size of the message in respect to the device height. 22 | public static var messageFontSizeRatio: CGFloat = 0.0225 23 | 24 | /// The height of the seperator. 25 | public static var seperatorHeight: CGFloat = 0.7 26 | 27 | /// The width of the buttons in respect to the device width. 28 | public static var buttonWidthRatio: CGFloat = 0.65 29 | 30 | /// The height of the button in respect to the height of the device. 31 | public static var buttonHeightRatio: CGFloat = 0.07 32 | 33 | /// The height of the cancel button in respect to the height of the device. 34 | public static var cancelButtonHeightRatio: CGFloat = 0.0449 35 | 36 | /// The width ratio of the dialog view in respect to the width of the device. 37 | public static var widthRatio: CGFloat = 0.75 38 | } 39 | 40 | @objc 41 | open class AZDialogViewController: UIViewController{ 42 | 43 | //MARK: - Private Properties 44 | 45 | /// The container that holds the image view. 46 | fileprivate var imageViewHolder: UIView! 47 | 48 | /// The image view. 49 | fileprivate var imageView: UIImageView! 50 | 51 | /// The Title Label. 52 | fileprivate var titleLabel: UILabel! 53 | 54 | /// The message label. 55 | fileprivate var messageLabel: UILabel! 56 | 57 | /// The cancel button. 58 | fileprivate var cancelButton: UIButton! 59 | 60 | /// The stackview that holds the buttons. 61 | fileprivate var buttonsStackView: UIStackView! 62 | 63 | /// The stack that holds all the view 64 | fileprivate var generalStackView: UIStackView! 65 | 66 | /// The seperatorView 67 | fileprivate var separatorView: UIView! 68 | 69 | /// The button on the left 70 | fileprivate var leftToolItem: UIButton! 71 | 72 | /// The button on the right 73 | fileprivate var rightToolItem: UIButton! 74 | 75 | /// The array which holds the actions 76 | fileprivate var actions: [AZDialogAction?]! 77 | 78 | /// The primary draggable view. 79 | fileprivate var baseView: BaseView! 80 | 81 | /// Did finish animating 82 | fileprivate var didInitAnimation = false 83 | 84 | /// The view holder's top anchor constraint 85 | fileprivate var imageViewHolderConstraint: NSLayoutConstraint! 86 | 87 | /// The image view holder's height constraint 88 | fileprivate var imageViewHolderHeightConstraint: NSLayoutConstraint! 89 | 90 | /// The general stack view top constraint 91 | fileprivate var generalStackViewTopConstraint: NSLayoutConstraint! 92 | 93 | /// The cancel button constraint 94 | fileprivate var cancelButtonHeightConstraint: NSLayoutConstraint!{ 95 | willSet{ 96 | if cancelButtonHeightConstraint != nil { cancelButtonHeightConstraint.isActive = false } 97 | }didSet{ 98 | cancelButtonHeightConstraint.isActive = true 99 | } 100 | } 101 | 102 | //The height constraint of the custom view `container` 103 | fileprivate var customViewHeightAnchor: NSLayoutConstraint!{ 104 | willSet{ 105 | if customViewHeightAnchor != nil { customViewHeightAnchor.isActive = false } 106 | }didSet{ 107 | customViewHeightAnchor.isActive = true 108 | } 109 | } 110 | 111 | fileprivate var baseViewCenterYConstraint: NSLayoutConstraint!{ 112 | willSet{ 113 | if baseViewCenterYConstraint != nil {baseViewCenterYConstraint.isActive = false } 114 | }didSet{ 115 | baseViewCenterYConstraint.isActive = true 116 | } 117 | } 118 | 119 | // Title of the dialog. 120 | fileprivate var mTitle: String?{ 121 | didSet{ 122 | if titleLabel != nil,generalStackView != nil { 123 | titleLabel.text = mTitle 124 | if mTitle == nil || mTitle?.count == 0{ 125 | titleLabel.isHidden = true 126 | separatorView.isHidden = true 127 | }else{ 128 | titleLabel.isHidden = false 129 | separatorView.isHidden = false 130 | } 131 | animateStackView() 132 | } 133 | } 134 | } 135 | 136 | // Message of the dialog. 137 | fileprivate var mMessage: String?{ 138 | didSet{ 139 | if messageLabel != nil,generalStackView != nil { 140 | messageLabel.text = mMessage 141 | if mMessage == nil || mMessage?.count == 0{ 142 | messageLabel.isHidden = true 143 | }else{ 144 | messageLabel.isHidden = false 145 | } 146 | animateStackView() 147 | } 148 | } 149 | } 150 | 151 | 152 | // Helper to get the real device width 153 | fileprivate var deviceWidth: CGFloat { 154 | let view = UIScreen.main 155 | let realValue = (view.bounds.width < view.bounds.height ? view.bounds.width : view.bounds.height) 156 | //let value = (realValue > 414 ? realValue / 2 : realValue) 157 | let value = UIDevice.current.userInterfaceIdiom == .pad ? realValue / 2 : realValue 158 | return value 159 | } 160 | 161 | // Helper to get the real device height 162 | fileprivate var deviceHeight: CGFloat { 163 | let view = UIScreen.main 164 | let safeAreaRemoval = parentSafeArea.sum 165 | let realValue = (view.bounds.width < view.bounds.height ? view.bounds.height : view.bounds.width) - safeAreaRemoval 166 | 167 | //let value = ((realValue > 736 && realValue < 818) ? realValue / 2 : realValue) 168 | let value = UIDevice.current.userInterfaceIdiom == .pad ? realValue / 2 : realValue 169 | return value 170 | } 171 | 172 | //used only if device is iPhone X 173 | fileprivate var parentSafeArea: UIEdgeInsets = .zero 174 | 175 | fileprivate var onAppearCompletion: ((AZDialogViewController) -> Void)? 176 | 177 | //MARK: - Getters 178 | 179 | @objc 180 | open fileprivate(set) var spacing: CGFloat = -1.0 181 | 182 | @objc 183 | open fileprivate(set) var stackSpacing: CGFloat = 0.0 184 | 185 | @objc 186 | open fileprivate(set) var sideSpacing: CGFloat = 20.0 187 | 188 | @objc 189 | open fileprivate(set) var buttonHeight: CGFloat = 0.0 190 | 191 | @objc 192 | open fileprivate(set) var cancelButtonHeight:CGFloat = 0.0 193 | 194 | @objc 195 | open fileprivate(set) var titleFontSize: CGFloat = 0.0 196 | 197 | @objc 198 | open fileprivate(set) var messageFontSize: CGFloat = 0.0 199 | 200 | @objc 201 | open fileprivate(set) var fontName: String = "Avenir-Light" 202 | 203 | @objc 204 | open fileprivate(set) var fontNameBold: String = "Avenir-Black" 205 | 206 | @objc 207 | open fileprivate(set) var widthRatio: CGFloat = DialogDefaults.widthRatio 208 | 209 | @objc 210 | open fileprivate(set) lazy var container: UIView = UIView() 211 | 212 | @objc 213 | open fileprivate(set) lazy var blurView: UIVisualEffectView = UIVisualEffectView() 214 | 215 | @objc 216 | open fileprivate(set) lazy var gestureRecognizer = UIPanGestureRecognizer( 217 | target: self, 218 | action: #selector(AZDialogViewController.handlePanGesture(_:)) 219 | ) 220 | 221 | @objc 222 | open var dialogView: UIView? { 223 | return baseView 224 | } 225 | 226 | @objc 227 | open var imageHolder: UIView { 228 | return imageViewHolder 229 | } 230 | 231 | //MARK: - Public 232 | 233 | @objc 234 | open var blurBackground: Bool = true 235 | 236 | @objc 237 | open var blurEffectStyle: UIBlurEffect.Style = .dark 238 | 239 | /// Show separator 240 | @objc 241 | open var showSeparator = true 242 | 243 | /// Separator Color 244 | @objc 245 | open var separatorColor: UIColor = UIColor(red: 208.0/255.0, green: 211.0/255.0, blue: 214.0/255.0, alpha: 1.0){ 246 | didSet{ 247 | separatorView?.backgroundColor = separatorColor 248 | } 249 | } 250 | 251 | /// Allow dismiss when touching outside of the dialog (touching the background) 252 | @objc 253 | open var dismissWithOutsideTouch = true 254 | 255 | /// Allow users to drag the dialog 256 | @objc 257 | open var allowDragGesture = true 258 | 259 | /// Button style closure, called when setting up an action. Where the 1st parameter is a reference to the button, the 2nd is the height of the button and the 3rd is the position of the button (index). 260 | @objc 261 | open var buttonStyle: ((UIButton,_ height: CGFloat,_ position: Int)->Void)? 262 | 263 | @objc 264 | open var buttonInit: ((_ index: Int) -> UIButton?)? 265 | 266 | /// Left Tool Style, is the style (closure) that is called when setting up the left tool item. Make sure to return true to show the item. 267 | @objc 268 | open var leftToolStyle: ((UIButton)->Bool)?{ 269 | didSet{ 270 | setupToolItems() 271 | } 272 | } 273 | 274 | /// Right Tool Style, is the style (closure) that is called when setting up the right tool item. Make sure to return true to show the item. 275 | @objc 276 | open var rightToolStyle: ((UIButton)->Bool)? { 277 | didSet{ 278 | setupToolItems() 279 | } 280 | } 281 | 282 | /// The action that is triggered when tool is clicked. 283 | @objc 284 | open var leftToolAction: ((UIButton)->Void)? 285 | 286 | /// The action that is triggered when tool is clicked. 287 | @objc 288 | open var rightToolAction: ((UIButton)->Void)? 289 | 290 | /// The cancel button style. where @UIButton is the refrence to the button, @CGFloat is the height of the button and @Bool is the value you return where true would show the button and false won't. 291 | @objc 292 | open var cancelButtonStyle: ((UIButton,CGFloat)->Bool)? 293 | 294 | /// Image handler, used when setting up an image using some sort of process. 295 | @objc 296 | open var imageHandler: ((UIImageView)->Bool)? 297 | 298 | /// Dismiss direction [top,bottom,both,none] 299 | @objc 300 | open var dismissDirection: AZDialogDismissDirection = .both 301 | 302 | /// Background alpha. default is 0.2 303 | @objc 304 | open var backgroundAlpha: CGFloat = 0.2 305 | 306 | /// Animation duration. 307 | @objc 308 | open var animationDuration: TimeInterval = 0.2 309 | 310 | /// The offset of the dialog. 311 | @objc 312 | open var contentOffset: CGFloat = 0.0 { 313 | didSet{ 314 | if let baseView = self.baseView{ 315 | baseViewCenterYConstraint = 316 | baseView.centerYAnchor.constraint(equalTo: view.centerYAnchor,constant: contentOffset) 317 | 318 | let center = self.view.center 319 | let offset = contentOffset 320 | UIView.animate(withDuration: animationDuration){ 321 | baseView.center = CGPoint(x: center.x ,y: center.y + offset) 322 | } 323 | baseView.lastLocation = baseView.center 324 | } 325 | } 326 | } 327 | 328 | /// Change the color of titleLabel 329 | @objc 330 | open var titleColor: UIColor? { 331 | didSet { 332 | titleLabel?.textColor = titleColor 333 | } 334 | } 335 | 336 | /// Change the color of messageLabel 337 | @objc 338 | open var messageColor: UIColor? { 339 | didSet { 340 | messageLabel?.textColor = messageColor 341 | } 342 | } 343 | 344 | /// Change the color of alert container 345 | @objc 346 | open var alertBackgroundColor: UIColor? { 347 | didSet { 348 | baseView?.backgroundColor = alertBackgroundColor 349 | } 350 | } 351 | 352 | /// Change the title of the dialog 353 | @objc 354 | open override var title: String?{ 355 | get{ 356 | return mTitle 357 | }set{ 358 | mTitle = newValue 359 | } 360 | } 361 | 362 | /// Change the message of the dialog 363 | @objc 364 | open var message: String? { 365 | get{ 366 | return mMessage 367 | }set{ 368 | mMessage = newValue 369 | } 370 | } 371 | 372 | /// Change the height of the custom view 373 | @objc 374 | open var customViewSizeRatio: CGFloat = 0.0{ 375 | didSet{ 376 | customViewHeightAnchor = 377 | container.heightAnchor 378 | .constraint(equalTo: container.widthAnchor, multiplier: customViewSizeRatio) 379 | 380 | let alpha: CGFloat = customViewSizeRatio > 0.0 ? 1.0 : 0.0 381 | animateStackView { [weak self] in 382 | self?.container.alpha = alpha 383 | } 384 | } 385 | } 386 | 387 | /// Change the text of the cancel button 388 | @objc 389 | open var cancelTitle: String = "CANCEL"{ 390 | didSet{ 391 | cancelButton?.setTitle(cancelTitle, for: []) 392 | } 393 | } 394 | 395 | /// Use this to hide/show the cancel button 396 | @objc 397 | open var cancelEnabled: Bool = false{ 398 | willSet{ 399 | //design the button if possible 400 | if newValue, cancelButton != nil, cancelButtonHeight != 0 { 401 | _ = cancelButtonStyle?(cancelButton,cancelButtonHeight) 402 | } 403 | 404 | } 405 | didSet{ 406 | //safe check if cancel button is not nil 407 | if cancelButton != nil { 408 | 409 | //update the height constraint 410 | cancelButtonHeightConstraint = 411 | cancelButton.heightAnchor 412 | .constraint(equalToConstant: cancelButtonHeight * (cancelEnabled ? 1.0 : 0.0)) 413 | 414 | //animate with alpha 415 | let alpha: CGFloat = (cancelEnabled ? 1.0 : 0.0) 416 | 417 | //copy values for use in closure 418 | let newValue = cancelEnabled 419 | if newValue {cancelButton.isHidden = false} 420 | 421 | //animate stack view with completion block and additional animations 422 | animateStackView(completionBlock: { [weak self] in 423 | if !newValue { 424 | self?.cancelButton.isHidden = true 425 | } 426 | 427 | }) {[weak self] in 428 | self?.cancelButton?.alpha = alpha 429 | 430 | } 431 | } 432 | 433 | 434 | } 435 | } 436 | 437 | /// The dialog's image. 438 | @objc 439 | open var image: UIImage?{ 440 | get{ 441 | return imageView?.image 442 | }set{ 443 | if let image = newValue{ 444 | //not nil 445 | if let _ = imageView.image { 446 | // old value not nil 447 | //new value not nil 448 | //only update the image 449 | imageView?.image = image 450 | }else { 451 | //old nil 452 | //new value not nil 453 | //update image and constraints 454 | imageView?.image = image 455 | updateConstraints(showImage: true) 456 | } 457 | }else{ 458 | //nil 459 | if let _ = imageView.image { 460 | //old value not nil 461 | self.imageView.image = nil 462 | updateConstraints(showImage: false) 463 | } 464 | } 465 | } 466 | } 467 | 468 | /// Enables the rubber effect you would see on a scroll view. 469 | @objc 470 | open var rubberEnabled: Bool = true 471 | 472 | /// Returns the estimated dialog frame height. 473 | @objc 474 | open var estimatedHeight: CGFloat { 475 | 476 | if isViewLoaded{ 477 | return baseView.bounds.height 478 | } 479 | 480 | func heightForView(_ text:String, font:UIFont, width:CGFloat) -> CGFloat{ 481 | let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)) 482 | label.numberOfLines = 0 483 | label.lineBreakMode = NSLineBreakMode.byWordWrapping 484 | label.font = font 485 | label.text = text 486 | label.textAlignment = .center 487 | 488 | label.sizeToFit() 489 | return label.frame.height 490 | } 491 | 492 | var titleFontSize: CGFloat 493 | var messageFontSize: CGFloat 494 | var buttonHeight: CGFloat 495 | var cancelButtonHeight: CGFloat 496 | 497 | let width: CGFloat = UIScreen.main.bounds.width 498 | let height: CGFloat = UIScreen.main.bounds.height 499 | let fontNameBold = self.fontNameBold 500 | let fontName = self.fontName 501 | let showSeparator = self.showSeparator 502 | let mTitle = self.mTitle 503 | let mMessage = self.mMessage 504 | let spacing = height * DialogDefaults.spacingRatio 505 | let sideSpacing: CGFloat = 20.0 506 | let showImage = image != nil 507 | let side: CGFloat = width / 8 508 | let labelWidth: CGFloat = width - side * 2 - sideSpacing 509 | let imageHolderSize: CGFloat = showImage ? CGFloat(Int((width - 2 * side) / 3)) : 0 510 | let imageMultiplier:CGFloat = showImage ? 0.0 : 1.0 511 | 512 | titleFontSize = height * DialogDefaults.titleFontSizeRatio 513 | let titleFont = UIFont(name: fontNameBold, size: titleFontSize) 514 | let titleHeight:CGFloat = mTitle == nil ? 0.0 : heightForView(mTitle!, font: titleFont!, width: labelWidth) 515 | 516 | let seperatorHeight: CGFloat = showSeparator ? DialogDefaults.seperatorHeight : 0.0 517 | let seperatorMultiplier: CGFloat = seperatorHeight > 0.0 ? 1.0 : 0.0 518 | 519 | messageFontSize = height * DialogDefaults.messageFontSizeRatio 520 | let labelFont = UIFont(name: fontName, size: messageFontSize)! 521 | let messageLableHeight:CGFloat = mMessage == nil ? 0 : heightForView(mMessage!, font: labelFont, width: labelWidth) 522 | let messageLabelMultiplier: CGFloat = messageLableHeight > 0 ? 1.0 : 0.0 523 | 524 | buttonHeight = CGFloat(Int(height * DialogDefaults.buttonHeightRatio)) 525 | let stackViewSize: CGFloat = CGFloat(self.actions.count) * buttonHeight + CGFloat(self.actions.count-1) * (stackSpacing) 526 | let stackMultiplier:CGFloat = stackViewSize > 0 ? 1.0 : 0.0 527 | 528 | cancelButtonHeight = height * DialogDefaults.cancelButtonHeightRatio 529 | let cancelMultiplier: CGFloat = cancelEnabled ? 1.0 : 0.0 530 | 531 | // Elaboration on spacingCalc: 532 | // 533 | // 3 * spacing : The space between titleLabel and baseView + space between stackView and cancelButton + space between baseView and cancelButton. 534 | // spacing * (seperatorMultiplier + messageLabelMultiplier + 2 * stackMultiplier) : This ranges from 0 to 4. 535 | // seperatorMultiplier: 0 if the seperator has no height, thus it will not have spacing. 536 | // messageLabelMultiplier: 0 if the messageLabel is empty and has no text which means it has no height thus it has no spacing. 537 | // 2 * stackMultiplier: 0 if the stack has no buttons. 2 if the stack has atleast 1 button. There is a 2 because the spacing between the stack and other views is 2 * spacing. 538 | // 539 | let spacingCalc = 3 * spacing + spacing * (imageMultiplier + seperatorMultiplier + messageLabelMultiplier + 2 * stackMultiplier) 540 | 541 | // The baseViewHeight: 542 | // Total Space Between Views 543 | // + Image Holder half height 544 | // + Title Height 545 | // + Seperator Height 546 | // + Message Label Height 547 | // + Stack View Height 548 | // + Cancel Button Height. 549 | var baseViewHeight:CGFloat = 0.0 550 | 551 | baseViewHeight += spacingCalc 552 | baseViewHeight += (2 * imageHolderSize / 3) 553 | baseViewHeight += titleHeight 554 | baseViewHeight += seperatorHeight 555 | baseViewHeight += messageLableHeight 556 | baseViewHeight += stackViewSize 557 | baseViewHeight += cancelButtonHeight * cancelMultiplier 558 | 559 | return baseViewHeight 560 | } 561 | 562 | /// Add an action button to the dialog. Make sure you add the actions before calling the .show() function. 563 | /// 564 | /// - Parameter action: The AZDialogAction. 565 | @objc 566 | open func addAction(_ action: AZDialogAction){ 567 | actions.append(action) 568 | 569 | if buttonsStackView != nil{ 570 | let button = setupButton(index: actions.count-1) 571 | self.buttonsStackView.addArrangedSubview(button) 572 | //button.frame = buttonsStackView.bounds 573 | button.center = CGPoint(x: buttonsStackView.bounds.midX,y: buttonsStackView.bounds.maxY) 574 | animateStackView() 575 | } 576 | } 577 | 578 | /// Remove a button at a certain index. 579 | /// 580 | /// - Parameter index: The index at which you would like to remove the action. 581 | @objc 582 | open func removeAction(at index: Int){ 583 | if actions.count <= index{ 584 | return 585 | } 586 | 587 | actions.remove(at: index) 588 | 589 | if buttonsStackView == nil { 590 | return 591 | } 592 | 593 | for subview in buttonsStackView.arrangedSubviews{ 594 | if subview.tag == index{ 595 | subview.removeFromSuperview() 596 | } 597 | } 598 | 599 | var i = 0 600 | for view in buttonsStackView.arrangedSubviews{ 601 | view.tag = i 602 | i+=1 603 | } 604 | 605 | animateStackView() 606 | } 607 | 608 | /// Remove all actions 609 | @objc 610 | open func removeAllActions(){ 611 | actions.removeAll() 612 | 613 | for view in buttonsStackView.arrangedSubviews{ 614 | view.removeFromSuperview() 615 | } 616 | 617 | animateStackView() 618 | } 619 | 620 | /// The primary fuction to present the dialog. 621 | /// 622 | /// - Parameter controller: The View controller in which you wish to present the dialog. 623 | @objc 624 | open func show(in controller: UIViewController, completion: ((AZDialogViewController)->Void)? = nil){ 625 | if #available(iOS 11.0, *) { 626 | parentSafeArea = controller.view.safeAreaInsets 627 | } 628 | onAppearCompletion = completion 629 | controller.present(self, animated: false,completion: nil) 630 | } 631 | 632 | 633 | /// Use this function to add additional sections to an existing dialog. Note that you can use this function only when the dialog has already appeared. 634 | /// 635 | /// - Parameter view: The view you wish to add. 636 | /// - Returns: Self. 637 | @objc 638 | @discardableResult 639 | open func section(view: UIView) -> Self { 640 | 641 | guard let baseView = baseView else { 642 | print("Dialog has not been displayed yet. Make sure to display it using \"show()\" before creating a section.") 643 | return self 644 | } 645 | 646 | let contentView = SectionView() 647 | contentView.isUserInteractionEnabled = true 648 | contentView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePanGestureForSection(_:)))) 649 | 650 | contentView.layer.cornerRadius = baseView.layer.cornerRadius 651 | contentView.layer.backgroundColor = alertBackgroundColor?.cgColor ?? UIColor.white.cgColor 652 | 653 | let stackView = UIStackView() 654 | stackView.distribution = .fill 655 | 656 | contentView.addSubview(stackView) 657 | stackView.translatesAutoresizingMaskIntoConstraints = false 658 | stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true 659 | stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true 660 | stackView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true 661 | let bottomStackAnchor = stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) 662 | bottomStackAnchor.priority = .init(rawValue: 999) 663 | bottomStackAnchor.isActive = true 664 | 665 | let lastView: UIView = baseView.subviews.last as? SectionView ?? baseView 666 | 667 | baseView.addSubview(contentView) 668 | contentView.translatesAutoresizingMaskIntoConstraints = false 669 | contentView.centerXAnchor.constraint(equalTo: baseView.centerXAnchor).isActive = true 670 | contentView.topAnchor.constraint(equalTo: lastView.bottomAnchor,constant: spacing).isActive = true 671 | contentView.widthAnchor.constraint(equalTo: baseView.widthAnchor).isActive = true 672 | 673 | stackView.addArrangedSubview(view) 674 | 675 | return self 676 | } 677 | 678 | 679 | /// Use this function to remove a section at a certain index. 680 | /// 681 | /// - Parameter index: The index of the section. 682 | open func removeSection(_ index: Int) { 683 | guard let baseView = baseView else { return } 684 | 685 | let sections = baseView.subviews.filter { $0 is SectionView } 686 | 687 | if sections.count > index { 688 | sections[index].removeFromSuperview() 689 | } 690 | } 691 | 692 | 693 | /// This function applies translation to the dialog with animation and then returns it to its original position. 694 | /// 695 | /// - Parameters: 696 | /// - translation: The translation value. 697 | /// - duration: The duration of the overall animation. 698 | open func applyAnimatedTranslation(_ translation: CGFloat, duration: TimeInterval = 0.35){ 699 | 700 | let yTranslation = baseView.lastLocation.y + translation 701 | 702 | UIView.animate(withDuration: duration / 2.0, animations: { [weak self] in 703 | guard let `self` = self else { return } 704 | self.baseView.center = CGPoint(x: self.baseView.lastLocation.x , y: yTranslation) 705 | }) { (completion) in 706 | var point = self.view.center 707 | point.y = point.y + self.contentOffset 708 | UIView.animate(withDuration: duration / 2.0) { 709 | [weak self] () -> Void in self?.baseView.center = point 710 | } 711 | } 712 | } 713 | 714 | //MARK: - Overriding methods 715 | 716 | /// The primary function to dismiss the dialog. 717 | /// 718 | /// - Parameters: 719 | /// - animated: Should it dismiss with animation? default is true. 720 | /// - completion: Completion block that is called after the controller is dismiss. 721 | override open func dismiss(animated: Bool = true,completion: (()->Void)?=nil){ 722 | if animated { 723 | UIView.animate(withDuration: animationDuration, animations: { [weak self] () -> Void in 724 | if let `self` = self,let baseView = self.baseView,let view = self.view{ 725 | baseView.center.y = view.bounds.maxY + baseView.bounds.midY 726 | if !self.blurBackground { 727 | view.backgroundColor = .clear 728 | }else { 729 | self.blurView.fadeOutEffect(withDuration: self.animationDuration) 730 | } 731 | 732 | } 733 | }, completion: { (complete) -> Void in 734 | super.dismiss(animated: false, completion: completion) 735 | }) 736 | }else{ 737 | super.dismiss(animated: false, completion: completion) 738 | } 739 | } 740 | 741 | 742 | /// Creates the view that the controller manages. 743 | override open func loadView() { 744 | super.loadView() 745 | 746 | baseView = BaseView() 747 | generalStackView = UIStackView() 748 | imageViewHolder = UIView() 749 | imageViewHolder.backgroundColor = .white 750 | imageView = UIImageView() 751 | buttonsStackView = UIStackView() 752 | titleLabel = UILabel() 753 | messageLabel = UILabel() 754 | cancelButton = UIButton(type: .system) 755 | separatorView = UIView() 756 | leftToolItem = UIButton(type: .system) 757 | rightToolItem = UIButton(type: .system) 758 | 759 | if spacing == -1 {spacing = deviceHeight * DialogDefaults.spacingRatio} 760 | let showImage = imageHandler?(imageView) ?? false 761 | 762 | // Disable translate auto resizing mask into constraints 763 | baseView.translatesAutoresizingMaskIntoConstraints = false 764 | generalStackView.translatesAutoresizingMaskIntoConstraints = false 765 | imageViewHolder.translatesAutoresizingMaskIntoConstraints = false 766 | imageView.translatesAutoresizingMaskIntoConstraints = false 767 | cancelButton.translatesAutoresizingMaskIntoConstraints = false 768 | leftToolItem.translatesAutoresizingMaskIntoConstraints = false 769 | rightToolItem.translatesAutoresizingMaskIntoConstraints = false 770 | 771 | view.addSubview(baseView) 772 | 773 | baseView.addSubview(generalStackView) 774 | baseView.addSubview(imageViewHolder) 775 | baseView.addSubview(cancelButton) 776 | 777 | generalStackView.addArrangedSubview(titleLabel) 778 | generalStackView.addArrangedSubview(separatorView) 779 | generalStackView.addArrangedSubview(messageLabel) 780 | generalStackView.addArrangedSubview(container) 781 | generalStackView.addArrangedSubview(buttonsStackView) 782 | 783 | imageViewHolder.addSubview(imageView) 784 | 785 | 786 | //setup image 787 | let imageMultiplier = setupImage(showImage: showImage) 788 | 789 | //Setup general stack view 790 | setupGeneralStackView(imageMultiplier: imageMultiplier) 791 | 792 | // Setup Title Label 793 | setupTitleLabel() 794 | 795 | // Setup Seperator Line 796 | setupSeparator() 797 | 798 | // Setup Message Label 799 | setupMessageLabel() 800 | 801 | //Setup Custom View 802 | setupContainer() 803 | 804 | // Setup Buttons (StackView) 805 | setupButtonsStack() 806 | 807 | // Setup Cancel button 808 | setupCancelButton() 809 | 810 | // Setup Base View 811 | setupBaseView() 812 | 813 | // Setup Tool Items 814 | setupToolItems() 815 | 816 | } 817 | 818 | /// Called after the controller'€™s view is loaded into memory. 819 | override open func viewDidLoad() { 820 | super.viewDidLoad() 821 | 822 | createGesutre(for: view) 823 | createGesutre(for: baseView) 824 | createGesutre(for: container) 825 | 826 | baseView.addGestureRecognizer(gestureRecognizer) 827 | 828 | baseView.layer.cornerRadius = 15 829 | baseView.layer.backgroundColor = alertBackgroundColor?.cgColor ?? UIColor.white.cgColor 830 | baseView.isHidden = true 831 | baseView.lastLocation = self.view.center 832 | baseView.lastLocation.y = baseView.lastLocation.y + contentOffset 833 | } 834 | 835 | /// Notifies the view controller that its view was added to a view hierarchy. 836 | override open func viewDidAppear(_ animated: Bool) { 837 | super.viewDidAppear(animated) 838 | if !didInitAnimation{ 839 | didInitAnimation = true 840 | baseView.center.y = self.view.bounds.maxY + baseView.bounds.midY 841 | baseView.isHidden = false 842 | 843 | if blurBackground { 844 | blurView.frame = self.view.frame 845 | blurView.autoresizingMask = [.flexibleWidth,.flexibleHeight] 846 | self.view.insertSubview(blurView, belowSubview: baseView) 847 | } 848 | 849 | UIView.animate(withDuration: animationDuration, 850 | delay: 0, 851 | usingSpringWithDamping: 1, 852 | initialSpringVelocity: 6.0, 853 | options: [], 854 | animations: { [weak self]() -> Void in 855 | if let `self` = self { 856 | self.baseView.center = self.view.center 857 | self.baseView.center.y = self.baseView.center.y + self.contentOffset 858 | if self.blurBackground { 859 | self.blurView.fadeInEffect(self.blurEffectStyle, withDuration: self.animationDuration) 860 | } else { 861 | let backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: self.backgroundAlpha) 862 | self.view.backgroundColor = backgroundColor 863 | } 864 | } 865 | // call apperance completion 866 | guard let `self` = self, let onAppearCompletion = self.onAppearCompletion else { return } 867 | onAppearCompletion(self) 868 | self.onAppearCompletion = nil 869 | }) 870 | } 871 | } 872 | 873 | /// Returns a newly initialized view controller with the nib file in the specified bundle. 874 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 875 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 876 | setup() 877 | } 878 | 879 | /// Not been implemented 880 | required public init?(coder aDecoder: NSCoder) { 881 | fatalError("init(coder:) has not been implemented") 882 | } 883 | 884 | /// Primary initializer 885 | /// 886 | /// - Parameters: 887 | /// - title: The title that will be set on the dialog. 888 | /// - message: The message that will be set on the dialog. 889 | /// - spacing: The vertical spacing between views. 890 | /// - stackSpacing: The vertical spacing between the buttons. 891 | /// - sideSpacing: The spacing on the side of the views (Between the views and the base dialog view) 892 | /// - titleFontSize: The title font size. 893 | /// - messageFontSize: The message font size. 894 | /// - buttonsHeight: The buttons' height. 895 | /// - cancelButtonHeight: The cancel button height. 896 | /// - fontName: The font name that will be used for the message label and the buttons. 897 | /// - boldFontName: The font name that will be used for the title. 898 | public convenience init(title: String?=nil, 899 | message: String?=nil, 900 | verticalSpacing spacing: CGFloat = -1, 901 | buttonSpacing stackSpacing:CGFloat = 10, 902 | sideSpacing: CGFloat = 20, 903 | titleFontSize: CGFloat = 0, 904 | messageFontSize: CGFloat = 0, 905 | buttonsHeight: CGFloat = 0, 906 | cancelButtonHeight: CGFloat = 0, 907 | fontName: String = "AvenirNext-Medium", 908 | boldFontName: String = "AvenirNext-DemiBold", 909 | widthRatio: CGFloat = DialogDefaults.widthRatio){ 910 | self.init(nibName: nil, bundle: nil) 911 | 912 | mTitle = title 913 | mMessage = message 914 | self.spacing = spacing 915 | self.stackSpacing = stackSpacing 916 | self.sideSpacing = sideSpacing 917 | self.titleFontSize = titleFontSize 918 | self.messageFontSize = messageFontSize 919 | self.buttonHeight = buttonsHeight 920 | self.cancelButtonHeight = cancelButtonHeight 921 | self.fontName = fontName 922 | self.fontNameBold = boldFontName 923 | self.widthRatio = widthRatio 924 | } 925 | 926 | /// Selector method - used to handle the dragging. 927 | /// 928 | /// - Parameter sender: The Gesture Recognizer. 929 | @objc internal func handlePanGesture(_ sender: UIPanGestureRecognizer){ 930 | 931 | //if panning is disabled return 932 | if !allowDragGesture{ 933 | return 934 | } 935 | 936 | //copy current offset 937 | let contentOffset = self.contentOffset 938 | 939 | //copy animation duration 940 | let animationDuration = self.animationDuration 941 | 942 | //get pan translation 943 | let translation = sender.translation(in: self.view) 944 | 945 | let yTranslation: CGFloat 946 | 947 | let centerWithOffset = view.center.y + contentOffset 948 | 949 | //check if should rubber: 950 | //view will rubber only if: 951 | //- rubber is enabled 952 | //- the view is above the center 953 | //- dismiss direction is bottom only. 954 | if rubberEnabled && 955 | dismissDirection == .bottom && 956 | baseView.center.y + translation.y < centerWithOffset { 957 | 958 | let distanceFromCenter = abs(centerWithOffset - (baseView.lastLocation.y + translation.y)) 959 | let precentage = sqrt(1.0 / (distanceFromCenter + 1.0)) + 0.10 960 | yTranslation = baseView.lastLocation.y + translation.y * precentage 961 | }else{ 962 | yTranslation = baseView.lastLocation.y + translation.y 963 | } 964 | 965 | //apply new center 966 | baseView.center = CGPoint(x: baseView.lastLocation.x , y: yTranslation) 967 | 968 | //create `return to center` function. 969 | let returnToCenter:(CGPoint,Bool)->Void = { [weak self] (finalPoint,animate) in 970 | var point = finalPoint 971 | point.y = point.y + contentOffset 972 | if !animate { 973 | self?.baseView.center = point 974 | return 975 | } 976 | UIView.animate(withDuration: 0.35, 977 | delay: 0, 978 | usingSpringWithDamping: 0.5, 979 | initialSpringVelocity: 2.0, 980 | options: [], 981 | animations: { [weak self] () -> Void in self?.baseView.center = point }, 982 | completion: nil) 983 | } 984 | 985 | //create `dismiss in direction` function. 986 | let dismissInDirection:(CGPoint)->Void = { [weak self] (finalPoint) in 987 | if let `self` = self { 988 | UIView.animate(withDuration: animationDuration, animations: { () -> Void in 989 | self.baseView.center = finalPoint 990 | self.view.backgroundColor = .clear 991 | 992 | if self.blurBackground { 993 | self.blurView.fadeOutEffect(withDuration: self.animationDuration) 994 | } 995 | 996 | }, completion: { (complete) -> Void in 997 | self.dismiss(animated: false, completion: nil) 998 | }) 999 | } 1000 | } 1001 | 1002 | //calculate final point, default is center. 1003 | var finalPoint = view.center 1004 | 1005 | //on gesture ended (by user) 1006 | if sender.state == .ended{ 1007 | 1008 | //calculate velocity 1009 | let velocity = sender.velocity(in: view) 1010 | 1011 | //calculate magnitude 1012 | let mag = sqrtf(Float(velocity.x * velocity.x) + Float(velocity.y * velocity.y)) 1013 | 1014 | //calculate slide multitude 1015 | let slideMult = mag / 200 1016 | 1017 | //check if to dismiss. 1018 | let dismissWithGesture = dismissDirection != .none ? true : false 1019 | 1020 | if dismissWithGesture && slideMult > 1 { 1021 | //dismiss 1022 | if velocity.y > 0{ 1023 | //dismiss downward 1024 | if dismissDirection == .bottom || dismissDirection == .both { 1025 | finalPoint.y = view.frame.maxY + baseView.bounds.midY + (imageView?.frame.height ?? 0.0) 1026 | dismissInDirection(finalPoint) 1027 | }else{ 1028 | returnToCenter(finalPoint,true) 1029 | } 1030 | }else{ 1031 | 1032 | //dismiss upward 1033 | if dismissDirection == .top || dismissDirection == .both { 1034 | finalPoint.y = -baseView.bounds.midY 1035 | dismissInDirection(finalPoint) 1036 | }else{ 1037 | returnToCenter(finalPoint,true) 1038 | } 1039 | } 1040 | }else{ 1041 | //return to center 1042 | returnToCenter(finalPoint,true) 1043 | } 1044 | } 1045 | 1046 | if sender.state == .cancelled || sender.state == .failed{ 1047 | returnToCenter(finalPoint,false) 1048 | } 1049 | 1050 | 1051 | } 1052 | 1053 | @objc internal func handlePanGestureForSection(_ sender: UIPanGestureRecognizer){ 1054 | handlePanGesture(sender) 1055 | } 1056 | 1057 | /// Selector method - used to handle view touch. 1058 | /// 1059 | /// - Parameter sender: The Gesture Recognizer. 1060 | @objc internal func handleTapGesture(_ sender: UITapGestureRecognizer){ 1061 | if sender.view is BaseView || sender.view == container{ 1062 | return 1063 | } 1064 | if dismissWithOutsideTouch{ 1065 | self.dismiss() 1066 | } 1067 | } 1068 | 1069 | /// Selector method - used when cancel button is clicked. 1070 | /// 1071 | /// - Parameter sender: The cancel button. 1072 | @objc internal func cancelAction(_ sender: UIButton){ 1073 | dismiss() 1074 | } 1075 | 1076 | /// Selector method - used when left tool item button is clicked. 1077 | /// 1078 | /// - Parameter sender: The left tool button. 1079 | @objc internal func handleLeftTool(_ sender: UIButton){ 1080 | leftToolAction?(sender) 1081 | } 1082 | 1083 | /// Selector method - used when right tool item button is clicked. 1084 | /// 1085 | /// - Parameter sender: The right tool button. 1086 | @objc internal func handleRightTool(_ sender: UIButton){ 1087 | rightToolAction?(sender) 1088 | } 1089 | 1090 | /// Selector method - used when one of the action buttons are clicked. 1091 | /// 1092 | /// - Parameter sender: Action Button 1093 | @objc 1094 | internal func handleAction(_ sender: UIButton){ 1095 | (actions[sender.tag]!.handler)?(self) 1096 | } 1097 | 1098 | /// Setup Image View 1099 | fileprivate func setupImage(showImage: Bool)->CGFloat{ 1100 | let imageHolderSize: CGFloat = showImage ? CGFloat(Int((deviceWidth - 2 * deviceWidth / 8) / 3)) : 0 1101 | let imageMultiplier:CGFloat = showImage ? 0.0 : 1.0 1102 | imageViewHolder.layer.cornerRadius = imageHolderSize / 2 1103 | imageViewHolder.layer.masksToBounds = true 1104 | imageViewHolder.backgroundColor = UIColor.white 1105 | 1106 | imageViewHolder.centerXAnchor.constraint(equalTo: baseView.centerXAnchor, constant: 0).isActive = true 1107 | 1108 | imageViewHolder.widthAnchor.constraint(equalTo: imageViewHolder.heightAnchor).isActive = true 1109 | 1110 | imageViewHolderConstraint = imageViewHolder.topAnchor.constraint(equalTo: baseView.topAnchor, constant: -imageHolderSize/3) 1111 | imageViewHolderConstraint.isActive = true 1112 | 1113 | imageViewHolderHeightConstraint = imageViewHolder.heightAnchor.constraint(equalToConstant: imageHolderSize) 1114 | imageViewHolderHeightConstraint.isActive = true 1115 | 1116 | imageView.layer.cornerRadius = (imageHolderSize - 2 * 5) / 2 1117 | imageView.layer.masksToBounds = true 1118 | 1119 | imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.0).isActive = true 1120 | imageView.widthAnchor.constraint(equalTo: imageViewHolder.widthAnchor, multiplier: 0.90).isActive = true 1121 | imageView.centerXAnchor.constraint(equalTo: imageViewHolder.centerXAnchor).isActive = true 1122 | imageView.centerYAnchor.constraint(equalTo: imageViewHolder.centerYAnchor).isActive = true 1123 | 1124 | return imageMultiplier 1125 | } 1126 | 1127 | /// Setup general stack view 1128 | fileprivate func setupGeneralStackView(imageMultiplier: CGFloat){ 1129 | generalStackView.distribution = .fill 1130 | generalStackView.alignment = .center 1131 | generalStackView.axis = .vertical 1132 | generalStackView.spacing = stackSpacing 1133 | 1134 | generalStackView.centerXAnchor.constraint(equalTo: baseView.centerXAnchor).isActive = true 1135 | generalStackView.leftAnchor.constraint(equalTo: baseView.leftAnchor,constant: sideSpacing/2).isActive = true 1136 | generalStackView.rightAnchor.constraint(equalTo: baseView.rightAnchor,constant: -sideSpacing/2).isActive = true 1137 | 1138 | generalStackViewTopConstraint = 1139 | generalStackView.topAnchor.constraint(equalTo: imageViewHolder.bottomAnchor, constant: spacing + spacing * imageMultiplier) 1140 | generalStackViewTopConstraint.isActive = true 1141 | } 1142 | 1143 | /// Setup Title Label 1144 | fileprivate func setupTitleLabel(){ 1145 | 1146 | if titleFontSize == 0 { titleFontSize = deviceHeight * DialogDefaults.titleFontSizeRatio } 1147 | let titleFont = UIFont(name: fontNameBold, size: titleFontSize) 1148 | //let titleHeight:CGFloat = mTitle == nil ? 0 : heightForView(mTitle!, font: titleFont!, width: deviceWidth * 0.6) 1149 | titleLabel.numberOfLines = 0 1150 | titleLabel.lineBreakMode = NSLineBreakMode.byWordWrapping 1151 | titleLabel.text = mTitle 1152 | titleLabel.font = titleFont 1153 | titleLabel.textAlignment = .center 1154 | titleLabel.widthAnchor.constraint(lessThanOrEqualTo: messageLabel.widthAnchor, multiplier: 1.0).isActive = true 1155 | if let titleColor = titleColor { 1156 | titleLabel.textColor = titleColor 1157 | } 1158 | } 1159 | 1160 | /// Setup Seperator Line 1161 | fileprivate func setupSeparator(){ 1162 | let seperatorHeight: CGFloat = self.showSeparator ? DialogDefaults.seperatorHeight : 0.0 1163 | separatorView.backgroundColor = separatorColor 1164 | separatorView.widthAnchor.constraint(equalTo: titleLabel.widthAnchor, multiplier: 1.0).isActive = true 1165 | separatorView.heightAnchor.constraint(equalToConstant: seperatorHeight).isActive = true 1166 | } 1167 | 1168 | /// Setup Message Label 1169 | fileprivate func setupMessageLabel(){ 1170 | if messageFontSize == 0 {messageFontSize = deviceHeight * DialogDefaults.messageFontSizeRatio} 1171 | let labelFont = UIFont(name: fontName, size: messageFontSize)! 1172 | messageLabel.numberOfLines = 0 1173 | messageLabel.lineBreakMode = NSLineBreakMode.byWordWrapping 1174 | messageLabel.font = labelFont 1175 | messageLabel.text = mMessage 1176 | messageLabel.textAlignment = .center 1177 | if let messageColor = messageColor { 1178 | messageLabel.textColor = messageColor 1179 | } 1180 | } 1181 | 1182 | /// Setup Custom View 1183 | fileprivate func setupContainer(){ 1184 | container.alpha = customViewSizeRatio > 0 ? 1.0 : 0.0 1185 | container.widthAnchor.constraint(equalTo: generalStackView.widthAnchor, multiplier: 1.0).isActive = true 1186 | customViewHeightAnchor = container.heightAnchor.constraint(equalTo: container.widthAnchor, multiplier: customViewSizeRatio) 1187 | } 1188 | 1189 | /// Setup Buttons (StackView) 1190 | fileprivate func setupButtonsStack(){ 1191 | buttonsStackView.distribution = .fill 1192 | buttonsStackView.alignment = .fill 1193 | buttonsStackView.axis = .vertical 1194 | buttonsStackView.spacing = stackSpacing 1195 | buttonsStackView.widthAnchor.constraint(equalTo: baseView.widthAnchor, multiplier: DialogDefaults.buttonWidthRatio).isActive = true 1196 | 1197 | for i in 0 ..< actions.count{ 1198 | let button = setupButton(index: i) 1199 | buttonsStackView.addArrangedSubview(button) 1200 | } 1201 | } 1202 | 1203 | /// A helper function to setup the button. 1204 | /// 1205 | /// - Parameters: 1206 | /// - index: The index for the current button. 1207 | /// 1208 | fileprivate func setupButton(index i:Int) -> UIButton{ 1209 | if buttonHeight == 0 {buttonHeight = CGFloat(Int(deviceHeight * DialogDefaults.buttonHeightRatio))} 1210 | 1211 | let finButton: UIButton 1212 | 1213 | if let button: UIButton = self.buttonInit?(i) { 1214 | finButton = button 1215 | }else { 1216 | let button = UIButton(type: .custom) 1217 | button.setTitleColor(button.tintColor, for: []) 1218 | button.layer.borderColor = button.tintColor.cgColor 1219 | button.layer.borderWidth = 1 1220 | button.layer.cornerRadius = buttonHeight/2 1221 | finButton = button 1222 | } 1223 | 1224 | let action = actions[i] 1225 | finButton.setTitle(action?.title, for: []) 1226 | finButton.isExclusiveTouch = true 1227 | 1228 | finButton.titleLabel?.font = UIFont(name: fontName, size: buttonHeight * 0.35) 1229 | finButton.translatesAutoresizingMaskIntoConstraints = false 1230 | let heightAnchor = finButton.heightAnchor.constraint(equalToConstant: buttonHeight) 1231 | 1232 | if actions.count == 1, i == 0 { 1233 | heightAnchor.priority = UILayoutPriority(rawValue: 999) 1234 | } 1235 | 1236 | heightAnchor.isActive = true 1237 | self.buttonStyle?(finButton,buttonHeight,i) 1238 | finButton.tag = i 1239 | finButton.addTarget(self, action: #selector(AZDialogViewController.handleAction(_:)), for: .touchUpInside) 1240 | return finButton 1241 | } 1242 | 1243 | /// Setup Cancel Button 1244 | fileprivate func setupCancelButton(){ 1245 | if cancelButtonHeight == 0 {cancelButtonHeight = deviceHeight * DialogDefaults.cancelButtonHeightRatio} 1246 | cancelButton.setTitle(cancelTitle, for: []) 1247 | cancelButton.titleLabel?.font = UIFont(name: fontName, size: cancelButtonHeight * 0.433) 1248 | let showCancelButton = (cancelButtonStyle?(cancelButton,cancelButtonHeight) ?? false) && cancelEnabled 1249 | let cancelMultiplier: CGFloat = showCancelButton ? 1.0 : 0.0 1250 | cancelButton.isHidden = (showCancelButton ? cancelButtonHeight : 0) <= 0 1251 | cancelButton.topAnchor.constraint(equalTo: generalStackView.bottomAnchor,constant: spacing).isActive = true 1252 | cancelButton.centerXAnchor.constraint(equalTo: baseView.centerXAnchor).isActive = true 1253 | cancelButton.bottomAnchor.constraint(equalTo: baseView.bottomAnchor,constant: -spacing).isActive = true 1254 | 1255 | cancelButtonHeightConstraint = cancelButton.heightAnchor.constraint(equalToConstant: cancelButtonHeight * cancelMultiplier) 1256 | cancelButton.addTarget(self, action: #selector(AZDialogViewController.cancelAction(_:)), for: .touchUpInside) 1257 | } 1258 | 1259 | /// Setup BaseView 1260 | fileprivate func setupBaseView(){ 1261 | self.baseView.isExclusiveTouch = true 1262 | baseView.widthAnchor.constraint(equalToConstant: deviceWidth * widthRatio).isActive = true 1263 | baseView.centerXAnchor.constraint(equalTo: view.centerXAnchor,constant: 0).isActive = true 1264 | baseViewCenterYConstraint = 1265 | baseView.centerYAnchor.constraint(equalTo: view.centerYAnchor,constant: contentOffset) 1266 | } 1267 | 1268 | // Setup Tool Items 1269 | fileprivate func setupToolItems(){ 1270 | 1271 | 1272 | 1273 | if let leftToolItem = leftToolItem, leftToolStyle?(leftToolItem) ?? false{ 1274 | leftToolItem.removeFromSuperview() 1275 | baseView.addSubview(leftToolItem) 1276 | leftToolItem.topAnchor.constraint(equalTo: baseView.topAnchor, constant: spacing*2).isActive = true 1277 | leftToolItem.leftAnchor.constraint(equalTo: baseView.leftAnchor,constant: spacing*2).isActive = true 1278 | leftToolItem.widthAnchor.constraint(equalTo: leftToolItem.heightAnchor).isActive = true 1279 | leftToolItem.addTarget(self, action: #selector(AZDialogViewController.handleLeftTool(_:)), for: .touchUpInside) 1280 | } 1281 | 1282 | if let rightToolItem = rightToolItem, rightToolStyle?(rightToolItem) ?? false{ 1283 | rightToolItem.removeFromSuperview() 1284 | baseView.addSubview(rightToolItem) 1285 | rightToolItem.topAnchor.constraint(equalTo: baseView.topAnchor, constant: spacing*2).isActive = true 1286 | rightToolItem.rightAnchor.constraint(equalTo: baseView.rightAnchor,constant: -spacing*2).isActive = true 1287 | rightToolItem.widthAnchor.constraint(equalTo: rightToolItem.heightAnchor).isActive = true 1288 | rightToolItem.heightAnchor.constraint(equalToConstant: 20).isActive = true 1289 | rightToolItem.addTarget(self, action: #selector(AZDialogViewController.handleRightTool(_:)), for: .touchUpInside) 1290 | } 1291 | } 1292 | 1293 | // Setup Controller Settings 1294 | fileprivate func setup(){ 1295 | actions = [AZDialogAction?]() 1296 | self.modalPresentationStyle = .overCurrentContext 1297 | self.modalTransitionStyle = .crossDissolve 1298 | } 1299 | 1300 | /// Helper function to change the dialog using an animation 1301 | /// 1302 | /// - Parameters: 1303 | /// - completionBlock: Block that is executed once aniamtions are finished. 1304 | /// - animations: Additional animations to execute. 1305 | fileprivate func animateStackView(completionBlock:(()->Void)?=nil,withOptionalAnimations animations: (()->Void)?=nil){ 1306 | UIView.animate(withDuration: animationDuration, animations: { [weak self] in 1307 | animations?() 1308 | self?.generalStackView?.setNeedsLayout() 1309 | self?.generalStackView?.layoutIfNeeded() 1310 | self?.baseView?.setNeedsLayout() 1311 | self?.baseView?.layoutIfNeeded() 1312 | }) { (bool) in 1313 | completionBlock?() 1314 | } 1315 | } 1316 | 1317 | /// Helper function to update the constraints 1318 | /// 1319 | /// - Parameters: 1320 | /// - showImage: shows the image if true 1321 | /// - completion: completion block, to execute after the animation 1322 | fileprivate func updateConstraints(showImage: Bool,completion: (()->Void)?=nil){ 1323 | 1324 | //update constraints only if they already exists. 1325 | if generalStackViewTopConstraint == nil || imageViewHolderConstraint == nil || imageViewHolderHeightConstraint == nil{ 1326 | return 1327 | } 1328 | 1329 | //remove existing constraint 1330 | generalStackViewTopConstraint.isActive = false 1331 | imageViewHolderConstraint.isActive = false 1332 | imageViewHolderHeightConstraint.isActive = false 1333 | 1334 | 1335 | //update constraints 1336 | let imageHolderSize: CGFloat = CGFloat(Int((deviceWidth - 2 * deviceWidth / 8) / 3)) 1337 | let constant = showImage ? imageHolderSize : 0.0 1338 | 1339 | generalStackViewTopConstraint = 1340 | generalStackView 1341 | .topAnchor.constraint(equalTo: imageViewHolder.bottomAnchor, constant: spacing + spacing * (showImage ? 0.0 : 1.0)) 1342 | 1343 | generalStackViewTopConstraint.isActive = true 1344 | 1345 | imageViewHolderConstraint = 1346 | imageViewHolder.topAnchor.constraint(equalTo: baseView.topAnchor, constant: -constant/3) 1347 | imageViewHolderConstraint.isActive = true 1348 | 1349 | imageViewHolderHeightConstraint = imageViewHolder.heightAnchor.constraint(equalToConstant: constant) 1350 | imageViewHolderHeightConstraint.isActive = true 1351 | 1352 | //setup layers 1353 | imageViewHolder.layer.cornerRadius = imageHolderSize / 2 1354 | imageViewHolder.layer.masksToBounds = true 1355 | 1356 | imageView.layer.cornerRadius = (imageHolderSize - 2 * 5) / 2 1357 | imageView.layer.masksToBounds = true 1358 | 1359 | //setup transform 1360 | let transform = showImage ? CGAffineTransform(scaleX: 0, y: 0) : .identity 1361 | imageViewHolder.transform = transform 1362 | imageView.transform = transform 1363 | 1364 | //animate changes 1365 | animateStackView(completionBlock: { 1366 | completion?() 1367 | }){ [weak self] in 1368 | 1369 | self?.imageViewHolder.alpha = showImage ? 1.0 : 0.0 1370 | let inverseTransform = showImage ? .identity : CGAffineTransform(scaleX: 0.1, y: 0.1) 1371 | self?.imageViewHolder.transform = inverseTransform 1372 | self?.imageView.transform = inverseTransform 1373 | } 1374 | } 1375 | 1376 | fileprivate func createGesutre(for view: UIView){ 1377 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(AZDialogViewController.handleTapGesture(_:))) 1378 | tapGesture.cancelsTouchesInView = false 1379 | view.addGestureRecognizer(tapGesture) 1380 | } 1381 | 1382 | } 1383 | 1384 | @objc 1385 | public enum AZDialogDismissDirection: Int { 1386 | case top 1387 | case bottom 1388 | case both 1389 | case none 1390 | } 1391 | 1392 | @objc 1393 | open class AZDialogAction: NSObject{ 1394 | 1395 | @objc 1396 | open var title: String? 1397 | 1398 | @objc 1399 | open var isEnabled: Bool = true 1400 | 1401 | @objc 1402 | open var handler: ActionHandler? 1403 | 1404 | @objc 1405 | public init(title: String,handler: ActionHandler? = nil){ 1406 | self.title = title 1407 | self.handler = handler 1408 | } 1409 | } 1410 | 1411 | fileprivate class BaseView: UIView{ 1412 | 1413 | var lastLocation = CGPoint(x: 0, y: 0) 1414 | 1415 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 1416 | lastLocation = self.center 1417 | super.touchesBegan(touches, with: event) 1418 | } 1419 | 1420 | override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 1421 | for subview in subviews { 1422 | if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) { 1423 | return true 1424 | } 1425 | } 1426 | return false 1427 | } 1428 | } 1429 | 1430 | fileprivate class SectionView: UIView {} 1431 | 1432 | fileprivate extension UIEdgeInsets{ 1433 | var sum: CGFloat { 1434 | return top + right + bottom + left 1435 | } 1436 | } 1437 | 1438 | fileprivate extension UIVisualEffectView { 1439 | 1440 | func fadeInEffect(_ style:UIBlurEffect.Style = .light, withDuration duration: TimeInterval = 1.0) { 1441 | if #available(iOS 10.0, *) { 1442 | let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) { 1443 | self.effect = UIBlurEffect(style: style) 1444 | } 1445 | 1446 | animator.startAnimation() 1447 | }else { 1448 | // Fallback on earlier versions 1449 | UIView.animate(withDuration: duration) { 1450 | self.effect = UIBlurEffect(style: style) 1451 | } 1452 | } 1453 | } 1454 | 1455 | func fadeOutEffect(withDuration duration: TimeInterval = 1.0, completion: (()->Void)? = nil) { 1456 | if #available(iOS 10.0, *) { 1457 | let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) { 1458 | self.effect = UIVisualEffect() 1459 | } 1460 | 1461 | animator.startAnimation() 1462 | animator.addCompletion { (position) in 1463 | completion?() 1464 | } 1465 | //animator.fractionComplete = 1 1466 | }else { 1467 | UIView.animate(withDuration: duration, animations: { 1468 | self.alpha = 0.0 1469 | }, completion: { (didComplete) in 1470 | completion?() 1471 | }) 1472 | } 1473 | } 1474 | 1475 | } 1476 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal --------------------------------------------------------------------------------