├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── .travis.yml ├── EasyTipView.podspec ├── EasyTipView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── EasyTipView.xcscheme ├── EasyTipView.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Example ├── EasyTipView-Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── EasyTipView-Example.xcscheme └── EasyTipView │ ├── AppDelegate.swift │ ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard │ ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── EasyTipView copy 2-1.png │ │ ├── EasyTipView copy 2.png │ │ ├── EasyTipView copy 3-1.png │ │ ├── EasyTipView copy 3.png │ │ ├── EasyTipView copy 4.png │ │ └── EasyTipView.png │ ├── Contents.json │ └── easytipview.imageset │ │ ├── Contents.json │ │ └── easytipview@2x.png │ ├── Info.plist │ └── ViewController.swift ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── EasyTipView │ ├── EasyTipView.h │ ├── EasyTipView.swift │ ├── Info.plist │ └── UIKitExtensions.swift ├── Tests ├── EasyTipViewTests │ └── Tests.swift └── Info.plist └── assets ├── animation.gif ├── easytipview.gif ├── easytipview.png └── static.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Finder 2 | .DS_Store 3 | 4 | # Xcode 5 | # 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.xcuserstate 22 | 23 | # CocoaPods 24 | # 25 | # We recommend against adding the Pods directory to your .gitignore. However 26 | # you should judge for yourself, the pros and cons are mentioned at: 27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 28 | # 29 | # Pods/ 30 | .build/ 31 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | language: swift 6 | osx_image: xcode12.2 7 | env: 8 | global: 9 | - WORKSPACE=EasyTipView.xcworkspace 10 | - FRAMEWORK_NAME=EasyTipView 11 | - PROJECT=EasyTipView.xcodeproj 12 | - SCHEME=EasyTipView 13 | before_deploy: 14 | - carthage build --no-skip-current 15 | - carthage archive $FRAMEWORK_NAME 16 | script: xcodebuild -scheme $SCHEME -workspace $WORKSPACE -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 12 Pro' build test 17 | -------------------------------------------------------------------------------- /EasyTipView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'EasyTipView' 3 | s.version = '2.1.0' 4 | s.license = 'MIT' 5 | s.summary = 'Elegant tooltip view written in Swift' 6 | s.description = 'EasyTipView is a fully customisable tooltip view written in Swift that can be used as a call to action or informative tip. It can be shown above of below any UIBarItem or UIView subclass.' 7 | s.homepage = 'https://github.com/teodorpatras/EasyTipView' 8 | s.authors = { 'Teodor Patraș' => 'hello@teodorpatras.com' } 9 | s.source = { :git => 'https://github.com/teodorpatras/EasyTipView.git', :tag => s.version } 10 | 11 | s.swift_version = '5.0' 12 | s.ios.deployment_target = '10.0' 13 | 14 | s.source_files = 'Sources/EasyTipView/*.swift' 15 | 16 | s.requires_arc = true 17 | end 18 | -------------------------------------------------------------------------------- /EasyTipView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1310EC961D0C53800000E71E /* EasyTipView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1310EC8B1D0C537F0000E71E /* EasyTipView.framework */; }; 11 | 13FB32A91D0C53E5001ACE20 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FB32A61D0C53E2001ACE20 /* Tests.swift */; }; 12 | 3DEF6DAB23A39E4F007B8C3C /* EasyTipView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DEF6DA823A39E4F007B8C3C /* EasyTipView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 3DEF6DAC23A39E4F007B8C3C /* UIKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DEF6DA923A39E4F007B8C3C /* UIKitExtensions.swift */; }; 14 | 3DEF6DAD23A39E4F007B8C3C /* EasyTipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DEF6DAA23A39E4F007B8C3C /* EasyTipView.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | 1310EC971D0C53800000E71E /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = 1310EC821D0C537F0000E71E /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = 1310EC8A1D0C537F0000E71E; 23 | remoteInfo = EasyTipView; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 1310EC8B1D0C537F0000E71E /* EasyTipView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = EasyTipView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 1310EC951D0C53800000E71E /* EasyTipViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EasyTipViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 13FB32A11D0C53CB001ACE20 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/EasyTipView/info.plist; sourceTree = SOURCE_ROOT; }; 31 | 13FB32A51D0C53E2001ACE20 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Tests/Info.plist; sourceTree = SOURCE_ROOT; }; 32 | 13FB32A61D0C53E2001ACE20 /* Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Tests.swift; path = Tests/EasyTipViewTests/Tests.swift; sourceTree = SOURCE_ROOT; }; 33 | 3DEF6DA823A39E4F007B8C3C /* EasyTipView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EasyTipView.h; sourceTree = ""; }; 34 | 3DEF6DA923A39E4F007B8C3C /* UIKitExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitExtensions.swift; sourceTree = ""; }; 35 | 3DEF6DAA23A39E4F007B8C3C /* EasyTipView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EasyTipView.swift; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 1310EC871D0C537F0000E71E /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | 1310EC921D0C53800000E71E /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | 1310EC961D0C53800000E71E /* EasyTipView.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 1310EC811D0C537F0000E71E = { 58 | isa = PBXGroup; 59 | children = ( 60 | 1310EC8D1D0C537F0000E71E /* Sources */, 61 | 1310EC991D0C53800000E71E /* Tests */, 62 | 1310EC8C1D0C537F0000E71E /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | 1310EC8C1D0C537F0000E71E /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 1310EC8B1D0C537F0000E71E /* EasyTipView.framework */, 70 | 1310EC951D0C53800000E71E /* EasyTipViewTests.xctest */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | 1310EC8D1D0C537F0000E71E /* Sources */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 3DEF6DA623A39E20007B8C3C /* EasyTipView */, 79 | 13FB32A11D0C53CB001ACE20 /* Info.plist */, 80 | ); 81 | path = Sources; 82 | sourceTree = ""; 83 | }; 84 | 1310EC991D0C53800000E71E /* Tests */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 13FB32A51D0C53E2001ACE20 /* Info.plist */, 88 | 3DEF6DA723A39E3E007B8C3C /* EasyTipViewTests */, 89 | ); 90 | path = Tests; 91 | sourceTree = ""; 92 | }; 93 | 3DEF6DA623A39E20007B8C3C /* EasyTipView */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 3DEF6DA823A39E4F007B8C3C /* EasyTipView.h */, 97 | 3DEF6DAA23A39E4F007B8C3C /* EasyTipView.swift */, 98 | 3DEF6DA923A39E4F007B8C3C /* UIKitExtensions.swift */, 99 | ); 100 | path = EasyTipView; 101 | sourceTree = ""; 102 | }; 103 | 3DEF6DA723A39E3E007B8C3C /* EasyTipViewTests */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 13FB32A61D0C53E2001ACE20 /* Tests.swift */, 107 | ); 108 | path = EasyTipViewTests; 109 | sourceTree = ""; 110 | }; 111 | /* End PBXGroup section */ 112 | 113 | /* Begin PBXHeadersBuildPhase section */ 114 | 1310EC881D0C537F0000E71E /* Headers */ = { 115 | isa = PBXHeadersBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | 3DEF6DAB23A39E4F007B8C3C /* EasyTipView.h in Headers */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | /* End PBXHeadersBuildPhase section */ 123 | 124 | /* Begin PBXNativeTarget section */ 125 | 1310EC8A1D0C537F0000E71E /* EasyTipView */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = 1310EC9F1D0C53800000E71E /* Build configuration list for PBXNativeTarget "EasyTipView" */; 128 | buildPhases = ( 129 | 1310EC861D0C537F0000E71E /* Sources */, 130 | 1310EC871D0C537F0000E71E /* Frameworks */, 131 | 1310EC881D0C537F0000E71E /* Headers */, 132 | 1310EC891D0C537F0000E71E /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = EasyTipView; 139 | productName = EasyTipView; 140 | productReference = 1310EC8B1D0C537F0000E71E /* EasyTipView.framework */; 141 | productType = "com.apple.product-type.framework"; 142 | }; 143 | 1310EC941D0C53800000E71E /* EasyTipViewTests */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = 1310ECA21D0C53800000E71E /* Build configuration list for PBXNativeTarget "EasyTipViewTests" */; 146 | buildPhases = ( 147 | 1310EC911D0C53800000E71E /* Sources */, 148 | 1310EC921D0C53800000E71E /* Frameworks */, 149 | 1310EC931D0C53800000E71E /* Resources */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | 1310EC981D0C53800000E71E /* PBXTargetDependency */, 155 | ); 156 | name = EasyTipViewTests; 157 | productName = EasyTipViewTests; 158 | productReference = 1310EC951D0C53800000E71E /* EasyTipViewTests.xctest */; 159 | productType = "com.apple.product-type.bundle.unit-test"; 160 | }; 161 | /* End PBXNativeTarget section */ 162 | 163 | /* Begin PBXProject section */ 164 | 1310EC821D0C537F0000E71E /* Project object */ = { 165 | isa = PBXProject; 166 | attributes = { 167 | LastSwiftUpdateCheck = 0730; 168 | LastUpgradeCheck = 1020; 169 | ORGANIZATIONNAME = teodorpatras; 170 | TargetAttributes = { 171 | 1310EC8A1D0C537F0000E71E = { 172 | CreatedOnToolsVersion = 7.3.1; 173 | LastSwiftMigration = 1220; 174 | ProvisioningStyle = Automatic; 175 | }; 176 | 1310EC941D0C53800000E71E = { 177 | CreatedOnToolsVersion = 7.3.1; 178 | LastSwiftMigration = 1220; 179 | }; 180 | }; 181 | }; 182 | buildConfigurationList = 1310EC851D0C537F0000E71E /* Build configuration list for PBXProject "EasyTipView" */; 183 | compatibilityVersion = "Xcode 3.2"; 184 | developmentRegion = en; 185 | hasScannedForEncodings = 0; 186 | knownRegions = ( 187 | en, 188 | Base, 189 | ); 190 | mainGroup = 1310EC811D0C537F0000E71E; 191 | productRefGroup = 1310EC8C1D0C537F0000E71E /* Products */; 192 | projectDirPath = ""; 193 | projectRoot = ""; 194 | targets = ( 195 | 1310EC8A1D0C537F0000E71E /* EasyTipView */, 196 | 1310EC941D0C53800000E71E /* EasyTipViewTests */, 197 | ); 198 | }; 199 | /* End PBXProject section */ 200 | 201 | /* Begin PBXResourcesBuildPhase section */ 202 | 1310EC891D0C537F0000E71E /* Resources */ = { 203 | isa = PBXResourcesBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | 1310EC931D0C53800000E71E /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXResourcesBuildPhase section */ 217 | 218 | /* Begin PBXSourcesBuildPhase section */ 219 | 1310EC861D0C537F0000E71E /* Sources */ = { 220 | isa = PBXSourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | 3DEF6DAD23A39E4F007B8C3C /* EasyTipView.swift in Sources */, 224 | 3DEF6DAC23A39E4F007B8C3C /* UIKitExtensions.swift in Sources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | 1310EC911D0C53800000E71E /* Sources */ = { 229 | isa = PBXSourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 13FB32A91D0C53E5001ACE20 /* Tests.swift in Sources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXSourcesBuildPhase section */ 237 | 238 | /* Begin PBXTargetDependency section */ 239 | 1310EC981D0C53800000E71E /* PBXTargetDependency */ = { 240 | isa = PBXTargetDependency; 241 | target = 1310EC8A1D0C537F0000E71E /* EasyTipView */; 242 | targetProxy = 1310EC971D0C53800000E71E /* PBXContainerItemProxy */; 243 | }; 244 | /* End PBXTargetDependency section */ 245 | 246 | /* Begin XCBuildConfiguration section */ 247 | 1310EC9D1D0C53800000E71E /* Debug */ = { 248 | isa = XCBuildConfiguration; 249 | buildSettings = { 250 | ALWAYS_SEARCH_USER_PATHS = NO; 251 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 252 | CLANG_ANALYZER_NONNULL = YES; 253 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 254 | CLANG_CXX_LIBRARY = "libc++"; 255 | CLANG_ENABLE_MODULES = YES; 256 | CLANG_ENABLE_OBJC_ARC = YES; 257 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 258 | CLANG_WARN_BOOL_CONVERSION = YES; 259 | CLANG_WARN_COMMA = YES; 260 | CLANG_WARN_CONSTANT_CONVERSION = YES; 261 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 262 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INFINITE_RECURSION = YES; 266 | CLANG_WARN_INT_CONVERSION = YES; 267 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 268 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 269 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 271 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 272 | CLANG_WARN_STRICT_PROTOTYPES = YES; 273 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 274 | CLANG_WARN_UNREACHABLE_CODE = YES; 275 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 276 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 277 | COPY_PHASE_STRIP = NO; 278 | CURRENT_PROJECT_VERSION = 1; 279 | DEBUG_INFORMATION_FORMAT = dwarf; 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | ENABLE_TESTABILITY = YES; 282 | GCC_C_LANGUAGE_STANDARD = gnu99; 283 | GCC_DYNAMIC_NO_PIC = NO; 284 | GCC_NO_COMMON_BLOCKS = YES; 285 | GCC_OPTIMIZATION_LEVEL = 0; 286 | GCC_PREPROCESSOR_DEFINITIONS = ( 287 | "DEBUG=1", 288 | "$(inherited)", 289 | ); 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 297 | MTL_ENABLE_DEBUG_INFO = YES; 298 | ONLY_ACTIVE_ARCH = YES; 299 | SDKROOT = iphoneos; 300 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 301 | TARGETED_DEVICE_FAMILY = "1,2"; 302 | VERSIONING_SYSTEM = "apple-generic"; 303 | VERSION_INFO_PREFIX = ""; 304 | }; 305 | name = Debug; 306 | }; 307 | 1310EC9E1D0C53800000E71E /* Release */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ALWAYS_SEARCH_USER_PATHS = NO; 311 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 312 | CLANG_ANALYZER_NONNULL = YES; 313 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 314 | CLANG_CXX_LIBRARY = "libc++"; 315 | CLANG_ENABLE_MODULES = YES; 316 | CLANG_ENABLE_OBJC_ARC = YES; 317 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 318 | CLANG_WARN_BOOL_CONVERSION = YES; 319 | CLANG_WARN_COMMA = YES; 320 | CLANG_WARN_CONSTANT_CONVERSION = YES; 321 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 322 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 323 | CLANG_WARN_EMPTY_BODY = YES; 324 | CLANG_WARN_ENUM_CONVERSION = YES; 325 | CLANG_WARN_INFINITE_RECURSION = YES; 326 | CLANG_WARN_INT_CONVERSION = YES; 327 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 329 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 330 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 331 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 332 | CLANG_WARN_STRICT_PROTOTYPES = YES; 333 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 334 | CLANG_WARN_UNREACHABLE_CODE = YES; 335 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 336 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 337 | COPY_PHASE_STRIP = NO; 338 | CURRENT_PROJECT_VERSION = 1; 339 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 340 | ENABLE_NS_ASSERTIONS = NO; 341 | ENABLE_STRICT_OBJC_MSGSEND = YES; 342 | GCC_C_LANGUAGE_STANDARD = gnu99; 343 | GCC_NO_COMMON_BLOCKS = YES; 344 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 345 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 346 | GCC_WARN_UNDECLARED_SELECTOR = YES; 347 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 348 | GCC_WARN_UNUSED_FUNCTION = YES; 349 | GCC_WARN_UNUSED_VARIABLE = YES; 350 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 351 | MTL_ENABLE_DEBUG_INFO = NO; 352 | SDKROOT = iphoneos; 353 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 354 | TARGETED_DEVICE_FAMILY = "1,2"; 355 | VALIDATE_PRODUCT = YES; 356 | VERSIONING_SYSTEM = "apple-generic"; 357 | VERSION_INFO_PREFIX = ""; 358 | }; 359 | name = Release; 360 | }; 361 | 1310ECA01D0C53800000E71E /* Debug */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | CLANG_ENABLE_MODULES = YES; 365 | CODE_SIGN_IDENTITY = "iPhone Developer"; 366 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 367 | DEFINES_MODULE = YES; 368 | DEVELOPMENT_TEAM = ""; 369 | DYLIB_COMPATIBILITY_VERSION = 1; 370 | DYLIB_CURRENT_VERSION = 1; 371 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 372 | INFOPLIST_FILE = Sources/EasyTipView/info.plist; 373 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 374 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 375 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 376 | PRODUCT_BUNDLE_IDENTIFIER = com.teodorpatras.EasyTipView; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | SKIP_INSTALL = YES; 379 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 380 | SWIFT_VERSION = 5.0; 381 | }; 382 | name = Debug; 383 | }; 384 | 1310ECA11D0C53800000E71E /* Release */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | CLANG_ENABLE_MODULES = YES; 388 | CODE_SIGN_IDENTITY = "iPhone Developer"; 389 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 390 | DEFINES_MODULE = YES; 391 | DEVELOPMENT_TEAM = ""; 392 | DYLIB_COMPATIBILITY_VERSION = 1; 393 | DYLIB_CURRENT_VERSION = 1; 394 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 395 | INFOPLIST_FILE = Sources/EasyTipView/info.plist; 396 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 397 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 399 | PRODUCT_BUNDLE_IDENTIFIER = com.teodorpatras.EasyTipView; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | SKIP_INSTALL = YES; 402 | SWIFT_VERSION = 5.0; 403 | }; 404 | name = Release; 405 | }; 406 | 1310ECA31D0C53800000E71E /* Debug */ = { 407 | isa = XCBuildConfiguration; 408 | buildSettings = { 409 | INFOPLIST_FILE = Tests/Info.plist; 410 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 411 | PRODUCT_BUNDLE_IDENTIFIER = com.teodorpatras.EasyTipViewTests; 412 | PRODUCT_NAME = "$(TARGET_NAME)"; 413 | SWIFT_VERSION = 5.0; 414 | }; 415 | name = Debug; 416 | }; 417 | 1310ECA41D0C53800000E71E /* Release */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | INFOPLIST_FILE = Tests/Info.plist; 421 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 422 | PRODUCT_BUNDLE_IDENTIFIER = com.teodorpatras.EasyTipViewTests; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SWIFT_VERSION = 5.0; 425 | }; 426 | name = Release; 427 | }; 428 | /* End XCBuildConfiguration section */ 429 | 430 | /* Begin XCConfigurationList section */ 431 | 1310EC851D0C537F0000E71E /* Build configuration list for PBXProject "EasyTipView" */ = { 432 | isa = XCConfigurationList; 433 | buildConfigurations = ( 434 | 1310EC9D1D0C53800000E71E /* Debug */, 435 | 1310EC9E1D0C53800000E71E /* Release */, 436 | ); 437 | defaultConfigurationIsVisible = 0; 438 | defaultConfigurationName = Release; 439 | }; 440 | 1310EC9F1D0C53800000E71E /* Build configuration list for PBXNativeTarget "EasyTipView" */ = { 441 | isa = XCConfigurationList; 442 | buildConfigurations = ( 443 | 1310ECA01D0C53800000E71E /* Debug */, 444 | 1310ECA11D0C53800000E71E /* Release */, 445 | ); 446 | defaultConfigurationIsVisible = 0; 447 | defaultConfigurationName = Release; 448 | }; 449 | 1310ECA21D0C53800000E71E /* Build configuration list for PBXNativeTarget "EasyTipViewTests" */ = { 450 | isa = XCConfigurationList; 451 | buildConfigurations = ( 452 | 1310ECA31D0C53800000E71E /* Debug */, 453 | 1310ECA41D0C53800000E71E /* Release */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | /* End XCConfigurationList section */ 459 | }; 460 | rootObject = 1310EC821D0C537F0000E71E /* Project object */; 461 | } 462 | -------------------------------------------------------------------------------- /EasyTipView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /EasyTipView.xcodeproj/xcshareddata/xcschemes/EasyTipView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /EasyTipView.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /EasyTipView.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/EasyTipView-Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1304D4D61D0C549300DC945E /* EasyTipView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1304D4D31D0C548900DC945E /* EasyTipView.framework */; }; 11 | 1304D4D71D0C549300DC945E /* EasyTipView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1304D4D31D0C548900DC945E /* EasyTipView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 13 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 14 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 15 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 16 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 1304D4D21D0C548900DC945E /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 1304D4CD1D0C548900DC945E /* EasyTipView.xcodeproj */; 23 | proxyType = 2; 24 | remoteGlobalIDString = 1310EC8B1D0C537F0000E71E; 25 | remoteInfo = EasyTipView; 26 | }; 27 | 1304D4D41D0C548900DC945E /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 1304D4CD1D0C548900DC945E /* EasyTipView.xcodeproj */; 30 | proxyType = 2; 31 | remoteGlobalIDString = 1310EC951D0C53800000E71E; 32 | remoteInfo = EasyTipViewTests; 33 | }; 34 | 1304D4D81D0C549300DC945E /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 1304D4CD1D0C548900DC945E /* EasyTipView.xcodeproj */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 1310EC8A1D0C537F0000E71E; 39 | remoteInfo = EasyTipView; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXCopyFilesBuildPhase section */ 44 | 1304D4DA1D0C549300DC945E /* Embed Frameworks */ = { 45 | isa = PBXCopyFilesBuildPhase; 46 | buildActionMask = 2147483647; 47 | dstPath = ""; 48 | dstSubfolderSpec = 10; 49 | files = ( 50 | 1304D4D71D0C549300DC945E /* EasyTipView.framework in Embed Frameworks */, 51 | ); 52 | name = "Embed Frameworks"; 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXCopyFilesBuildPhase section */ 56 | 57 | /* Begin PBXFileReference section */ 58 | 1304D4CD1D0C548900DC945E /* EasyTipView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = EasyTipView.xcodeproj; path = ../EasyTipView.xcodeproj; sourceTree = ""; }; 59 | 25C0D45918694E34370CD90F /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 60 | 607FACD01AFB9204008FA782 /* EasyTipView-Example_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "EasyTipView-Example_Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 63 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 64 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 65 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 66 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 67 | A4634EACF3352E09D72EE057 /* EasyTipView.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = EasyTipView.podspec; path = ../EasyTipView.podspec; sourceTree = ""; }; 68 | B009D6802E476C56C927E400 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 69 | /* End PBXFileReference section */ 70 | 71 | /* Begin PBXFrameworksBuildPhase section */ 72 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | 1304D4D61D0C549300DC945E /* EasyTipView.framework in Frameworks */, 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | /* End PBXFrameworksBuildPhase section */ 81 | 82 | /* Begin PBXGroup section */ 83 | 1304D4CE1D0C548900DC945E /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 1304D4D31D0C548900DC945E /* EasyTipView.framework */, 87 | 1304D4D51D0C548900DC945E /* EasyTipViewTests.xctest */, 88 | ); 89 | name = Products; 90 | sourceTree = ""; 91 | }; 92 | 607FACC71AFB9204008FA782 = { 93 | isa = PBXGroup; 94 | children = ( 95 | 1304D4CD1D0C548900DC945E /* EasyTipView.xcodeproj */, 96 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 97 | 607FACD21AFB9204008FA782 /* Example for EasyTipView */, 98 | 607FACD11AFB9204008FA782 /* Products */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | 607FACD11AFB9204008FA782 /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 607FACD01AFB9204008FA782 /* EasyTipView-Example_Example.app */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | 607FACD21AFB9204008FA782 /* Example for EasyTipView */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 114 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 115 | 607FACD91AFB9204008FA782 /* Main.storyboard */, 116 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 117 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 118 | 607FACD31AFB9204008FA782 /* Supporting Files */, 119 | ); 120 | name = "Example for EasyTipView"; 121 | path = EasyTipView; 122 | sourceTree = ""; 123 | }; 124 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 607FACD41AFB9204008FA782 /* Info.plist */, 128 | ); 129 | name = "Supporting Files"; 130 | sourceTree = ""; 131 | }; 132 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | A4634EACF3352E09D72EE057 /* EasyTipView.podspec */, 136 | 25C0D45918694E34370CD90F /* README.md */, 137 | B009D6802E476C56C927E400 /* LICENSE */, 138 | ); 139 | name = "Podspec Metadata"; 140 | sourceTree = ""; 141 | }; 142 | /* End PBXGroup section */ 143 | 144 | /* Begin PBXNativeTarget section */ 145 | 607FACCF1AFB9204008FA782 /* EasyTipView-Example */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "EasyTipView-Example" */; 148 | buildPhases = ( 149 | 607FACCC1AFB9204008FA782 /* Sources */, 150 | 607FACCD1AFB9204008FA782 /* Frameworks */, 151 | 607FACCE1AFB9204008FA782 /* Resources */, 152 | 1304D4DA1D0C549300DC945E /* Embed Frameworks */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | 1304D4D91D0C549300DC945E /* PBXTargetDependency */, 158 | ); 159 | name = "EasyTipView-Example"; 160 | productName = EasyTipView; 161 | productReference = 607FACD01AFB9204008FA782 /* EasyTipView-Example_Example.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 607FACC81AFB9204008FA782 /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | LastSwiftMigration = 0700; 171 | LastSwiftUpdateCheck = 0700; 172 | LastUpgradeCheck = 1020; 173 | ORGANIZATIONNAME = CocoaPods; 174 | TargetAttributes = { 175 | 607FACCF1AFB9204008FA782 = { 176 | CreatedOnToolsVersion = 6.3.1; 177 | DevelopmentTeam = 752CFWL5Y4; 178 | LastSwiftMigration = 0930; 179 | }; 180 | }; 181 | }; 182 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "EasyTipView-Example" */; 183 | compatibilityVersion = "Xcode 3.2"; 184 | developmentRegion = en; 185 | hasScannedForEncodings = 0; 186 | knownRegions = ( 187 | en, 188 | Base, 189 | ); 190 | mainGroup = 607FACC71AFB9204008FA782; 191 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 192 | projectDirPath = ""; 193 | projectReferences = ( 194 | { 195 | ProductGroup = 1304D4CE1D0C548900DC945E /* Products */; 196 | ProjectRef = 1304D4CD1D0C548900DC945E /* EasyTipView.xcodeproj */; 197 | }, 198 | ); 199 | projectRoot = ""; 200 | targets = ( 201 | 607FACCF1AFB9204008FA782 /* EasyTipView-Example */, 202 | ); 203 | }; 204 | /* End PBXProject section */ 205 | 206 | /* Begin PBXReferenceProxy section */ 207 | 1304D4D31D0C548900DC945E /* EasyTipView.framework */ = { 208 | isa = PBXReferenceProxy; 209 | fileType = wrapper.framework; 210 | path = EasyTipView.framework; 211 | remoteRef = 1304D4D21D0C548900DC945E /* PBXContainerItemProxy */; 212 | sourceTree = BUILT_PRODUCTS_DIR; 213 | }; 214 | 1304D4D51D0C548900DC945E /* EasyTipViewTests.xctest */ = { 215 | isa = PBXReferenceProxy; 216 | fileType = wrapper.cfbundle; 217 | path = EasyTipViewTests.xctest; 218 | remoteRef = 1304D4D41D0C548900DC945E /* PBXContainerItemProxy */; 219 | sourceTree = BUILT_PRODUCTS_DIR; 220 | }; 221 | /* End PBXReferenceProxy section */ 222 | 223 | /* Begin PBXResourcesBuildPhase section */ 224 | 607FACCE1AFB9204008FA782 /* Resources */ = { 225 | isa = PBXResourcesBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 229 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 230 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXResourcesBuildPhase section */ 235 | 236 | /* Begin PBXSourcesBuildPhase section */ 237 | 607FACCC1AFB9204008FA782 /* Sources */ = { 238 | isa = PBXSourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 242 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | /* End PBXSourcesBuildPhase section */ 247 | 248 | /* Begin PBXTargetDependency section */ 249 | 1304D4D91D0C549300DC945E /* PBXTargetDependency */ = { 250 | isa = PBXTargetDependency; 251 | name = EasyTipView; 252 | targetProxy = 1304D4D81D0C549300DC945E /* PBXContainerItemProxy */; 253 | }; 254 | /* End PBXTargetDependency section */ 255 | 256 | /* Begin PBXVariantGroup section */ 257 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = { 258 | isa = PBXVariantGroup; 259 | children = ( 260 | 607FACDA1AFB9204008FA782 /* Base */, 261 | ); 262 | name = Main.storyboard; 263 | sourceTree = ""; 264 | }; 265 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 266 | isa = PBXVariantGroup; 267 | children = ( 268 | 607FACDF1AFB9204008FA782 /* Base */, 269 | ); 270 | name = LaunchScreen.xib; 271 | sourceTree = ""; 272 | }; 273 | /* End PBXVariantGroup section */ 274 | 275 | /* Begin XCBuildConfiguration section */ 276 | 607FACED1AFB9204008FA782 /* Debug */ = { 277 | isa = XCBuildConfiguration; 278 | buildSettings = { 279 | ALWAYS_SEARCH_USER_PATHS = NO; 280 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 286 | CLANG_WARN_BOOL_CONVERSION = YES; 287 | CLANG_WARN_COMMA = YES; 288 | CLANG_WARN_CONSTANT_CONVERSION = YES; 289 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 290 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 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_UNREACHABLE_CODE = YES; 303 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 304 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 305 | COPY_PHASE_STRIP = NO; 306 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 307 | ENABLE_STRICT_OBJC_MSGSEND = YES; 308 | ENABLE_TESTABILITY = YES; 309 | GCC_C_LANGUAGE_STANDARD = gnu99; 310 | GCC_DYNAMIC_NO_PIC = NO; 311 | GCC_NO_COMMON_BLOCKS = YES; 312 | GCC_OPTIMIZATION_LEVEL = 0; 313 | GCC_PREPROCESSOR_DEFINITIONS = ( 314 | "DEBUG=1", 315 | "$(inherited)", 316 | ); 317 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 325 | MTL_ENABLE_DEBUG_INFO = YES; 326 | ONLY_ACTIVE_ARCH = YES; 327 | SDKROOT = iphoneos; 328 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 329 | }; 330 | name = Debug; 331 | }; 332 | 607FACEE1AFB9204008FA782 /* Release */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 342 | CLANG_WARN_BOOL_CONVERSION = YES; 343 | CLANG_WARN_COMMA = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 346 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 347 | CLANG_WARN_EMPTY_BODY = YES; 348 | CLANG_WARN_ENUM_CONVERSION = YES; 349 | CLANG_WARN_INFINITE_RECURSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 353 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 356 | CLANG_WARN_STRICT_PROTOTYPES = YES; 357 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 363 | ENABLE_NS_ASSERTIONS = NO; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu99; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 374 | MTL_ENABLE_DEBUG_INFO = NO; 375 | SDKROOT = iphoneos; 376 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 377 | VALIDATE_PRODUCT = YES; 378 | }; 379 | name = Release; 380 | }; 381 | 607FACF01AFB9204008FA782 /* Debug */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 385 | DEVELOPMENT_TEAM = 752CFWL5Y4; 386 | INFOPLIST_FILE = EasyTipView/Info.plist; 387 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 388 | MODULE_NAME = ExampleApp; 389 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.EasyTipView-demo"; 390 | PRODUCT_NAME = "EasyTipView-Example_Example"; 391 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 392 | SWIFT_VERSION = 4.0; 393 | }; 394 | name = Debug; 395 | }; 396 | 607FACF11AFB9204008FA782 /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 400 | DEVELOPMENT_TEAM = 752CFWL5Y4; 401 | INFOPLIST_FILE = EasyTipView/Info.plist; 402 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 403 | MODULE_NAME = ExampleApp; 404 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.EasyTipView-demo"; 405 | PRODUCT_NAME = "EasyTipView-Example_Example"; 406 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 407 | SWIFT_VERSION = 4.0; 408 | }; 409 | name = Release; 410 | }; 411 | /* End XCBuildConfiguration section */ 412 | 413 | /* Begin XCConfigurationList section */ 414 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "EasyTipView-Example" */ = { 415 | isa = XCConfigurationList; 416 | buildConfigurations = ( 417 | 607FACED1AFB9204008FA782 /* Debug */, 418 | 607FACEE1AFB9204008FA782 /* Release */, 419 | ); 420 | defaultConfigurationIsVisible = 0; 421 | defaultConfigurationName = Release; 422 | }; 423 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "EasyTipView-Example" */ = { 424 | isa = XCConfigurationList; 425 | buildConfigurations = ( 426 | 607FACF01AFB9204008FA782 /* Debug */, 427 | 607FACF11AFB9204008FA782 /* Release */, 428 | ); 429 | defaultConfigurationIsVisible = 0; 430 | defaultConfigurationName = Release; 431 | }; 432 | /* End XCConfigurationList section */ 433 | }; 434 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 435 | } 436 | -------------------------------------------------------------------------------- /Example/EasyTipView-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/EasyTipView-Example.xcodeproj/xcshareddata/xcschemes/EasyTipView-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/EasyTipView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // EasyTipView 4 | // 5 | // Created by Teodor Patraş on 08/16/2015. 6 | // Copyright (c) 2015 Teodor Patraş. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/EasyTipView/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/EasyTipView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 58 | 78 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 162 | 182 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "EasyTipView.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "EasyTipView copy 2-1.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "EasyTipView copy 4.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "EasyTipView copy 3.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "EasyTipView copy 3-1.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "EasyTipView copy 2.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ios-marketing", 51 | "size" : "1024x1024", 52 | "scale" : "1x" 53 | } 54 | ], 55 | "info" : { 56 | "version" : 1, 57 | "author" : "xcode" 58 | } 59 | } -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 2-1.png -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 2.png -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 3-1.png -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 3.png -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView copy 4.png -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/Example/EasyTipView/Images.xcassets/AppIcon.appiconset/EasyTipView.png -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/easytipview.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "easytipview@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyTipView/Images.xcassets/easytipview.imageset/easytipview@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/Example/EasyTipView/Images.xcassets/easytipview.imageset/easytipview@2x.png -------------------------------------------------------------------------------- /Example/EasyTipView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeRight 37 | UIInterfaceOrientationPortraitUpsideDown 38 | UIInterfaceOrientationLandscapeLeft 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/EasyTipView/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // 4 | // Copyright (c) 2015 Teodor Patraş 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | import UIKit 25 | import Darwin 26 | import EasyTipView 27 | 28 | class ViewController: UIViewController, EasyTipViewDelegate { 29 | 30 | @IBOutlet weak var toolbarItem: UIBarButtonItem! 31 | @IBOutlet weak var smallContainerView: UIView! 32 | @IBOutlet weak var navBarItem: UIBarButtonItem! 33 | @IBOutlet weak var buttonA: UIButton! 34 | @IBOutlet weak var buttonB: UIButton! 35 | @IBOutlet weak var buttonC: UIButton! 36 | @IBOutlet weak var buttonD: UIButton! 37 | @IBOutlet weak var buttonE: UIButton! 38 | @IBOutlet weak var buttonF: UIButton! 39 | @IBOutlet weak var buttonG: UIButton! 40 | 41 | weak var tipView: EasyTipView? 42 | 43 | override func viewDidLoad() { 44 | super.viewDidLoad() 45 | 46 | self.configureUI() 47 | 48 | var preferences = EasyTipView.Preferences() 49 | 50 | preferences.drawing.font = UIFont(name: "Futura-Medium", size: 13)! 51 | preferences.drawing.foregroundColor = UIColor.white 52 | preferences.drawing.backgroundColor = UIColor(hue:0.46, saturation:0.99, brightness:0.6, alpha:1) 53 | 54 | EasyTipView.globalPreferences = preferences 55 | self.view.backgroundColor = UIColor(hue:0.75, saturation:0.01, brightness:0.96, alpha:1.00) 56 | } 57 | 58 | override func viewDidAppear(_ animated: Bool) { 59 | super.viewDidAppear(animated) 60 | self.toolbarItemAction() 61 | } 62 | 63 | func easyTipViewDidTap(_ tipView: EasyTipView) { 64 | print("\(tipView) did tap!") 65 | } 66 | 67 | func easyTipViewDidDismiss(_ tipView: EasyTipView) { 68 | print("\(tipView) did dismiss!") 69 | } 70 | 71 | @IBAction func barButtonAction(sender: UIBarButtonItem) { 72 | let text = "Tip view for bar button item displayed within the navigation controller's view. Tap to dismiss." 73 | EasyTipView.show(forItem: self.navBarItem, 74 | withinSuperview: self.navigationController?.view, 75 | text: text, 76 | delegate : self) 77 | } 78 | 79 | @IBAction func toolbarItemAction() { 80 | if let tipView = tipView { 81 | tipView.dismiss(withCompletion: { 82 | print("Completion called!") 83 | }) 84 | } else { 85 | let text = "EasyTipView is an easy to use tooltip view. It can point to any UIView or UIBarItem subclasses. Tap the buttons to see other tooltips." 86 | 87 | var preferences = EasyTipView.globalPreferences 88 | preferences.drawing.shadowColor = UIColor.black 89 | preferences.drawing.shadowRadius = 2 90 | preferences.drawing.shadowOpacity = 0.75 91 | 92 | let tip = EasyTipView(text: text, preferences: preferences, delegate: self) 93 | tip.show(forItem: toolbarItem) 94 | tipView = tip 95 | } 96 | } 97 | 98 | @IBAction func buttonAction(sender : UIButton) { 99 | switch sender { 100 | case buttonA: 101 | 102 | var preferences = EasyTipView.Preferences() 103 | preferences.drawing.backgroundColor = UIColor(hue:0.58, saturation:0.1, brightness:1, alpha:1) 104 | preferences.drawing.foregroundColor = UIColor.darkGray 105 | preferences.drawing.textAlignment = NSTextAlignment.center 106 | 107 | preferences.animating.dismissTransform = CGAffineTransform(translationX: 100, y: 0) 108 | preferences.animating.showInitialTransform = CGAffineTransform(translationX: -100, y: 0) 109 | preferences.animating.showInitialAlpha = 0 110 | preferences.animating.showDuration = 1 111 | preferences.animating.dismissDuration = 1 112 | 113 | let view = EasyTipView(text: "Tip view within the green superview. Tap to dismiss.", preferences: preferences) 114 | view.show(forView: buttonA, withinSuperview: self.smallContainerView) 115 | 116 | case buttonB: 117 | 118 | var preferences = EasyTipView.globalPreferences 119 | preferences.drawing.foregroundColor = UIColor.white 120 | preferences.drawing.font = UIFont(name: "HelveticaNeue-Light", size: 14)! 121 | preferences.drawing.textAlignment = NSTextAlignment.justified 122 | 123 | preferences.animating.dismissTransform = CGAffineTransform(translationX: 0, y: -15) 124 | preferences.animating.showInitialTransform = CGAffineTransform(translationX: 0, y: 15) 125 | preferences.animating.showInitialAlpha = 0 126 | preferences.animating.showDuration = 1 127 | preferences.animating.dismissDuration = 1 128 | preferences.drawing.arrowPosition = .top 129 | 130 | let text = "Tip view inside the navigation controller's view. Tap to dismiss!" 131 | EasyTipView.show(forView: self.buttonB, 132 | withinSuperview: self.navigationController?.view, 133 | text: text, 134 | preferences: preferences) 135 | 136 | case buttonC: 137 | 138 | var preferences = EasyTipView.globalPreferences 139 | preferences.drawing.backgroundColor = buttonC.backgroundColor! 140 | 141 | preferences.animating.dismissTransform = CGAffineTransform(translationX: 0, y: -15) 142 | preferences.animating.showInitialTransform = CGAffineTransform(translationX: 0, y: -15) 143 | preferences.animating.showInitialAlpha = 0 144 | preferences.animating.showDuration = 1.5 145 | preferences.animating.dismissDuration = 1.5 146 | preferences.drawing.arrowPosition = .top 147 | 148 | let text = "This tip view cannot be presented with the arrow on the top position, so position bottom has been chosen instead. Tap to dismiss." 149 | EasyTipView.show(forView: buttonC, 150 | withinSuperview: navigationController?.view, 151 | text: text, 152 | preferences: preferences) 153 | 154 | case buttonE: 155 | 156 | var preferences = EasyTipView.Preferences() 157 | preferences.drawing.backgroundColor = buttonE.backgroundColor! 158 | preferences.drawing.foregroundColor = UIColor.white 159 | preferences.drawing.textAlignment = NSTextAlignment.center 160 | 161 | preferences.drawing.arrowPosition = .right 162 | 163 | preferences.animating.dismissTransform = CGAffineTransform(translationX: 0, y: 100) 164 | preferences.animating.showInitialTransform = CGAffineTransform(translationX: 0, y: -100) 165 | preferences.animating.showInitialAlpha = 0 166 | preferences.animating.showDuration = 1 167 | preferences.animating.dismissDuration = 1 168 | 169 | preferences.positioning.maxWidth = 150 170 | 171 | let view = EasyTipView(text: "Tip view positioned with the arrow on the right. Tap to dismiss.", preferences: preferences) 172 | view.show(forView: buttonE, withinSuperview: self.navigationController?.view!) 173 | 174 | case buttonF: 175 | 176 | var preferences = EasyTipView.Preferences() 177 | preferences.drawing.backgroundColor = buttonF.backgroundColor! 178 | preferences.drawing.foregroundColor = UIColor.white 179 | preferences.drawing.textAlignment = NSTextAlignment.center 180 | 181 | preferences.drawing.arrowPosition = .left 182 | 183 | preferences.animating.dismissTransform = CGAffineTransform(translationX: -30, y: -100) 184 | preferences.animating.showInitialTransform = CGAffineTransform(translationX: 30, y: 100) 185 | preferences.animating.showInitialAlpha = 0 186 | preferences.animating.showDuration = 1 187 | preferences.animating.dismissDuration = 1 188 | preferences.animating.dismissOnTap = false 189 | 190 | preferences.positioning.maxWidth = 150 191 | 192 | let view = EasyTipView(text: "Tip view positioned with the arrow on the left. Tap won't dismiss.", preferences: preferences) 193 | view.show(forView: buttonF, withinSuperview: self.navigationController?.view!) 194 | 195 | case buttonG: 196 | 197 | var preferences = EasyTipView.globalPreferences 198 | preferences.drawing.backgroundColor = buttonG.backgroundColor! 199 | 200 | preferences.animating.dismissTransform = CGAffineTransform(translationX: 0, y: -15) 201 | preferences.animating.showInitialTransform = CGAffineTransform(translationX: 0, y: 15) 202 | preferences.animating.showInitialAlpha = 0 203 | preferences.animating.showDuration = 1 204 | preferences.animating.dismissDuration = 1 205 | preferences.drawing.arrowPosition = .bottom 206 | 207 | preferences.positioning.contentInsets = UIEdgeInsetsMake(5, 5, 5, 5) 208 | 209 | let contentView = UIImageView(frame: CGRect(x: 0, y: 0, width: 300, height: 82)) 210 | contentView.image = UIImage(named: "easytipview") 211 | EasyTipView.show(forView: self.buttonG, 212 | contentView: contentView, 213 | preferences: preferences) 214 | 215 | default: 216 | 217 | var preferences = EasyTipView.globalPreferences 218 | preferences.drawing.arrowPosition = .bottom 219 | preferences.drawing.font = UIFont.systemFont(ofSize: 14) 220 | preferences.drawing.textAlignment = .center 221 | preferences.drawing.backgroundColor = buttonD.backgroundColor! 222 | 223 | preferences.positioning.maxWidth = 230 224 | 225 | preferences.animating.dismissTransform = CGAffineTransform(translationX: 100, y: 0) 226 | preferences.animating.showInitialTransform = CGAffineTransform(translationX: 100, y: 0) 227 | preferences.animating.showInitialAlpha = 0 228 | preferences.animating.showDuration = 1 229 | preferences.animating.dismissDuration = 1 230 | 231 | let firstAttributes: [NSAttributedString.Key: Any] = [.backgroundColor: UIColor(hue:0.07, saturation:0.82, brightness:0.89, alpha:1.00), .font: UIFont.systemFont(ofSize: 14)] 232 | let secondAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.white, .backgroundColor: UIColor(hue:0.79, saturation:0.67, brightness:0.63, alpha:1.00), .font: UIFont.systemFont(ofSize: 14)] 233 | let thirdAttributes: [NSAttributedString.Key: Any] = [.backgroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 14)] 234 | let fourthAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.white, .backgroundColor: UIColor(hue:0.59, saturation:0.44, brightness:0.27, alpha:1.00), .font: UIFont.systemFont(ofSize: 14)] 235 | 236 | let firstString = NSMutableAttributedString(string: "Tooltip in ", attributes: firstAttributes) 237 | let secondString = NSAttributedString(string: "top most ", attributes: secondAttributes) 238 | let thirdString = NSAttributedString(string: " window.\n", attributes: thirdAttributes) 239 | let fourthString = NSAttributedString(string: "Tap to dismiss.", attributes: fourthAttributes) 240 | 241 | firstString.append(secondString) 242 | firstString.append(thirdString) 243 | firstString.append(fourthString) 244 | 245 | EasyTipView.show(forView: self.buttonD, 246 | attributedText: firstString, 247 | preferences: preferences) 248 | } 249 | } 250 | 251 | func configureUI () { 252 | let color = UIColor(hue:0.46, saturation:0.99, brightness:0.6, alpha:1) 253 | 254 | buttonA.backgroundColor = UIColor(hue:0.58, saturation:0.1, brightness:1, alpha:1) 255 | 256 | self.navigationController?.view.tintColor = color 257 | 258 | self.buttonB.backgroundColor = color 259 | self.smallContainerView.backgroundColor = color 260 | } 261 | } 262 | 263 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Teodor Patraş 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 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: "EasyTipView", 8 | platforms: [ 9 | .iOS(.v9), 10 | ], 11 | products: [ 12 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 13 | .library( 14 | name: "EasyTipView", 15 | targets: ["EasyTipView"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 24 | .target( 25 | name: "EasyTipView", 26 | dependencies: [], 27 | exclude: ["assets", "Example", "Source"]), 28 | .testTarget( 29 | name: "EasyTipViewTests", 30 | dependencies: ["EasyTipView"], 31 | exclude: ["assets", "Example", "Source"]), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EasyTipView: fully customisable tooltip view written in Swift 2 | 3 | ![Swift3](https://img.shields.io/badge/Swift-5.0-orange.svg?style=flat") 4 | [![Platform](https://img.shields.io/cocoapods/p/EasyTipView.svg?style=flat)](http://cocoapods.org/pods/EasyTipView) 5 | [![Build Status](https://travis-ci.org/teodorpatras/EasyTipView.svg)](https://travis-ci.org/teodorpatras/EasyTipView) 6 | [![Version](https://img.shields.io/cocoapods/v/EasyTipView.svg?style=flat)](http://cocoapods.org/pods/EasyTipView) 7 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | [![License](https://img.shields.io/cocoapods/l/EasyTipView.svg?style=flat)](http://cocoapods.org/pods/EasyTipView) 9 | 10 | Description 11 | -------------- 12 | 13 | ```EasyTipView``` is a fully customizable tooltip view written in Swift that can be used as a call to action or informative tip. 14 | 15 | ||| 16 | |----------|-------------| 17 | 18 | # Contents 19 | 1. [Features](#features) 20 | 3. [Installation](#installation) 21 | 4. [Supported OS & SDK versions](#supported-versions) 22 | 5. [Usage](#usage) 23 | 6. [Customizing the appearance](#customising) 24 | 7. [Customising the presentation and dismissal animations](#customising-animations) 25 | 8. [License](#license) 26 | 9. [Contact](#contact) 27 | 28 | Features 29 | -------------- 30 | 31 | - [x] Can be shown pointing to any ``UIBarItem`` or ``UIView`` subclass. 32 | - [x] Support for any arrow direction `←, →, ↑, ↓` 33 | - [x] Automatic orientation change adjustments. 34 | - [x] Fully customizable appearance (custom content view or simply just text - including `NSAttributedString` - see the Example app). 35 | - [x] Fully customizable presentation and dismissal animations. 36 | 37 | 38 | Installation 39 | -------------- 40 | 41 | ### CocoaPods 42 | 43 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. 44 | 45 | CocoaPods 0.36 adds supports for Swift and embedded frameworks. You can install it with the following command: 46 | 47 | ```bash 48 | $ gem install cocoapods 49 | ``` 50 | 51 | To integrate EasyTipView into your Xcode project using CocoaPods, specify it in your `Podfile`: 52 | 53 | 54 | ```ruby 55 | pod 'EasyTipView', '~> 2.1' 56 | ``` 57 | 58 | Then, run the following command: 59 | 60 | ```bash 61 | $ pod install 62 | ``` 63 | 64 | In case Xcode complains ("Cannot load underlying module for EasyTipView") go to Product and choose Clean (or simply press K). 65 | 66 | 67 | ### Carthage 68 | 69 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. 70 | 71 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 72 | 73 | ```bash 74 | $ brew update 75 | $ brew install carthage 76 | ``` 77 | 78 | To integrate EasyTipView into your Xcode project using Carthage, specify it in your `Cartfile`: 79 | 80 | ```ogdl 81 | github "teodorpatras/EasyTipView" 82 | ``` 83 | 84 | Run `carthage update` to build the framework and drag the built `EasyTipView.framework` into your Xcode project. 85 | 86 | ### Manually 87 | 88 | If you prefer not to use either of the aforementioned dependency managers, you can integrate EasyTipView into your project manually. 89 | 90 | Supported OS & SDK Versions 91 | ----------------------------- 92 | 93 | * Supported build target - iOS 8+ (Xcode 8) 94 | 95 | Usage 96 | -------------- 97 | 98 | 1) First you should customize the preferences: 99 | ```swift 100 | 101 | var preferences = EasyTipView.Preferences() 102 | preferences.drawing.font = UIFont(name: "Futura-Medium", size: 13)! 103 | preferences.drawing.foregroundColor = UIColor.whiteColor() 104 | preferences.drawing.backgroundColor = UIColor(hue:0.46, saturation:0.99, brightness:0.6, alpha:1) 105 | preferences.drawing.arrowPosition = EasyTipView.ArrowPosition.top 106 | 107 | /* 108 | * Optionally you can make these preferences global for all future EasyTipViews 109 | */ 110 | EasyTipView.globalPreferences = preferences 111 | 112 | ``` 113 | 114 | 2) Secondly call the ``show(animated: forView: withinSuperview: text: preferences: delegate:)`` method: 115 | ```swift 116 | EasyTipView.show(forView: self.buttonB, 117 | withinSuperview: self.navigationController?.view, 118 | text: "Tip view inside the navigation controller's view. Tap to dismiss!", 119 | preferences: preferences, 120 | delegate: self) 121 | ``` 122 | 123 | **Note that if you set the** ```EasyTipView.globalPreferences```**, you can ommit the** ```preferences``` **parameter in all calls. Additionally, you can also ommit the** ``withinSuperview`` **parameter and the** ``EasyTipView`` **will be shown within the main application window**. 124 | 125 | *Alternatively, if you want to dismiss the ``EasyTipView`` programmatically later on, you can use one of the instance methods:* 126 | 127 | ```swift 128 | 129 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 130 | tipView.show(forView: someView, withinSuperview: someSuperview) 131 | 132 | // later on you can dismiss it 133 | tipView.dismiss() 134 | ``` 135 | Customizing the appearance 136 | -------------- 137 | In order to customize the `EasyTipView` appearance and behavior, you can play with the `Preferences` structure which encapsulates all the customizable properties of the ``EasyTipView``. These preferences have been split into three structures: 138 | * ```Drawing``` - encapsulates customizable properties specifying how ```EastTipView``` will be drawn on screen. 139 | * ```Positioning``` - encapsulates customizable properties specifying where ```EasyTipView``` will be drawn within its own bounds. 140 | * ```Animating``` - encapsulates customizable properties specifying how ```EasyTipView``` will animate on and off screen. 141 | 142 | | `Drawing ` attribute | Description | 143 | |----------|-------------| 144 | |`cornerRadius`| The corner radius of the tip view bubble.| 145 | |`arrowHeight`| The height of the arrow positioned at the top or bottom of the bubble.| 146 | |`arrowWidth`| The width of the above mentioned arrow.| 147 | |`foregroundColor`| The text color.| 148 | |`backgroundColor`| The background color of the bubble.| 149 | |`arrowPosition`| The position of the arrow. This can be:
**+** `.top`: on top of the bubble
**+** `.bottom`: at the bottom of the bubble.
**+** `.left`: on the left of the bubble
**+** `.right`: on the right of the bubble
**+** `.any`: use this option to let the `EasyTipView` automatically find the best arrow position.
**If the passed in arrow cannot be applied due to layout restrictions, a different arrow position will be automatically assigned.**| 150 | |`textAlignment`| The alignment of the text.| 151 | |`borderWidth`| Width of the optional border to be applied on the bubble.| 152 | |`borderColor`| Color of the optional border to be applied on the bubble. **In order for the border to be applied, `borderColor` needs to be different that `UIColor.clear` and `borderWidth` > 0**| 153 | |`font`| Font to be applied on the text. | 154 | |`shadowColor`| The color of the shadow (default `UIColor.clearcolor`).| 155 | |`shadowOpacity`| The opacity of the shadow (default `0`). **For the shadow to be drawn, both** `shadowColor` **and** `shadowOpacity` **must be set to a valid value.**| 156 | |`shadowRadius`| The radius of the shadow (default `0`).| 157 | |`shadowOffset` | The offset of the shadow. | 158 | 159 | | `Positioning ` attribute | Description | 160 | |----------|-------------| 161 | |`bubbleHInset`| Horizontal bubble inset within its container.| 162 | |`bubbleVInset`| Vertical bubble inset within its container.| 163 | |`contentHInset`| Content horizontal inset within the bubble.| 164 | |`contentVInset`| Content vertical inset within the bubble.| 165 | |`maxWidth`| Max bubble width.| 166 | 167 | | `Animating ` attribute | Description | 168 | |----------|-------------| 169 | |`dismissTransform`| `CGAffineTransform` specifying how the bubble will be dismissed. | 170 | |`showInitialTransform`| `CGAffineTransform` specifying the initial transform to be applied on the bubble before it is animated on screen. | 171 | |`showFinalTransform`| `CGAffineTransform` specifying how the bubble will be animated on screen. | 172 | |`springDamping`| Spring animation damping.| 173 | |`springVelocity`| Spring animation velocity.| 174 | |`showInitialAlpha`|Initial alpha to be applied on the tip view before it is animated on screen.| 175 | |`dismissFinalAlpha`|The alpha to be applied on the tip view when it is animating off screen.| 176 | |`showDuration`|Show animation duration.| 177 | |`dismissDuration`|Dismiss animation duration.| 178 | |`dismissOnTap`|Prevents view from dismissing on tap if it is set to false. (Default value is true.)| 179 | 180 | Customising the presentation or dismissal animations 181 | -------------- 182 | 183 | The default animations for showing or dismissing are scale up and down. If you want to change the default behavior, you need to change the attributes of the ``animating`` property within the preferences. An example could be: 184 | 185 | ```swift 186 | preferences.animating.dismissTransform = CGAffineTransform(translationX: 0, y: -15) 187 | preferences.animating.showInitialTransform = CGAffineTransform(translationX: 0, y: -15) 188 | preferences.animating.showInitialAlpha = 0 189 | preferences.animating.showDuration = 1.5 190 | preferences.animating.dismissDuration = 1.5 191 | ``` 192 | 193 | This produces the following animations: 194 |

