├── .gitignore ├── ALPopup.podspec ├── Assets ├── ALPopup.sketch ├── SocialBanner.png └── about.png ├── ExampleApp ├── ALPopup Example.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── ALPopup Example.xcscheme └── iOS App │ ├── App │ └── AppDelegate.swift │ ├── Controllers │ ├── BaseController │ │ └── BaseViewController.swift │ ├── ForNavigationController │ │ ├── DetailViewController.swift │ │ └── TableViewController.swift │ ├── HomeViewController.swift │ ├── InheritedController │ │ ├── InheritedFromCardController.swift │ │ ├── InheritedFromPopupController.swift.swift │ │ └── UIElements │ │ │ └── ContentViewForInheritedController.swift │ ├── Launch │ │ └── Base.lproj │ │ │ └── LaunchScreen.storyboard │ ├── ScrollControllers │ │ ├── HorizontallScrollController.swift │ │ └── MixedScrollViewController.swift │ └── Settings │ │ └── SettingsViewController.swift │ ├── Library │ └── UIElements │ │ └── ALFakeNavigationView.swift │ └── Sources │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── ios-marketing-1024x1024.png │ │ ├── ipad-20x20.png │ │ ├── ipad-20x20@2x.png │ │ ├── ipad-29x29.png │ │ ├── ipad-29x29@2x.png │ │ ├── ipad-40x40.png │ │ ├── ipad-40x40@2x.png │ │ ├── ipad-76x76.png │ │ ├── ipad-76x76@2x.png │ │ ├── ipad-83.5x83.5@2x.png │ │ ├── iphone-20x20@2x.png │ │ ├── iphone-20x20@3x.png │ │ ├── iphone-29x29@2x.png │ │ ├── iphone-29x29@3x.png │ │ ├── iphone-40x40@2x.png │ │ ├── iphone-40x40@3x.png │ │ ├── iphone-60x60@2x.png │ │ └── iphone-60x60@3x.png │ └── Contents.json │ └── Info.plist ├── LICENSE ├── Package.swift ├── README.md └── Sources └── ALPopup ├── ALPopup.swift ├── Controllers ├── ALBaseOverlayController.swift ├── Card │ ├── ALCardController.swift │ └── ALCardTemplateController.swift └── Popup │ ├── ALPopupController.swift │ └── ALPopupTemplateVontroller.swift ├── Extensions ├── UIColorExtension.swift ├── UIScreenExtension.swift └── UIViewExtension.swift ├── Helpers ├── ALAnimate.swift └── ALHaptic.swift ├── Model └── ALTemplateSettings.swift ├── Resources └── Assets.xcassets │ ├── Contents.json │ └── smallCloseButton.imageset │ ├── Contents.json │ ├── smallCloseButton.svg │ └── smallCloseButtonDark.svg ├── Source.swift └── UIElements ├── Controls ├── ALActionButton.swift └── ALBasicButton.swift └── Views ├── ALPopupContentView.swift └── ALTemplateView.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # osX files 2 | .DS_Store 3 | .Trashes 4 | Icon 5 | Icon? 6 | 7 | ## Xcode Patch 8 | *.xcworkspace 9 | *.xcuserdata 10 | *.xcodeproj/* 11 | !*.xcodeproj/project.pbxproj 12 | !*.xcodeproj/xcshareddata/ 13 | !*.xcworkspace/contents.xcworkspacedata 14 | /*.gcno 15 | 16 | ### Xcode Patch ### 17 | **/xcshareddata/WorkspaceSettings.xcsettings 18 | 19 | # Swift Package Manager 20 | .swiftpm 21 | /.build 22 | 23 | 24 | ## Build generated 25 | build/ 26 | DerivedData/ 27 | 28 | ## Various settings 29 | *.pbxuser 30 | !default.pbxuser 31 | *.mode1v3 32 | !default.mode1v3 33 | *.mode2v3 34 | !default.mode2v3 35 | *.perspectivev3 36 | !default.perspectivev3 37 | xcuserdata/ -------------------------------------------------------------------------------- /ALPopup.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = 'ALPopup' 4 | s.version = '1.2.0' 5 | s.summary = 'Native card controller like AirPods or Wi-Fi password sharing' 6 | 7 | s.homepage = 'https://github.com/alxrguz/ALPopup' 8 | s.source = { :git => 'https://github.com/alxrguz/ALPopup.git', :tag => s.version } 9 | s.license = { :type => "MIT", :file => "LICENSE" } 10 | 11 | s.author = { "Alexandr Guzenko" => "alxrguz@icloud.com" } 12 | 13 | s.swift_version = '5.1' 14 | s.ios.deployment_target = '11.0' 15 | 16 | s.platform = :ios 17 | s.ios.framework = 'UIKit' 18 | s.swift_version = '5.1' 19 | s.ios.deployment_target = "11.0" 20 | 21 | s.source_files = 'Sources/ALPopup/**/*.swift' 22 | 23 | s.pod_target_xcconfig = { 24 | "SWIFT_ACTIVE_COMPILATION_CONDITIONS" => "ALPOPUP_COCOAPODS" 25 | } 26 | 27 | s.resource_bundles = { 28 | "ALPopup" => [ 29 | "Sources/ALPopup/Resources/Assets.xcassets", 30 | ] 31 | } 32 | 33 | end 34 | -------------------------------------------------------------------------------- /Assets/ALPopup.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/Assets/ALPopup.sketch -------------------------------------------------------------------------------- /Assets/SocialBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/Assets/SocialBanner.png -------------------------------------------------------------------------------- /Assets/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/Assets/about.png -------------------------------------------------------------------------------- /ExampleApp/ALPopup Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BB3959F724EA71EE00B965F5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3959F624EA71EE00B965F5 /* AppDelegate.swift */; }; 11 | BB3959FB24EA71EE00B965F5 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3959FA24EA71EE00B965F5 /* HomeViewController.swift */; }; 12 | BB395A0024EA71F200B965F5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BB3959FF24EA71F200B965F5 /* Assets.xcassets */; }; 13 | BB395A0324EA71F200B965F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BB395A0124EA71F200B965F5 /* LaunchScreen.storyboard */; }; 14 | BB3C5BBB26C9A0DB000C7B24 /* ALFakeNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BBA26C9A0DB000C7B24 /* ALFakeNavigationView.swift */; }; 15 | BB3C5BC126C9B821000C7B24 /* ALRadioButtons in Frameworks */ = {isa = PBXBuildFile; productRef = BB3C5BC026C9B821000C7B24 /* ALRadioButtons */; }; 16 | BB3C5BC326C9B84C000C7B24 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BC226C9B84C000C7B24 /* BaseViewController.swift */; }; 17 | BB3C5BC626C9BECF000C7B24 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BC526C9BECF000C7B24 /* TableViewController.swift */; }; 18 | BB3C5BC826C9BEDA000C7B24 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BC726C9BEDA000C7B24 /* DetailViewController.swift */; }; 19 | BB3C5BCB26C9C4DC000C7B24 /* ALProgressView in Frameworks */ = {isa = PBXBuildFile; productRef = BB3C5BCA26C9C4DC000C7B24 /* ALProgressView */; }; 20 | BB3C5BCE26C9C561000C7B24 /* InheritedFromCardController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BCD26C9C561000C7B24 /* InheritedFromCardController.swift */; }; 21 | BB3C5BD026C9C56C000C7B24 /* InheritedFromPopupController.swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BCF26C9C56C000C7B24 /* InheritedFromPopupController.swift.swift */; }; 22 | BB3C5BD326C9C5A0000C7B24 /* ContentViewForInheritedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BD226C9C5A0000C7B24 /* ContentViewForInheritedController.swift */; }; 23 | BB3C5BD626C9CBAF000C7B24 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BD526C9CBAF000C7B24 /* SettingsViewController.swift */; }; 24 | BB3C5BDC26CA5BE0000C7B24 /* HorizontallScrollController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BDB26CA5BE0000C7B24 /* HorizontallScrollController.swift */; }; 25 | BB3C5BDE26CA62E1000C7B24 /* MixedScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3C5BDD26CA62E1000C7B24 /* MixedScrollViewController.swift */; }; 26 | BBA76B99276115EF004B75A1 /* ALPopup in Frameworks */ = {isa = PBXBuildFile; productRef = BBA76B98276115EF004B75A1 /* ALPopup */; }; 27 | BBC5A33B262824B60000ADDA /* ALDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = BBC5A33A262824B60000ADDA /* ALDevKit */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | BB3959F324EA71EE00B965F5 /* ALPopup Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ALPopup Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | BB3959F624EA71EE00B965F5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | BB3959FA24EA71EE00B965F5 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 34 | BB3959FF24EA71F200B965F5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 35 | BB395A0224EA71F200B965F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 36 | BB395A0424EA71F200B965F5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | BB3C5BBA26C9A0DB000C7B24 /* ALFakeNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALFakeNavigationView.swift; sourceTree = ""; }; 38 | BB3C5BC226C9B84C000C7B24 /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; 39 | BB3C5BC526C9BECF000C7B24 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 40 | BB3C5BC726C9BEDA000C7B24 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 41 | BB3C5BCD26C9C561000C7B24 /* InheritedFromCardController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InheritedFromCardController.swift; sourceTree = ""; }; 42 | BB3C5BCF26C9C56C000C7B24 /* InheritedFromPopupController.swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InheritedFromPopupController.swift.swift; sourceTree = ""; }; 43 | BB3C5BD226C9C5A0000C7B24 /* ContentViewForInheritedController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewForInheritedController.swift; sourceTree = ""; }; 44 | BB3C5BD526C9CBAF000C7B24 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 45 | BB3C5BDB26CA5BE0000C7B24 /* HorizontallScrollController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontallScrollController.swift; sourceTree = ""; }; 46 | BB3C5BDD26CA62E1000C7B24 /* MixedScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixedScrollViewController.swift; sourceTree = ""; }; 47 | BBA76B97276115E5004B75A1 /* ALPopup */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ALPopup; path = ..; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | BB3959F024EA71EE00B965F5 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | BBA76B99276115EF004B75A1 /* ALPopup in Frameworks */, 56 | BB3C5BC126C9B821000C7B24 /* ALRadioButtons in Frameworks */, 57 | BBC5A33B262824B60000ADDA /* ALDevKit in Frameworks */, 58 | BB3C5BCB26C9C4DC000C7B24 /* ALProgressView in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | /* End PBXFrameworksBuildPhase section */ 63 | 64 | /* Begin PBXGroup section */ 65 | BB3959EA24EA71EE00B965F5 = { 66 | isa = PBXGroup; 67 | children = ( 68 | BB8E4EC0271814EF00D58888 /* Packages */, 69 | BB3959F524EA71EE00B965F5 /* iOS App */, 70 | BB3959F424EA71EE00B965F5 /* Products */, 71 | BB9AA92E26AAFB8A0024E2C2 /* Frameworks */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | BB3959F424EA71EE00B965F5 /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | BB3959F324EA71EE00B965F5 /* ALPopup Example.app */, 79 | ); 80 | name = Products; 81 | sourceTree = ""; 82 | }; 83 | BB3959F524EA71EE00B965F5 /* iOS App */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | BB395A0C24EA726500B965F5 /* App */, 87 | BB395A0E24EA728200B965F5 /* Library */, 88 | BB395A0A24EA722A00B965F5 /* Controllers */, 89 | BB395A0B24EA723800B965F5 /* Sources */, 90 | ); 91 | path = "iOS App"; 92 | sourceTree = ""; 93 | }; 94 | BB395A0A24EA722A00B965F5 /* Controllers */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | BB3959FA24EA71EE00B965F5 /* HomeViewController.swift */, 98 | BB3C5BBE26C9B7D7000C7B24 /* BaseController */, 99 | BB3C5BC426C9BEA1000C7B24 /* ForNavigationController */, 100 | BB3C5BCC26C9C530000C7B24 /* InheritedController */, 101 | BB3C5BDA26CA5BC1000C7B24 /* ScrollControllers */, 102 | BB3C5BD426C9CB90000C7B24 /* Settings */, 103 | BB395A0D24EA727000B965F5 /* Launch */, 104 | ); 105 | path = Controllers; 106 | sourceTree = ""; 107 | }; 108 | BB395A0B24EA723800B965F5 /* Sources */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | BB3959FF24EA71F200B965F5 /* Assets.xcassets */, 112 | BB395A0424EA71F200B965F5 /* Info.plist */, 113 | ); 114 | path = Sources; 115 | sourceTree = ""; 116 | }; 117 | BB395A0C24EA726500B965F5 /* App */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | BB3959F624EA71EE00B965F5 /* AppDelegate.swift */, 121 | ); 122 | path = App; 123 | sourceTree = ""; 124 | }; 125 | BB395A0D24EA727000B965F5 /* Launch */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | BB395A0124EA71F200B965F5 /* LaunchScreen.storyboard */, 129 | ); 130 | path = Launch; 131 | sourceTree = ""; 132 | }; 133 | BB395A0E24EA728200B965F5 /* Library */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | BB3C5BB926C9A0C3000C7B24 /* UIElements */, 137 | ); 138 | path = Library; 139 | sourceTree = ""; 140 | }; 141 | BB3C5BB926C9A0C3000C7B24 /* UIElements */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | BB3C5BBA26C9A0DB000C7B24 /* ALFakeNavigationView.swift */, 145 | ); 146 | path = UIElements; 147 | sourceTree = ""; 148 | }; 149 | BB3C5BBE26C9B7D7000C7B24 /* BaseController */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | BB3C5BC226C9B84C000C7B24 /* BaseViewController.swift */, 153 | ); 154 | path = BaseController; 155 | sourceTree = ""; 156 | }; 157 | BB3C5BC426C9BEA1000C7B24 /* ForNavigationController */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | BB3C5BC526C9BECF000C7B24 /* TableViewController.swift */, 161 | BB3C5BC726C9BEDA000C7B24 /* DetailViewController.swift */, 162 | ); 163 | path = ForNavigationController; 164 | sourceTree = ""; 165 | }; 166 | BB3C5BCC26C9C530000C7B24 /* InheritedController */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | BB3C5BD126C9C583000C7B24 /* UIElements */, 170 | BB3C5BCD26C9C561000C7B24 /* InheritedFromCardController.swift */, 171 | BB3C5BCF26C9C56C000C7B24 /* InheritedFromPopupController.swift.swift */, 172 | ); 173 | path = InheritedController; 174 | sourceTree = ""; 175 | }; 176 | BB3C5BD126C9C583000C7B24 /* UIElements */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | BB3C5BD226C9C5A0000C7B24 /* ContentViewForInheritedController.swift */, 180 | ); 181 | path = UIElements; 182 | sourceTree = ""; 183 | }; 184 | BB3C5BD426C9CB90000C7B24 /* Settings */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | BB3C5BD526C9CBAF000C7B24 /* SettingsViewController.swift */, 188 | ); 189 | path = Settings; 190 | sourceTree = ""; 191 | }; 192 | BB3C5BDA26CA5BC1000C7B24 /* ScrollControllers */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | BB3C5BDB26CA5BE0000C7B24 /* HorizontallScrollController.swift */, 196 | BB3C5BDD26CA62E1000C7B24 /* MixedScrollViewController.swift */, 197 | ); 198 | path = ScrollControllers; 199 | sourceTree = ""; 200 | }; 201 | BB8E4EC0271814EF00D58888 /* Packages */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | BBA76B97276115E5004B75A1 /* ALPopup */, 205 | ); 206 | name = Packages; 207 | sourceTree = ""; 208 | }; 209 | BB9AA92E26AAFB8A0024E2C2 /* Frameworks */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | ); 213 | name = Frameworks; 214 | sourceTree = ""; 215 | }; 216 | /* End PBXGroup section */ 217 | 218 | /* Begin PBXNativeTarget section */ 219 | BB3959F224EA71EE00B965F5 /* ALPopup Example */ = { 220 | isa = PBXNativeTarget; 221 | buildConfigurationList = BB395A0724EA71F200B965F5 /* Build configuration list for PBXNativeTarget "ALPopup Example" */; 222 | buildPhases = ( 223 | BB3959EF24EA71EE00B965F5 /* Sources */, 224 | BB3959F024EA71EE00B965F5 /* Frameworks */, 225 | BB3959F124EA71EE00B965F5 /* Resources */, 226 | ); 227 | buildRules = ( 228 | ); 229 | dependencies = ( 230 | ); 231 | name = "ALPopup Example"; 232 | packageProductDependencies = ( 233 | BBC5A33A262824B60000ADDA /* ALDevKit */, 234 | BB3C5BC026C9B821000C7B24 /* ALRadioButtons */, 235 | BB3C5BCA26C9C4DC000C7B24 /* ALProgressView */, 236 | BBA76B98276115EF004B75A1 /* ALPopup */, 237 | ); 238 | productName = "Panda School"; 239 | productReference = BB3959F324EA71EE00B965F5 /* ALPopup Example.app */; 240 | productType = "com.apple.product-type.application"; 241 | }; 242 | /* End PBXNativeTarget section */ 243 | 244 | /* Begin PBXProject section */ 245 | BB3959EB24EA71EE00B965F5 /* Project object */ = { 246 | isa = PBXProject; 247 | attributes = { 248 | LastSwiftUpdateCheck = 1160; 249 | LastUpgradeCheck = 1250; 250 | ORGANIZATIONNAME = alxrguz; 251 | TargetAttributes = { 252 | BB3959F224EA71EE00B965F5 = { 253 | CreatedOnToolsVersion = 11.6; 254 | }; 255 | }; 256 | }; 257 | buildConfigurationList = BB3959EE24EA71EE00B965F5 /* Build configuration list for PBXProject "ALPopup Example" */; 258 | compatibilityVersion = "Xcode 9.3"; 259 | developmentRegion = en; 260 | hasScannedForEncodings = 0; 261 | knownRegions = ( 262 | en, 263 | Base, 264 | ); 265 | mainGroup = BB3959EA24EA71EE00B965F5; 266 | packageReferences = ( 267 | BBC5A339262824B60000ADDA /* XCRemoteSwiftPackageReference "ALDevKit" */, 268 | BB3C5BBF26C9B821000C7B24 /* XCRemoteSwiftPackageReference "ALRadioButtons" */, 269 | BB3C5BC926C9C4DB000C7B24 /* XCRemoteSwiftPackageReference "ALProgressView" */, 270 | ); 271 | productRefGroup = BB3959F424EA71EE00B965F5 /* Products */; 272 | projectDirPath = ""; 273 | projectRoot = ""; 274 | targets = ( 275 | BB3959F224EA71EE00B965F5 /* ALPopup Example */, 276 | ); 277 | }; 278 | /* End PBXProject section */ 279 | 280 | /* Begin PBXResourcesBuildPhase section */ 281 | BB3959F124EA71EE00B965F5 /* Resources */ = { 282 | isa = PBXResourcesBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | BB395A0324EA71F200B965F5 /* LaunchScreen.storyboard in Resources */, 286 | BB395A0024EA71F200B965F5 /* Assets.xcassets in Resources */, 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | /* End PBXResourcesBuildPhase section */ 291 | 292 | /* Begin PBXSourcesBuildPhase section */ 293 | BB3959EF24EA71EE00B965F5 /* Sources */ = { 294 | isa = PBXSourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | BB3959FB24EA71EE00B965F5 /* HomeViewController.swift in Sources */, 298 | BB3C5BD326C9C5A0000C7B24 /* ContentViewForInheritedController.swift in Sources */, 299 | BB3C5BC326C9B84C000C7B24 /* BaseViewController.swift in Sources */, 300 | BB3C5BD626C9CBAF000C7B24 /* SettingsViewController.swift in Sources */, 301 | BB3C5BCE26C9C561000C7B24 /* InheritedFromCardController.swift in Sources */, 302 | BB3959F724EA71EE00B965F5 /* AppDelegate.swift in Sources */, 303 | BB3C5BDC26CA5BE0000C7B24 /* HorizontallScrollController.swift in Sources */, 304 | BB3C5BC826C9BEDA000C7B24 /* DetailViewController.swift in Sources */, 305 | BB3C5BC626C9BECF000C7B24 /* TableViewController.swift in Sources */, 306 | BB3C5BD026C9C56C000C7B24 /* InheritedFromPopupController.swift.swift in Sources */, 307 | BB3C5BBB26C9A0DB000C7B24 /* ALFakeNavigationView.swift in Sources */, 308 | BB3C5BDE26CA62E1000C7B24 /* MixedScrollViewController.swift in Sources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | /* End PBXSourcesBuildPhase section */ 313 | 314 | /* Begin PBXVariantGroup section */ 315 | BB395A0124EA71F200B965F5 /* LaunchScreen.storyboard */ = { 316 | isa = PBXVariantGroup; 317 | children = ( 318 | BB395A0224EA71F200B965F5 /* Base */, 319 | ); 320 | name = LaunchScreen.storyboard; 321 | sourceTree = ""; 322 | }; 323 | /* End PBXVariantGroup section */ 324 | 325 | /* Begin XCBuildConfiguration section */ 326 | BB395A0524EA71F200B965F5 /* Debug */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ALWAYS_SEARCH_USER_PATHS = NO; 330 | CLANG_ANALYZER_NONNULL = YES; 331 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 333 | CLANG_CXX_LIBRARY = "libc++"; 334 | CLANG_ENABLE_MODULES = YES; 335 | CLANG_ENABLE_OBJC_ARC = YES; 336 | CLANG_ENABLE_OBJC_WEAK = YES; 337 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 338 | CLANG_WARN_BOOL_CONVERSION = YES; 339 | CLANG_WARN_COMMA = YES; 340 | CLANG_WARN_CONSTANT_CONVERSION = YES; 341 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 342 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 343 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 344 | CLANG_WARN_EMPTY_BODY = YES; 345 | CLANG_WARN_ENUM_CONVERSION = YES; 346 | CLANG_WARN_INFINITE_RECURSION = YES; 347 | CLANG_WARN_INT_CONVERSION = YES; 348 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 349 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 350 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 351 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 352 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 353 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 354 | CLANG_WARN_STRICT_PROTOTYPES = YES; 355 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 356 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 357 | CLANG_WARN_UNREACHABLE_CODE = YES; 358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 359 | COPY_PHASE_STRIP = NO; 360 | DEBUG_INFORMATION_FORMAT = dwarf; 361 | ENABLE_STRICT_OBJC_MSGSEND = YES; 362 | ENABLE_TESTABILITY = YES; 363 | GCC_C_LANGUAGE_STANDARD = gnu11; 364 | GCC_DYNAMIC_NO_PIC = NO; 365 | GCC_NO_COMMON_BLOCKS = YES; 366 | GCC_OPTIMIZATION_LEVEL = 0; 367 | GCC_PREPROCESSOR_DEFINITIONS = ( 368 | "DEBUG=1", 369 | "$(inherited)", 370 | ); 371 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 372 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 373 | GCC_WARN_UNDECLARED_SELECTOR = YES; 374 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 375 | GCC_WARN_UNUSED_FUNCTION = YES; 376 | GCC_WARN_UNUSED_VARIABLE = YES; 377 | IPHONEOS_DEPLOYMENT_TARGET = 13.6; 378 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 379 | MTL_FAST_MATH = YES; 380 | ONLY_ACTIVE_ARCH = YES; 381 | SDKROOT = iphoneos; 382 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 383 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 384 | }; 385 | name = Debug; 386 | }; 387 | BB395A0624EA71F200B965F5 /* Release */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | ALWAYS_SEARCH_USER_PATHS = NO; 391 | CLANG_ANALYZER_NONNULL = YES; 392 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 393 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 394 | CLANG_CXX_LIBRARY = "libc++"; 395 | CLANG_ENABLE_MODULES = YES; 396 | CLANG_ENABLE_OBJC_ARC = YES; 397 | CLANG_ENABLE_OBJC_WEAK = YES; 398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_COMMA = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 403 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 404 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 405 | CLANG_WARN_EMPTY_BODY = YES; 406 | CLANG_WARN_ENUM_CONVERSION = YES; 407 | CLANG_WARN_INFINITE_RECURSION = YES; 408 | CLANG_WARN_INT_CONVERSION = YES; 409 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 410 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 411 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 412 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 413 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 414 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 415 | CLANG_WARN_STRICT_PROTOTYPES = YES; 416 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 417 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 418 | CLANG_WARN_UNREACHABLE_CODE = YES; 419 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 420 | COPY_PHASE_STRIP = NO; 421 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 422 | ENABLE_NS_ASSERTIONS = NO; 423 | ENABLE_STRICT_OBJC_MSGSEND = YES; 424 | GCC_C_LANGUAGE_STANDARD = gnu11; 425 | GCC_NO_COMMON_BLOCKS = YES; 426 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 427 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 428 | GCC_WARN_UNDECLARED_SELECTOR = YES; 429 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 430 | GCC_WARN_UNUSED_FUNCTION = YES; 431 | GCC_WARN_UNUSED_VARIABLE = YES; 432 | IPHONEOS_DEPLOYMENT_TARGET = 13.6; 433 | MTL_ENABLE_DEBUG_INFO = NO; 434 | MTL_FAST_MATH = YES; 435 | SDKROOT = iphoneos; 436 | SWIFT_COMPILATION_MODE = wholemodule; 437 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 438 | VALIDATE_PRODUCT = YES; 439 | }; 440 | name = Release; 441 | }; 442 | BB395A0824EA71F200B965F5 /* Debug */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 446 | CODE_SIGN_STYLE = Automatic; 447 | DEVELOPMENT_TEAM = RW83L2JUQB; 448 | INFOPLIST_FILE = "iOS App/Sources/Info.plist"; 449 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 450 | LD_RUNPATH_SEARCH_PATHS = ( 451 | "$(inherited)", 452 | "@executable_path/Frameworks", 453 | ); 454 | PRODUCT_BUNDLE_IDENTIFIER = alxrguz.ALPopupController.example; 455 | PRODUCT_NAME = "$(TARGET_NAME)"; 456 | SWIFT_VERSION = 5.0; 457 | TARGETED_DEVICE_FAMILY = "1,2"; 458 | }; 459 | name = Debug; 460 | }; 461 | BB395A0924EA71F200B965F5 /* Release */ = { 462 | isa = XCBuildConfiguration; 463 | buildSettings = { 464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 465 | CODE_SIGN_STYLE = Automatic; 466 | DEVELOPMENT_TEAM = RW83L2JUQB; 467 | INFOPLIST_FILE = "iOS App/Sources/Info.plist"; 468 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 469 | LD_RUNPATH_SEARCH_PATHS = ( 470 | "$(inherited)", 471 | "@executable_path/Frameworks", 472 | ); 473 | PRODUCT_BUNDLE_IDENTIFIER = alxrguz.ALPopupController.example; 474 | PRODUCT_NAME = "$(TARGET_NAME)"; 475 | SWIFT_VERSION = 5.0; 476 | TARGETED_DEVICE_FAMILY = "1,2"; 477 | }; 478 | name = Release; 479 | }; 480 | /* End XCBuildConfiguration section */ 481 | 482 | /* Begin XCConfigurationList section */ 483 | BB3959EE24EA71EE00B965F5 /* Build configuration list for PBXProject "ALPopup Example" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | BB395A0524EA71F200B965F5 /* Debug */, 487 | BB395A0624EA71F200B965F5 /* Release */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | BB395A0724EA71F200B965F5 /* Build configuration list for PBXNativeTarget "ALPopup Example" */ = { 493 | isa = XCConfigurationList; 494 | buildConfigurations = ( 495 | BB395A0824EA71F200B965F5 /* Debug */, 496 | BB395A0924EA71F200B965F5 /* Release */, 497 | ); 498 | defaultConfigurationIsVisible = 0; 499 | defaultConfigurationName = Release; 500 | }; 501 | /* End XCConfigurationList section */ 502 | 503 | /* Begin XCRemoteSwiftPackageReference section */ 504 | BB3C5BBF26C9B821000C7B24 /* XCRemoteSwiftPackageReference "ALRadioButtons" */ = { 505 | isa = XCRemoteSwiftPackageReference; 506 | repositoryURL = "https://github.com/alxrguz/ALRadioButtons"; 507 | requirement = { 508 | kind = upToNextMajorVersion; 509 | minimumVersion = 1.2.1; 510 | }; 511 | }; 512 | BB3C5BC926C9C4DB000C7B24 /* XCRemoteSwiftPackageReference "ALProgressView" */ = { 513 | isa = XCRemoteSwiftPackageReference; 514 | repositoryURL = "https://github.com/alxrguz/ALProgressView"; 515 | requirement = { 516 | kind = upToNextMajorVersion; 517 | minimumVersion = 2.0.0; 518 | }; 519 | }; 520 | BBC5A339262824B60000ADDA /* XCRemoteSwiftPackageReference "ALDevKit" */ = { 521 | isa = XCRemoteSwiftPackageReference; 522 | repositoryURL = "https://github.com/alxrguz/ALDevKit"; 523 | requirement = { 524 | branch = master; 525 | kind = branch; 526 | }; 527 | }; 528 | /* End XCRemoteSwiftPackageReference section */ 529 | 530 | /* Begin XCSwiftPackageProductDependency section */ 531 | BB3C5BC026C9B821000C7B24 /* ALRadioButtons */ = { 532 | isa = XCSwiftPackageProductDependency; 533 | package = BB3C5BBF26C9B821000C7B24 /* XCRemoteSwiftPackageReference "ALRadioButtons" */; 534 | productName = ALRadioButtons; 535 | }; 536 | BB3C5BCA26C9C4DC000C7B24 /* ALProgressView */ = { 537 | isa = XCSwiftPackageProductDependency; 538 | package = BB3C5BC926C9C4DB000C7B24 /* XCRemoteSwiftPackageReference "ALProgressView" */; 539 | productName = ALProgressView; 540 | }; 541 | BBA76B98276115EF004B75A1 /* ALPopup */ = { 542 | isa = XCSwiftPackageProductDependency; 543 | productName = ALPopup; 544 | }; 545 | BBC5A33A262824B60000ADDA /* ALDevKit */ = { 546 | isa = XCSwiftPackageProductDependency; 547 | package = BBC5A339262824B60000ADDA /* XCRemoteSwiftPackageReference "ALDevKit" */; 548 | productName = ALDevKit; 549 | }; 550 | /* End XCSwiftPackageProductDependency section */ 551 | }; 552 | rootObject = BB3959EB24EA71EE00B965F5 /* Project object */; 553 | } 554 | -------------------------------------------------------------------------------- /ExampleApp/ALPopup Example.xcodeproj/xcshareddata/xcschemes/ALPopup Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | @UIApplicationMain 26 | class AppDelegate: UIResponder, UIApplicationDelegate { 27 | var window: UIWindow? 28 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 29 | window = UIWindow() 30 | window?.rootViewController = UINavigationController(rootViewController: HomeViewController()) 31 | window?.makeKeyAndVisible() 32 | return true 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/BaseController/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | import ALDevKit 25 | import ALRadioButtons 26 | 27 | final class BaseViewController: UIViewController { 28 | // MARK: - UI Elements 29 | private lazy var titleLabel = UILabel() 30 | private lazy var subtitleLabel = UILabel() 31 | private lazy var radioGroup = ALRadioGroup(items: [ 32 | .init(title: "First item", subtitle: "Subtitle for 1st item", detail: "Info"), 33 | .init(title: "Second item", subtitle: "Subtitle for 2nd item", detail: "Info"), 34 | ], style: .grouped) 35 | private lazy var actionButton = ALActionButton() 36 | 37 | // MARK: - Public Proporties 38 | var needCloseController: (() -> Void)? 39 | 40 | // MARK: - Private Proporties 41 | 42 | // MARK: - Life cycle 43 | init() { 44 | super.init(nibName: nil, bundle: nil) 45 | } 46 | 47 | required init?(coder: NSCoder) { 48 | fatalError("init(coder:) has not been implemented") 49 | } 50 | 51 | override func viewDidLoad() { 52 | super.viewDidLoad() 53 | setupView() 54 | setupConstraints() 55 | setupActions() 56 | } 57 | } 58 | 59 | // MARK: - Handlers 60 | private extension BaseViewController { 61 | 62 | } 63 | 64 | // MARK: - Open Methods 65 | extension BaseViewController { 66 | @objc func actionButtonTapped() { 67 | needCloseController?() 68 | } 69 | } 70 | 71 | // MARK: - Private Methods 72 | private extension BaseViewController { 73 | func setupActions() { 74 | actionButton.addTarget(self, action: #selector(actionButtonTapped), for: .touchUpInside) 75 | } 76 | } 77 | 78 | // MARK: - Navigation 79 | private extension BaseViewController { 80 | 81 | } 82 | 83 | // MARK: - Layout Setup 84 | private extension BaseViewController { 85 | func setupColors() { 86 | view.backgroundColor = .systemGroupedBackground 87 | titleLabel.textColor = .label 88 | subtitleLabel.textColor = .secondaryLabel 89 | } 90 | 91 | func setupView() { 92 | setupColors() 93 | 94 | titleLabel.do { 95 | $0.font = .preferredFont(forTextStyle: .title2, weight: .bold) 96 | $0.text = "Base View Controller" 97 | $0.textAlignment = .center 98 | } 99 | 100 | subtitleLabel.do { 101 | $0.font = .preferredFont(forTextStyle: .callout) 102 | $0.text = "This controller provides the ALRadioButtons library" 103 | $0.textAlignment = .center 104 | $0.numberOfLines = 0 105 | } 106 | 107 | actionButton.do { 108 | $0.setTitle("Choose") 109 | } 110 | } 111 | 112 | func setupConstraints() { 113 | view.addSubviews([titleLabel, subtitleLabel, radioGroup, actionButton]) 114 | 115 | let safeArea = view.safeAreaLayoutGuide 116 | 117 | titleLabel.snp.makeConstraints { 118 | $0.top.equalToSuperview().offset(35) 119 | $0.leading.trailing.equalTo(safeArea).inset(16) 120 | } 121 | 122 | subtitleLabel.snp.makeConstraints { 123 | $0.top.equalTo(titleLabel.snp.bottom).offset(6) 124 | $0.leading.trailing.equalTo(safeArea).inset(16) 125 | } 126 | 127 | radioGroup.snp.makeConstraints { 128 | $0.top.equalTo(subtitleLabel.snp.bottom).offset(25) 129 | $0.leading.trailing.equalTo(safeArea).inset(16) 130 | } 131 | 132 | actionButton.snp.makeConstraints { 133 | $0.top.equalTo(radioGroup.snp.bottom).offset(35) 134 | $0.leading.trailing.equalToSuperview().inset(16) 135 | $0.bottom.equalTo(safeArea).priority(999) 136 | $0.bottom.lessThanOrEqualToSuperview().offset(-17) 137 | } 138 | } 139 | } 140 | 141 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/ForNavigationController/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | final class DetailViewController: UIViewController { 26 | // MARK: - UI Elements 27 | lazy var titleLabel = UILabel() 28 | 29 | // MARK: - Life cycle 30 | init() { 31 | super.init(nibName: nil, bundle: nil) 32 | } 33 | 34 | required init?(coder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | setupView() 41 | setupConstraints() 42 | } 43 | } 44 | 45 | 46 | // MARK: - Layout Setup 47 | private extension DetailViewController { 48 | func setupColors() { 49 | view.backgroundColor = .systemBackground 50 | } 51 | 52 | func setupView() { 53 | setupColors() 54 | 55 | navigationItem.do { 56 | $0.title = "Detail" 57 | } 58 | 59 | titleLabel.do { 60 | $0.font = .systemFont(ofSize: 152, weight: .bold) 61 | } 62 | } 63 | 64 | func setupConstraints() { 65 | view.addSubview(titleLabel) 66 | 67 | titleLabel.snp.makeConstraints { 68 | $0.center.equalToSuperview() 69 | } 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/ForNavigationController/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | final class TableViewController: UITableViewController { 26 | 27 | // MARK: - Life cycle 28 | init() { 29 | super.init(nibName: nil, bundle: nil) 30 | } 31 | 32 | required init?(coder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | setupView() 39 | } 40 | } 41 | 42 | // MARK: - Open Methods 43 | extension TableViewController { 44 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 45 | return 10 46 | } 47 | 48 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 49 | let cell = UITableViewCell(style: .value1, reuseIdentifier: "") 50 | cell.textLabel?.text = String("Content #" + String(indexPath.row + 1)) 51 | cell.detailTextLabel?.text = "Detail" 52 | cell.accessoryType = .disclosureIndicator 53 | return cell 54 | } 55 | 56 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 57 | tableView.deselectRow(at: indexPath, animated: true) 58 | presentDetail(at: indexPath.row + 1) 59 | } 60 | } 61 | 62 | // MARK: - Navigation 63 | private extension TableViewController { 64 | func presentDetail(at index: Int) { 65 | let vc = DetailViewController() 66 | vc.titleLabel.text = String(index) 67 | navigationController?.pushViewController(vc) 68 | } 69 | } 70 | 71 | // MARK: - Layout Setup 72 | private extension TableViewController { 73 | func setupColors() { 74 | 75 | } 76 | 77 | func setupView() { 78 | setupColors() 79 | 80 | navigationItem.do { 81 | $0.title = "Navigation Example" 82 | } 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/HomeViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | import SparrowKit 25 | import ALDevKit 26 | import ALPopup 27 | 28 | final class HomeViewController: UIViewController { 29 | 30 | // MARK: - UI Elements 31 | 32 | private lazy var settingsButton = UIBarButtonItem(image: .init(systemName: "gear"), style: .plain, target: self, action: #selector(presentSettings)) 33 | private lazy var segmentControl = UISegmentedControl() 34 | private lazy var navigationView = ALFakeNavigationView() 35 | private lazy var tableView = ALFakeTableView() 36 | 37 | // MARK: - Private Proporties 38 | 39 | private var isEnableCloseButton = true 40 | private var dismissByTapAround = true 41 | private var allowsSwipeInteraction = true 42 | private var controllerStyle: ControllerStyle = .card 43 | 44 | // MARK: - Life cycle 45 | 46 | init() { 47 | super.init(nibName: nil, bundle: nil) 48 | } 49 | 50 | required init?(coder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | 54 | override func viewDidLoad() { 55 | super.viewDidLoad() 56 | setupView() 57 | setupConstraints() 58 | setupActions() 59 | } 60 | } 61 | 62 | // MARK: - Handlers 63 | 64 | private extension HomeViewController { 65 | @objc func segmentChanged() { 66 | guard let style = ControllerStyle(rawValue: segmentControl.selectedSegmentIndex) else { return } 67 | UIFeedbackGenerator.impactOccurred(.selectionChanged) 68 | controllerStyle = style 69 | } 70 | } 71 | 72 | 73 | // MARK: - Private Methods 74 | 75 | private extension HomeViewController { 76 | func setupActions() { 77 | segmentControl.addTarget(self, action: #selector(segmentChanged), for: .valueChanged) 78 | } 79 | 80 | func configureALPopup(_ controller: ALBaseOverlayController) { 81 | controller.isEnableCloseButton = isEnableCloseButton 82 | controller.dismissByTapAround = dismissByTapAround 83 | controller.allowsSwipeInteraction = allowsSwipeInteraction 84 | } 85 | } 86 | 87 | // MARK: - Navigation 88 | 89 | private extension HomeViewController { 90 | func showTemplate() { 91 | let vc: ALBaseOverlayController 92 | 93 | switch controllerStyle { 94 | case .popup: 95 | let popupVC = ALPopup.popup(template: .init( 96 | title: "Wi-Fi Password", 97 | subtitle: "Share the Wi-Fi password?\n(Template controller Example)", 98 | image: UIImage(systemName: "wifi"), 99 | privaryButtonTitle: "Share Password", 100 | secondaryButtonTitle: "Not Now") 101 | ) 102 | 103 | popupVC.tempateView.do { 104 | $0.primaryButton.applyDefaultAppearance(with: .init(content: .systemBlue, background: .systemGroupedBackground)) 105 | $0.secondaryButton.applyDefaultAppearance(with: .init(content: .systemGray2, background: .clear)) 106 | $0.imageView.tintColor = .secondarySystemFill 107 | $0.primaryButtonAction = { 108 | popupVC.pop() 109 | } 110 | $0.secondaryButtonAction = { 111 | popupVC.pop() 112 | } 113 | } 114 | 115 | vc = popupVC 116 | case .card: 117 | let cardVC = ALPopup.card(template: .init( 118 | title: "Wi-Fi Password", 119 | subtitle: "Do u want to share the Wi-Fi password?\n(Template controller Example)", 120 | image: UIImage(systemName: "wifi"), 121 | privaryButtonTitle: "Share Password", 122 | secondaryButtonTitle: nil) 123 | ) 124 | 125 | cardVC.tempateView.do { 126 | $0.primaryButton.applyDefaultAppearance(with: .init(content: .systemBlue, background: .systemGroupedBackground)) 127 | $0.imageView.tintColor = .secondarySystemFill 128 | $0.primaryButtonAction = { 129 | cardVC.pop() 130 | } 131 | $0.secondaryButtonAction = { 132 | cardVC.pop() 133 | } 134 | } 135 | 136 | vc = cardVC 137 | } 138 | 139 | configureALPopup(vc) 140 | vc.push(from: self) 141 | } 142 | 143 | func showBaseController() { 144 | let vc: ALBaseOverlayController 145 | let baseViewController = BaseViewController() 146 | 147 | switch controllerStyle { 148 | case .popup: 149 | let popupVC = ALPopup.popup(controller: baseViewController) 150 | vc = popupVC 151 | case .card: 152 | let cardVC = ALPopup.card(controller: baseViewController) 153 | vc = cardVC 154 | } 155 | 156 | baseViewController.needCloseController = { vc.pop() } 157 | 158 | configureALPopup(vc) 159 | vc.push(from: self) 160 | } 161 | 162 | func showNavigationController() { 163 | let vc: ALBaseOverlayController 164 | let navVC = UINavigationController(rootViewController: TableViewController()) 165 | navVC.view.snp.makeConstraints { 166 | $0.height.equalTo(380) 167 | } 168 | 169 | switch controllerStyle { 170 | case .popup: 171 | let popupVC = ALPopup.popup(controller: navVC) 172 | vc = popupVC 173 | case .card: 174 | let cardVC = ALPopup.card(controller: navVC) 175 | vc = cardVC 176 | } 177 | 178 | configureALPopup(vc) 179 | vc.push(from: self) 180 | } 181 | 182 | func showInheritedController() { 183 | let vc: ALBaseOverlayController 184 | 185 | switch controllerStyle { 186 | case .popup: 187 | vc = InheritedFromPopupController() 188 | case .card: 189 | vc = InheritedFromCardController() 190 | } 191 | configureALPopup(vc) 192 | vc.push(from: self) 193 | } 194 | 195 | func showHorizontalScrrolledController() { 196 | let vc: ALBaseOverlayController 197 | 198 | switch controllerStyle { 199 | case .popup: 200 | vc = ALPopup.popup(controller: HorizontallScrollController()) 201 | case .card: 202 | vc = ALPopup.card(controller: HorizontallScrollController()) 203 | } 204 | configureALPopup(vc) 205 | vc.push(from: self) 206 | } 207 | 208 | func showMixedScrrolledController() { 209 | let vc: ALBaseOverlayController 210 | 211 | switch controllerStyle { 212 | case .popup: 213 | vc = ALPopup.popup(controller: MixedScrollViewController()) 214 | case .card: 215 | vc = ALPopup.card(controller: MixedScrollViewController()) 216 | } 217 | configureALPopup(vc) 218 | vc.push(from: self) 219 | } 220 | 221 | @objc func presentSettings() { 222 | let vc = SettingsViewController( 223 | isEnableCloseButton: isEnableCloseButton, 224 | dismissByTapAround: dismissByTapAround, 225 | allowsSwipeInteraction: allowsSwipeInteraction) 226 | 227 | vc.allowsSwipeInteractionChanged = { [weak self] value in 228 | self?.allowsSwipeInteraction = value 229 | } 230 | 231 | vc.isEnableCloseButtonChanged = { [weak self] value in 232 | self?.isEnableCloseButton = value 233 | } 234 | 235 | vc.dismissByTapAroundChanged = { [weak self] value in 236 | self?.dismissByTapAround = value 237 | } 238 | 239 | let cardVC = ALPopup.card(controller: vc) 240 | 241 | cardVC.push(from: self) 242 | } 243 | } 244 | 245 | // MARK: - Layout Setup 246 | private extension HomeViewController { 247 | func setupColors() { 248 | view.backgroundColor = .systemGroupedBackground 249 | } 250 | 251 | func setupView() { 252 | setupColors() 253 | 254 | navigationController?.navigationBar.do { 255 | $0.makeTransparent() 256 | } 257 | 258 | navigationItem.do { 259 | $0.title = "ALPopupController" 260 | $0.rightBarButtonItem = settingsButton 261 | } 262 | 263 | segmentControl.do { segment in 264 | ControllerStyle.allCases.forEach { 265 | segment.insertSegment(withTitle: $0.title, at: $0.rawValue, animated: false) 266 | } 267 | segment.selectedSegmentIndex = controllerStyle.rawValue 268 | } 269 | 270 | let templateSetction = ALFakeTableSection().do { 271 | $0.title = "Template".uppercased() 272 | $0.footer = "The controller is already contained in the library, you only need to configure the content" 273 | let cell = ALFakeCell().do { 274 | $0.titleLabel.text = "Show template" 275 | $0.titleLabel.textColor = .systemBlue 276 | $0.tapped = { [weak self] _ in self?.showTemplate() } 277 | } 278 | $0.addCell(cell) 279 | } 280 | 281 | let viewControllerSection = ALFakeTableSection().do { 282 | $0.title = "View Controller".uppercased() 283 | $0.footer = "Show any of your controller. Everything happens automatically, you only need to specify your controller" 284 | // 285 | let cell = ALFakeCell().do { 286 | $0.titleLabel.text = "Show View Controller" 287 | $0.titleLabel.textColor = .systemBlue 288 | $0.tapped = { [weak self] _ in self?.showBaseController() } 289 | } 290 | $0.addCell(cell) 291 | } 292 | 293 | let navigationControllerSection = ALFakeTableSection().do { 294 | $0.title = "Navigation Controller".uppercased() 295 | $0.footer = "Show any of your Navigation controller. Be careful not to forget to specify the height of the controller" 296 | let cell = ALFakeCell().do { 297 | $0.titleLabel.text = "Show Navigation Controller" 298 | $0.titleLabel.textColor = .systemBlue 299 | $0.tapped = { [weak self] _ in self?.showNavigationController() } 300 | } 301 | $0.addCell(cell) 302 | } 303 | 304 | let inheritedControllerSection = ALFakeTableSection().do { 305 | $0.title = "Inherited Controller".uppercased() 306 | $0.footer = "In this example, you yourself inherit from the desired controller and manage it." 307 | let cell = ALFakeCell().do { 308 | $0.titleLabel.text = "Show Inherited Controller" 309 | $0.titleLabel.textColor = .systemBlue 310 | $0.tapped = { [weak self] _ in self?.showInheritedController() } 311 | } 312 | $0.addCell(cell) 313 | } 314 | 315 | let scrolledControllerSection = ALFakeTableSection().do { 316 | $0.title = "Scrolled Controller".uppercased() 317 | $0.footer = "This section is only for performing tests with scrollViews" 318 | let cell = ALFakeCell().do { 319 | $0.titleLabel.text = "Show Horizontal Scroll" 320 | $0.titleLabel.textColor = .systemBlue 321 | $0.tapped = { [weak self] _ in self?.showHorizontalScrrolledController() } 322 | } 323 | 324 | let cell1 = ALFakeCell().do { 325 | $0.titleLabel.text = "Show Mexid Scroll" 326 | $0.titleLabel.textColor = .systemBlue 327 | $0.tapped = { [weak self] _ in self?.showMixedScrrolledController() } 328 | } 329 | $0.addCells([cell, cell1]) 330 | } 331 | 332 | tableView.do { 333 | $0.scrollView.contentInset = .init(top: 75, left: 0, bottom: 0, right: 0) 334 | $0.sectionsStack.spacing = 35 335 | $0.addSections([templateSetction, viewControllerSection, navigationControllerSection, inheritedControllerSection]) 336 | } 337 | } 338 | 339 | func setupConstraints() { 340 | view.addSubviews([tableView, navigationView, segmentControl]) 341 | 342 | let safeArea = view.safeAreaLayoutGuide 343 | 344 | tableView.snp.makeConstraints { 345 | $0.edges.equalToSuperview() 346 | } 347 | 348 | navigationView.snp.makeConstraints { 349 | $0.top.leading.trailing.equalToSuperview() 350 | $0.bottom.equalTo(segmentControl).offset(10) 351 | } 352 | 353 | segmentControl.snp.makeConstraints { 354 | $0.top.equalTo(safeArea).offset(5) 355 | $0.leading.trailing.equalTo(safeArea).inset(16) 356 | } 357 | } 358 | } 359 | 360 | 361 | // MARK: - Data Structures 362 | 363 | extension HomeViewController { 364 | enum ControllerStyle: Int, CaseIterable { 365 | case card, popup 366 | 367 | var title: String { 368 | switch self { 369 | case .card: return "Card" 370 | case .popup: return "Popup" 371 | } 372 | } 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/InheritedController/InheritedFromCardController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | import ALPopup 25 | 26 | final class InheritedFromCardController: ALCardController { 27 | // MARK: - UI Elements 28 | 29 | private lazy var controllerView = ContentViewForInheritedController() 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | setupView() 34 | setupConstraints() 35 | } 36 | 37 | override func viewDidAppear(_ animated: Bool) { 38 | super.viewDidAppear(animated) 39 | controllerView.setRandomProgress() 40 | } 41 | } 42 | 43 | // MARK: - Layout Setup 44 | private extension InheritedFromCardController { 45 | func setupColors() { 46 | 47 | } 48 | 49 | func setupView() { 50 | setupColors() 51 | } 52 | 53 | func setupConstraints() { 54 | contentView.addSubview(controllerView) 55 | 56 | controllerView.snp.makeConstraints { 57 | $0.edges.equalToSuperview() 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/InheritedController/InheritedFromPopupController.swift.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | import ALPopup 25 | 26 | final class InheritedFromPopupController: ALPopupController { 27 | // MARK: - UI Elements 28 | 29 | private lazy var controllerView = ContentViewForInheritedController() 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | setupView() 34 | setupConstraints() 35 | } 36 | 37 | override func viewDidAppear(_ animated: Bool) { 38 | super.viewDidAppear(animated) 39 | controllerView.setRandomProgress() 40 | } 41 | } 42 | 43 | // MARK: - Layout Setup 44 | private extension InheritedFromPopupController { 45 | func setupColors() { 46 | 47 | } 48 | 49 | func setupView() { 50 | setupColors() 51 | } 52 | 53 | func setupConstraints() { 54 | contentView.addSubview(controllerView) 55 | 56 | controllerView.snp.makeConstraints { 57 | $0.edges.equalToSuperview() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/InheritedController/UIElements/ContentViewForInheritedController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewForInheritedController.swift 3 | // ALPopupController Example 4 | // 5 | // Created by Alexandr Guzenko on 16.08.2021. 6 | // Copyright © 2021 alxrguz. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ALDevKit 11 | import ALProgressView 12 | 13 | final class ContentViewForInheritedController: UIView { 14 | 15 | // MARK: - UI Elements 16 | 17 | private lazy var titleLabel = UILabel() 18 | private lazy var subtitleLabel = UILabel() 19 | private lazy var percentLabel = UILabel() 20 | private lazy var progressRing = ALProgressRing() 21 | private lazy var progressBar = ALProgressBar() 22 | private lazy var actionButton = ALActionButton() 23 | 24 | // MARK: - Public Proporties 25 | 26 | // MARK: - Private Proporties 27 | 28 | // MARK: - Life cycle 29 | init() { 30 | super.init(frame: .zero) 31 | setupView() 32 | setupConstraints() 33 | setupActions() 34 | } 35 | 36 | required init?(coder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | } 40 | 41 | // MARK: - Handlers 42 | private extension ContentViewForInheritedController { 43 | 44 | } 45 | 46 | // MARK: - Public Methods 47 | extension ContentViewForInheritedController { 48 | @objc func setRandomProgress() { 49 | let randomProgress = Float.random(in: 0...1) 50 | 51 | progressRing.setProgress(randomProgress, animated: true) 52 | progressBar.setProgress(randomProgress, animated: true) 53 | percentLabel.text = String(Int(randomProgress * 100)) 54 | } 55 | } 56 | 57 | // MARK: - Private Methods 58 | private extension ContentViewForInheritedController { 59 | func setupActions() { 60 | actionButton.addTarget(self, action: #selector(setRandomProgress), for: .touchUpInside) 61 | } 62 | } 63 | 64 | // MARK: - Layout Setup 65 | private extension ContentViewForInheritedController { 66 | func setupColors() { 67 | progressRing.startColor = .systemBlue.alpha(0.7) 68 | progressRing.endColor = .systemBlue 69 | 70 | progressBar.startColor = .systemBlue.alpha(0.8) 71 | progressBar.endColor = .systemBlue 72 | 73 | titleLabel.textColor = .label 74 | subtitleLabel.textColor = .secondaryLabel 75 | percentLabel.textColor = .tertiaryLabel 76 | 77 | actionButton.applyDefaultAppearance(with: .init(content: .systemBlue, background: .systemGroupedBackground)) 78 | } 79 | 80 | func setupView() { 81 | setupColors() 82 | 83 | titleLabel.do { 84 | $0.font = .preferredFont(forTextStyle: .title2, weight: .bold) 85 | $0.text = "Inherited View Controller" 86 | $0.textAlignment = .center 87 | } 88 | 89 | subtitleLabel.do { 90 | $0.font = .preferredFont(forTextStyle: .callout) 91 | $0.text = "This controller provides the ALProgressView library" 92 | $0.textAlignment = .center 93 | $0.numberOfLines = 0 94 | } 95 | 96 | percentLabel.do { 97 | $0.font = .systemFont(ofSize: 40, weight: .bold).rounded 98 | $0.text = "0" 99 | } 100 | 101 | actionButton.do { 102 | $0.setTitle("Randomize") 103 | } 104 | } 105 | 106 | func setupConstraints() { 107 | addSubviews([titleLabel, subtitleLabel, progressRing, progressBar, percentLabel, actionButton]) 108 | 109 | let safeArea = safeAreaLayoutGuide 110 | 111 | titleLabel.snp.makeConstraints { 112 | $0.top.equalToSuperview().offset(35) 113 | $0.leading.trailing.equalTo(safeArea).inset(16) 114 | } 115 | 116 | subtitleLabel.snp.makeConstraints { 117 | $0.top.equalTo(titleLabel.snp.bottom).offset(6) 118 | $0.leading.trailing.equalTo(safeArea).inset(16) 119 | } 120 | 121 | progressRing.snp.makeConstraints { 122 | $0.size.equalTo(150) 123 | $0.top.equalTo(subtitleLabel.snp.bottom).offset(40) 124 | $0.centerX.equalToSuperview() 125 | } 126 | 127 | progressBar.snp.makeConstraints { 128 | $0.height.equalTo(10) 129 | $0.top.equalTo(progressRing.snp.bottom).offset(30) 130 | $0.leading.trailing.equalTo(safeArea).inset(40) 131 | } 132 | 133 | percentLabel.snp.makeConstraints { 134 | $0.center.equalTo(progressRing) 135 | } 136 | 137 | actionButton.snp.makeConstraints { 138 | $0.top.equalTo(progressBar.snp.bottom).offset(35) 139 | $0.leading.trailing.equalToSuperview().inset(16) 140 | $0.bottom.equalTo(safeArea).priority(999) 141 | $0.bottom.lessThanOrEqualToSuperview().offset(-17) 142 | } 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/Launch/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 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/ScrollControllers/HorizontallScrollController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | import ALDevKit 25 | 26 | final class HorizontallScrollController: UIViewController { 27 | // MARK: - UI Elements 28 | 29 | // MARK: - Public Proporties 30 | private lazy var collectionView = ALCollectionView(collectionViewLayout: collectionLayout) 31 | private lazy var collectionLayout = UICollectionViewFlowLayout() 32 | private lazy var collectionView1 = ALCollectionView(collectionViewLayout: collectionLayout) 33 | private lazy var collectionLayout1 = UICollectionViewFlowLayout() 34 | private lazy var actionButton = ALActionButton() 35 | 36 | // MARK: - Private Proporties 37 | 38 | // MARK: - Life cycle 39 | init() { 40 | super.init(nibName: nil, bundle: nil) 41 | } 42 | 43 | required init?(coder: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | 47 | override func viewDidLoad() { 48 | super.viewDidLoad() 49 | setupView() 50 | setupConstraints() 51 | setupActions() 52 | } 53 | } 54 | 55 | // MARK: - Handlers 56 | private extension HorizontallScrollController { 57 | 58 | } 59 | 60 | // MARK: - Open Methods 61 | extension HorizontallScrollController { 62 | 63 | } 64 | 65 | // MARK: - Private Methods 66 | private extension HorizontallScrollController { 67 | func setupActions() { 68 | 69 | } 70 | } 71 | 72 | // MARK: - Navigation 73 | private extension HorizontallScrollController { 74 | 75 | } 76 | 77 | // MARK: - Navigation 78 | 79 | extension HorizontallScrollController: UICollectionViewDataSource { 80 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 81 | 10 82 | } 83 | 84 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 85 | let cell = collectionView.dequeueReusableCell(withClass: UICollectionViewCell.self, for: indexPath) 86 | cell.backgroundColor = [UIColor.systemGray2, UIColor.systemGray3, UIColor.systemGray4].randomElement() 87 | cell.roundCorners(radius: 16) 88 | return cell 89 | } 90 | } 91 | 92 | // MARK: - Layout Setup 93 | private extension HorizontallScrollController { 94 | func setupColors() { 95 | 96 | } 97 | 98 | func setupView() { 99 | setupColors() 100 | 101 | [collectionView, collectionView1].forEach { 102 | $0.dataSource = self 103 | $0.register(UICollectionViewCell.self) 104 | $0.showsHorizontalScrollIndicator = false 105 | } 106 | 107 | [collectionLayout, collectionLayout1].forEach { 108 | $0.scrollDirection = .horizontal 109 | $0.itemSize = .init(width: 300, height: 200) 110 | $0.sectionInset = .init(horizontal: 16, vertical: 0) 111 | } 112 | } 113 | 114 | func setupConstraints() { 115 | view.addSubviews([collectionView, collectionView1, actionButton]) 116 | 117 | collectionView.snp.makeConstraints { 118 | $0.top.equalToSuperview().offset(70) 119 | $0.leading.trailing.equalToSuperview() 120 | $0.height.equalTo(200) 121 | } 122 | 123 | collectionView1.snp.makeConstraints { 124 | $0.top.equalTo(collectionView.snp.bottom).offset(20) 125 | $0.leading.trailing.equalToSuperview() 126 | $0.height.equalTo(200) 127 | } 128 | 129 | actionButton.snp.makeConstraints { 130 | $0.top.equalTo(collectionView1.snp.bottom).offset(20) 131 | $0.leading.trailing.equalToSuperview().inset(20) 132 | $0.bottom.equalTo(view.safeAreaLayoutGuide) 133 | } 134 | } 135 | } 136 | 137 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/ScrollControllers/MixedScrollViewController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | import ALDevKit 25 | 26 | final class MixedScrollViewController: UIViewController { 27 | // MARK: - UI Elements 28 | 29 | // MARK: - Public Proporties 30 | private lazy var scrollView = ALScrollView() 31 | private lazy var collectionView = ALCollectionView(collectionViewLayout: collectionLayout) 32 | private lazy var collectionLayout = UICollectionViewFlowLayout() 33 | private lazy var collectionView1 = ALCollectionView(collectionViewLayout: collectionLayout) 34 | private lazy var collectionLayout1 = UICollectionViewFlowLayout() 35 | private lazy var actionButton = ALActionButton() 36 | 37 | // MARK: - Private Proporties 38 | 39 | // MARK: - Life cycle 40 | init() { 41 | super.init(nibName: nil, bundle: nil) 42 | } 43 | 44 | required init?(coder: NSCoder) { 45 | fatalError("init(coder:) has not been implemented") 46 | } 47 | 48 | override func viewDidLoad() { 49 | super.viewDidLoad() 50 | setupView() 51 | setupConstraints() 52 | } 53 | } 54 | 55 | // MARK: - Navigation 56 | 57 | extension MixedScrollViewController: UICollectionViewDataSource { 58 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 59 | 10 60 | } 61 | 62 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 63 | let cell = collectionView.dequeueReusableCell(withClass: UICollectionViewCell.self, for: indexPath) 64 | cell.backgroundColor = [UIColor.systemGray2, UIColor.systemGray3, UIColor.systemGray4].randomElement() 65 | cell.roundCorners(radius: 16) 66 | return cell 67 | } 68 | } 69 | 70 | // MARK: - Layout Setup 71 | private extension MixedScrollViewController { 72 | func setupColors() { 73 | 74 | } 75 | 76 | func setupView() { 77 | setupColors() 78 | 79 | [collectionView, collectionView1].forEach { 80 | $0.dataSource = self 81 | $0.register(UICollectionViewCell.self) 82 | $0.showsHorizontalScrollIndicator = false 83 | } 84 | 85 | [collectionLayout, collectionLayout1].forEach { 86 | $0.scrollDirection = .horizontal 87 | $0.itemSize = .init(width: 300, height: 200) 88 | $0.sectionInset = .init(horizontal: 16, vertical: 0) 89 | } 90 | 91 | 92 | scrollView.do { 93 | $0.alwaysBounceVertical = true 94 | } 95 | } 96 | 97 | func setupConstraints() { 98 | view.addSubview(scrollView) 99 | scrollView.contentView.addSubviews([collectionView, collectionView1, actionButton]) 100 | 101 | scrollView.snp.makeConstraints { 102 | $0.edges.equalToSuperview() 103 | $0.height.equalTo(600) 104 | } 105 | 106 | scrollView.contentView.snp.makeConstraints { 107 | $0.height.equalTo(scrollView.safeAreaLayoutGuide) 108 | } 109 | 110 | collectionView.snp.makeConstraints { 111 | $0.top.equalToSuperview().offset(70) 112 | $0.leading.trailing.equalToSuperview() 113 | $0.height.equalTo(200) 114 | } 115 | 116 | collectionView1.snp.makeConstraints { 117 | $0.top.equalTo(collectionView.snp.bottom).offset(20) 118 | $0.leading.trailing.equalToSuperview() 119 | $0.height.equalTo(200) 120 | } 121 | 122 | actionButton.snp.makeConstraints { 123 | $0.top.greaterThanOrEqualTo(collectionView1.snp.bottom).offset(20) 124 | $0.leading.trailing.equalToSuperview().inset(20) 125 | $0.bottom.equalToSuperview() 126 | } 127 | } 128 | } 129 | 130 | 131 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Controllers/Settings/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.swift 3 | // ALPopupController Example 4 | // 5 | // Created by Alexandr Guzenko on 16.08.2021. 6 | // Copyright © 2021 alxrguz. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ALDevKit 11 | 12 | final class SettingsViewController: UIViewController { 13 | // MARK: - UI Elements 14 | 15 | private lazy var titleLabel = UILabel() 16 | private lazy var closeButtonSection = ALFakeTableSection() 17 | private lazy var tapAroundSection = ALFakeTableSection() 18 | private lazy var swipeInteractionSection = ALFakeTableSection() 19 | private lazy var sectionStack = UIStackView() 20 | 21 | // MARK: - Public Proporties 22 | 23 | var isEnableCloseButtonChanged: ((Bool) -> Void)? 24 | var dismissByTapAroundChanged: ((Bool) -> Void)? 25 | var allowsSwipeInteractionChanged: ((Bool) -> Void)? 26 | 27 | 28 | // MARK: - Private Proporties 29 | 30 | private let isEnableCloseButton: Bool 31 | private let dismissByTapAround: Bool 32 | private let allowsSwipeInteraction: Bool 33 | 34 | // MARK: - Life cycle 35 | init(isEnableCloseButton: Bool, dismissByTapAround: Bool, allowsSwipeInteraction: Bool) { 36 | self.isEnableCloseButton = isEnableCloseButton 37 | self.dismissByTapAround = dismissByTapAround 38 | self.allowsSwipeInteraction = allowsSwipeInteraction 39 | super.init(nibName: nil, bundle: nil) 40 | } 41 | 42 | required init?(coder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | setupView() 49 | setupConstraints() 50 | } 51 | } 52 | 53 | // MARK: - Handlers 54 | private extension SettingsViewController { 55 | @objc func closeButtonUptaded(_ sender: UISwitch) { 56 | isEnableCloseButtonChanged?(sender.isOn) 57 | } 58 | 59 | @objc func tapAroundUptaded(_ sender: UISwitch) { 60 | dismissByTapAroundChanged?(sender.isOn) 61 | } 62 | 63 | @objc func swipeInteractionUptaded(_ sender: UISwitch) { 64 | allowsSwipeInteractionChanged?(sender.isOn) 65 | } 66 | } 67 | 68 | // MARK: - Layout Setup 69 | private extension SettingsViewController { 70 | func setupColors() { 71 | view.backgroundColor = .systemGroupedBackground 72 | } 73 | 74 | func setupView() { 75 | setupColors() 76 | 77 | titleLabel.do { 78 | $0.font = .preferredFont(forTextStyle: .title2, weight: .bold) 79 | $0.text = "Settings" 80 | $0.textAlignment = .center 81 | } 82 | 83 | closeButtonSection.do { 84 | $0.style = .insertGroup 85 | $0.footer = "Is Close Button show on controller" 86 | let switchCell = ALFakeSwitchCell().do { 87 | $0.switchControl.isOn = isEnableCloseButton 88 | $0.titleLabel.text = "Close Button" 89 | $0.switchControl.addTarget(self, action: #selector(closeButtonUptaded), for: .valueChanged) 90 | } 91 | $0.addCell(switchCell) 92 | } 93 | 94 | tapAroundSection.do { 95 | $0.style = .insertGroup 96 | $0.footer = "Should the controller be closed by tap on the darkened area" 97 | let switchCell = ALFakeSwitchCell().do { 98 | $0.switchControl.isOn = dismissByTapAround 99 | $0.titleLabel.text = "Dissmiss by tap Around" 100 | $0.switchControl.addTarget(self, action: #selector(tapAroundUptaded), for: .valueChanged) 101 | } 102 | $0.addCell(switchCell) 103 | } 104 | 105 | swipeInteractionSection.do { 106 | $0.style = .insertGroup 107 | $0.footer = "Whether swipe to dismiss should be allowed" 108 | let switchCell = ALFakeSwitchCell().do { 109 | $0.switchControl.isOn = allowsSwipeInteraction 110 | $0.titleLabel.text = "Swipe to Close" 111 | $0.switchControl.addTarget(self, action: #selector(swipeInteractionUptaded), for: .valueChanged) 112 | } 113 | $0.addCell(switchCell) 114 | } 115 | 116 | sectionStack.do { 117 | $0.axis = .vertical 118 | $0.spacing = 25 119 | } 120 | } 121 | 122 | func setupConstraints() { 123 | sectionStack.addArrangedSubviews([closeButtonSection, tapAroundSection, swipeInteractionSection]) 124 | view.addSubviews([titleLabel, sectionStack]) 125 | 126 | let safeArea = view.safeAreaLayoutGuide 127 | 128 | titleLabel.snp.makeConstraints { 129 | $0.top.equalToSuperview().offset(35) 130 | $0.leading.trailing.equalTo(safeArea).inset(16) 131 | } 132 | 133 | sectionStack.snp.makeConstraints { 134 | $0.top.equalTo(titleLabel.snp.bottom).offset(20) 135 | $0.leading.trailing.equalToSuperview() 136 | $0.bottom.equalTo(safeArea).offset(-8).priority(999) 137 | $0.bottom.lessThanOrEqualToSuperview().offset(-17) 138 | } 139 | } 140 | } 141 | 142 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Library/UIElements/ALFakeNavigationView.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | final class ALFakeNavigationView: UIView { 26 | // MARK: - UI Elements 27 | lazy var blurView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial)) 28 | lazy var separator = UIView() 29 | 30 | // MARK: - Life cycle 31 | init() { 32 | super.init(frame: .zero) 33 | setupView() 34 | setupConstraints() 35 | } 36 | 37 | required init?(coder: NSCoder) { 38 | fatalError("init(coder:) has not been implemented") 39 | } 40 | } 41 | 42 | // MARK: - Layout Setup 43 | private extension ALFakeNavigationView { 44 | func setupColors() { 45 | separator.backgroundColor = .separator 46 | } 47 | 48 | func setupView() { 49 | setupColors() 50 | } 51 | 52 | func setupConstraints() { 53 | addSubview(blurView) 54 | addSubview(separator) 55 | 56 | blurView.snp.makeConstraints { 57 | $0.edges.equalToSuperview() 58 | } 59 | 60 | separator.snp.makeConstraints { 61 | $0.left.right.bottom.equalToSuperview() 62 | $0.height.equalTo(0.5) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "iphone-20x20@2x.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "iphone-20x20@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "iphone-29x29@2x.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "iphone-29x29@3x.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "iphone-40x40@2x.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "iphone-40x40@3x.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "iphone-60x60@2x.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "iphone-60x60@3x.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "ipad-20x20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "ipad-20x20@2x.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "ipad-29x29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "ipad-29x29@2x.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "ipad-40x40.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "ipad-40x40@2x.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "ipad-76x76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "ipad-76x76@2x.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "ipad-83.5x83.5@2x.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "ios-marketing-1024x1024.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ios-marketing-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ios-marketing-1024x1024.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-20x20.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-20x20@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-29x29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-29x29.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-29x29@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-40x40.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-40x40@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-76x76.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-76x76@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/ipad-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-20x20@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-20x20@3x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-29x29@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-29x29@3x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-40x40@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-40x40@3x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-60x60@2x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxrguz/ALPopup/f9e88d059bb5b3b64bc36bf7befb0c872ab01476/ExampleApp/iOS App/Sources/Assets.xcassets/AppIcon.appiconset/iphone-60x60@3x.png -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ExampleApp/iOS App/Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | ALPopup 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alexandr Guzenko 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ALPopup", 8 | platforms: [ 9 | .iOS(.v11) 10 | ], 11 | products: [ 12 | .library( 13 | name: "ALPopup", 14 | targets: ["ALPopup"]), 15 | ], 16 | targets: [ 17 | .target( 18 | name: "ALPopup", 19 | resources: [ 20 | .process("Resources") 21 | ], 22 | swiftSettings: [ 23 | .define("ALPOPUP_SPM") 24 | ] 25 | ) 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ALPopup 2 | 3 | 4 | 5 |   6 | 7 | ## Navigation 8 | 9 | - [Requirements](#requirements) 10 | - [Installation](#installation) 11 | - [Swift Package Manager](#Swift-Package-Manager) 12 | - [CocoaPods](#CocoaPods) 13 | - [Manually](#Manually) 14 | - [Usage](#usage) 15 | - [Quick Start](#Quick-Start) 16 | - [Customization](#Customization) 17 | - [Base customization](#Base-customization) 18 | - [Template customization](#Template-customization) 19 | - [License](#License) 20 | 21 | 22 | 23 | ## Requirements 24 | 25 | - iOS 11.0 + 26 | - Swift 5.3 + 27 | 28 | 29 | 30 | ## Installation 31 | 32 | #### Swift Package Manager 33 | 34 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. 35 | 36 | To integrate **ALPopup** click `File -> Swift Package -> Add Package Dependency` and insert: 37 | 38 | ```ogdl 39 | https://github.com/alxrguz/ALPopup 40 | ``` 41 | 42 | #### CocoaPods 43 | 44 | **ALPopup** is available through [CocoaPods](https://cocoapods.org/pods/ALProgressView). To install it, simply add the following line to your Podfile: 45 | 46 | ```ruby 47 | pod 'ALPopup' 48 | ``` 49 | 50 | #### Manually 51 | 52 | If you prefer not to use either of the aforementioned dependency managers, you can integrate ALPopupController into your project manually. Put `Source/ALPopup` folder in your Xcode project. 53 | 54 | 55 | 56 | ## Usage 57 | 58 | ### Quick Start 59 | 60 | First of all import library 61 | 62 | ```swift 63 | import ALPopup 64 | ``` 65 | 66 | Use `ALPopup.card` for create card (it is similar to the one shown when the airpods are connected to your device), if you need a regular popup - use `ALPopup.popup` (it will be displayed in the middle of the screen). 67 | 68 | For present controller use: 69 | 70 | ```swift 71 | cardController.push(from: UIViewController, haptic: ALHaptic) 72 | ``` 73 | 74 | For dismiss controller use: 75 | 76 | ```swift 77 | cardController.pop(completion: (() -> Void)) 78 | ``` 79 | 80 |   81 | 82 | **There are 3 options for configure the controller:** 83 | 84 | 1) As a template 85 | 86 | Create controller with `.init(template: ALTemplateSettings)` 87 | 88 | ```swift 89 | let popupVC = ALPopup.card(template: .init( 90 | title: "Hi", 91 | subtitle: "This is popup template controller", 92 | image: UIImage(systemName: "airpodspro"), 93 | privaryButtonTitle: "Connect", 94 | secondaryButtonTitle: "Not Now") 95 | ) 96 | popupVC.push(from: self) 97 | ``` 98 | 99 | All template fields are optional, if you do not specify some property, then it simply will not be shown. 100 | 101 | 2. Using your controller 102 | 103 | Create controller with `.init(controller: UIViewController)` 104 | 105 | ```swift 106 | let baseViewController = BaseViewController() 107 | let cardVC = ALPopup.card(controller: baseViewController) 108 | cardVC.push(from: self) 109 | ``` 110 | 111 | In order for the size to be calculated correctly, you need your controller to be correctly laid out (If you have difficulty - see **Example App** `BaseViewController`). 112 | 113 | 3. Inherit from controller 114 | 115 | ```swift 116 | class InheritedFromCardController: ALCardController { } 117 | 118 | class InheritedFromPopupController: ALPopupControlle { } 119 | 120 | let myCardVC = InheritedFromCardController() 121 | myCardVC.push(from: self) 122 | 123 | let myPopupVC = InheritedFromPopupController() 124 | myPopupVC.push(from: self) 125 | ``` 126 | 127 | Be careful, if you inherit, then all your elements must be added to the contentView (see **Example App** `InheritedFromPopupController`) 128 | 129 |   130 | 131 | ### Customization 132 | 133 | #### **Base customization** 134 | 135 | Is close Button show on controller 136 | 137 | ```swift 138 | myPopupVC.isEnableCloseButton = true 139 | ``` 140 | 141 | Should the controller be closed by tap on the darkened area 142 | 143 | ```swift 144 | myPopupVC.dismissByTapAround = true 145 | ``` 146 | 147 | Whether swipe to dismiss should be allowed. Defaults is true. 148 | 149 | ```swift 150 | myPopupVC.allowsSwipeInteraction = true 151 | ``` 152 | 153 | If you set this to false, the pan gesture will continue to be recognized, but with noticeable resistance and the user won't be able to close the controller with a swipe. 154 | 155 | The home indicator for iPhone X should be hidden or not. Defaults is `true`. 156 | 157 | ```swift 158 | myPopupVC.isHomeIndicatorVisible = true 159 | ``` 160 | 161 | Rounding corners in superellipse for `contentView`. Default is `true` 162 | 163 | ```swift 164 | myPopupVC.superellipticRounding = true 165 | ``` 166 | 167 | Gives nicer and more correct rounding, but does not work well with frame animations. 168 | 169 |   170 | 171 | #### Template customization 172 | 173 | Suitable if the controller is created using: `.init(template: ALTemplateSettings)` 174 | 175 | The template has 5 elements in total, all of them are open and can be changed at your discretion 176 | 177 | ```swift 178 | // Create Controller 179 | let popupVC = ALPopup.card(template: .init( 180 | title: "Hi", 181 | subtitle: "This is popup template controller", 182 | image: UIImage(systemName: "airpodspro"), 183 | privaryButtonTitle: "Connect", 184 | secondaryButtonTitle: "Not Now") 185 | ) 186 | 187 | // Setup templateView 188 | popupVC.tempateView.titleLabel 189 | popupVC.tempateView.subtitleLabel 190 | popupVC.tempateView.imageView 191 | popupVC.tempateView.primaryButton 192 | popupVC.tempateView.secondaryButton 193 | 194 | // Show 195 | vc.push(from: self) 196 | ``` 197 | 198 | Callbacks are prepared for handling button presses 199 | 200 | ```swift 201 | popupVC.tempateView.primaryButtonAction = { [weak self] in } 202 | popupVC.tempateView.secondaryButtonAction = { [weak self] in } 203 | ``` 204 | 205 | By the way, the template uses extended `ALActionButton` buttons, so it is more convenient to change their color using: 206 | 207 | ```swift 208 | popupVC.tempateView.primaryButton.applyDefaultAppearance(with: .init(content: .systemBlue, background: .systemGroupedBackground)) 209 | ``` 210 | 211 | 212 | 213 | ## License 214 | 215 | **ALPopup** is under MIT license. See the [LICENSE](https://github.com/alxrguz/ALPopup/blob/master/LICENSE) file for more info. 216 | 217 | -------------------------------------------------------------------------------- /Sources/ALPopup/ALPopup.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | public class ALPopup { 26 | 27 | // MARK: Private Proporties 28 | 29 | private static var isPhone: Bool { 30 | UIDevice.current.userInterfaceIdiom == .phone 31 | } 32 | 33 | // MARK: Open Methods 34 | 35 | /** 36 | Returns a controller that is presented as a card (AirPods style). On iPhone, it will appear at the bottom of the screen, and on iPad - centered. 37 | 38 | See the description for the `ALTemplateSettings` properties for a better understanding. 39 | 40 | **Example:** 41 | ``` 42 | let template = ALTemplateSettings( 43 | title: "Privacy Policy", 44 | subtitle: "Subtitle", 45 | privaryButtonTitle: "Agree & Continue" 46 | ) 47 | let cardVC = ALPopup.card(template: template) 48 | cardVC.push(from: self) 49 | ``` 50 | - Parameter template: Structure describing the template for the controller. If 1 or several properties of the structure are not specified, then no UI will be created for it. 51 | */ 52 | public static func card(template: ALTemplateSettings) -> ALCardTemplateController { 53 | let vc = ALCardTemplateController() 54 | vc.tempateView.configuration = template 55 | return vc 56 | } 57 | 58 | /** 59 | Returns a controller that is presented as a card (AirPods style). On iPhone, it will appear at the bottom of the screen, and on iPad - centered. 60 | 61 | For the UIViewController classes, the height is calculated automatically if the content has a height, the first element is nailed to the top border of the controller, and the last to the bottom (except for elements inherited from UIScrollViews) 62 | 63 | In other cases (UINavigationController, UITabBarController, etc.) you need to explicitly specify the height for the view controller. 64 | 65 | **Example:** 66 | ``` 67 | let myNavigationVC = UINavigationController(rootViewController: UIViewController()) 68 | myNavigationVC.view.translatesAutoresizingMaskIntoConstraints = false 69 | myNavigationVC.view.heightAnchor.constraint(equalToConstant: 500).isActive = true 70 | let cardVC = ALPopup.card(controller: myNavigationVC) 71 | cardVC.push(from: self) 72 | ``` 73 | 74 | - Parameter controller: Your controller that you want to wrap in ALCardController 75 | */ 76 | public static func card(controller: UIViewController) -> ALCardController { 77 | let vc = ALCardController() 78 | vc.addChild(controller) 79 | vc.contentView.addSubview(controller.view) 80 | 81 | controller.view.translatesAutoresizingMaskIntoConstraints = false 82 | controller.view.topAnchor.constraint(equalTo: vc.contentView.topAnchor).isActive = true 83 | controller.view.leftAnchor.constraint(equalTo: vc.contentView.leftAnchor).isActive = true 84 | controller.view.bottomAnchor.constraint(equalTo: vc.contentView.bottomAnchor).isActive = true 85 | controller.view.rightAnchor.constraint(equalTo: vc.contentView.rightAnchor).isActive = true 86 | 87 | controller.didMove(toParent: vc) 88 | 89 | if controller is UINavigationController { 90 | let offset: CGFloat = isPhone ? 12 : 8 91 | controller.additionalSafeAreaInsets = .init(top: offset, left: 8, bottom: 0, right: 8) 92 | } 93 | 94 | return vc 95 | } 96 | 97 | /** 98 | Returns a controller that is presented as a popup or window (like UIAlertView). Displayed in the center of the screen. 99 | 100 | See the description for the `ALTemplateSettings` properties for a better understanding. 101 | 102 | **Example:** 103 | ``` 104 | let template = ALTemplateSettings( 105 | title: "Privacy Policy", 106 | subtitle: "Subtitle", 107 | privaryButtonTitle: "Agree & Continue" 108 | ) 109 | let popupVC = ALPopup.popup(template: template) 110 | popupVC.push(from: self) 111 | ``` 112 | - Parameter template: Structure describing the template for the controller. If 1 or several properties of the structure are not specified, then no UI will be created for it. 113 | */ 114 | public static func popup(template: ALTemplateSettings) -> ALPopupTemplateController { 115 | let vc = ALPopupTemplateController() 116 | vc.tempateView.configuration = template 117 | return vc 118 | } 119 | 120 | /** 121 | Returns a controller that is presented as a popup or window (like UIAlertView). Displayed in the center of the screen. 122 | 123 | For the UIViewController classes, the height is calculated automatically if the content has a height, the first element is nailed to the top border of the controller, and the last to the bottom (except for elements inherited from UIScrollViews) 124 | 125 | In other cases (UINavigationController, UITabBarController, etc.) you need to explicitly specify the height for the view controller. 126 | 127 | **Example:** 128 | ``` 129 | let myNavigationVC = UINavigationController(rootViewController: UIViewController()) 130 | myNavigationVC.view.translatesAutoresizingMaskIntoConstraints = false 131 | myNavigationVC.view.heightAnchor.constraint(equalToConstant: 500).isActive = true 132 | let popupVC = ALPopup.popup(controller: myNavigationVC) 133 | popupVC.push(from: self) 134 | ``` 135 | 136 | - Parameter controller: Your controller that you want to wrap in ALCardController 137 | */ 138 | public static func popup(controller: UIViewController) -> ALPopupController { 139 | let vc = ALPopupController() 140 | vc.addChild(controller) 141 | vc.contentView.addSubview(controller.view) 142 | 143 | controller.view.translatesAutoresizingMaskIntoConstraints = false 144 | controller.view.topAnchor.constraint(equalTo: vc.contentView.topAnchor).isActive = true 145 | controller.view.leftAnchor.constraint(equalTo: vc.contentView.leftAnchor).isActive = true 146 | controller.view.bottomAnchor.constraint(equalTo: vc.contentView.bottomAnchor).isActive = true 147 | controller.view.rightAnchor.constraint(equalTo: vc.contentView.rightAnchor).isActive = true 148 | 149 | controller.didMove(toParent: vc) 150 | 151 | if controller is UINavigationController { 152 | let offset: CGFloat = isPhone ? 12 : 8 153 | controller.additionalSafeAreaInsets = .init(top: offset, left: 0, bottom: 0, right: 0) 154 | } 155 | 156 | return vc 157 | } 158 | 159 | // MARK: - Life cycle 160 | 161 | private init() {} 162 | } 163 | 164 | -------------------------------------------------------------------------------- /Sources/ALPopup/Controllers/ALBaseOverlayController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | open class ALBaseOverlayController: UIViewController { 26 | 27 | // MARK: UI Elements 28 | 29 | public lazy var contentView = ALPopupContentView() 30 | public lazy var closeButton = UIButton() 31 | public lazy var backgroundView = UIView() 32 | 33 | // MARK: Open Proporties 34 | 35 | /** 36 | Dimmed card background color 37 | */ 38 | public var dimmedBackgroudColor: UIColor? { 39 | didSet { 40 | backgroundView.backgroundColor = dimmedBackgroudColor ?? .black.alpha(0.5) 41 | } 42 | } 43 | 44 | /** 45 | Is Close Button show on controller 46 | */ 47 | public var isEnableCloseButton = true { didSet { closeButton.isHidden = !isEnableCloseButton } } 48 | 49 | /** 50 | Should the controller be closed by tap on the darkened area 51 | */ 52 | public var dismissByTapAround = true 53 | 54 | /** 55 | Whether swipe to dismiss should be allowed. Defaults is true. 56 | 57 | If you set this to false, the pan gesture will continue to be recognized, but with noticeable resistance and the user won't be able to close the controller with a swipe. 58 | */ 59 | public var allowsSwipeInteraction = true 60 | 61 | /** 62 | The home indicator for iPhone X should be hidden or not. Defaults is true. 63 | */ 64 | public var isHomeIndicatorVisible = true { 65 | didSet { 66 | setNeedsUpdateOfHomeIndicatorAutoHidden() 67 | } 68 | } 69 | 70 | /** 71 | Popup started to close 72 | */ 73 | public var popupWillClose: (() -> Void)? 74 | 75 | 76 | /** 77 | Popup closed 78 | */ 79 | public var popupDidClose: (() -> Void)? 80 | 81 | // MARK: Private Proporties 82 | 83 | private lazy var panGesture: UIPanGestureRecognizer = { 84 | let gesture = UIPanGestureRecognizer(target: self, action: #selector(swipeHandler(_:))) 85 | gesture.delegate = self 86 | gesture.cancelsTouchesInView = false 87 | return gesture 88 | }() 89 | 90 | // MARK: - Life cycle 91 | 92 | public init() { 93 | super.init(nibName: nil, bundle: nil) 94 | modalPresentationStyle = .overFullScreen 95 | modalTransitionStyle = .crossDissolve 96 | } 97 | 98 | required public init?(coder: NSCoder) { 99 | fatalError("init(coder:) has not been implemented") 100 | } 101 | 102 | // MARK: UIViewController 103 | 104 | open override func viewDidLoad() { 105 | super.viewDidLoad() 106 | setupActions() 107 | setupView() 108 | setupConstraints() 109 | } 110 | 111 | open override func viewDidAppear(_ animated: Bool) { 112 | super.viewDidAppear(animated) 113 | presentAnimation() 114 | } 115 | 116 | open override func viewDidLayoutSubviews() { 117 | super.viewDidLayoutSubviews() 118 | closeButton.setImage(Source.Image.smallCloseButton, for: .normal) 119 | contentView.bringSubviewToFront(closeButton) 120 | contentView.subviews(ofType: UIScrollView.self).forEach { 121 | $0.panGestureRecognizer.require(toFail: panGesture) 122 | } 123 | } 124 | 125 | open override var prefersHomeIndicatorAutoHidden: Bool { 126 | return !isHomeIndicatorVisible 127 | } 128 | 129 | // MARK: - Custom Present & Dissmiss 130 | 131 | public func push(from viewController: UIViewController, haptic: ALHaptic = .none) { 132 | viewController.present(self, animated: false) { 133 | haptic.impact() 134 | } 135 | } 136 | 137 | public func pop(completion: (() -> Void)? = nil) { 138 | dismissAnimation { 139 | self.dismiss(animated: false, completion: completion) 140 | } 141 | } 142 | 143 | // MARK: - Animations 144 | 145 | /** 146 | Animation that will fire when the controller is presented 147 | */ 148 | open func presentAnimation() { 149 | fatalError("Method must be overridden") 150 | } 151 | 152 | /** 153 | Animation that will fire when the controller is dismiss 154 | */ 155 | open func dismissAnimation(competion: @escaping () -> Void) { 156 | fatalError("Method must be overridden") 157 | } 158 | 159 | /** 160 | An animation that returns the controller to its original position. For example, when the gesture was canceled 161 | */ 162 | open func resetAnimation() { 163 | fatalError("Method must be overridden") 164 | } 165 | } 166 | 167 | // MARK: - Actions 168 | extension ALBaseOverlayController { 169 | @objc func closeController() { 170 | pop() 171 | } 172 | 173 | private func setupActions() { 174 | closeButton.addTarget(self, action: #selector(closeController), for: .touchUpInside) 175 | contentView.addGestureRecognizer(panGesture) 176 | 177 | let tap = UITapGestureRecognizer(target: self, action: #selector(tapAround)) 178 | backgroundView.addGestureRecognizer(tap) 179 | } 180 | } 181 | 182 | // MARK: - UIGestureRecognizerDelegate 183 | extension ALBaseOverlayController: UIGestureRecognizerDelegate { 184 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 185 | return true 186 | } 187 | 188 | public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 189 | let velosity = panGesture.velocity(in: contentView) 190 | guard abs(velosity.y) > abs(velosity.x) else { return false } 191 | 192 | guard let scrollView = contentView.subviews(ofType: UIScrollView.self).first else { return true } 193 | 194 | let touchPoint = panGesture.location(in: scrollView) 195 | guard touchPoint.x >= 0 && touchPoint.y >= 0 else { return true } 196 | 197 | let translate = panGesture.translation(in: contentView).y 198 | guard translate > 0 else { return false } 199 | let scrollTopPosition = scrollView.contentOffset.y + scrollView.safeAreaInsets.top 200 | let scrollTopOffset = -scrollView.contentInset.top 201 | return scrollTopOffset >= scrollTopPosition 202 | } 203 | } 204 | 205 | // MARK: - Handlers 206 | private extension ALBaseOverlayController { 207 | @objc func tapAround() { 208 | guard dismissByTapAround else { return } 209 | closeController() 210 | } 211 | 212 | @objc func swipeHandler(_ gestureRecognizer : UIPanGestureRecognizer) { 213 | switch gestureRecognizer.state { 214 | case .began: gestureRecognizer.setTranslation(.zero, in: contentView) 215 | case .cancelled, .ended: swipeCanceled() 216 | case .changed: swipeChanged() 217 | default: break 218 | } 219 | } 220 | 221 | func swipeCanceled() { 222 | var scrollViews = [UIScrollView]() 223 | for view in contentView.subviews { 224 | if let scrollView = view as? UIScrollView { 225 | scrollViews.append(scrollView) 226 | } 227 | } 228 | scrollViews.forEach { $0.isScrollEnabled = true } 229 | 230 | guard allowsSwipeInteraction else { 231 | resetAnimation() 232 | return 233 | } 234 | let maxSwipeHeight = contentView.frame.height 235 | let velocity = panGesture.velocity(in: contentView) 236 | if velocity.y > 1000 { pop(); return } 237 | let verticalTranslation = panGesture.translation(in: contentView).y 238 | if abs(verticalTranslation) > (maxSwipeHeight / 2) && verticalTranslation > 0 { 239 | pop() 240 | } else { 241 | resetAnimation() 242 | } 243 | } 244 | 245 | func swipeChanged() { 246 | let maxSwipeHeight = contentView.frame.maxY 247 | let verticalTranslation = panGesture.translation(in: contentView).y 248 | 249 | guard allowsSwipeInteraction else { 250 | contentView.transform = .init(translationX: 0, y: verticalTranslation * 0.08) 251 | return 252 | } 253 | 254 | func swipeTop() { 255 | let translation = verticalTranslation * 0.2 256 | let maxSwipe: CGFloat = -70 257 | if translation > maxSwipe { 258 | contentView.transform = .init(translationX: 0, y: translation) 259 | } else { 260 | var coefficient = 1 - (abs(translation) / 100) 261 | if coefficient < 0.3 { 262 | coefficient = 0.3 263 | } else if coefficient > 1 { 264 | coefficient = 1 265 | } 266 | let offset = (translation - maxSwipe) * coefficient 267 | contentView.transform = .init(translationX: 0, y: maxSwipe + offset) 268 | } 269 | } 270 | 271 | func swipeDown() { 272 | let translation = verticalTranslation * 0.95 273 | backgroundView.alpha = 1 - (translation / maxSwipeHeight) 274 | contentView.transform = .init(translationX: 0, y: translation) 275 | } 276 | verticalTranslation < 0 ? swipeTop() : swipeDown() 277 | } 278 | } 279 | 280 | // MARK: - Layout Setup 281 | 282 | private extension ALBaseOverlayController { 283 | func setupView() { 284 | if #available(iOS 13.4, *) { 285 | closeButton.pointerStyleProvider = { button, effect, shape in 286 | let preview = UITargetedPreview(view: button) 287 | return UIPointerStyle(effect: .lift(preview)) 288 | } 289 | } 290 | } 291 | 292 | func setupConstraints() { 293 | contentView.addSubview(closeButton) 294 | 295 | closeButton.translatesAutoresizingMaskIntoConstraints = false 296 | closeButton.heightAnchor.constraint(equalToConstant: 44).isActive = true 297 | closeButton.widthAnchor.constraint(equalToConstant: 44).isActive = true 298 | closeButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true 299 | closeButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12).isActive = true 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /Sources/ALPopup/Controllers/Card/ALCardController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | open class ALCardController: ALBaseOverlayController { 26 | 27 | // MARK: - Private Proporties 28 | 29 | private var bottomConstraint: NSLayoutConstraint? 30 | private var centerConstraint: NSLayoutConstraint? 31 | private var isPresented = false 32 | 33 | 34 | // MARK: UIViewController 35 | 36 | open override func viewDidLoad() { 37 | super.viewDidLoad() 38 | setupView() 39 | setupConstraints() 40 | } 41 | 42 | open override func viewDidLayoutSubviews() { 43 | super.viewDidLayoutSubviews() 44 | if view.frame.width >= 500 { 45 | bottomConstraint?.isActive = false 46 | centerConstraint?.isActive = true 47 | 48 | } else { 49 | bottomConstraint?.isActive = true 50 | centerConstraint?.isActive = false 51 | } 52 | updateContentCorners() 53 | } 54 | 55 | // MARK: - ALBaseOverlayController 56 | 57 | open override func presentAnimation() { 58 | guard isPresented == false else { return } 59 | isPresented = true 60 | let height = contentView.frame.maxY 61 | contentView.transform = .init(translationX: 0, y: height) 62 | backgroundView.alpha = 0 63 | contentView.alpha = 1 64 | resetAnimation() 65 | } 66 | 67 | open override func dismissAnimation(competion: @escaping () -> Void) { 68 | let height = contentView.frame.maxY 69 | 70 | popupWillClose?() 71 | ALAnimate.spring(time: 0.6) { 72 | self.backgroundView.alpha = 0 73 | self.contentView.transform = .init(translationX: 0, y: height) 74 | } completion: { [weak self] in 75 | competion() 76 | self?.popupDidClose?() 77 | } 78 | } 79 | 80 | open override func resetAnimation() { 81 | ALAnimate.spring(time: 0.4, damping: 1) { 82 | self.backgroundView.alpha = 1 83 | } 84 | 85 | ALAnimate.spring(time: 0.5, damping: 0.85, velocity: 0.8) { 86 | self.contentView.transform = .identity 87 | } 88 | } 89 | } 90 | 91 | // MARK: - Layout Setup 92 | 93 | private extension ALCardController { 94 | func setupView() { 95 | backgroundView.alpha = 0 96 | backgroundView.backgroundColor = dimmedBackgroudColor ?? .black.alpha(0.4) 97 | 98 | contentView.alpha = 0 99 | } 100 | 101 | func setupConstraints() { 102 | view.addSubview(backgroundView) 103 | view.addSubview(contentView) 104 | 105 | let cardOffset: CGFloat = 4 106 | 107 | backgroundView.translatesAutoresizingMaskIntoConstraints = false 108 | backgroundView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 109 | backgroundView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true 110 | backgroundView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 111 | backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 112 | 113 | contentView.translatesAutoresizingMaskIntoConstraints = false 114 | contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 115 | let leadingContent = contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: cardOffset) 116 | leadingContent.priority = .init(999) 117 | leadingContent.isActive = true 118 | let trailingContent = contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -cardOffset) 119 | trailingContent.priority = .init(999) 120 | trailingContent.isActive = true 121 | contentView.widthAnchor.constraint(lessThanOrEqualToConstant: 485).isActive = true 122 | contentView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor).isActive = true 123 | bottomConstraint = contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -cardOffset) 124 | centerConstraint = contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor) 125 | bottomConstraint?.priority = .defaultLow 126 | } 127 | 128 | func updateContentCorners() { 129 | let cornerRadius = UIScreen.main.displayCornerRadius 130 | 131 | var isRoundDisplay: Bool { 132 | view.safeAreaInsets.bottom > 0 133 | } 134 | 135 | var isBottomPosition: Bool { 136 | bottomConstraint?.isActive == true 137 | } 138 | 139 | var isIpad: Bool { 140 | let idiom = UIDevice.current.userInterfaceIdiom 141 | if #available(iOS 14.0, *) { 142 | return idiom == .pad || idiom == .mac 143 | } else { 144 | return idiom == .pad 145 | } 146 | } 147 | 148 | let shiftCompensator: CGFloat = 3 149 | let iphoneCornerRadius = cornerRadius > 0 ? cornerRadius - shiftCompensator : 18 150 | let ipadCornerRadius = cornerRadius > 0 ? cornerRadius : 12 151 | 152 | contentView.cornerRadius = isIpad ? ipadCornerRadius : iphoneCornerRadius 153 | contentView.layer.masksToBounds = true 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Sources/ALPopup/Controllers/Card/ALCardTemplateController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | public final class ALCardTemplateController: ALCardController { 26 | 27 | // MARK: - UI Elements 28 | 29 | public lazy var tempateView = ALTemplateView() 30 | 31 | // MARK: UIViewController 32 | 33 | public override func viewDidLoad() { 34 | super.viewDidLoad() 35 | setupConstraints() 36 | tempateView.insetsLayoutMarginsFromSafeArea = true 37 | } 38 | } 39 | 40 | // MARK: - Layout Setup 41 | 42 | private extension ALCardTemplateController { 43 | func setupConstraints() { 44 | contentView.addSubview(tempateView) 45 | 46 | let sideOffset: CGFloat = 26 47 | let maxWidth: CGFloat = 380 48 | 49 | tempateView.translatesAutoresizingMaskIntoConstraints = false 50 | tempateView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 36).isActive = true 51 | 52 | let leadingConstraint = tempateView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: sideOffset) 53 | leadingConstraint.priority = .init(999) 54 | leadingConstraint.isActive = true 55 | 56 | let trailingConstraint = tempateView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -sideOffset) 57 | trailingConstraint.priority = .init(999) 58 | trailingConstraint.isActive = true 59 | 60 | tempateView.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true 61 | tempateView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true 62 | 63 | 64 | tempateView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -20).isActive = true 65 | let bottomSafeAreaConstraint = tempateView.bottomAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.bottomAnchor, constant: -10) 66 | bottomSafeAreaConstraint.priority = .init(990) 67 | bottomSafeAreaConstraint.isActive = true 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/ALPopup/Controllers/Popup/ALPopupController.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | open class ALPopupController: ALBaseOverlayController { 26 | 27 | // MARK: - Private Proporties 28 | 29 | private var isPresented = false 30 | 31 | // MARK: - Life cycle 32 | 33 | public override init() { 34 | super.init() 35 | allowsSwipeInteraction = false 36 | } 37 | 38 | required public init?(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | // MARK: - UIViewController 43 | 44 | open override func viewDidLoad() { 45 | super.viewDidLoad() 46 | setupView() 47 | setupConstraints() 48 | } 49 | 50 | open override func viewDidLayoutSubviews() { 51 | super.viewDidLayoutSubviews() 52 | updateContentCorners() 53 | } 54 | 55 | // MARK: - ALBaseOverlayController 56 | 57 | open override func presentAnimation() { 58 | guard isPresented == false else { return } 59 | isPresented = true 60 | contentView.transform = .init(translationX: 0, y: 50) 61 | backgroundView.alpha = 0 62 | contentView.alpha = 0 63 | resetAnimation() 64 | } 65 | 66 | open override func dismissAnimation(competion: @escaping () -> Void) { 67 | let translationY: CGFloat = { 68 | let currentTranlation = contentView.transform.ty 69 | if currentTranlation > 0 { 70 | return currentTranlation + currentTranlation * 0.7 71 | } else { 72 | return 50 73 | } 74 | }() 75 | 76 | popupWillClose?() 77 | ALAnimate.spring(time: 0.4) { 78 | self.backgroundView.alpha = 0 79 | self.contentView.alpha = 0 80 | self.contentView.transform = .init(translationX: 0, y: translationY) 81 | } completion: { [weak self] in 82 | competion() 83 | self?.popupDidClose?() 84 | } 85 | } 86 | 87 | open override func resetAnimation() { 88 | ALAnimate.spring(time: 0.4, damping: 1) { 89 | self.backgroundView.alpha = 1 90 | } 91 | 92 | ALAnimate.spring(time: 0.4, damping: 0.85, velocity: 0.8) { 93 | self.contentView.transform = .identity 94 | self.contentView.alpha = 1 95 | } 96 | } 97 | } 98 | 99 | // MARK: - Layout Setup 100 | private extension ALPopupController { 101 | func setupView() { 102 | backgroundView.alpha = 0 103 | backgroundView.backgroundColor = dimmedBackgroudColor ?? .black.alpha(0.6) 104 | contentView.alpha = 0 105 | } 106 | 107 | func setupConstraints() { 108 | view.addSubview(backgroundView) 109 | view.addSubview(contentView) 110 | 111 | let cardOffset: CGFloat = 16 112 | 113 | backgroundView.translatesAutoresizingMaskIntoConstraints = false 114 | backgroundView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 115 | backgroundView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true 116 | backgroundView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 117 | backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 118 | 119 | contentView.translatesAutoresizingMaskIntoConstraints = false 120 | contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 121 | let leadingContent = contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: cardOffset) 122 | leadingContent.priority = .init(999) 123 | leadingContent.isActive = true 124 | let trailingContent = contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -cardOffset) 125 | trailingContent.priority = .init(999) 126 | trailingContent.isActive = true 127 | contentView.widthAnchor.constraint(lessThanOrEqualToConstant: 485).isActive = true 128 | contentView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor).isActive = true 129 | contentView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor).isActive = true 130 | contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 131 | } 132 | 133 | func updateContentCorners() { 134 | contentView.cornerRadius = 24 135 | contentView.layer.masksToBounds = true 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/ALPopup/Controllers/Popup/ALPopupTemplateVontroller.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | public final class ALPopupTemplateController: ALPopupController { 26 | 27 | // MARK: - UI Elements 28 | 29 | public lazy var tempateView = ALTemplateView() 30 | 31 | // MARK: UIViewController 32 | 33 | public override func viewDidLoad() { 34 | super.viewDidLoad() 35 | setupConstraints() 36 | tempateView.insetsLayoutMarginsFromSafeArea = true 37 | } 38 | } 39 | 40 | // MARK: - Layout Setup 41 | 42 | private extension ALPopupTemplateController { 43 | func setupConstraints() { 44 | contentView.addSubview(tempateView) 45 | 46 | let sideOffset: CGFloat = 26 47 | let maxWidth: CGFloat = 380 48 | 49 | tempateView.translatesAutoresizingMaskIntoConstraints = false 50 | tempateView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 36).isActive = true 51 | 52 | let leadingConstraint = tempateView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: sideOffset) 53 | leadingConstraint.priority = .init(999) 54 | leadingConstraint.isActive = true 55 | 56 | let trailingConstraint = tempateView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -sideOffset) 57 | trailingConstraint.priority = .init(999) 58 | trailingConstraint.isActive = true 59 | 60 | tempateView.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true 61 | tempateView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true 62 | 63 | 64 | tempateView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -20).isActive = true 65 | let bottomSafeAreaConstraint = tempateView.bottomAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.bottomAnchor, constant: -10) 66 | bottomSafeAreaConstraint.priority = .init(990) 67 | bottomSafeAreaConstraint.isActive = true 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/ALPopup/Extensions/UIColorExtension.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | extension UIColor { 26 | convenience init(light: UIColor, dark: UIColor) { 27 | if #available(iOS 13.0, tvOS 13.0, *) { 28 | self.init(dynamicProvider: { $0.userInterfaceStyle == .dark ? dark : light }) 29 | } else { 30 | self.init(cgColor: light.cgColor) 31 | } 32 | } 33 | 34 | convenience init(baseInterfaceLevel: UIColor, elevatedInterfaceLevel: UIColor ) { 35 | if #available(iOS 13.0, tvOS 13.0, *) { 36 | self.init { traitCollection in 37 | switch traitCollection.userInterfaceLevel { 38 | case .base: 39 | return baseInterfaceLevel 40 | case .elevated: 41 | return elevatedInterfaceLevel 42 | case .unspecified: 43 | return baseInterfaceLevel 44 | @unknown default: 45 | return baseInterfaceLevel 46 | } 47 | } 48 | } 49 | else { 50 | self.init(cgColor: baseInterfaceLevel.cgColor) 51 | } 52 | } 53 | 54 | convenience init(hex: String) { 55 | let hex = UIColor.parseHex(hex: hex, alpha: nil) 56 | self.init(red: hex.red, green: hex.green, blue: hex.blue, alpha: hex.alpha) 57 | } 58 | 59 | convenience init(hex: String, alpha: CGFloat) { 60 | let hex = UIColor.parseHex(hex: hex, alpha: alpha) 61 | self.init(red: hex.red, green: hex.green, blue: hex.blue, alpha: hex.alpha) 62 | } 63 | 64 | func alpha(_ alpha: CGFloat) -> UIColor { 65 | self.withAlphaComponent(alpha) 66 | } 67 | 68 | private static func parseHex(hex: String, alpha: CGFloat?) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { 69 | var red: CGFloat = 0.0 70 | var green: CGFloat = 0.0 71 | var blue: CGFloat = 0.0 72 | var newAlpha: CGFloat = alpha ?? 1.0 73 | var hex: String = hex 74 | 75 | if hex.hasPrefix("#") { 76 | let index = hex.index(hex.startIndex, offsetBy: 1) 77 | hex = String(hex[index...]) 78 | } 79 | 80 | let scanner = Scanner(string: hex) 81 | var hexValue: CUnsignedLongLong = 0 82 | if scanner.scanHexInt64(&hexValue) { 83 | switch (hex.count) { 84 | case 3: 85 | red = CGFloat((hexValue & 0xF00) >> 8) / 15.0 86 | green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0 87 | blue = CGFloat(hexValue & 0x00F) / 15.0 88 | case 4: 89 | red = CGFloat((hexValue & 0xF000) >> 12) / 15.0 90 | green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0 91 | blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0 92 | if alpha == nil { 93 | newAlpha = CGFloat(hexValue & 0x000F) / 15.0 94 | } 95 | case 6: 96 | red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0 97 | green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0 98 | blue = CGFloat(hexValue & 0x0000FF) / 255.0 99 | case 8: 100 | red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0 101 | green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0 102 | blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0 103 | if alpha == nil { 104 | newAlpha = CGFloat(hexValue & 0x000000FF) / 255.0 105 | } 106 | default: 107 | print("UIColorExtension - Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8") 108 | } 109 | } else { 110 | print("UIColorExtension - Scan hex error") 111 | } 112 | return (red, green, blue, newAlpha) 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /Sources/ALPopup/Extensions/UIScreenExtension.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | extension UIScreen { 26 | private static let cornerRadiusKey: String = { 27 | let components = ["Radius", "Corner", "display", "_"] 28 | return components.reversed().joined() 29 | }() 30 | 31 | /// The corner radius of the display. Uses a private property of `UIScreen`, 32 | /// and may report 0 if the API changes. 33 | public var displayCornerRadius: CGFloat { 34 | guard let cornerRadius = self.value(forKey: Self.cornerRadiusKey) as? CGFloat else { 35 | return 0 36 | } 37 | 38 | return cornerRadius 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ALPopup/Extensions/UIViewExtension.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | extension UIView { 26 | /** 27 | ALPopup: Correct rounded corners by current frame. 28 | 29 | - important: 30 | Need call after changed frame. Better leave it in `layoutSubviews` method. 31 | 32 | - parameter corners: Case of `CACornerMask` 33 | - parameter radius: Amount of radius. 34 | */ 35 | public func roundCorners(_ corners: CACornerMask = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner], radius: CGFloat, continuous: Bool = false) { 36 | layer.cornerRadius = radius 37 | layer.maskedCorners = corners 38 | 39 | if #available(iOS 13, *), continuous { 40 | layer.cornerCurve = .continuous 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/ALPopup/Helpers/ALAnimate.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | enum ALAnimate { 26 | static func spring(time: TimeInterval, delay: TimeInterval = 0, damping: CGFloat = 0.8, velocity: CGFloat = 0.3, animate: @escaping() -> Void, completion: @escaping () -> Void) { 27 | UIView.animate(withDuration: time, 28 | delay: delay, 29 | usingSpringWithDamping: damping, 30 | initialSpringVelocity: velocity, 31 | options: [.curveEaseInOut, .allowUserInteraction], 32 | animations: { 33 | animate() 34 | }, completion: { _ in 35 | completion() 36 | }) 37 | } 38 | 39 | static func spring(time: TimeInterval, delay: TimeInterval = 0, damping: CGFloat = 0.8, velocity: CGFloat = 0.3, animate: @escaping() -> Void) { 40 | UIView.animate(withDuration: time, 41 | delay: delay, 42 | usingSpringWithDamping: damping, 43 | initialSpringVelocity: velocity, 44 | options: [.curveEaseInOut, 45 | .allowUserInteraction, 46 | .preferredFramesPerSecond60], 47 | animations: { 48 | animate() 49 | }, completion: { _ in }) 50 | } 51 | 52 | static func base(time: TimeInterval, delay: TimeInterval = 0 , animate: @escaping() -> Void, completion: @escaping () -> Void) { 53 | UIView.animate(withDuration: time, 54 | delay: delay, 55 | animations: { 56 | animate() 57 | }, completion: { _ in 58 | completion() 59 | }) 60 | } 61 | 62 | static func base(time: TimeInterval, delay: TimeInterval = 0 , animate: @escaping() -> Void) { 63 | UIView.animate(withDuration: time, 64 | delay: delay, 65 | animations: { 66 | animate() 67 | }, completion: { _ in }) 68 | } 69 | 70 | static func transition(with view: UIView, time: TimeInterval, options: UIView.AnimationOptions = .transitionCrossDissolve, animate: @escaping() -> Void) { 71 | UIView.transition(with: view, duration: time, options: options) { 72 | animate() 73 | } completion: { _ in } 74 | } 75 | 76 | static func transition(with view: UIView, time: TimeInterval, options: UIView.AnimationOptions = .transitionCrossDissolve, animate: @escaping() -> Void, completion: @escaping () -> Void) { 77 | UIView.transition(with: view, duration: time, options: options) { 78 | animate() 79 | } completion: { _ in completion() } 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Sources/ALPopup/Helpers/ALHaptic.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | /** 26 | Wrapper of haptic styles. 27 | */ 28 | public enum ALHaptic { 29 | 30 | case success 31 | case warning 32 | case error 33 | case none 34 | 35 | func impact() { 36 | let generator = UINotificationFeedbackGenerator() 37 | switch self { 38 | case .success: 39 | generator.notificationOccurred(.success) 40 | case .warning: 41 | generator.notificationOccurred(.warning) 42 | case .error: 43 | generator.notificationOccurred(.error) 44 | case .none: 45 | break 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/ALPopup/Model/ALTemplateSettings.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | /** 26 | The structure of the template controller. 27 | 28 | If any of the parameters is not specified in the initializer, then it will not be added to the controller. 29 | */ 30 | public struct ALTemplateSettings { 31 | /// Title for `ALTemplateView.titleLabel` 32 | public let title: String? 33 | 34 | /// Subtitle for `ALTemplateView.subtitleLabel` 35 | public let subtitle: String? 36 | 37 | /// Image for `ALTemplateView.imageView` 38 | public let image: UIImage? 39 | 40 | /// Button title for `ALTemplateView.primaryButton` 41 | public let primaryButtonTitle: String? 42 | 43 | /// Button title for `ALTemplateView.secondaryButton` 44 | public let secondaryButtonTitle: String? 45 | 46 | public init(title: String?, subtitle: String?, image: UIImage? = nil, privaryButtonTitle: String? = nil, secondaryButtonTitle: String? = nil) { 47 | self.title = title 48 | self.subtitle = subtitle 49 | self.image = image 50 | self.primaryButtonTitle = privaryButtonTitle 51 | self.secondaryButtonTitle = secondaryButtonTitle 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/ALPopup/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/ALPopup/Resources/Assets.xcassets/smallCloseButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "smallCloseButton.svg", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "smallCloseButtonDark.svg", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | }, 22 | "properties" : { 23 | "preserves-vector-representation" : true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/ALPopup/Resources/Assets.xcassets/smallCloseButton.imageset/smallCloseButton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Sources/ALPopup/Resources/Assets.xcassets/smallCloseButton.imageset/smallCloseButtonDark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Sources/ALPopup/Source.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | 26 | enum Source { 27 | static var bundle: Bundle { 28 | 29 | // If installed via SPM, will be available bundle .module. 30 | 31 | #if ALPOPUP_SPM 32 | return .module 33 | #else 34 | 35 | // If installed via Cocoapods, should use bundle from podspec. 36 | 37 | let path = Bundle(for: ALPopup.self).path(forResource: "ALPopup", ofType: "bundle") ?? "" 38 | let bundle = Bundle(path: path) ?? Bundle.main 39 | return bundle 40 | #endif 41 | } 42 | 43 | enum Color { 44 | static var contentColor: UIColor { 45 | .init(light: .init(hex: "fff"), dark: .init(hex: "1C1C1E")) 46 | } 47 | 48 | static var accent: UIColor { 49 | .init(hex: "237EF0") 50 | } 51 | 52 | static var overlayAccentColor: UIColor { 53 | .init(hex: "fff") 54 | } 55 | 56 | static var labelPrimary: UIColor { 57 | .init(light: .init(hex: "000"), dark: .init(hex: "fff")) 58 | } 59 | 60 | static var labelSecondary: UIColor { 61 | .init(light: .init(hex: "#3C3C43", alpha: 0.6), dark: .init(hex: "#EBEBF5", alpha: 0.6)) 62 | } 63 | } 64 | 65 | enum Image { 66 | static var smallCloseButton: UIImage? { 67 | UIImage(named: "smallCloseButton", in: bundle, compatibleWith: nil)?.withRenderingMode(.alwaysOriginal) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/ALPopup/UIElements/Controls/ALActionButton.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | open class ALActionButton: ALBasicButton { 26 | public var roundCorners: CGFloat = 14 { didSet { layoutSubviews() } } 27 | private lazy var indicatorView = UIActivityIndicatorView() 28 | 29 | public var isLoading = false { 30 | didSet { 31 | loading() 32 | } 33 | } 34 | 35 | init() { 36 | super.init(frame: .zero) 37 | applyDefaultAppearance(with: Colorise(content: Source.Color.overlayAccentColor, background: Source.Color.accent)) 38 | titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold) 39 | 40 | 41 | addSubview(indicatorView) 42 | translatesAutoresizingMaskIntoConstraints = false 43 | indicatorView.translatesAutoresizingMaskIntoConstraints = false 44 | let heightConstraint = heightAnchor.constraint(equalToConstant: 50) 45 | heightConstraint.priority = .init(999) 46 | heightConstraint.isActive = true 47 | indicatorView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true 48 | indicatorView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true 49 | 50 | adjustsImageWhenHighlighted = false 51 | adjustsImageWhenDisabled = false 52 | imageEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: 3.5) 53 | titleEdgeInsets = .init(top: 0, left: 3.5, bottom: 0, right: 0) 54 | } 55 | 56 | required public init?(coder: NSCoder) { 57 | fatalError("init(coder:) has not been implemented") 58 | } 59 | 60 | open override func layoutSubviews() { 61 | super.layoutSubviews() 62 | roundCorners(radius: roundCorners, continuous: true) 63 | } 64 | 65 | open override func applyDefaultAppearance(with colorise: ALBasicButton.Colorise? = nil) { 66 | super.applyDefaultAppearance(with: colorise) 67 | if case .custom(let titleColor) = colorise?.title { 68 | indicatorView.color = titleColor 69 | } 70 | } 71 | 72 | func loading() { 73 | if isLoading { 74 | isUserInteractionEnabled = false 75 | self.titleLabel?.alpha = 0 76 | self.imageView?.transform = .init(scaleX: 0, y: 0) 77 | self.indicatorView.startAnimating() 78 | } else { 79 | isUserInteractionEnabled = true 80 | indicatorView.stopAnimating() 81 | self.titleLabel?.alpha = 1 82 | self.imageView?.transform = .init(scaleX: 1, y: 1) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/ALPopup/UIElements/Controls/ALBasicButton.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | open class ALBasicButton: UIButton { 26 | 27 | // MARK: - Ovveride 28 | 29 | open override var isHighlighted: Bool { 30 | didSet { 31 | updateAppearance() 32 | } 33 | } 34 | 35 | open override var isEnabled: Bool { 36 | didSet { 37 | updateAppearance() 38 | } 39 | } 40 | 41 | open override func tintColorDidChange() { 42 | super.tintColorDidChange() 43 | updateAppearance() 44 | } 45 | 46 | // MARK: - Colorises Process 47 | 48 | /** 49 | This method sets the color scheme for a specific button state 50 | */ 51 | open func setColorise(_ colorise: Colorise, for state: AppearanceState) { 52 | switch state { 53 | case .default: 54 | defaultColorise = colorise 55 | case .dimmed: 56 | dimmedColorise = colorise 57 | case .disabled: 58 | disabledColorise = colorise 59 | } 60 | updateAppearance() 61 | } 62 | 63 | /** 64 | Get colorise for specific state. 65 | */ 66 | open func getColorise(for state: AppearanceState) -> Colorise { 67 | switch state { 68 | case .default: 69 | return defaultColorise 70 | case .dimmed: 71 | return dimmedColorise 72 | case .disabled: 73 | return disabledColorise 74 | } 75 | } 76 | 77 | /** 78 | Set the color scheme for the default state. 79 | `.dimmed` and `.disabled` states calculate automaticaly. 80 | */ 81 | open func applyDefaultAppearance(with colorise: Colorise? = nil) { 82 | defaultColorise = colorise ?? Colorise(content: .tint, background: .custom(.clear)) 83 | let defaultBackgroundColor = colorFromColoriseMode(defaultColorise.background) 84 | let dimmedBackground = defaultBackgroundColor == .clear ? .clear : dimmedContentColor.alpha(0.1) 85 | dimmedColorise = Colorise(content: dimmedContentColor, background: dimmedBackground) 86 | disabledColorise = Colorise(content: dimmedContentColor, background: dimmedBackground) 87 | updateAppearance() 88 | } 89 | 90 | /** 91 | Update colors for current state. 92 | */ 93 | private func updateAppearance() { 94 | if tintAdjustmentMode == .dimmed { 95 | applyColorise(dimmedColorise) 96 | } else if isEnabled { 97 | applyColorise(defaultColorise) 98 | var imageTintColor = colorFromColoriseMode(defaultColorise.icon) 99 | imageTintColor = imageTintColor.withAlphaComponent(isHighlighted ? highlightOpacity : 1) 100 | imageView?.tintColor = imageTintColor 101 | } else { 102 | applyColorise(disabledColorise) 103 | } 104 | } 105 | 106 | /** 107 | Apply colors from special colorise. 108 | */ 109 | private func applyColorise(_ colorise: Colorise) { 110 | let titleColor = colorFromColoriseMode(colorise.title) 111 | setTitleColor(titleColor, for: .normal) 112 | setTitleColor(titleColor.withAlphaComponent(highlightOpacity), for: .highlighted) 113 | imageView?.tintColor = colorFromColoriseMode(colorise.icon) 114 | backgroundColor = colorFromColoriseMode(colorise.background) 115 | } 116 | 117 | /** 118 | Get valid color by `Colorise.Mode` for this button. 119 | 120 | For example each call can return diffrent color for if changing tint. 121 | If mode in custom always return this value. 122 | 123 | - parameter mode: Colorise mode from which need get color. 124 | */ 125 | public func colorFromColoriseMode(_ mode: Colorise.Mode) -> UIColor { 126 | switch mode { 127 | case .tint: return self.tintColor 128 | case .custom(let color): return color 129 | } 130 | } 131 | 132 | // MARK: - Colorises Models 133 | 134 | /** 135 | Colorise for default state. 136 | */ 137 | private lazy var defaultColorise = Colorise(content: tintColor, background: .clear) 138 | 139 | /** 140 | Colorise for disabled state. 141 | */ 142 | private lazy var disabledColorise = Colorise(content: dimmedContentColor, background: .clear) 143 | 144 | /** 145 | Colorise for dimmed mode. 146 | */ 147 | private lazy var dimmedColorise = Colorise(content: dimmedContentColor, background: .clear) 148 | 149 | // MARK: - Data 150 | 151 | /** 152 | Opacity of elements when button higlight. 153 | */ 154 | open var highlightOpacity: CGFloat = 0.7 155 | 156 | /** 157 | Default dimmed content color with compability iOS 12. 158 | */ 159 | private var dimmedContentColor: UIColor { 160 | if #available(iOS 13, tvOS 13, *) { 161 | return UIColor.secondaryLabel 162 | } else { 163 | return UIColor(hex: "3c3c4399") 164 | } 165 | } 166 | 167 | // MARK: - Models 168 | 169 | /** 170 | Represent colors for state of button for elements. 171 | */ 172 | public struct Colorise { 173 | 174 | public var title: Mode 175 | public var icon: Mode 176 | public var background: Mode 177 | 178 | /** 179 | This init allow create colorise with automatically tint oberving. 180 | 181 | - parameter content: Colorise mode for `title` & `icon`. 182 | - parameter background: Colorise mode for background. 183 | */ 184 | public init(content: Mode, background: Mode) { 185 | self.title = content 186 | self.icon = content 187 | self.background = background 188 | } 189 | 190 | /** 191 | - parameter content: Color for `title` & `icon`. 192 | - parameter background: Color for background. 193 | */ 194 | public init(content: UIColor, background: UIColor) { 195 | self.title = .custom(content) 196 | self.icon = .custom(content) 197 | self.background = .custom(background) 198 | } 199 | 200 | /** 201 | - parameter content: Color for `title` & `icon`. 202 | - parameter background: Color for image. 203 | - parameter background: Color for background. 204 | */ 205 | public init(content: UIColor, icon: UIColor, background: UIColor) { 206 | self.title = .custom(content) 207 | self.icon = .custom(icon) 208 | self.background = .custom(background) 209 | } 210 | 211 | /** 212 | Represent of rendering colors in button. 213 | 214 | It need for have dynamic tint color and fixed custom. 215 | */ 216 | public enum Mode { 217 | 218 | /** 219 | Always specoific color. 220 | */ 221 | case custom(UIColor) 222 | 223 | /** 224 | Observe and apply tint color of button. 225 | */ 226 | case tint 227 | } 228 | } 229 | 230 | /** 231 | Button state. 232 | */ 233 | public enum AppearanceState { 234 | 235 | /** 236 | The button is in the normal state. 237 | */ 238 | case `default` 239 | 240 | /** 241 | The button is in a dimmed state, for example when another `UIViewController` is presented. 242 | */ 243 | case dimmed 244 | 245 | /** 246 | The button is disabled. Controlled through the `isDisabled` property. 247 | */ 248 | case disabled 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /Sources/ALPopup/UIElements/Views/ALPopupContentView.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | public class ALPopupContentView: UIView { 26 | 27 | // MARK: - Open Proporties 28 | 29 | // MARK: - Internal Proporties 30 | 31 | internal var subviewAdded: ((UIView) -> Void)? 32 | 33 | internal var cornerRadius: CGFloat = 0 { 34 | didSet { 35 | layoutSubviews() 36 | } 37 | } 38 | 39 | public init() { 40 | super.init(frame: .zero) 41 | backgroundColor = Source.Color.contentColor 42 | } 43 | 44 | required init?(coder: NSCoder) { 45 | fatalError("init(coder:) has not been implemented") 46 | } 47 | 48 | // MARK: - Life cycle 49 | open override func layoutSubviews() { 50 | super.layoutSubviews() 51 | roundCorners(radius: cornerRadius, continuous: true) 52 | } 53 | } 54 | 55 | extension UIView { 56 | internal func subviews(ofType WhatType: T.Type) -> [T] { 57 | var result = self.subviews.compactMap {$0 as? T} 58 | for sub in self.subviews { 59 | result.append(contentsOf: sub.subviews(ofType: WhatType)) 60 | } 61 | return result 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/ALPopup/UIElements/Views/ALTemplateView.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Alexandr Guzenko (alxrguz@icloud.com) 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 | 23 | import UIKit 24 | 25 | public class ALTemplateView: UIView { 26 | 27 | // MARK: - UI Elements 28 | 29 | public lazy var titleLabel = UILabel() 30 | public lazy var subtitleLabel = UILabel() 31 | public lazy var imageView = UIImageView() 32 | public lazy var primaryButton = ALActionButton() 33 | public lazy var secondaryButton = ALActionButton() 34 | private lazy var titleStackView = UIStackView() 35 | private lazy var buttonStackView = UIStackView() 36 | private lazy var contentStackView = UIStackView() 37 | 38 | // MARK: - Public Proporties 39 | 40 | /// Template configuration. The corresponding views will be created for the specified parameters. 41 | public var configuration: ALTemplateSettings? { didSet { updateUI() } } 42 | 43 | /// A closure indicating that the primary button was pressed 44 | public var primaryButtonAction: (() -> Void)? = nil 45 | 46 | /// A closure indicating that the secondary button was pressed 47 | public var secondaryButtonAction: (() -> Void)? = nil 48 | 49 | // MARK: - Life cycle 50 | 51 | init() { 52 | super.init(frame: .zero) 53 | setupView() 54 | setupConstraints() 55 | setupActions() 56 | } 57 | 58 | required init?(coder: NSCoder) { 59 | fatalError("init(coder:) has not been implemented") 60 | } 61 | } 62 | 63 | // MARK: - Handlers 64 | 65 | private extension ALTemplateView { 66 | @objc func primaryButtonTapped() { 67 | primaryButtonAction?() 68 | } 69 | 70 | @objc func secondaryButtonTapped() { 71 | secondaryButtonAction?() 72 | } 73 | } 74 | 75 | // MARK: - Private Methods 76 | 77 | private extension ALTemplateView { 78 | func setupActions() { 79 | primaryButton.addTarget(self, action: #selector(primaryButtonTapped), for: .touchUpInside) 80 | secondaryButton.addTarget(self, action: #selector(secondaryButtonTapped), for: .touchUpInside) 81 | } 82 | } 83 | 84 | // MARK: - Layout Setup 85 | 86 | private extension ALTemplateView { 87 | func setupView() { 88 | titleLabel.font = .systemFont(ofSize: 28, weight: .bold) 89 | titleLabel.textColor = Source.Color.labelPrimary 90 | titleLabel.text = "Title" 91 | titleLabel.textAlignment = .center 92 | 93 | subtitleLabel.font = .systemFont(ofSize: 17, weight: .regular) 94 | subtitleLabel.textColor = Source.Color.labelSecondary 95 | subtitleLabel.text = "Subtitle" 96 | subtitleLabel.numberOfLines = 0 97 | subtitleLabel.textAlignment = .center 98 | 99 | imageView.contentMode = .scaleAspectFit 100 | 101 | primaryButton.setTitle("Ok", for: .normal) 102 | 103 | secondaryButton.setTitle("Cancel", for: .normal) 104 | secondaryButton.applyDefaultAppearance(with: .init(content: Source.Color.accent, background: .clear)) 105 | secondaryButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) 106 | 107 | titleStackView.axis = .vertical 108 | titleStackView.spacing = 6 109 | 110 | buttonStackView.axis = .vertical 111 | buttonStackView.spacing = 10 112 | 113 | contentStackView.axis = .vertical 114 | contentStackView.alignment = .center 115 | } 116 | 117 | func updateUI() { 118 | if let title = configuration?.title { 119 | titleLabel.text = title 120 | titleLabel.isHidden = false 121 | } else { 122 | titleLabel.isHidden = true 123 | } 124 | 125 | if let subtitle = configuration?.subtitle { 126 | subtitleLabel.text = subtitle 127 | subtitleLabel.isHidden = false 128 | } else { 129 | subtitleLabel.isHidden = true 130 | } 131 | 132 | if let image = configuration?.image { 133 | imageView.image = image 134 | imageView.isHidden = false 135 | } else { 136 | imageView.isHidden = true 137 | } 138 | 139 | if let primaryButtonTitle = configuration?.primaryButtonTitle { 140 | primaryButton.setTitle(primaryButtonTitle, for: .normal) 141 | primaryButton.isHidden = false 142 | } else { 143 | primaryButton.isHidden = true 144 | } 145 | 146 | if let secondaryButtonTitle = configuration?.secondaryButtonTitle { 147 | secondaryButton.setTitle(secondaryButtonTitle, for: .normal) 148 | secondaryButton.isHidden = false 149 | } else { 150 | secondaryButton.isHidden = true 151 | } 152 | } 153 | 154 | func setupConstraints() { 155 | addSubview(contentStackView) 156 | 157 | contentStackView.addArrangedSubview(titleStackView) 158 | contentStackView.addArrangedSubview(imageView) 159 | contentStackView.addArrangedSubview(buttonStackView) 160 | 161 | contentStackView.setCustomSpacing(20, after: titleStackView) 162 | contentStackView.setCustomSpacing(25, after: imageView) 163 | 164 | titleStackView.addArrangedSubview(titleLabel) 165 | titleStackView.addArrangedSubview(subtitleLabel) 166 | buttonStackView.addArrangedSubview(primaryButton) 167 | buttonStackView.addArrangedSubview(secondaryButton) 168 | 169 | contentStackView.translatesAutoresizingMaskIntoConstraints = false 170 | contentStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true 171 | contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true 172 | contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true 173 | contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true 174 | 175 | imageView.translatesAutoresizingMaskIntoConstraints = false 176 | imageView.widthAnchor.constraint(lessThanOrEqualToConstant: 130).isActive = true 177 | let imageWidthConstraint = imageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.4) 178 | imageWidthConstraint.priority = .init(999) 179 | imageWidthConstraint.isActive = true 180 | let imageHeightConstraint = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor) 181 | imageHeightConstraint.priority = .init(999) 182 | imageHeightConstraint.isActive = true 183 | 184 | buttonStackView.translatesAutoresizingMaskIntoConstraints = false 185 | buttonStackView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true 186 | 187 | 188 | secondaryButton.heightAnchor.constraint(equalToConstant: 40).isActive = true 189 | } 190 | } 191 | 192 | --------------------------------------------------------------------------------