├── .gitignore ├── Assets ├── logo.png ├── screen_1.png ├── screen_10.png ├── screen_11.png ├── screen_2.png ├── screen_3.png ├── screen_4.png ├── screen_5.png ├── screen_6.png ├── screen_7.png ├── screen_8.png └── screen_9.png ├── ElegantPopover.podspec ├── ElegantPopover.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ElegantPopover.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ElegantPopover ├── ElegantPopover.h ├── ElegantPopoverController.swift ├── Info.plist ├── PSArrow.swift ├── PSBorder.swift ├── PSDesign.swift └── PSPopoverBackgroundView.swift ├── LICENSE ├── Podfile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata/ 2 | Pods/ 3 | Podfile.lock 4 | -------------------------------------------------------------------------------- /Assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/logo.png -------------------------------------------------------------------------------- /Assets/screen_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_1.png -------------------------------------------------------------------------------- /Assets/screen_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_10.png -------------------------------------------------------------------------------- /Assets/screen_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_11.png -------------------------------------------------------------------------------- /Assets/screen_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_2.png -------------------------------------------------------------------------------- /Assets/screen_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_3.png -------------------------------------------------------------------------------- /Assets/screen_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_4.png -------------------------------------------------------------------------------- /Assets/screen_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_5.png -------------------------------------------------------------------------------- /Assets/screen_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_6.png -------------------------------------------------------------------------------- /Assets/screen_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_7.png -------------------------------------------------------------------------------- /Assets/screen_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_8.png -------------------------------------------------------------------------------- /Assets/screen_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prasad1120/ElegantPopover/2cfd5cc0703c3830330b02f1979ff9b03f3147fe/Assets/screen_9.png -------------------------------------------------------------------------------- /ElegantPopover.podspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pod::Spec.new do |spec| 4 | 5 | spec.name = "ElegantPopover" 6 | spec.version = "1.0.0" 7 | spec.summary = "Highly customisable popovers with different shapes, borders, arrow styles and gradient in iOS." 8 | spec.description = <<-DESC 9 | A Cocoapod which can be used to create highly customisable popovers with different shapes, borders, arrow styles and gradient in iOS. 10 | DESC 11 | spec.homepage = "https://github.com/prasad1120/ElegantPopover" 12 | spec.license = { :type => 'MIT', :file => 'LICENSE' } 13 | spec.author = { "Prasad" => "prasadshinde1120@gmail.com" } 14 | spec.platform = :ios, "11.0" 15 | spec.ios.deployment_target = "11.0" 16 | spec.source = { :git => "https://github.com/prasad1120/ElegantPopover.git", :tag => "#{spec.version}" } 17 | spec.source_files = "ElegantPopover/**/*.{swift}" 18 | spec.requires_arc = true 19 | spec.dependency 'ClippingBezier', '1.0.6' 20 | spec.swift_version = "4.0" 21 | 22 | end 23 | -------------------------------------------------------------------------------- /ElegantPopover.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 868448E724B03A28001E8243 /* ElegantPopoverController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 868448E624B03A28001E8243 /* ElegantPopoverController.swift */; }; 11 | 86A25A9F2486C15600843B4D /* ElegantPopover.h in Headers */ = {isa = PBXBuildFile; fileRef = 86A25A9D2486C15600843B4D /* ElegantPopover.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | 86ECEFA7249DD9EF00D1F2CE /* PSDesign.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86ECEFA4249DD9EE00D1F2CE /* PSDesign.swift */; }; 13 | 86ECEFA9249DD9EF00D1F2CE /* PSArrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86ECEFA6249DD9EF00D1F2CE /* PSArrow.swift */; }; 14 | 86ECEFAB249DDA0C00D1F2CE /* PSPopoverBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86ECEFAA249DDA0C00D1F2CE /* PSPopoverBackgroundView.swift */; }; 15 | 86ECEFAD249DDBE700D1F2CE /* PSBorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86ECEFAC249DDBE700D1F2CE /* PSBorder.swift */; }; 16 | EFAD672A8642B043B4448722 /* libPods-ElegantPopover.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DCA2D9114581F7C41BAF73 /* libPods-ElegantPopover.a */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 1158D4B1E3EC71C848DDE0B0 /* Pods-ElegantPopover.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ElegantPopover.debug.xcconfig"; path = "Target Support Files/Pods-ElegantPopover/Pods-ElegantPopover.debug.xcconfig"; sourceTree = ""; }; 21 | 868448E624B03A28001E8243 /* ElegantPopoverController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElegantPopoverController.swift; sourceTree = ""; }; 22 | 86A25A9A2486C15600843B4D /* ElegantPopover.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ElegantPopover.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 86A25A9D2486C15600843B4D /* ElegantPopover.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ElegantPopover.h; sourceTree = ""; }; 24 | 86A25A9E2486C15600843B4D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | 86ECEFA4249DD9EE00D1F2CE /* PSDesign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PSDesign.swift; sourceTree = ""; }; 26 | 86ECEFA6249DD9EF00D1F2CE /* PSArrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PSArrow.swift; sourceTree = ""; }; 27 | 86ECEFAA249DDA0C00D1F2CE /* PSPopoverBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PSPopoverBackgroundView.swift; sourceTree = ""; }; 28 | 86ECEFAC249DDBE700D1F2CE /* PSBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PSBorder.swift; sourceTree = ""; }; 29 | 92DCA2D9114581F7C41BAF73 /* libPods-ElegantPopover.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ElegantPopover.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | F8DA824FEDFF2A9DCBEBD121 /* Pods-ElegantPopover.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ElegantPopover.release.xcconfig"; path = "Target Support Files/Pods-ElegantPopover/Pods-ElegantPopover.release.xcconfig"; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 86A25A972486C15600843B4D /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | EFAD672A8642B043B4448722 /* libPods-ElegantPopover.a in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 4779FF1E5238034ACDAE9EC5 /* Pods */ = { 46 | isa = PBXGroup; 47 | children = ( 48 | 1158D4B1E3EC71C848DDE0B0 /* Pods-ElegantPopover.debug.xcconfig */, 49 | F8DA824FEDFF2A9DCBEBD121 /* Pods-ElegantPopover.release.xcconfig */, 50 | ); 51 | path = Pods; 52 | sourceTree = ""; 53 | }; 54 | 86A25A902486C15600843B4D = { 55 | isa = PBXGroup; 56 | children = ( 57 | 86A25A9C2486C15600843B4D /* ElegantPopover */, 58 | 86A25A9B2486C15600843B4D /* Products */, 59 | 4779FF1E5238034ACDAE9EC5 /* Pods */, 60 | CD1E6F6011D64609E0C53998 /* Frameworks */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | 86A25A9B2486C15600843B4D /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 86A25A9A2486C15600843B4D /* ElegantPopover.framework */, 68 | ); 69 | name = Products; 70 | sourceTree = ""; 71 | }; 72 | 86A25A9C2486C15600843B4D /* ElegantPopover */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 868448E624B03A28001E8243 /* ElegantPopoverController.swift */, 76 | 86ECEFA6249DD9EF00D1F2CE /* PSArrow.swift */, 77 | 86ECEFA4249DD9EE00D1F2CE /* PSDesign.swift */, 78 | 86ECEFAA249DDA0C00D1F2CE /* PSPopoverBackgroundView.swift */, 79 | 86A25A9D2486C15600843B4D /* ElegantPopover.h */, 80 | 86A25A9E2486C15600843B4D /* Info.plist */, 81 | 86ECEFAC249DDBE700D1F2CE /* PSBorder.swift */, 82 | ); 83 | path = ElegantPopover; 84 | sourceTree = ""; 85 | }; 86 | CD1E6F6011D64609E0C53998 /* Frameworks */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 92DCA2D9114581F7C41BAF73 /* libPods-ElegantPopover.a */, 90 | ); 91 | name = Frameworks; 92 | sourceTree = ""; 93 | }; 94 | /* End PBXGroup section */ 95 | 96 | /* Begin PBXHeadersBuildPhase section */ 97 | 86A25A952486C15600843B4D /* Headers */ = { 98 | isa = PBXHeadersBuildPhase; 99 | buildActionMask = 2147483647; 100 | files = ( 101 | 86A25A9F2486C15600843B4D /* ElegantPopover.h in Headers */, 102 | ); 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | /* End PBXHeadersBuildPhase section */ 106 | 107 | /* Begin PBXNativeTarget section */ 108 | 86A25A992486C15600843B4D /* ElegantPopover */ = { 109 | isa = PBXNativeTarget; 110 | buildConfigurationList = 86A25AA22486C15600843B4D /* Build configuration list for PBXNativeTarget "ElegantPopover" */; 111 | buildPhases = ( 112 | DB03FE207673D1018971E4CC /* [CP] Check Pods Manifest.lock */, 113 | 86A25A952486C15600843B4D /* Headers */, 114 | 86A25A962486C15600843B4D /* Sources */, 115 | 86A25A972486C15600843B4D /* Frameworks */, 116 | 86A25A982486C15600843B4D /* Resources */, 117 | ); 118 | buildRules = ( 119 | ); 120 | dependencies = ( 121 | ); 122 | name = ElegantPopover; 123 | productName = ElegantPopover; 124 | productReference = 86A25A9A2486C15600843B4D /* ElegantPopover.framework */; 125 | productType = "com.apple.product-type.framework"; 126 | }; 127 | /* End PBXNativeTarget section */ 128 | 129 | /* Begin PBXProject section */ 130 | 86A25A912486C15600843B4D /* Project object */ = { 131 | isa = PBXProject; 132 | attributes = { 133 | LastUpgradeCheck = 1130; 134 | ORGANIZATIONNAME = prasad.shinde; 135 | TargetAttributes = { 136 | 86A25A992486C15600843B4D = { 137 | CreatedOnToolsVersion = 11.3.1; 138 | }; 139 | }; 140 | }; 141 | buildConfigurationList = 86A25A942486C15600843B4D /* Build configuration list for PBXProject "ElegantPopover" */; 142 | compatibilityVersion = "Xcode 11.0"; 143 | developmentRegion = en; 144 | hasScannedForEncodings = 0; 145 | knownRegions = ( 146 | en, 147 | Base, 148 | ); 149 | mainGroup = 86A25A902486C15600843B4D; 150 | productRefGroup = 86A25A9B2486C15600843B4D /* Products */; 151 | projectDirPath = ""; 152 | projectRoot = ""; 153 | targets = ( 154 | 86A25A992486C15600843B4D /* ElegantPopover */, 155 | ); 156 | }; 157 | /* End PBXProject section */ 158 | 159 | /* Begin PBXResourcesBuildPhase section */ 160 | 86A25A982486C15600843B4D /* Resources */ = { 161 | isa = PBXResourcesBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXResourcesBuildPhase section */ 168 | 169 | /* Begin PBXShellScriptBuildPhase section */ 170 | DB03FE207673D1018971E4CC /* [CP] Check Pods Manifest.lock */ = { 171 | isa = PBXShellScriptBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | ); 175 | inputFileListPaths = ( 176 | ); 177 | inputPaths = ( 178 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 179 | "${PODS_ROOT}/Manifest.lock", 180 | ); 181 | name = "[CP] Check Pods Manifest.lock"; 182 | outputFileListPaths = ( 183 | ); 184 | outputPaths = ( 185 | "$(DERIVED_FILE_DIR)/Pods-ElegantPopover-checkManifestLockResult.txt", 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | shellPath = /bin/sh; 189 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 190 | showEnvVarsInLog = 0; 191 | }; 192 | /* End PBXShellScriptBuildPhase section */ 193 | 194 | /* Begin PBXSourcesBuildPhase section */ 195 | 86A25A962486C15600843B4D /* Sources */ = { 196 | isa = PBXSourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 86ECEFAB249DDA0C00D1F2CE /* PSPopoverBackgroundView.swift in Sources */, 200 | 86ECEFA7249DD9EF00D1F2CE /* PSDesign.swift in Sources */, 201 | 86ECEFAD249DDBE700D1F2CE /* PSBorder.swift in Sources */, 202 | 86ECEFA9249DD9EF00D1F2CE /* PSArrow.swift in Sources */, 203 | 868448E724B03A28001E8243 /* ElegantPopoverController.swift in Sources */, 204 | ); 205 | runOnlyForDeploymentPostprocessing = 0; 206 | }; 207 | /* End PBXSourcesBuildPhase section */ 208 | 209 | /* Begin XCBuildConfiguration section */ 210 | 86A25AA02486C15600843B4D /* Debug */ = { 211 | isa = XCBuildConfiguration; 212 | buildSettings = { 213 | ALWAYS_SEARCH_USER_PATHS = NO; 214 | CLANG_ANALYZER_NONNULL = YES; 215 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 216 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 217 | CLANG_CXX_LIBRARY = "libc++"; 218 | CLANG_ENABLE_MODULES = YES; 219 | CLANG_ENABLE_OBJC_ARC = YES; 220 | CLANG_ENABLE_OBJC_WEAK = YES; 221 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 222 | CLANG_WARN_BOOL_CONVERSION = YES; 223 | CLANG_WARN_COMMA = YES; 224 | CLANG_WARN_CONSTANT_CONVERSION = YES; 225 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 226 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 227 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 228 | CLANG_WARN_EMPTY_BODY = YES; 229 | CLANG_WARN_ENUM_CONVERSION = YES; 230 | CLANG_WARN_INFINITE_RECURSION = YES; 231 | CLANG_WARN_INT_CONVERSION = YES; 232 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 233 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 234 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 235 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 236 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 237 | CLANG_WARN_STRICT_PROTOTYPES = YES; 238 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 239 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 240 | CLANG_WARN_UNREACHABLE_CODE = YES; 241 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 242 | COPY_PHASE_STRIP = NO; 243 | CURRENT_PROJECT_VERSION = 1; 244 | DEBUG_INFORMATION_FORMAT = dwarf; 245 | ENABLE_STRICT_OBJC_MSGSEND = YES; 246 | ENABLE_TESTABILITY = YES; 247 | GCC_C_LANGUAGE_STANDARD = gnu11; 248 | GCC_DYNAMIC_NO_PIC = NO; 249 | GCC_NO_COMMON_BLOCKS = YES; 250 | GCC_OPTIMIZATION_LEVEL = 0; 251 | GCC_PREPROCESSOR_DEFINITIONS = ( 252 | "DEBUG=1", 253 | "$(inherited)", 254 | ); 255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 256 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 257 | GCC_WARN_UNDECLARED_SELECTOR = YES; 258 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 259 | GCC_WARN_UNUSED_FUNCTION = YES; 260 | GCC_WARN_UNUSED_VARIABLE = YES; 261 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 262 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 263 | MTL_FAST_MATH = YES; 264 | ONLY_ACTIVE_ARCH = YES; 265 | SDKROOT = iphoneos; 266 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 267 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 268 | VERSIONING_SYSTEM = "apple-generic"; 269 | VERSION_INFO_PREFIX = ""; 270 | }; 271 | name = Debug; 272 | }; 273 | 86A25AA12486C15600843B4D /* Release */ = { 274 | isa = XCBuildConfiguration; 275 | buildSettings = { 276 | ALWAYS_SEARCH_USER_PATHS = NO; 277 | CLANG_ANALYZER_NONNULL = YES; 278 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 280 | CLANG_CXX_LIBRARY = "libc++"; 281 | CLANG_ENABLE_MODULES = YES; 282 | CLANG_ENABLE_OBJC_ARC = YES; 283 | CLANG_ENABLE_OBJC_WEAK = YES; 284 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_COMMA = YES; 287 | CLANG_WARN_CONSTANT_CONVERSION = YES; 288 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 289 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 290 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 291 | CLANG_WARN_EMPTY_BODY = YES; 292 | CLANG_WARN_ENUM_CONVERSION = YES; 293 | CLANG_WARN_INFINITE_RECURSION = YES; 294 | CLANG_WARN_INT_CONVERSION = YES; 295 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 297 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 299 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 300 | CLANG_WARN_STRICT_PROTOTYPES = YES; 301 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 302 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 303 | CLANG_WARN_UNREACHABLE_CODE = YES; 304 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 305 | COPY_PHASE_STRIP = NO; 306 | CURRENT_PROJECT_VERSION = 1; 307 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 308 | ENABLE_NS_ASSERTIONS = NO; 309 | ENABLE_STRICT_OBJC_MSGSEND = YES; 310 | GCC_C_LANGUAGE_STANDARD = gnu11; 311 | GCC_NO_COMMON_BLOCKS = YES; 312 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 313 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 314 | GCC_WARN_UNDECLARED_SELECTOR = YES; 315 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 316 | GCC_WARN_UNUSED_FUNCTION = YES; 317 | GCC_WARN_UNUSED_VARIABLE = YES; 318 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 319 | MTL_ENABLE_DEBUG_INFO = NO; 320 | MTL_FAST_MATH = YES; 321 | SDKROOT = iphoneos; 322 | SWIFT_COMPILATION_MODE = wholemodule; 323 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 324 | VALIDATE_PRODUCT = YES; 325 | VERSIONING_SYSTEM = "apple-generic"; 326 | VERSION_INFO_PREFIX = ""; 327 | }; 328 | name = Release; 329 | }; 330 | 86A25AA32486C15600843B4D /* Debug */ = { 331 | isa = XCBuildConfiguration; 332 | baseConfigurationReference = 1158D4B1E3EC71C848DDE0B0 /* Pods-ElegantPopover.debug.xcconfig */; 333 | buildSettings = { 334 | CODE_SIGN_STYLE = Automatic; 335 | DEFINES_MODULE = YES; 336 | DEVELOPMENT_TEAM = PY5VQ6GQ8Y; 337 | DYLIB_COMPATIBILITY_VERSION = 1; 338 | DYLIB_CURRENT_VERSION = 1; 339 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 340 | INFOPLIST_FILE = ElegantPopover/Info.plist; 341 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 342 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 343 | LD_RUNPATH_SEARCH_PATHS = ( 344 | "$(inherited)", 345 | "@executable_path/Frameworks", 346 | "@loader_path/Frameworks", 347 | ); 348 | MARKETING_VERSION = 1.0.0; 349 | PRODUCT_BUNDLE_IDENTIFIER = com.prasadshinde.ElegantPopover; 350 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 351 | SKIP_INSTALL = YES; 352 | SUPPORTS_MACCATALYST = NO; 353 | SWIFT_VERSION = 4.0; 354 | TARGETED_DEVICE_FAMILY = "1,2"; 355 | }; 356 | name = Debug; 357 | }; 358 | 86A25AA42486C15600843B4D /* Release */ = { 359 | isa = XCBuildConfiguration; 360 | baseConfigurationReference = F8DA824FEDFF2A9DCBEBD121 /* Pods-ElegantPopover.release.xcconfig */; 361 | buildSettings = { 362 | CODE_SIGN_STYLE = Automatic; 363 | DEFINES_MODULE = YES; 364 | DEVELOPMENT_TEAM = PY5VQ6GQ8Y; 365 | DYLIB_COMPATIBILITY_VERSION = 1; 366 | DYLIB_CURRENT_VERSION = 1; 367 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 368 | INFOPLIST_FILE = ElegantPopover/Info.plist; 369 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 370 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 371 | LD_RUNPATH_SEARCH_PATHS = ( 372 | "$(inherited)", 373 | "@executable_path/Frameworks", 374 | "@loader_path/Frameworks", 375 | ); 376 | MARKETING_VERSION = 1.0.0; 377 | PRODUCT_BUNDLE_IDENTIFIER = com.prasadshinde.ElegantPopover; 378 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 379 | SKIP_INSTALL = YES; 380 | SUPPORTS_MACCATALYST = NO; 381 | SWIFT_VERSION = 4.0; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | }; 384 | name = Release; 385 | }; 386 | /* End XCBuildConfiguration section */ 387 | 388 | /* Begin XCConfigurationList section */ 389 | 86A25A942486C15600843B4D /* Build configuration list for PBXProject "ElegantPopover" */ = { 390 | isa = XCConfigurationList; 391 | buildConfigurations = ( 392 | 86A25AA02486C15600843B4D /* Debug */, 393 | 86A25AA12486C15600843B4D /* Release */, 394 | ); 395 | defaultConfigurationIsVisible = 0; 396 | defaultConfigurationName = Release; 397 | }; 398 | 86A25AA22486C15600843B4D /* Build configuration list for PBXNativeTarget "ElegantPopover" */ = { 399 | isa = XCConfigurationList; 400 | buildConfigurations = ( 401 | 86A25AA32486C15600843B4D /* Debug */, 402 | 86A25AA42486C15600843B4D /* Release */, 403 | ); 404 | defaultConfigurationIsVisible = 0; 405 | defaultConfigurationName = Release; 406 | }; 407 | /* End XCConfigurationList section */ 408 | }; 409 | rootObject = 86A25A912486C15600843B4D /* Project object */; 410 | } 411 | -------------------------------------------------------------------------------- /ElegantPopover.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ElegantPopover.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ElegantPopover.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ElegantPopover.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ElegantPopover/ElegantPopover.h: -------------------------------------------------------------------------------- 1 | // 2 | // ElegantPopover.h 3 | // ElegantPopover 4 | // 5 | // Created by prasad-shinde on 02/06/20. 6 | // Copyright © 2020 prasad.shinde. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ElegantPopover. 12 | FOUNDATION_EXPORT double ElegantPopoverVersionNumber; 13 | 14 | //! Project version string for ElegantPopover. 15 | FOUNDATION_EXPORT const unsigned char ElegantPopoverVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /ElegantPopover/ElegantPopoverController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ElegantPopoverController.swift 3 | // ElegantPopover 4 | // 5 | // Created by Prasad Shinde on 04/02/20. 6 | // Copyright © 2020 Prasad Shinde. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | 12 | /// An object that manages the **ElegantPopover** 13 | public class ElegantPopoverController: UIViewController, UIPopoverPresentationControllerDelegate { 14 | 15 | /// The view to be inserted inside the popover 16 | private var contentView: UIView! 17 | 18 | /// An object representing the arrow of the popover. 19 | private var arrow: PSArrow 20 | 21 | /// An object used for defining visual attributes of the popover. 22 | private var design: PSDesign! 23 | 24 | /// Popover presentation controller of the popover 25 | private var popover: UIPopoverPresentationController! 26 | 27 | public var popoverSize: CGSize { 28 | return CGSize(width: contentView.frame.width + design.insets.left + design.insets.right, height: contentView.frame.height + design.insets.top + design.insets.bottom) 29 | } 30 | 31 | /** 32 | A controller that manages the popover. 33 | - Parameter contentView: The view to be inserted inside the popover. 34 | - Parameter design: An object used for defining visual attributes of the popover. 35 | - Parameter arrow: An object representing the arrow in popover. 36 | - Parameter sourceView: The view containing the anchor rectangle for the popover. 37 | - Parameter sourceRect: The rectangle in the specified view in which to anchor the popover. 38 | - Parameter barButtonItem: The bar button item on which to anchor the popover. 39 | 40 | Assign a value to `barButton` to anchor the popover to the specified bar button item. When presented, the popover’s arrow points to the specified item. Alternatively, you may specify the anchor location for the popover using the `sourceView` and `sourceRect` properties. 41 | */ 42 | public init(contentView: UIView, design: PSDesign, arrow: PSArrow, sourceView: UIView? = nil, sourceRect: CGRect? = nil, barButtonItem: UIBarButtonItem? = nil) { 43 | self.contentView = contentView 44 | self.arrow = arrow 45 | self.design = design 46 | super.init(nibName: nil, bundle: nil) 47 | setupPopover(sourceView, sourceRect, barButtonItem) 48 | } 49 | 50 | required init?(coder aDecoder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | 54 | override public func viewDidLoad() { 55 | super.viewDidLoad() 56 | } 57 | 58 | override public func viewDidLayoutSubviews() { 59 | 60 | switch arrow.direction { 61 | case .left: 62 | contentView.frame.origin = CGPoint(x: design.insets.left + arrow.height, y: design.insets.top) 63 | case .right, .down: 64 | contentView.frame.origin = CGPoint(x: design.insets.left, y: design.insets.top) 65 | case .up: 66 | contentView.frame.origin = CGPoint(x: design.insets.left, y: design.insets.top + arrow.height) 67 | default: 68 | break 69 | } 70 | } 71 | 72 | ///Sets up the Popover and starts the timer for its closing. 73 | private func setupPopover(_ sourceView: UIView?, _ sourceRect: CGRect?, _ barButtonItem: UIBarButtonItem?) { 74 | modalPresentationStyle = .popover 75 | switch design.shape { 76 | case .circle: 77 | 78 | let difference = popoverSize.width - popoverSize.height 79 | if difference < 0 { 80 | if design.insets.left <= design.insets.right { 81 | let temp = equaliseInsets(design.insets.left, design.insets.right, difference) 82 | design.insets.left = temp.first 83 | design.insets.right = temp.second 84 | } else { 85 | let temp = equaliseInsets(design.insets.right, design.insets.left, difference) 86 | design.insets.right = temp.first 87 | design.insets.left = temp.second 88 | } 89 | } else if difference > 0 { 90 | if design.insets.top <= design.insets.bottom { 91 | let temp = equaliseInsets(design.insets.top, design.insets.bottom, difference) 92 | design.insets.top = temp.first 93 | design.insets.bottom = temp.second 94 | } else { 95 | let temp = equaliseInsets(design.insets.top, design.insets.bottom, difference) 96 | design.insets.bottom = temp.first 97 | design.insets.top = temp.second 98 | } 99 | } 100 | self.design.cornerRadius = (design.insets.left + design.insets.right + contentView.frame.width)/2 101 | default: break 102 | } 103 | 104 | PSPopoverBackgroundView.currPopover = self 105 | self.modalPresentationStyle = .popover 106 | self.view.addSubview(contentView) 107 | 108 | 109 | let popOver = self.popoverPresentationController! 110 | popOver.popoverBackgroundViewClass = PSPopoverBackgroundView.self 111 | popOver.sourceView = sourceView 112 | 113 | if let sourceRect = sourceRect { 114 | popOver.sourceRect = sourceRect 115 | } 116 | 117 | popOver.barButtonItem = barButtonItem 118 | popOver.delegate = self 119 | popOver.permittedArrowDirections = arrow.direction 120 | popOver.backgroundColor = UIColor.clear 121 | } 122 | 123 | private func equaliseInsets( _ first: CGFloat, _ second: CGFloat, _ difference: CGFloat) -> (first: CGFloat, second: CGFloat) { 124 | var insets = (first: first, second: second) 125 | if insets.second - insets.first >= difference { 126 | insets.first += difference 127 | } else { 128 | insets.second += (difference - (insets.second - insets.first))/2 129 | insets.first = insets.second 130 | } 131 | return insets 132 | } 133 | 134 | func setArrowDirection(_ direction: UIPopoverArrowDirection) { 135 | arrow.direction = direction 136 | preferredContentSize = popoverSize 137 | 138 | switch direction { 139 | case .left, .right: 140 | preferredContentSize.width += arrow.height 141 | case .up, .down: 142 | preferredContentSize.height += arrow.height 143 | default: 144 | break 145 | } 146 | } 147 | 148 | public func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { 149 | return .none 150 | } 151 | 152 | // Returns design of the popover 153 | func getDesign() -> PSDesign { 154 | return design 155 | } 156 | 157 | // Returns arrow of the popover 158 | func getArrow() -> PSArrow { 159 | return arrow 160 | } 161 | 162 | // Returns presentation controller of the popover 163 | public func getPopoverPresentationController() -> UIPopoverPresentationController { 164 | return popover 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /ElegantPopover/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /ElegantPopover/PSArrow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PSArrow.swift 3 | // ElegantPopover 4 | // 5 | // Created by Prasad on 04/02/20. 6 | // Copyright © 2020 Prasad Shinde. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// An object representing the arrow in the popover. 12 | public struct PSArrow { 13 | /// The height of the arrow from its base to the top. 14 | public var height: CGFloat 15 | 16 | /// The width of the base of the arrow 17 | public var base: CGFloat 18 | 19 | /// The radius of curve at the two sides meeting the popover. 20 | public var baseCornerRadius: CGFloat 21 | 22 | /** 23 | The direction of the arrow. 24 | 25 | Set value in this property as `UIPopoverArrowDirection.any` to automatically set any appropriate direction for the arrow. 26 | */ 27 | public var direction: UIPopoverArrowDirection 28 | 29 | /** 30 | An object representing the arrow in the popover. 31 | 32 | - Parameter height: The height of the arrow from its base to the top. 33 | - Parameter base: The base width of the arrow. 34 | - Parameter baseCornerRadius: The radius of curve at the two sides meeting the popover. 35 | - Parameter direction: The direction of the arrow. 36 | */ 37 | public init(height: CGFloat, base: CGFloat, baseCornerRadius: CGFloat, direction: UIPopoverArrowDirection) { 38 | self.height = height 39 | self.base = base 40 | self.baseCornerRadius = baseCornerRadius 41 | self.direction = direction 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ElegantPopover/PSBorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PSBorder.swift 3 | // ElegantPopover 4 | // 5 | // Created by prasad-shinde on 20/06/20. 6 | // Copyright © 2020 prasad.shinde. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// An object representing a border of the popover. 12 | public struct PSBorder { 13 | 14 | /** 15 | An enum value representing the way to fill the border. 16 | 17 | # Cases # 18 | - `pureColor(UIColor)`: Fill the border homogeneously with the passed `UIColor`. 19 | - `layer(CALayer)`: Fill the border with the passed `CALayer`. 20 | */ 21 | public enum Filling { 22 | /// Fill the border homogeneously with the passed `UIColor`. 23 | case pureColor(UIColor) 24 | 25 | /** 26 | Fill the border with the passed `CALayer`. 27 | 28 | Any `CALayer` can be passed including a configured `CAGradientLayer` to have a gradient as a border filler. 29 | */ 30 | case layer(CALayer) 31 | } 32 | 33 | /// An enum `Filling` value representing the way to fill the border. 34 | public var filling: Filling 35 | 36 | /// The width of the border. 37 | public var width: CGFloat 38 | 39 | /** 40 | Creates an object representing a border of the popover. 41 | 42 | - Parameter filling: An enum `Filling` value representing the way to fill the border. 43 | - Parameter width: The width of the border. 44 | */ 45 | public init(filling: Filling, width: CGFloat) { 46 | self.filling = filling 47 | self.width = width 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ElegantPopover/PSDesign.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PSDesign.swift 3 | // ElegantPopover 4 | // 5 | // Created by prasad-shinde on 04/02/20. 6 | // Copyright © 2020 prasadshinde. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// An object representing the design of the popover. 12 | public struct PSDesign { 13 | 14 | /** An enum value representing the shape of the popover. 15 | 16 | # Cases # 17 | - `circle`: A circular shape. 18 | - `rectangle`: A rectangular shape. 19 | */ 20 | public enum PSShape { 21 | /** 22 | A circular shape. 23 | 24 | In this case, `ElegantPopoverController` would adjust the values of `insets` and `cornerRadius` 25 | */ 26 | case circle 27 | 28 | /** 29 | A rectangular shape. 30 | */ 31 | case rectangle 32 | } 33 | 34 | /** 35 | Array consisting of objects of `PSBorder` for setting borders of the popover. 36 | 37 | Values in the array of this property start from outermost border and end at the innermost border. Widths values should not be negative. 38 | */ 39 | public var borders = [PSBorder]() 40 | 41 | /** 42 | The popover's background color. 43 | 44 | The default value of this property is `UIColor.clear`, which results in a transparent background color. 45 | */ 46 | public var backGroundColor: UIColor = .clear 47 | 48 | /** 49 | The radius to use when drawing rounded corners of the popover. 50 | 51 | If `shape` property is set as `Shape.circle` then the value in this property is redundant. 52 | 53 | The default value of this property is `0`. 54 | */ 55 | public var cornerRadius: CGFloat = 0 56 | 57 | /** 58 | The shape of the popover. 59 | 60 | The default value of this property is `Shape.rectangle`. 61 | */ 62 | public var shape: PSShape = .rectangle 63 | 64 | /** 65 | The index of border required to be solid for the popover. 66 | 67 | The value in this property is the index of border required to take the shape of the arrow and not outline it. If `nil`, all the borders will outline the arrow and none will be solid. 68 | 69 | The default value of this property is `nil`. 70 | */ 71 | public var solidArrowBorderIndex: Int? 72 | 73 | ///An object representing the offsets between popover boundary and the contentView. 74 | public var insets: UIEdgeInsets = .zero 75 | 76 | /** 77 | Creates an object representing the design of the popover. 78 | */ 79 | public init() { } 80 | } 81 | -------------------------------------------------------------------------------- /ElegantPopover/PSPopoverBackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PSPopoverBackgroundView.swift 3 | // ElegantPopover 4 | // 5 | // Created by Prasad on 04/02/20. 6 | // Copyright © 2020 Prasad Shinde. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | import ClippingBezier 12 | 13 | class PSPopoverBackgroundView: UIPopoverBackgroundView { 14 | 15 | private struct Arrow { 16 | var height: CGFloat! 17 | var apex: CGPoint! 18 | var base: CGFloat! 19 | var halfBase: CGFloat { 20 | return base/2 21 | } 22 | var leftCurveRadius: CGFloat! 23 | var rightCurveRadius: CGFloat! 24 | var inset: CGFloat! 25 | 26 | init (apex: CGPoint, height: CGFloat, base: CGFloat, leftCurveRadius: CGFloat, rightCurveRadius: CGFloat, inset: CGFloat = 0) { 27 | 28 | self.inset = inset 29 | self.height = height 30 | self.apex = apex 31 | self.base = base 32 | self.leftCurveRadius = leftCurveRadius 33 | self.rightCurveRadius = rightCurveRadius 34 | } 35 | } 36 | 37 | static weak var currPopover: ElegantPopoverController? 38 | private var path : UIBezierPath! 39 | private var arrow: Arrow! 40 | private var cornerRadius: CGFloat! 41 | private var _arrowDirection : UIPopoverArrowDirection! 42 | private var _arrowOffset : CGFloat! 43 | private var mainRect: CGRect! 44 | private var subRect: CGRect! 45 | private var design: PSDesign! 46 | private var elegantArrow: PSArrow! 47 | private var startPoint: CGPoint! 48 | private var popoverBounds: CGRect! 49 | 50 | private var arcAngle: CGFloat { 51 | return atan(arrow.height / arrow.halfBase) 52 | } 53 | 54 | private var rightArcX: CGFloat { 55 | return arrow.apex.x + arrow.halfBase + arrow.rightCurveRadius * tan(arcAngle/2) 56 | } 57 | 58 | private var leftArcX: CGFloat { 59 | return arrow.apex.x - arrow.halfBase - arrow.leftCurveRadius * tan(arcAngle/2) 60 | } 61 | 62 | override var arrowOffset: CGFloat { 63 | get { 64 | return self.arrowOffset 65 | } 66 | set { 67 | _arrowOffset = newValue 68 | } 69 | } 70 | 71 | override var arrowDirection: UIPopoverArrowDirection { 72 | get { 73 | return UIPopoverArrowDirection.any 74 | } 75 | set { 76 | self._arrowDirection = newValue 77 | PSPopoverBackgroundView.currPopover?.setArrowDirection(self._arrowDirection!) 78 | } 79 | } 80 | 81 | override init(frame: CGRect) { 82 | super.init(frame: frame) 83 | backgroundColor = UIColor.clear 84 | self.design = PSPopoverBackgroundView.currPopover?.getDesign() 85 | self.elegantArrow = PSPopoverBackgroundView.currPopover?.getArrow() 86 | layer.shadowColor = UIColor.clear.cgColor 87 | } 88 | 89 | required init(coder aDecoder: NSCoder) { 90 | fatalError("init(coder:) has not been implemented") 91 | } 92 | 93 | override class func contentViewInsets() -> UIEdgeInsets{ 94 | return UIEdgeInsets.zero 95 | } 96 | 97 | override class func arrowHeight() -> CGFloat { 98 | return 0 99 | } 100 | 101 | override class func arrowBase() -> CGFloat{ 102 | return 0 103 | } 104 | 105 | override func didMoveToWindow() { 106 | super.didMoveToWindow() 107 | if #available(iOS 13, *) { 108 | // iOS 13 (or newer) 109 | if let window = UIApplication.shared.keyWindow { 110 | let transitionViews = window.subviews.filter { String(describing: type(of: $0)) == "UITransitionView" } 111 | for transitionView in transitionViews { 112 | let shadowView = transitionView.subviews.filter { String(describing: type(of: $0)) == "_UICutoutShadowView" }.first 113 | shadowView?.isHidden = true 114 | } 115 | } 116 | } 117 | } 118 | 119 | private func initialize(inset: CGFloat, borderWidth: CGFloat) { 120 | path = UIBezierPath() 121 | cornerRadius -= borderWidth 122 | cornerRadius = max(cornerRadius, 0) 123 | 124 | let multiplier: CGFloat = (_arrowDirection! == .left || _arrowDirection! == .down) ? -1 : 1 125 | var apexX = min(mainRect.width/2 + _arrowOffset * multiplier, mainRect.width - elegantArrow.base/2) 126 | apexX = max(apexX, elegantArrow.base/2) 127 | 128 | arrow = Arrow(apex: CGPoint(x: apexX, y: 0 + inset / cos(atan(elegantArrow.height/(elegantArrow.base/2)))), height: elegantArrow.height, 129 | base: elegantArrow.base, 130 | leftCurveRadius: elegantArrow.baseCornerRadius, 131 | rightCurveRadius: elegantArrow.baseCornerRadius, 132 | inset: inset) 133 | 134 | subRect = CGRect(x: 0 + inset, 135 | y: arrow.height + inset, 136 | width: mainRect.width - 2 * inset, 137 | height: mainRect.height - arrow.height - 2 * inset) 138 | 139 | if rightArcX > subRect.maxX { 140 | if arrow.rightCurveRadius * tan(arcAngle/2) <= rightArcX - subRect.maxX { 141 | arrow.rightCurveRadius = 0 142 | } else { 143 | let diff = (rightArcX - subRect.maxX) - arrow.rightCurveRadius * tan(arcAngle/2) 144 | let rightCurveRadius = (diff / tan(arcAngle/2)) 145 | arrow.rightCurveRadius = rightCurveRadius 146 | } 147 | } 148 | 149 | if leftArcX < subRect.minX { 150 | if arrow.leftCurveRadius * tan(arcAngle/2) <= subRect.minX - leftArcX { 151 | arrow.leftCurveRadius = 0 152 | } else { 153 | let diff = (subRect.minX - leftArcX) - arrow.leftCurveRadius * tan(arcAngle/2) 154 | let leftCurveRadius = (diff / tan(arcAngle/2)) 155 | arrow.leftCurveRadius = leftCurveRadius 156 | } 157 | } 158 | 159 | } 160 | 161 | override func draw(_ rect: CGRect) { 162 | 163 | cornerRadius = design.cornerRadius 164 | if _arrowDirection! == .up || _arrowDirection! == .down { 165 | mainRect = rect 166 | } else { 167 | mainRect = CGRect(origin: rect.origin, 168 | size: CGSize(width: rect.height, height: rect.width)) 169 | } 170 | elegantArrow.base = min(elegantArrow.base, mainRect.width) 171 | 172 | drawPopover() 173 | rotatePath(using: rect) 174 | 175 | popoverBounds = path.bounds 176 | 177 | var cumulativeBorders = design.borders 178 | let borders = design.borders 179 | 180 | if cumulativeBorders.isEmpty { 181 | fillPath(with: .pureColor(design.backGroundColor)) 182 | return 183 | } 184 | 185 | fillPath(with: cumulativeBorders[0].filling) 186 | 187 | for i in 1..= subRect.width - 1 { 222 | path = fullPath 223 | } 224 | } 225 | } 226 | } 227 | 228 | private func fillPath(with filling: PSBorder.Filling) { 229 | var layerToBeInserted: CALayer 230 | 231 | switch filling { 232 | 233 | case .layer(let layer): 234 | layerToBeInserted = layer 235 | layerToBeInserted.frame = popoverBounds 236 | 237 | case .pureColor(let color): 238 | layerToBeInserted = CALayer() 239 | layerToBeInserted.frame = popoverBounds 240 | layerToBeInserted.backgroundColor = color.cgColor 241 | } 242 | 243 | let shapeLayer = CAShapeLayer() 244 | shapeLayer.path = path.cgPath 245 | layerToBeInserted.mask = shapeLayer 246 | 247 | if popoverBounds == path.bounds { 248 | self.layer.mask = shapeLayer 249 | } 250 | 251 | self.layer.insertSublayer(layerToBeInserted, at: UInt32(self.layer.sublayers?.count ?? 0)) 252 | } 253 | 254 | private func drawLeftLine() { 255 | 256 | if leftArcX < subRect.minX + cornerRadius, 257 | arrow.height > 0 { 258 | 259 | let curveRadius = max(leftArcX - subRect.minX, 0) 260 | 261 | startPoint = CGPoint(x: leftArcX, 262 | y: subRect.minY) 263 | 264 | path.addArc(withCenter: CGPoint(x: leftArcX, y: subRect.minY + leftArcX - subRect.minX), 265 | radius: curveRadius, 266 | startAngle: getRadians(270), 267 | endAngle: getRadians(180), 268 | clockwise: false) 269 | } else { 270 | startPoint = CGPoint(x: subRect.minX + cornerRadius, 271 | y: subRect.minY) 272 | 273 | path.addArc(withCenter: CGPoint(x: subRect.minX + cornerRadius, y: subRect.minY + cornerRadius), 274 | radius: cornerRadius, 275 | startAngle: getRadians(270), 276 | endAngle: getRadians(180), 277 | clockwise: false) 278 | } 279 | 280 | path.addLine(to: CGPoint(x: subRect.minX, y: subRect.maxY - cornerRadius)) 281 | path.addArc(withCenter: CGPoint(x: subRect.minX + cornerRadius,y: subRect.maxY - cornerRadius), 282 | radius: cornerRadius, 283 | startAngle: getRadians(180), 284 | endAngle: getRadians(90), 285 | clockwise: false) 286 | } 287 | 288 | private func drawBottomLine() { 289 | path.addLine(to: CGPoint(x: subRect.maxX - cornerRadius, 290 | y: subRect.maxY)) 291 | } 292 | 293 | private func drawRightLine() { 294 | path.addArc(withCenter: CGPoint(x: subRect.maxX - cornerRadius, y: subRect.maxY - cornerRadius), 295 | radius: cornerRadius, 296 | startAngle: getRadians(90), 297 | endAngle: getRadians(0), 298 | clockwise: false) 299 | 300 | var curveRadius: CGFloat = self.cornerRadius 301 | 302 | if arrow.height > 0, 303 | rightArcX > (subRect.maxX - curveRadius) { 304 | curveRadius = max(subRect.maxX - (rightArcX), 0) 305 | } 306 | 307 | path.addLine(to: CGPoint(x: subRect.maxX, 308 | y: subRect.minY + curveRadius)) 309 | 310 | path.addArc(withCenter: CGPoint(x: subRect.maxX - curveRadius,y: subRect.minY + curveRadius), 311 | radius: curveRadius, 312 | startAngle: getRadians(0), 313 | endAngle: getRadians(270), 314 | clockwise: false) 315 | } 316 | 317 | private func drawTopLine(_ inset: CGFloat, _ shouldDrawArrow: Bool) { 318 | if arrow.height > 0 { 319 | 320 | if shouldDrawArrow { 321 | path.addLine(to: CGPoint(x: rightArcX, y: subRect.minY)) 322 | drawArrow() 323 | } else if rightArcX < path.currentPoint.x{ 324 | path.addLine(to: CGPoint(x: rightArcX, y: subRect.minY)) 325 | } 326 | } 327 | path.addLine(to: startPoint) 328 | } 329 | 330 | private func drawArrow() { 331 | 332 | let leftArcY = subRect.minY - arrow.leftCurveRadius - arrow.inset 333 | let rightArcY = subRect.minY - arrow.rightCurveRadius - arrow.inset 334 | let leftArcRadius = arrow.leftCurveRadius + arrow.inset 335 | let rightArcRadius = arrow.rightCurveRadius + arrow.inset 336 | 337 | var rightArcEndAngle = getRadians(90) + arcAngle 338 | var leftArcStartAngle = getRadians(90) - arcAngle 339 | 340 | if rightArcX - rightArcRadius * sin(arcAngle) < arrow.apex.x { 341 | let rightAngle = asin((rightArcX - arrow.apex.x) / rightArcRadius) 342 | let leftAngle = asin((arrow.apex.x - leftArcX) / leftArcRadius) 343 | 344 | rightArcEndAngle = rightAngle + getRadians(90) 345 | leftArcStartAngle = getRadians(90) - leftAngle 346 | 347 | if arrow.rightCurveRadius != 0 { 348 | path.addArc(withCenter: CGPoint(x: rightArcX, y: rightArcY), 349 | radius: rightArcRadius, 350 | startAngle: getRadians(90), 351 | endAngle: rightArcEndAngle, 352 | clockwise: true) 353 | 354 | arrow.apex.y = path.currentPoint.y 355 | } 356 | 357 | } else { 358 | path.addArc(withCenter: CGPoint(x: rightArcX, y: rightArcY), 359 | radius: rightArcRadius, 360 | startAngle: getRadians(90), 361 | endAngle: rightArcEndAngle, 362 | clockwise: true) 363 | 364 | path.addLine(to: arrow.apex) 365 | 366 | path.addLine(to: CGPoint(x: leftArcX + leftArcRadius * sin(arcAngle), 367 | y: leftArcY + leftArcRadius * cos(arcAngle))) 368 | } 369 | 370 | path.addArc(withCenter: CGPoint(x: leftArcX, y: leftArcY), 371 | radius: leftArcRadius, 372 | startAngle: leftArcStartAngle, 373 | endAngle: getRadians(90), 374 | clockwise: true) 375 | } 376 | 377 | private func rotatePath(using rect: CGRect) { 378 | switch _arrowDirection! { 379 | case .left: 380 | path.translateAndRotate(angle: getRadians(-90),translation: (0, rect.height)) 381 | case .right: 382 | path.translateAndRotate(angle: getRadians(90), translation: (rect.width, 0)) 383 | case .down: 384 | path.translateAndRotate(angle: getRadians(180), translation: (rect.width, rect.height)) 385 | default: 386 | break 387 | } 388 | } 389 | 390 | private func cosOf(_ angle: CGFloat) -> CGFloat { 391 | return cos(getRadians(angle)) 392 | } 393 | 394 | private func sinOf(_ angle: CGFloat) -> CGFloat { 395 | return sin(getRadians(angle)) 396 | } 397 | 398 | private func tanOf(_ angle: CGFloat) -> CGFloat { 399 | return tan(getRadians(angle)) 400 | } 401 | 402 | private func getRadians(_ degree: CGFloat) -> CGFloat { 403 | return (degree * CGFloat.pi)/180 404 | } 405 | 406 | private func getDegrees(_ radians: CGFloat) -> CGFloat { 407 | return radians * 180 / .pi 408 | } 409 | } 410 | 411 | extension UIBezierPath { 412 | 413 | func translateAndRotate(angle: CGFloat, translation: (x: CGFloat, y: CGFloat)) { 414 | 415 | var transform = CGAffineTransform.identity 416 | transform = transform.translatedBy(x: translation.x, y: translation.y) 417 | transform = transform.rotated(by: angle) 418 | self.apply(transform) 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Prasad Shinde 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 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '12.0' 2 | 3 | target 'ElegantPopover' do 4 | pod 'ClippingBezier', '1.0.6', :modular_headers => true 5 | end 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |

6 | 7 | Top Language 8 | 9 | 10 | Platform 11 | 12 | 13 | Version 14 | 15 | 16 | License 17 | 18 |

19 | 20 | # Elegant Popover 21 | 22 | Highly customisable popovers with multiple borders, borders styles (color and gradient) and arrow styles in iOS. 23 | 24 | ## Features 25 | - [x] Border with Color or Gradient (Any layer). 26 | - [x] Multiple Borders for a Popover. 27 | - [x] Customisable Arrow shape. 28 | - [x] Solid and Outlined Arrow. 29 | 30 |
31 | 32 | 33 |
34 | 35 | ## Requirements 36 | - Swift 4 37 | - iOS 11 and above 38 | 39 | ## Installation 40 | ElegantPopover is available on [CocoaPods](https://cocoapods.org/pods/ElegantPopover). 41 | 42 | Add this line to your `Podfile`. 43 | ~~~ 44 | pod 'ElegantPopover' 45 | ~~~ 46 | Run `pod install`. 47 | 48 | ## Dependencies 49 | This library depends on [ClippingBezier](https://github.com/adamwulf/ClippingBezier) library. 50 | 51 | ## Usage 52 | 53 | ```swift 54 | // In your view controller 55 | let arrow = PSArrow(height: 25, base: 35, baseCornerRadius: 0, direction: .up) 56 | var design = PSDesign() 57 | design.backGroundColor = UIColor.white 58 | 59 | // 'contentView' is the UIView which contains 'Elegant Popover' UILabel 60 | let popoverController = ElegantPopoverController(contentView: contentView, 61 | design: design, 62 | arrow: arrow, 63 | sourceView: view, 64 | sourceRect: CGRect(origin: CGPoint(x: 100, y: 170), size:CGSize.zero)) 65 | 66 | present(popoverController, animated: true) 67 | ``` 68 |
69 | 70 |
71 | 72 |
73 | 74 | ## Customisations 75 | **Note**: The following customisations are to be done before creating an instance of `ElegantPopoverController` 76 | 77 |
78 | 79 | ### Gradient border 80 | ```swift 81 | let gradient = CAGradientLayer() 82 | gradient.colors = [UIColor(red: CGFloat(222/255.0), 83 | green: CGFloat(98/255.0), 84 | blue: CGFloat(98/255.0), 85 | alpha: CGFloat(1.0)).cgColor, 86 | UIColor(red: CGFloat(255/255.0), 87 | green: CGFloat(184/255.0), 88 | blue: CGFloat(140/255.0), 89 | alpha: CGFloat(1.0)).cgColor] 90 | gradient.startPoint = CGPoint(x: 0, y: 0) 91 | gradient.endPoint = CGPoint(x: 1, y: 1) 92 | 93 | design.borders = [PSBorder(filling: .layer(gradient), width: 12)] 94 | ``` 95 | 96 |
97 | 98 |
99 | 100 |