195 | 196 | For more animations, checkout the example project. 197 | *Once you configured the animations, a good idea would be to __make these preferences global__, for all future instances of `EasyTipView` by assigning it to ```EasyTipView.globalPreferences```.* 198 | 199 | 200 | License 201 | -------------- 202 | 203 | ```EasyTipView``` is developed by [Teodor Patraş](https://www.teodorpatras.com) and is released under the MIT license. See the ```LICENSE``` file for details. 204 | 205 | Logo was created using Bud Icons Launch graphic by [Budi Tanrim](http://buditanrim.co) from [FlatIcon](http://www.flaticon.com/) which is licensed under [Creative Commons BY 3.0](http://creativecommons.org/licenses/by/3.0/). Made with [Logo Maker](http://logomakr.com). 206 | 207 | Contact 208 | -------------- 209 | 210 | You can follow or drop me a line on [my Twitter account](https://twitter.com/teodorpatras). If you find any issues on the project, you can open a ticket. Pull requests are also welcome. 211 | -------------------------------------------------------------------------------- /Sources/EasyTipView/EasyTipView.h: -------------------------------------------------------------------------------- 1 | // 2 | // EasyTipView.h 3 | // EasyTipView 4 | // 5 | // Created by Teodor Patras on 11/06/16. 6 | // Copyright © 2016 teodorpatras. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for EasyTipView. 12 | FOUNDATION_EXPORT double EasyTipViewVersionNumber; 13 | 14 | //! Project version string for EasyTipView. 15 | FOUNDATION_EXPORT const unsigned char EasyTipViewVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/EasyTipView/EasyTipView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyTipView.swift 3 | // 4 | // Copyright (c) 2015 Teodor Patraş 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #if canImport(UIKit) 25 | import UIKit 26 | 27 | public protocol EasyTipViewDelegate : class { 28 | func easyTipViewDidTap(_ tipView: EasyTipView) 29 | func easyTipViewDidDismiss(_ tipView : EasyTipView) 30 | } 31 | 32 | 33 | // MARK: - Public methods extension 34 | 35 | public extension EasyTipView { 36 | 37 | // MARK:- Class methods - 38 | 39 | /** 40 | Presents an EasyTipView pointing to a particular UIBarItem instance within the specified superview 41 | 42 | - parameter animated: Pass true to animate the presentation. 43 | - parameter item: The UIBarButtonItem or UITabBarItem instance which the EasyTipView will be pointing to. 44 | - parameter superview: A view which is part of the UIBarButtonItem instances superview hierarchy. Ignore this parameter in order to display the EasyTipView within the main window. 45 | - parameter text: The text to be displayed. 46 | - parameter preferences: The preferences which will configure the EasyTipView. 47 | - parameter delegate: The delegate. 48 | */ 49 | class func show(animated: Bool = true, forItem item: UIBarItem, withinSuperview superview: UIView? = nil, text: String, preferences: Preferences = EasyTipView.globalPreferences, delegate: EasyTipViewDelegate? = nil){ 50 | 51 | if let view = item.view { 52 | show(animated: animated, forView: view, withinSuperview: superview, text: text, preferences: preferences, delegate: delegate) 53 | } 54 | } 55 | 56 | /** 57 | Presents an EasyTipView pointing to a particular UIView instance within the specified superview 58 | 59 | - parameter animated: Pass true to animate the presentation. 60 | - parameter view: The UIView instance which the EasyTipView will be pointing to. 61 | - parameter superview: A view which is part of the UIView instances superview hierarchy. Ignore this parameter in order to display the EasyTipView within the main window. 62 | - parameter text: The text to be displayed. 63 | - parameter preferences: The preferences which will configure the EasyTipView. 64 | - parameter delegate: The delegate. 65 | */ 66 | class func show(animated: Bool = true, forView view: UIView, withinSuperview superview: UIView? = nil, text: String, preferences: Preferences = EasyTipView.globalPreferences, delegate: EasyTipViewDelegate? = nil){ 67 | 68 | let ev = EasyTipView(text: text, preferences: preferences, delegate: delegate) 69 | ev.show(animated: animated, forView: view, withinSuperview: superview) 70 | } 71 | 72 | /** 73 | Presents an EasyTipView pointing to a particular UIBarItem instance within the specified superview 74 | 75 | - parameter animated: Pass true to animate the presentation. 76 | - parameter item: The UIBarButtonItem or UITabBarItem instance which the EasyTipView will be pointing to. 77 | - parameter superview: A view which is part of the UIBarButtonItem instances superview hierarchy. Ignore this parameter in order to display the EasyTipView within the main window. 78 | - parameter contentView: The view to be displayed. 79 | - parameter preferences: The preferences which will configure the EasyTipView. 80 | - parameter delegate: The delegate. 81 | */ 82 | class func show(animated: Bool = true, forItem item: UIBarItem, withinSuperview superview: UIView? = nil, contentView: UIView, preferences: Preferences = EasyTipView.globalPreferences, delegate: EasyTipViewDelegate? = nil){ 83 | 84 | if let view = item.view { 85 | show(animated: animated, forView: view, withinSuperview: superview, contentView: contentView, preferences: preferences, delegate: delegate) 86 | } 87 | } 88 | 89 | /** 90 | Presents an EasyTipView pointing to a particular UIView instance within the specified superview 91 | 92 | - parameter animated: Pass true to animate the presentation. 93 | - parameter view: The UIView instance which the EasyTipView will be pointing to. 94 | - parameter superview: A view which is part of the UIView instances superview hierarchy. Ignore this parameter in order to display the EasyTipView within the main window. 95 | - parameter contentView: The view to be displayed. 96 | - parameter preferences: The preferences which will configure the EasyTipView. 97 | - parameter delegate: The delegate. 98 | */ 99 | class func show(animated: Bool = true, forView view: UIView, withinSuperview superview: UIView? = nil, contentView: UIView, preferences: Preferences = EasyTipView.globalPreferences, delegate: EasyTipViewDelegate? = nil){ 100 | 101 | let ev = EasyTipView(contentView: contentView, preferences: preferences, delegate: delegate) 102 | ev.show(animated: animated, forView: view, withinSuperview: superview) 103 | } 104 | 105 | /** 106 | Presents an EasyTipView pointing to a particular UIView instance within the specified superview containing attributed text. 107 | - parameter animated: Pass true to animate the presentation. 108 | - parameter view: The UIView instance which the EasyTipView will be pointing to. 109 | - parameter superview: A view which is part of the UIView instances superview hierarchy. Ignore this parameter in order to display the EasyTipView within the main window. 110 | - parameter attributedText: The attributed text to be displayed. 111 | - parameter preferences: The preferences which will configure the EasyTipView. 112 | - parameter delegate: The delegate. 113 | */ 114 | class func show(animated: Bool = true, forView view: UIView, withinSuperview superview: UIView? = nil, attributedText: NSAttributedString, preferences: Preferences = EasyTipView.globalPreferences, delegate: EasyTipViewDelegate? = nil){ 115 | 116 | let ev = EasyTipView(text: attributedText, preferences: preferences, delegate: delegate) 117 | ev.show(animated: animated, forView: view, withinSuperview: superview) 118 | } 119 | 120 | // MARK:- Instance methods - 121 | 122 | /** 123 | Presents an EasyTipView pointing to a particular UIBarItem instance within the specified superview 124 | 125 | - parameter animated: Pass true to animate the presentation. 126 | - parameter item: The UIBarButtonItem or UITabBarItem instance which the EasyTipView will be pointing to. 127 | - parameter superview: A view which is part of the UIBarButtonItem instances superview hierarchy. Ignore this parameter in order to display the EasyTipView within the main window. 128 | */ 129 | func show(animated: Bool = true, forItem item: UIBarItem, withinSuperView superview: UIView? = nil) { 130 | if let view = item.view { 131 | show(animated: animated, forView: view, withinSuperview: superview) 132 | } 133 | } 134 | 135 | /** 136 | Presents an EasyTipView pointing to a particular UIView instance within the specified superview 137 | 138 | - parameter animated: Pass true to animate the presentation. 139 | - parameter view: The UIView instance which the EasyTipView will be pointing to. 140 | - parameter superview: A view which is part of the UIView instances superview hierarchy. Ignore this parameter in order to display the EasyTipView within the main window. 141 | */ 142 | func show(animated: Bool = true, forView view: UIView, withinSuperview superview: UIView? = nil) { 143 | 144 | #if TARGET_APP_EXTENSIONS 145 | precondition(superview != nil, "The supplied superview parameter cannot be nil for app extensions.") 146 | 147 | let superview = superview! 148 | #else 149 | precondition(superview == nil || view.hasSuperview(superview!), "The supplied superview <\(superview!)> is not a direct nor an indirect superview of the supplied reference view <\(view)>. The superview passed to this method should be a direct or an indirect superview of the reference view. To display the tooltip within the main window, ignore the superview parameter.") 150 | 151 | let superview = superview ?? UIApplication.shared.windows.first! 152 | #endif 153 | 154 | let initialTransform = preferences.animating.showInitialTransform 155 | let finalTransform = preferences.animating.showFinalTransform 156 | let initialAlpha = preferences.animating.showInitialAlpha 157 | let damping = preferences.animating.springDamping 158 | let velocity = preferences.animating.springVelocity 159 | 160 | presentingView = view 161 | arrange(withinSuperview: superview) 162 | 163 | transform = initialTransform 164 | alpha = initialAlpha 165 | 166 | let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap)) 167 | addGestureRecognizer(tap) 168 | 169 | superview.addSubview(self) 170 | 171 | let animations : () -> () = { 172 | self.transform = finalTransform 173 | self.alpha = 1 174 | } 175 | 176 | if animated { 177 | UIView.animate(withDuration: preferences.animating.showDuration, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: velocity, options: [.curveEaseInOut], animations: animations, completion: nil) 178 | }else{ 179 | animations() 180 | } 181 | } 182 | 183 | /** 184 | Dismisses the EasyTipView 185 | 186 | - parameter completion: Completion block to be executed after the EasyTipView is dismissed. 187 | */ 188 | func dismiss(withCompletion completion: (() -> ())? = nil){ 189 | 190 | let damping = preferences.animating.springDamping 191 | let velocity = preferences.animating.springVelocity 192 | 193 | UIView.animate(withDuration: preferences.animating.dismissDuration, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: velocity, options: [.curveEaseInOut], animations: { 194 | self.transform = self.preferences.animating.dismissTransform 195 | self.alpha = self.preferences.animating.dismissFinalAlpha 196 | }) { (finished) -> Void in 197 | completion?() 198 | self.delegate?.easyTipViewDidDismiss(self) 199 | self.removeFromSuperview() 200 | self.transform = CGAffineTransform.identity 201 | } 202 | } 203 | } 204 | 205 | // MARK: - EasyTipView class implementation - 206 | 207 | open class EasyTipView: UIView { 208 | 209 | // MARK:- Nested types - 210 | 211 | public enum ArrowPosition { 212 | case any 213 | case top 214 | case bottom 215 | case right 216 | case left 217 | 218 | static let allValues = [top, bottom, right, left] 219 | } 220 | 221 | public struct Preferences { 222 | 223 | public struct Drawing { 224 | public var cornerRadius = CGFloat(5) 225 | public var arrowHeight = CGFloat(5) 226 | public var arrowWidth = CGFloat(10) 227 | public var foregroundColor = UIColor.white 228 | public var backgroundColor = UIColor.red 229 | public var arrowPosition = ArrowPosition.any 230 | public var textAlignment = NSTextAlignment.center 231 | public var borderWidth = CGFloat(0) 232 | public var borderColor = UIColor.clear 233 | public var font = UIFont.systemFont(ofSize: 15) 234 | public var shadowColor = UIColor.clear 235 | public var shadowOffset = CGSize(width: 0.0, height: 0.0) 236 | public var shadowRadius = CGFloat(0) 237 | public var shadowOpacity = CGFloat(0) 238 | } 239 | 240 | public struct Positioning { 241 | public var bubbleInsets = UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) 242 | public var contentInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) 243 | public var maxWidth = CGFloat(200) 244 | } 245 | 246 | public struct Animating { 247 | public var dismissTransform = CGAffineTransform(scaleX: 0.1, y: 0.1) 248 | public var showInitialTransform = CGAffineTransform(scaleX: 0, y: 0) 249 | public var showFinalTransform = CGAffineTransform.identity 250 | public var springDamping = CGFloat(0.7) 251 | public var springVelocity = CGFloat(0.7) 252 | public var showInitialAlpha = CGFloat(0) 253 | public var dismissFinalAlpha = CGFloat(0) 254 | public var showDuration = 0.7 255 | public var dismissDuration = 0.7 256 | public var dismissOnTap = true 257 | } 258 | 259 | public var drawing = Drawing() 260 | public var positioning = Positioning() 261 | public var animating = Animating() 262 | public var hasBorder : Bool { 263 | return drawing.borderWidth > 0 && drawing.borderColor != UIColor.clear 264 | } 265 | 266 | public var hasShadow : Bool { 267 | return drawing.shadowOpacity > 0 && drawing.shadowColor != UIColor.clear 268 | } 269 | 270 | public init() {} 271 | } 272 | 273 | public enum Content: CustomStringConvertible { 274 | 275 | case text(String) 276 | case attributedText(NSAttributedString) 277 | case view(UIView) 278 | 279 | public var description: String { 280 | switch self { 281 | case .text(let text): 282 | return "text : '\(text)'" 283 | case .attributedText(let text): 284 | return "attributed text : '\(text)'" 285 | case .view(let contentView): 286 | return "view : \(contentView)" 287 | } 288 | } 289 | } 290 | 291 | 292 | // MARK:- Variables - 293 | 294 | override open var backgroundColor: UIColor? { 295 | didSet { 296 | guard let color = backgroundColor 297 | , color != UIColor.clear else {return} 298 | 299 | preferences.drawing.backgroundColor = color 300 | backgroundColor = UIColor.clear 301 | } 302 | } 303 | 304 | override open var description: String { 305 | let type = "'\(String(reflecting: Swift.type(of: self)))'".components(separatedBy: ".").last! 306 | 307 | return "<< \(type) with \(content) >>" 308 | } 309 | 310 | fileprivate weak var presentingView: UIView? 311 | fileprivate weak var delegate: EasyTipViewDelegate? 312 | fileprivate var arrowTip = CGPoint.zero 313 | fileprivate(set) open var preferences: Preferences 314 | private let content: Content 315 | 316 | // MARK: - Lazy variables - 317 | 318 | fileprivate lazy var contentSize: CGSize = { 319 | 320 | [unowned self] in 321 | 322 | switch content { 323 | case .text(let text): 324 | #if swift(>=4.2) 325 | var attributes = [NSAttributedString.Key.font : self.preferences.drawing.font] 326 | #else 327 | var attributes = [NSAttributedStringKey.font : self.preferences.drawing.font] 328 | #endif 329 | 330 | var textSize = text.boundingRect(with: CGSize(width: self.preferences.positioning.maxWidth, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil).size 331 | 332 | textSize.width = ceil(textSize.width) 333 | textSize.height = ceil(textSize.height) 334 | 335 | if textSize.width < self.preferences.drawing.arrowWidth { 336 | textSize.width = self.preferences.drawing.arrowWidth 337 | } 338 | 339 | return textSize 340 | 341 | case .attributedText(let text): 342 | var textSize = text.boundingRect(with: CGSize(width: self.preferences.positioning.maxWidth, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil).size 343 | 344 | textSize.width = ceil(textSize.width) 345 | textSize.height = ceil(textSize.height) 346 | 347 | if textSize.width < self.preferences.drawing.arrowWidth { 348 | textSize.width = self.preferences.drawing.arrowWidth 349 | } 350 | 351 | return textSize 352 | 353 | case .view(let contentView): 354 | return contentView.frame.size 355 | } 356 | }() 357 | 358 | fileprivate lazy var tipViewSize: CGSize = { 359 | 360 | [unowned self] in 361 | 362 | var tipViewSize = 363 | CGSize( 364 | width: self.contentSize.width + self.preferences.positioning.contentInsets.left + self.preferences.positioning.contentInsets.right + self.preferences.positioning.bubbleInsets.left + self.preferences.positioning.bubbleInsets.right, 365 | height: self.contentSize.height + self.preferences.positioning.contentInsets.top + self.preferences.positioning.contentInsets.bottom + self.preferences.positioning.bubbleInsets.top + self.preferences.positioning.bubbleInsets.bottom + self.preferences.drawing.arrowHeight) 366 | 367 | return tipViewSize 368 | }() 369 | 370 | // MARK: - Static variables - 371 | 372 | public static var globalPreferences = Preferences() 373 | 374 | // MARK:- Initializer - 375 | 376 | public convenience init (text: String, preferences: Preferences = EasyTipView.globalPreferences, delegate: EasyTipViewDelegate? = nil) { 377 | self.init(content: .text(text), preferences: preferences, delegate: delegate) 378 | 379 | self.isAccessibilityElement = true 380 | self.accessibilityTraits = UIAccessibilityTraits.staticText 381 | self.accessibilityLabel = text 382 | } 383 | 384 | public convenience init (contentView: UIView, preferences: Preferences = EasyTipView.globalPreferences, delegate: EasyTipViewDelegate? = nil) { 385 | self.init(content: .view(contentView), preferences: preferences, delegate: delegate) 386 | } 387 | 388 | public convenience init (text: NSAttributedString, preferences: Preferences = EasyTipView.globalPreferences, delegate: EasyTipViewDelegate? = nil) { 389 | self.init(content: .attributedText(text), preferences: preferences, delegate: delegate) 390 | } 391 | 392 | public init (content: Content, preferences: Preferences = EasyTipView.globalPreferences, delegate: EasyTipViewDelegate? = nil) { 393 | 394 | self.content = content 395 | self.preferences = preferences 396 | self.delegate = delegate 397 | 398 | super.init(frame: CGRect.zero) 399 | 400 | self.backgroundColor = UIColor.clear 401 | 402 | #if swift(>=4.2) 403 | let notificationName = UIDevice.orientationDidChangeNotification 404 | #else 405 | let notificationName = NSNotification.Name.UIDeviceOrientationDidChange 406 | #endif 407 | 408 | NotificationCenter.default.addObserver(self, selector: #selector(handleRotation), name: notificationName, object: nil) 409 | } 410 | 411 | deinit 412 | { 413 | NotificationCenter.default.removeObserver(self) 414 | } 415 | 416 | /** 417 | NSCoding not supported. Use init(text, preferences, delegate) instead! 418 | */ 419 | required public init?(coder aDecoder: NSCoder) { 420 | fatalError("NSCoding not supported. Use init(text, preferences, delegate) instead!") 421 | } 422 | 423 | // MARK: - Rotation support - 424 | 425 | @objc func handleRotation() { 426 | guard let sview = superview 427 | , presentingView != nil else { return } 428 | 429 | UIView.animate(withDuration: 0.3) { 430 | self.arrange(withinSuperview: sview) 431 | self.setNeedsDisplay() 432 | } 433 | } 434 | 435 | // MARK: - Private methods - 436 | 437 | fileprivate func computeFrame(arrowPosition position: ArrowPosition, refViewFrame: CGRect, superviewFrame: CGRect) -> CGRect { 438 | var xOrigin: CGFloat = 0 439 | var yOrigin: CGFloat = 0 440 | 441 | switch position { 442 | case .top, .any: 443 | xOrigin = refViewFrame.center.x - tipViewSize.width / 2 444 | yOrigin = refViewFrame.y + refViewFrame.height 445 | case .bottom: 446 | xOrigin = refViewFrame.center.x - tipViewSize.width / 2 447 | yOrigin = refViewFrame.y - tipViewSize.height 448 | case .right: 449 | xOrigin = refViewFrame.x - tipViewSize.width 450 | yOrigin = refViewFrame.center.y - tipViewSize.height / 2 451 | case .left: 452 | xOrigin = refViewFrame.x + refViewFrame.width 453 | yOrigin = refViewFrame.center.y - tipViewSize.height / 2 454 | } 455 | 456 | var frame = CGRect(x: xOrigin, y: yOrigin, width: tipViewSize.width, height: tipViewSize.height) 457 | adjustFrame(&frame, forSuperviewFrame: superviewFrame) 458 | return frame 459 | } 460 | 461 | fileprivate func adjustFrame(_ frame: inout CGRect, forSuperviewFrame superviewFrame: CGRect) { 462 | 463 | // adjust horizontally 464 | if frame.x < 0 { 465 | frame.x = 0 466 | } else if frame.maxX > superviewFrame.width { 467 | frame.x = superviewFrame.width - frame.width 468 | } 469 | 470 | //adjust vertically 471 | if frame.y < 0 { 472 | frame.y = 0 473 | } else if frame.maxY > superviewFrame.maxY { 474 | frame.y = superviewFrame.height - frame.height 475 | } 476 | } 477 | 478 | fileprivate func isFrameValid(_ frame: CGRect, forRefViewFrame: CGRect, withinSuperviewFrame: CGRect) -> Bool { 479 | return !frame.intersects(forRefViewFrame) 480 | } 481 | 482 | fileprivate func arrange(withinSuperview superview: UIView) { 483 | 484 | var position = preferences.drawing.arrowPosition 485 | 486 | let refViewFrame = presentingView!.convert(presentingView!.bounds, to: superview); 487 | 488 | let superviewFrame: CGRect 489 | if let scrollview = superview as? UIScrollView { 490 | superviewFrame = CGRect(origin: scrollview.frame.origin, size: scrollview.contentSize) 491 | } else { 492 | superviewFrame = superview.frame 493 | } 494 | 495 | var frame = computeFrame(arrowPosition: position, refViewFrame: refViewFrame, superviewFrame: superviewFrame) 496 | 497 | if !isFrameValid(frame, forRefViewFrame: refViewFrame, withinSuperviewFrame: superviewFrame) { 498 | for value in ArrowPosition.allValues where value != position { 499 | let newFrame = computeFrame(arrowPosition: value, refViewFrame: refViewFrame, superviewFrame: superviewFrame) 500 | if isFrameValid(newFrame, forRefViewFrame: refViewFrame, withinSuperviewFrame: superviewFrame) { 501 | 502 | if position != .any { 503 | print("[EasyTipView - Info] The arrow position you chose <\(position)> could not be applied. Instead, position <\(value)> has been applied! Please specify position <\(ArrowPosition.any)> if you want EasyTipView to choose a position for you.") 504 | } 505 | 506 | frame = newFrame 507 | position = value 508 | preferences.drawing.arrowPosition = value 509 | break 510 | } 511 | } 512 | } 513 | 514 | switch position { 515 | case .bottom, .top, .any: 516 | var arrowTipXOrigin: CGFloat 517 | if frame.width < refViewFrame.width { 518 | arrowTipXOrigin = tipViewSize.width / 2 519 | } else { 520 | arrowTipXOrigin = abs(frame.x - refViewFrame.x) + refViewFrame.width / 2 521 | } 522 | 523 | arrowTip = CGPoint(x: arrowTipXOrigin, y: position == .bottom ? tipViewSize.height - preferences.positioning.bubbleInsets.bottom : preferences.positioning.bubbleInsets.top) 524 | case .right, .left: 525 | var arrowTipYOrigin: CGFloat 526 | if frame.height < refViewFrame.height { 527 | arrowTipYOrigin = tipViewSize.height / 2 528 | } else { 529 | arrowTipYOrigin = abs(frame.y - refViewFrame.y) + refViewFrame.height / 2 530 | } 531 | 532 | arrowTip = CGPoint(x: preferences.drawing.arrowPosition == .left ? preferences.positioning.bubbleInsets.left : tipViewSize.width - preferences.positioning.bubbleInsets.right, y: arrowTipYOrigin) 533 | } 534 | 535 | if case .view(let contentView) = content { 536 | contentView.translatesAutoresizingMaskIntoConstraints = false 537 | contentView.frame = getContentRect(from: getBubbleFrame()) 538 | } 539 | 540 | self.frame = frame 541 | } 542 | 543 | // MARK:- Callbacks - 544 | 545 | @objc func handleTap() { 546 | self.delegate?.easyTipViewDidTap(self) 547 | guard preferences.animating.dismissOnTap else { return } 548 | dismiss() 549 | } 550 | 551 | // MARK:- Drawing - 552 | 553 | fileprivate func drawBubble(_ bubbleFrame: CGRect, arrowPosition: ArrowPosition, context: CGContext) { 554 | 555 | let arrowWidth = preferences.drawing.arrowWidth 556 | let arrowHeight = preferences.drawing.arrowHeight 557 | let cornerRadius = preferences.drawing.cornerRadius 558 | 559 | let contourPath = CGMutablePath() 560 | 561 | contourPath.move(to: CGPoint(x: arrowTip.x, y: arrowTip.y)) 562 | 563 | switch arrowPosition { 564 | case .bottom, .top, .any: 565 | 566 | contourPath.addLine(to: CGPoint(x: arrowTip.x - arrowWidth / 2, y: arrowTip.y + (arrowPosition == .bottom ? -1 : 1) * arrowHeight)) 567 | if arrowPosition == .bottom { 568 | drawBubbleBottomShape(bubbleFrame, cornerRadius: cornerRadius, path: contourPath) 569 | } else { 570 | drawBubbleTopShape(bubbleFrame, cornerRadius: cornerRadius, path: contourPath) 571 | } 572 | contourPath.addLine(to: CGPoint(x: arrowTip.x + arrowWidth / 2, y: arrowTip.y + (arrowPosition == .bottom ? -1 : 1) * arrowHeight)) 573 | 574 | case .right, .left: 575 | 576 | contourPath.addLine(to: CGPoint(x: arrowTip.x + (arrowPosition == .right ? -1 : 1) * arrowHeight, y: arrowTip.y - arrowWidth / 2)) 577 | 578 | if arrowPosition == .right { 579 | drawBubbleRightShape(bubbleFrame, cornerRadius: cornerRadius, path: contourPath) 580 | } else { 581 | drawBubbleLeftShape(bubbleFrame, cornerRadius: cornerRadius, path: contourPath) 582 | } 583 | 584 | contourPath.addLine(to: CGPoint(x: arrowTip.x + (arrowPosition == .right ? -1 : 1) * arrowHeight, y: arrowTip.y + arrowWidth / 2)) 585 | } 586 | 587 | contourPath.closeSubpath() 588 | context.addPath(contourPath) 589 | context.clip() 590 | 591 | paintBubble(context) 592 | 593 | if preferences.hasBorder { 594 | drawBorder(contourPath, context: context) 595 | } 596 | } 597 | 598 | fileprivate func drawBubbleBottomShape(_ frame: CGRect, cornerRadius: CGFloat, path: CGMutablePath) { 599 | 600 | path.addArc(tangent1End: CGPoint(x: frame.x, y: frame.y + frame.height), tangent2End: CGPoint(x: frame.x, y: frame.y), radius: cornerRadius) 601 | path.addArc(tangent1End: CGPoint(x: frame.x, y: frame.y), tangent2End: CGPoint(x: frame.x + frame.width, y: frame.y), radius: cornerRadius) 602 | path.addArc(tangent1End: CGPoint(x: frame.x + frame.width, y: frame.y), tangent2End: CGPoint(x: frame.x + frame.width, y: frame.y + frame.height), radius: cornerRadius) 603 | path.addArc(tangent1End: CGPoint(x: frame.x + frame.width, y: frame.y + frame.height), tangent2End: CGPoint(x: frame.x, y: frame.y + frame.height), radius: cornerRadius) 604 | } 605 | 606 | fileprivate func drawBubbleTopShape(_ frame: CGRect, cornerRadius: CGFloat, path: CGMutablePath) { 607 | 608 | path.addArc(tangent1End: CGPoint(x: frame.x, y: frame.y), tangent2End: CGPoint(x: frame.x, y: frame.y + frame.height), radius: cornerRadius) 609 | path.addArc(tangent1End: CGPoint(x: frame.x, y: frame.y + frame.height), tangent2End: CGPoint(x: frame.x + frame.width, y: frame.y + frame.height), radius: cornerRadius) 610 | path.addArc(tangent1End: CGPoint(x: frame.x + frame.width, y: frame.y + frame.height), tangent2End: CGPoint(x: frame.x + frame.width, y: frame.y), radius: cornerRadius) 611 | path.addArc(tangent1End: CGPoint(x: frame.x + frame.width, y: frame.y), tangent2End: CGPoint(x: frame.x, y: frame.y), radius: cornerRadius) 612 | } 613 | 614 | fileprivate func drawBubbleRightShape(_ frame: CGRect, cornerRadius: CGFloat, path: CGMutablePath) { 615 | 616 | path.addArc(tangent1End: CGPoint(x: frame.x + frame.width, y: frame.y), tangent2End: CGPoint(x: frame.x, y: frame.y), radius: cornerRadius) 617 | path.addArc(tangent1End: CGPoint(x: frame.x, y: frame.y), tangent2End: CGPoint(x: frame.x, y: frame.y + frame.height), radius: cornerRadius) 618 | path.addArc(tangent1End: CGPoint(x: frame.x, y: frame.y + frame.height), tangent2End: CGPoint(x: frame.x + frame.width, y: frame.y + frame.height), radius: cornerRadius) 619 | path.addArc(tangent1End: CGPoint(x: frame.x + frame.width, y: frame.y + frame.height), tangent2End: CGPoint(x: frame.x + frame.width, y: frame.height), radius: cornerRadius) 620 | 621 | } 622 | 623 | fileprivate func drawBubbleLeftShape(_ frame: CGRect, cornerRadius: CGFloat, path: CGMutablePath) { 624 | 625 | path.addArc(tangent1End: CGPoint(x: frame.x, y: frame.y), tangent2End: CGPoint(x: frame.x + frame.width, y: frame.y), radius: cornerRadius) 626 | path.addArc(tangent1End: CGPoint(x: frame.x + frame.width, y: frame.y), tangent2End: CGPoint(x: frame.x + frame.width, y: frame.y + frame.height), radius: cornerRadius) 627 | path.addArc(tangent1End: CGPoint(x: frame.x + frame.width, y: frame.y + frame.height), tangent2End: CGPoint(x: frame.x, y: frame.y + frame.height), radius: cornerRadius) 628 | path.addArc(tangent1End: CGPoint(x: frame.x, y: frame.y + frame.height), tangent2End: CGPoint(x: frame.x, y: frame.y), radius: cornerRadius) 629 | } 630 | 631 | fileprivate func paintBubble(_ context: CGContext) { 632 | context.setFillColor(preferences.drawing.backgroundColor.cgColor) 633 | context.fill(bounds) 634 | } 635 | 636 | fileprivate func drawBorder(_ borderPath: CGPath, context: CGContext) { 637 | context.addPath(borderPath) 638 | context.setStrokeColor(preferences.drawing.borderColor.cgColor) 639 | context.setLineWidth(preferences.drawing.borderWidth) 640 | context.strokePath() 641 | } 642 | 643 | fileprivate func drawText(_ bubbleFrame: CGRect, context : CGContext) { 644 | guard case .text(let text) = content else { return } 645 | let paragraphStyle = NSMutableParagraphStyle() 646 | paragraphStyle.alignment = preferences.drawing.textAlignment 647 | paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping 648 | 649 | 650 | let textRect = getContentRect(from: bubbleFrame) 651 | 652 | #if swift(>=4.2) 653 | let attributes = [NSAttributedString.Key.font : preferences.drawing.font, NSAttributedString.Key.foregroundColor : preferences.drawing.foregroundColor, NSAttributedString.Key.paragraphStyle : paragraphStyle] 654 | #else 655 | let attributes = [NSAttributedStringKey.font : preferences.drawing.font, NSAttributedStringKey.foregroundColor : preferences.drawing.foregroundColor, NSAttributedStringKey.paragraphStyle : paragraphStyle] 656 | #endif 657 | 658 | text.draw(in: textRect, withAttributes: attributes) 659 | } 660 | 661 | fileprivate func drawAttributedText(_ bubbleFrame: CGRect, context : CGContext) { 662 | guard 663 | case .attributedText(let text) = content 664 | else { 665 | return 666 | } 667 | 668 | let textRect = getContentRect(from: bubbleFrame) 669 | 670 | text.draw(with: textRect, options: .usesLineFragmentOrigin, context: .none) 671 | } 672 | 673 | fileprivate func drawShadow() { 674 | if preferences.hasShadow { 675 | self.layer.masksToBounds = false 676 | self.layer.shadowColor = preferences.drawing.shadowColor.cgColor 677 | self.layer.shadowOffset = preferences.drawing.shadowOffset 678 | self.layer.shadowRadius = preferences.drawing.shadowRadius 679 | self.layer.shadowOpacity = Float(preferences.drawing.shadowOpacity) 680 | } 681 | } 682 | 683 | override open func draw(_ rect: CGRect) { 684 | 685 | let bubbleFrame = getBubbleFrame() 686 | 687 | let context = UIGraphicsGetCurrentContext()! 688 | context.saveGState () 689 | 690 | drawBubble(bubbleFrame, arrowPosition: preferences.drawing.arrowPosition, context: context) 691 | 692 | switch content { 693 | case .text: 694 | drawText(bubbleFrame, context: context) 695 | case .attributedText: 696 | drawAttributedText(bubbleFrame, context: context) 697 | case .view (let view): 698 | addSubview(view) 699 | } 700 | 701 | drawShadow() 702 | context.restoreGState() 703 | } 704 | 705 | private func getBubbleFrame() -> CGRect { 706 | let arrowPosition = preferences.drawing.arrowPosition 707 | let bubbleWidth: CGFloat 708 | let bubbleHeight: CGFloat 709 | let bubbleXOrigin: CGFloat 710 | let bubbleYOrigin: CGFloat 711 | switch arrowPosition { 712 | case .bottom, .top, .any: 713 | 714 | bubbleWidth = tipViewSize.width - preferences.positioning.bubbleInsets.left - preferences.positioning.bubbleInsets.right 715 | bubbleHeight = tipViewSize.height - preferences.positioning.bubbleInsets.top - preferences.positioning.bubbleInsets.bottom - preferences.drawing.arrowHeight 716 | 717 | bubbleXOrigin = preferences.positioning.bubbleInsets.left 718 | bubbleYOrigin = arrowPosition == .bottom ? preferences.positioning.bubbleInsets.top : preferences.positioning.bubbleInsets.top + preferences.drawing.arrowHeight 719 | 720 | case .left, .right: 721 | 722 | bubbleWidth = tipViewSize.width - preferences.positioning.bubbleInsets.left - preferences.positioning.bubbleInsets.right - preferences.drawing.arrowHeight 723 | bubbleHeight = tipViewSize.height - preferences.positioning.bubbleInsets.top - preferences.positioning.bubbleInsets.left 724 | 725 | bubbleXOrigin = arrowPosition == .right ? preferences.positioning.bubbleInsets.left : preferences.positioning.bubbleInsets.left + preferences.drawing.arrowHeight 726 | bubbleYOrigin = preferences.positioning.bubbleInsets.top 727 | 728 | } 729 | return CGRect(x: bubbleXOrigin, y: bubbleYOrigin, width: bubbleWidth, height: bubbleHeight) 730 | } 731 | 732 | private func getContentRect(from bubbleFrame: CGRect) -> CGRect { 733 | return CGRect(x: bubbleFrame.origin.x + preferences.positioning.contentInsets.left, y: bubbleFrame.origin.y + preferences.positioning.contentInsets.top, width: contentSize.width, height: contentSize.height) 734 | } 735 | } 736 | #endif 737 | -------------------------------------------------------------------------------- /Sources/EasyTipView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/EasyTipView/UIKitExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKitExtensions.swift 3 | // EasyTipView 4 | // 5 | // Created by Teodor Patras on 29/06/16. 6 | // Copyright © 2016 teodorpatras. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import UIKit 11 | 12 | // MARK: - UIBarItem extension - 13 | 14 | extension UIBarItem { 15 | var view: UIView? { 16 | if let item = self as? UIBarButtonItem, let customView = item.customView { 17 | return customView 18 | } 19 | return self.value(forKey: "view") as? UIView 20 | } 21 | } 22 | 23 | // MARK:- UIView extension - 24 | 25 | extension UIView { 26 | 27 | func hasSuperview(_ superview: UIView) -> Bool{ 28 | return viewHasSuperview(self, superview: superview) 29 | } 30 | 31 | fileprivate func viewHasSuperview(_ view: UIView, superview: UIView) -> Bool { 32 | if let sview = view.superview { 33 | if sview === superview { 34 | return true 35 | } else{ 36 | return viewHasSuperview(sview, superview: superview) 37 | } 38 | } else{ 39 | return false 40 | } 41 | } 42 | } 43 | 44 | // MARK:- CGRect extension - 45 | 46 | extension CGRect { 47 | var x: CGFloat { 48 | get { 49 | return self.origin.x 50 | } 51 | set { 52 | self.origin.x = newValue 53 | } 54 | } 55 | 56 | var y: CGFloat { 57 | get { 58 | return self.origin.y 59 | } 60 | 61 | set { 62 | self.origin.y = newValue 63 | } 64 | } 65 | 66 | var center: CGPoint { 67 | return CGPoint(x: self.x + self.width / 2, y: self.y + self.height / 2) 68 | } 69 | } 70 | #endif 71 | -------------------------------------------------------------------------------- /Tests/EasyTipViewTests/Tests.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | import XCTest 4 | import EasyTipView 5 | 6 | class Tests: XCTestCase { 7 | 8 | var view : UIView! 9 | var superview : UIView! 10 | 11 | override func setUp() { 12 | super.setUp() 13 | 14 | superview = UIView(frame: CGRect(x: 0, y: 0, width: 600, height: 600)) 15 | view = UIView(frame: CGRect(x: 0, y: 500, width: 100, height: 100)) 16 | 17 | superview.addSubview(view) 18 | } 19 | 20 | func testViewPositionTop() { 21 | var preferences = EasyTipView.Preferences() 22 | preferences.drawing.arrowPosition = EasyTipView.ArrowPosition.top 23 | 24 | view.center = superview.center 25 | 26 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 27 | tipView.show(animated: false, forView: view, withinSuperview: superview) 28 | 29 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 30 | XCTAssert(tipView.preferences.drawing.arrowPosition == .top, "Position should be top") 31 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 32 | XCTAssert((tipView.frame).minY == (view.frame).maxY, "EasyTipView should be below the presenting view") 33 | } 34 | 35 | func testViewPositionBottom() { 36 | 37 | var preferences = EasyTipView.Preferences() 38 | preferences.drawing.arrowPosition = EasyTipView.ArrowPosition.bottom 39 | 40 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 41 | tipView.show(animated: false, forView: view, withinSuperview: superview) 42 | 43 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 44 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 45 | XCTAssert(tipView.preferences.drawing.arrowPosition == .bottom, "Position should be bottom") 46 | XCTAssert((tipView.frame).maxY == (view.frame).minY, "EasyTipView should be above the presenting view") 47 | } 48 | 49 | func testViewPositionLeft() { 50 | 51 | var preferences = EasyTipView.Preferences() 52 | preferences.drawing.arrowPosition = .left 53 | 54 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 55 | tipView.show(animated: false, forView: view, withinSuperview: superview) 56 | 57 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 58 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 59 | XCTAssert(tipView.preferences.drawing.arrowPosition == .left, "Position should be left") 60 | XCTAssert((tipView.frame).minX == (view.frame).maxX, "EasyTipView should be to the right of the presenting view") 61 | } 62 | 63 | func testViewPositionRight() { 64 | 65 | var preferences = EasyTipView.Preferences() 66 | preferences.drawing.arrowPosition = .right 67 | 68 | view.frame = CGRect(x: superview.frame.size.width - 100, y: 0, width: 100, height: 100) 69 | 70 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 71 | tipView.show(animated: false, forView: view, withinSuperview: superview) 72 | 73 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 74 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 75 | XCTAssert(tipView.preferences.drawing.arrowPosition == .right, "Position should be right") 76 | XCTAssert((tipView.frame).maxX == (view.frame).minX, "EasyTipView should be to the right of the presenting view") 77 | } 78 | 79 | func testViewPositionAny() { 80 | 81 | var preferences = EasyTipView.Preferences() 82 | preferences.drawing.arrowPosition = .any 83 | 84 | view.center = superview.center 85 | 86 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 87 | tipView.show(animated: false, forView: view, withinSuperview: superview) 88 | 89 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 90 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 91 | XCTAssert(tipView.preferences.drawing.arrowPosition == .any, "Position should be any") 92 | XCTAssert((tipView.frame).minY == (view.frame).maxY, "EasyTipView should be below the presenting view") 93 | } 94 | 95 | func testAutoPositionAdjustmentAny() { 96 | var preferences = EasyTipView.Preferences() 97 | preferences.drawing.arrowPosition = .any 98 | 99 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 100 | tipView.show(animated: false, forView: view, withinSuperview: superview) 101 | 102 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 103 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 104 | XCTAssert(tipView.preferences.drawing.arrowPosition == .bottom, "Position should be bottom") 105 | XCTAssert((tipView.frame).maxY == (view.frame).minY, "EasyTipView should be above the presenting view") 106 | } 107 | 108 | func testAutoPositionAdjustmentTop() { 109 | var preferences = EasyTipView.Preferences() 110 | preferences.drawing.arrowPosition = EasyTipView.ArrowPosition.top 111 | 112 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 113 | tipView.show(animated: false, forView: view, withinSuperview: superview) 114 | 115 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 116 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 117 | XCTAssert(tipView.preferences.drawing.arrowPosition == .bottom, "Position should be bottom") 118 | XCTAssert((tipView.frame).maxY == (view.frame).minY, "EasyTipView should be above the presenting view") 119 | } 120 | 121 | func testAutoPositionAdjustmentRight() { 122 | var preferences = EasyTipView.Preferences() 123 | preferences.drawing.arrowPosition = .right 124 | 125 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 126 | tipView.show(animated: false, forView: view, withinSuperview: superview) 127 | 128 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 129 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 130 | XCTAssert(tipView.preferences.drawing.arrowPosition == .bottom, "Position should be bottom") 131 | XCTAssert((tipView.frame).maxY == (view.frame).minY, "EasyTipView should be above the presenting view") 132 | } 133 | 134 | func testAutoPositionAdjustmentLeft() { 135 | var preferences = EasyTipView.Preferences() 136 | preferences.drawing.arrowPosition = .left 137 | 138 | view.frame = CGRect(x: superview.frame.size.width - 100, y: superview.frame.size.height - 100, width: 100, height: 100) 139 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 140 | tipView.show(animated: false, forView: view, withinSuperview: superview) 141 | 142 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 143 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 144 | XCTAssert(tipView.preferences.drawing.arrowPosition == .bottom, "Position should be bottom") 145 | XCTAssert((tipView.frame).maxY == (view.frame).minY, "EasyTipView should be above the presenting view") 146 | } 147 | 148 | func testAutoPositionAdjustmentBottom() { 149 | 150 | var frame = view.frame 151 | frame.origin.y = 0 152 | 153 | view.frame = frame 154 | 155 | var preferences = EasyTipView.Preferences() 156 | preferences.drawing.arrowPosition = EasyTipView.ArrowPosition.bottom 157 | 158 | let tipView = EasyTipView(text: "Some text", preferences: preferences) 159 | tipView.show(animated: false, forView: view, withinSuperview: superview) 160 | 161 | XCTAssert(tipView.superview === superview, "EasyTipView should be present") 162 | XCTAssert(tipView.alpha == 1, "EasyTipView should be visible") 163 | XCTAssert(tipView.preferences.drawing.arrowPosition == .top, "Position should be bottom") 164 | XCTAssert((tipView.frame).minY == (view.frame).maxY, "EasyTipView should be below the presenting view") 165 | } 166 | } 167 | #endif 168 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /assets/animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/assets/animation.gif -------------------------------------------------------------------------------- /assets/easytipview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/assets/easytipview.gif -------------------------------------------------------------------------------- /assets/easytipview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/assets/easytipview.png -------------------------------------------------------------------------------- /assets/static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teodorpatras/EasyTipView/8a9133085074c41119516a22b4223f79b8698b40/assets/static.png --------------------------------------------------------------------------------