101 | 102 | ### Insets of popover 103 | ```swift 104 | design.insets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) 105 | ``` 106 |
107 | 108 |
109 | 110 |

111 | 112 | ### Corner radius of popover 113 | ```swift 114 | design.cornerRadius = 15 115 | ``` 116 |
117 | 118 |
119 | 120 |

121 | 122 | ### Arrow direction 123 | Can be set as `.any` for the popover to figure out appropriate direction on its own 124 | ```swift 125 | arrow.direction = .left 126 | ``` 127 |
128 | 129 |
130 | 131 |

132 | 133 | ### Customise arrow shape 134 | ```swift 135 | arrow.height = 60 136 | arrow.base = 140 137 | ``` 138 |
139 | 140 |
141 | 142 |

143 | 144 | ### Multiple borders 145 | Multiple borders consisting of either `UIColor` or `CALayer` can be added in any combination 146 | ```swift 147 | design.borders = [PSBorder(filling: .layer(gradient), width: 12), 148 | PSBorder(filling: .pureColor(UIColor(red: CGFloat(255/255.0), 149 | green: CGFloat(184/255.0), 150 | blue: CGFloat(140/255.0), 151 | alpha: CGFloat(1.0))), width: 8)] 152 | ``` 153 |
154 | 155 |
156 | 157 |

158 | 159 | ### Solid Arrow 160 | The index of border required to take the shape of a solid arrow and not outline it. Indices go from outermost border starting with `0` to innermost. Default value is `nil` which means all the borders will outline the arrow. 161 | ```swift 162 | design.solidArrowBorderIndex = 1 163 | ``` 164 |
165 | 166 |
167 | 168 |

169 | 170 | ### Anchor to UIBarButtonItem 171 | ```swift 172 | let popoverController = ElegantPopoverController(contentView: contentView, 173 | design: design, 174 | arrow: arrow, 175 | barButtonItem: barButtonItem) 176 | ``` 177 | 178 | To adjust the arrow position to barButtonItem 179 | ```swift 180 | arrow.height = 25 181 | arrow.base = 45 182 | ``` 183 |
184 | 185 |
186 | 187 |

188 | 189 | ## License 190 | This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details 191 | --------------------------------------------------------------------------------