├── LICENSE ├── PointerInteraction.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── Tim.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── PointerInteraction ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── sunset thumbnail.imageset │ │ ├── Contents.json │ │ └── sunset-5004905_1920.jpg ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── MiscViewController.swift ├── SVG Support │ ├── SVGStrings.swift │ └── SVGtoBezierPath.swift ├── SceneDelegate.swift ├── UIButtonViewController.swift ├── UIViewViewController+ContextMenu.swift └── UIViewViewController.swift └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tim Roesner 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 | -------------------------------------------------------------------------------- /PointerInteraction.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2291B5BE2443AD08001ED764 /* SVGtoBezierPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2291B5BD2443AD08001ED764 /* SVGtoBezierPath.swift */; }; 11 | 2291B5C02443AD70001ED764 /* SVGStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2291B5BF2443AD70001ED764 /* SVGStrings.swift */; }; 12 | 2291B5C22443BEAE001ED764 /* UIViewViewController+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2291B5C12443BEAE001ED764 /* UIViewViewController+ContextMenu.swift */; }; 13 | 22D63FFA2442BDB500C44CD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22D63FF92442BDB500C44CD1 /* AppDelegate.swift */; }; 14 | 22D63FFC2442BDB500C44CD1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22D63FFB2442BDB500C44CD1 /* SceneDelegate.swift */; }; 15 | 22D63FFE2442BDB500C44CD1 /* UIButtonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22D63FFD2442BDB500C44CD1 /* UIButtonViewController.swift */; }; 16 | 22D640002442BDB500C44CD1 /* UIViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22D63FFF2442BDB500C44CD1 /* UIViewViewController.swift */; }; 17 | 22D640032442BDB500C44CD1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 22D640012442BDB500C44CD1 /* Main.storyboard */; }; 18 | 22D640052442BDB800C44CD1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 22D640042442BDB800C44CD1 /* Assets.xcassets */; }; 19 | 22D640082442BDB800C44CD1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 22D640062442BDB800C44CD1 /* LaunchScreen.storyboard */; }; 20 | 22D640102442C38400C44CD1 /* MiscViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22D6400F2442C38400C44CD1 /* MiscViewController.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 2291B5BD2443AD08001ED764 /* SVGtoBezierPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SVGtoBezierPath.swift; sourceTree = ""; }; 25 | 2291B5BF2443AD70001ED764 /* SVGStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SVGStrings.swift; sourceTree = ""; }; 26 | 2291B5C12443BEAE001ED764 /* UIViewViewController+ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewViewController+ContextMenu.swift"; sourceTree = ""; }; 27 | 22D63FF62442BDB500C44CD1 /* PointerInteraction.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PointerInteraction.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 22D63FF92442BDB500C44CD1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 29 | 22D63FFB2442BDB500C44CD1 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 30 | 22D63FFD2442BDB500C44CD1 /* UIButtonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButtonViewController.swift; sourceTree = ""; }; 31 | 22D63FFF2442BDB500C44CD1 /* UIViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewViewController.swift; sourceTree = ""; }; 32 | 22D640022442BDB500C44CD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 33 | 22D640042442BDB800C44CD1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 34 | 22D640072442BDB800C44CD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 35 | 22D640092442BDB800C44CD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 22D6400F2442C38400C44CD1 /* MiscViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiscViewController.swift; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 22D63FF32442BDB500C44CD1 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 2291B5C32443C2B7001ED764 /* SVG Support */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 2291B5BD2443AD08001ED764 /* SVGtoBezierPath.swift */, 54 | 2291B5BF2443AD70001ED764 /* SVGStrings.swift */, 55 | ); 56 | path = "SVG Support"; 57 | sourceTree = ""; 58 | }; 59 | 22D63FED2442BDB500C44CD1 = { 60 | isa = PBXGroup; 61 | children = ( 62 | 22D63FF82442BDB500C44CD1 /* PointerInteraction */, 63 | 22D63FF72442BDB500C44CD1 /* Products */, 64 | ); 65 | sourceTree = ""; 66 | }; 67 | 22D63FF72442BDB500C44CD1 /* Products */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 22D63FF62442BDB500C44CD1 /* PointerInteraction.app */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | 22D63FF82442BDB500C44CD1 /* PointerInteraction */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 22D63FF92442BDB500C44CD1 /* AppDelegate.swift */, 79 | 22D63FFB2442BDB500C44CD1 /* SceneDelegate.swift */, 80 | 22D63FFD2442BDB500C44CD1 /* UIButtonViewController.swift */, 81 | 22D63FFF2442BDB500C44CD1 /* UIViewViewController.swift */, 82 | 2291B5C12443BEAE001ED764 /* UIViewViewController+ContextMenu.swift */, 83 | 22D6400F2442C38400C44CD1 /* MiscViewController.swift */, 84 | 2291B5C32443C2B7001ED764 /* SVG Support */, 85 | 22D640012442BDB500C44CD1 /* Main.storyboard */, 86 | 22D640042442BDB800C44CD1 /* Assets.xcassets */, 87 | 22D640062442BDB800C44CD1 /* LaunchScreen.storyboard */, 88 | 22D640092442BDB800C44CD1 /* Info.plist */, 89 | ); 90 | path = PointerInteraction; 91 | sourceTree = ""; 92 | }; 93 | /* End PBXGroup section */ 94 | 95 | /* Begin PBXNativeTarget section */ 96 | 22D63FF52442BDB500C44CD1 /* PointerInteraction */ = { 97 | isa = PBXNativeTarget; 98 | buildConfigurationList = 22D6400C2442BDB800C44CD1 /* Build configuration list for PBXNativeTarget "PointerInteraction" */; 99 | buildPhases = ( 100 | 22D63FF22442BDB500C44CD1 /* Sources */, 101 | 22D63FF32442BDB500C44CD1 /* Frameworks */, 102 | 22D63FF42442BDB500C44CD1 /* Resources */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = PointerInteraction; 109 | productName = PointerInteraction; 110 | productReference = 22D63FF62442BDB500C44CD1 /* PointerInteraction.app */; 111 | productType = "com.apple.product-type.application"; 112 | }; 113 | /* End PBXNativeTarget section */ 114 | 115 | /* Begin PBXProject section */ 116 | 22D63FEE2442BDB500C44CD1 /* Project object */ = { 117 | isa = PBXProject; 118 | attributes = { 119 | LastSwiftUpdateCheck = 1140; 120 | LastUpgradeCheck = 1140; 121 | ORGANIZATIONNAME = "Tim Roesner"; 122 | TargetAttributes = { 123 | 22D63FF52442BDB500C44CD1 = { 124 | CreatedOnToolsVersion = 11.4; 125 | }; 126 | }; 127 | }; 128 | buildConfigurationList = 22D63FF12442BDB500C44CD1 /* Build configuration list for PBXProject "PointerInteraction" */; 129 | compatibilityVersion = "Xcode 9.3"; 130 | developmentRegion = en; 131 | hasScannedForEncodings = 0; 132 | knownRegions = ( 133 | en, 134 | Base, 135 | ); 136 | mainGroup = 22D63FED2442BDB500C44CD1; 137 | productRefGroup = 22D63FF72442BDB500C44CD1 /* Products */; 138 | projectDirPath = ""; 139 | projectRoot = ""; 140 | targets = ( 141 | 22D63FF52442BDB500C44CD1 /* PointerInteraction */, 142 | ); 143 | }; 144 | /* End PBXProject section */ 145 | 146 | /* Begin PBXResourcesBuildPhase section */ 147 | 22D63FF42442BDB500C44CD1 /* Resources */ = { 148 | isa = PBXResourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | 22D640082442BDB800C44CD1 /* LaunchScreen.storyboard in Resources */, 152 | 22D640052442BDB800C44CD1 /* Assets.xcassets in Resources */, 153 | 22D640032442BDB500C44CD1 /* Main.storyboard in Resources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXResourcesBuildPhase section */ 158 | 159 | /* Begin PBXSourcesBuildPhase section */ 160 | 22D63FF22442BDB500C44CD1 /* Sources */ = { 161 | isa = PBXSourcesBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | 2291B5C02443AD70001ED764 /* SVGStrings.swift in Sources */, 165 | 22D640002442BDB500C44CD1 /* UIViewViewController.swift in Sources */, 166 | 22D63FFE2442BDB500C44CD1 /* UIButtonViewController.swift in Sources */, 167 | 22D63FFA2442BDB500C44CD1 /* AppDelegate.swift in Sources */, 168 | 2291B5BE2443AD08001ED764 /* SVGtoBezierPath.swift in Sources */, 169 | 22D63FFC2442BDB500C44CD1 /* SceneDelegate.swift in Sources */, 170 | 2291B5C22443BEAE001ED764 /* UIViewViewController+ContextMenu.swift in Sources */, 171 | 22D640102442C38400C44CD1 /* MiscViewController.swift in Sources */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXSourcesBuildPhase section */ 176 | 177 | /* Begin PBXVariantGroup section */ 178 | 22D640012442BDB500C44CD1 /* Main.storyboard */ = { 179 | isa = PBXVariantGroup; 180 | children = ( 181 | 22D640022442BDB500C44CD1 /* Base */, 182 | ); 183 | name = Main.storyboard; 184 | sourceTree = ""; 185 | }; 186 | 22D640062442BDB800C44CD1 /* LaunchScreen.storyboard */ = { 187 | isa = PBXVariantGroup; 188 | children = ( 189 | 22D640072442BDB800C44CD1 /* Base */, 190 | ); 191 | name = LaunchScreen.storyboard; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXVariantGroup section */ 195 | 196 | /* Begin XCBuildConfiguration section */ 197 | 22D6400A2442BDB800C44CD1 /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | ALWAYS_SEARCH_USER_PATHS = NO; 201 | CLANG_ANALYZER_NONNULL = YES; 202 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 204 | CLANG_CXX_LIBRARY = "libc++"; 205 | CLANG_ENABLE_MODULES = YES; 206 | CLANG_ENABLE_OBJC_ARC = YES; 207 | CLANG_ENABLE_OBJC_WEAK = YES; 208 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 209 | CLANG_WARN_BOOL_CONVERSION = YES; 210 | CLANG_WARN_COMMA = YES; 211 | CLANG_WARN_CONSTANT_CONVERSION = YES; 212 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 213 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 214 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 215 | CLANG_WARN_EMPTY_BODY = YES; 216 | CLANG_WARN_ENUM_CONVERSION = YES; 217 | CLANG_WARN_INFINITE_RECURSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 220 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 221 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 222 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 223 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 224 | CLANG_WARN_STRICT_PROTOTYPES = YES; 225 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 226 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 227 | CLANG_WARN_UNREACHABLE_CODE = YES; 228 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 229 | COPY_PHASE_STRIP = NO; 230 | DEBUG_INFORMATION_FORMAT = dwarf; 231 | ENABLE_STRICT_OBJC_MSGSEND = YES; 232 | ENABLE_TESTABILITY = YES; 233 | GCC_C_LANGUAGE_STANDARD = gnu11; 234 | GCC_DYNAMIC_NO_PIC = NO; 235 | GCC_NO_COMMON_BLOCKS = YES; 236 | GCC_OPTIMIZATION_LEVEL = 0; 237 | GCC_PREPROCESSOR_DEFINITIONS = ( 238 | "DEBUG=1", 239 | "$(inherited)", 240 | ); 241 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 242 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 243 | GCC_WARN_UNDECLARED_SELECTOR = YES; 244 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 245 | GCC_WARN_UNUSED_FUNCTION = YES; 246 | GCC_WARN_UNUSED_VARIABLE = YES; 247 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 248 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 249 | MTL_FAST_MATH = YES; 250 | ONLY_ACTIVE_ARCH = YES; 251 | SDKROOT = iphoneos; 252 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 253 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 254 | }; 255 | name = Debug; 256 | }; 257 | 22D6400B2442BDB800C44CD1 /* Release */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | ALWAYS_SEARCH_USER_PATHS = NO; 261 | CLANG_ANALYZER_NONNULL = YES; 262 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 263 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 264 | CLANG_CXX_LIBRARY = "libc++"; 265 | CLANG_ENABLE_MODULES = YES; 266 | CLANG_ENABLE_OBJC_ARC = YES; 267 | CLANG_ENABLE_OBJC_WEAK = YES; 268 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_COMMA = YES; 271 | CLANG_WARN_CONSTANT_CONVERSION = YES; 272 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 274 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 275 | CLANG_WARN_EMPTY_BODY = YES; 276 | CLANG_WARN_ENUM_CONVERSION = YES; 277 | CLANG_WARN_INFINITE_RECURSION = YES; 278 | CLANG_WARN_INT_CONVERSION = YES; 279 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 281 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 283 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 284 | CLANG_WARN_STRICT_PROTOTYPES = YES; 285 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 286 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 287 | CLANG_WARN_UNREACHABLE_CODE = YES; 288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 289 | COPY_PHASE_STRIP = NO; 290 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 291 | ENABLE_NS_ASSERTIONS = NO; 292 | ENABLE_STRICT_OBJC_MSGSEND = YES; 293 | GCC_C_LANGUAGE_STANDARD = gnu11; 294 | GCC_NO_COMMON_BLOCKS = YES; 295 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 296 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 297 | GCC_WARN_UNDECLARED_SELECTOR = YES; 298 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 299 | GCC_WARN_UNUSED_FUNCTION = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 302 | MTL_ENABLE_DEBUG_INFO = NO; 303 | MTL_FAST_MATH = YES; 304 | SDKROOT = iphoneos; 305 | SWIFT_COMPILATION_MODE = wholemodule; 306 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 307 | VALIDATE_PRODUCT = YES; 308 | }; 309 | name = Release; 310 | }; 311 | 22D6400D2442BDB800C44CD1 /* Debug */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 315 | CODE_SIGN_STYLE = Automatic; 316 | DEVELOPMENT_TEAM = 7BT7DC73YL; 317 | INFOPLIST_FILE = PointerInteraction/Info.plist; 318 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 319 | LD_RUNPATH_SEARCH_PATHS = ( 320 | "$(inherited)", 321 | "@executable_path/Frameworks", 322 | ); 323 | PRODUCT_BUNDLE_IDENTIFIER = com.timroesner.PointerInteraction; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | SWIFT_VERSION = 5.0; 326 | TARGETED_DEVICE_FAMILY = "1,2"; 327 | }; 328 | name = Debug; 329 | }; 330 | 22D6400E2442BDB800C44CD1 /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 334 | CODE_SIGN_STYLE = Automatic; 335 | DEVELOPMENT_TEAM = 7BT7DC73YL; 336 | INFOPLIST_FILE = PointerInteraction/Info.plist; 337 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 338 | LD_RUNPATH_SEARCH_PATHS = ( 339 | "$(inherited)", 340 | "@executable_path/Frameworks", 341 | ); 342 | PRODUCT_BUNDLE_IDENTIFIER = com.timroesner.PointerInteraction; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | SWIFT_VERSION = 5.0; 345 | TARGETED_DEVICE_FAMILY = "1,2"; 346 | }; 347 | name = Release; 348 | }; 349 | /* End XCBuildConfiguration section */ 350 | 351 | /* Begin XCConfigurationList section */ 352 | 22D63FF12442BDB500C44CD1 /* Build configuration list for PBXProject "PointerInteraction" */ = { 353 | isa = XCConfigurationList; 354 | buildConfigurations = ( 355 | 22D6400A2442BDB800C44CD1 /* Debug */, 356 | 22D6400B2442BDB800C44CD1 /* Release */, 357 | ); 358 | defaultConfigurationIsVisible = 0; 359 | defaultConfigurationName = Release; 360 | }; 361 | 22D6400C2442BDB800C44CD1 /* Build configuration list for PBXNativeTarget "PointerInteraction" */ = { 362 | isa = XCConfigurationList; 363 | buildConfigurations = ( 364 | 22D6400D2442BDB800C44CD1 /* Debug */, 365 | 22D6400E2442BDB800C44CD1 /* Release */, 366 | ); 367 | defaultConfigurationIsVisible = 0; 368 | defaultConfigurationName = Release; 369 | }; 370 | /* End XCConfigurationList section */ 371 | }; 372 | rootObject = 22D63FEE2442BDB500C44CD1 /* Project object */; 373 | } 374 | -------------------------------------------------------------------------------- /PointerInteraction.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PointerInteraction.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PointerInteraction.xcodeproj/xcuserdata/Tim.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PointerInteraction.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PointerInteraction/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PointerInteraction 4 | // 5 | // Created by Tim Roesner on 4/11/20. 6 | // Copyright © 2020 Tim Roesner. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /PointerInteraction/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /PointerInteraction/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PointerInteraction/Assets.xcassets/sunset thumbnail.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "sunset-5004905_1920.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PointerInteraction/Assets.xcassets/sunset thumbnail.imageset/sunset-5004905_1920.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timroesner/Pointer-Interactions/218e6eb04d4caf5e6a8ea7747c030cea1766cec3/PointerInteraction/Assets.xcassets/sunset thumbnail.imageset/sunset-5004905_1920.jpg -------------------------------------------------------------------------------- /PointerInteraction/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PointerInteraction/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 | 30 | 42 | 54 | 63 | 72 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 128 | 134 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 200 | 205 | 210 | 215 | 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 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 301 | 302 | 303 | 304 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | -------------------------------------------------------------------------------- /PointerInteraction/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UIStatusBarTintParameters 51 | 52 | UINavigationBar 53 | 54 | Style 55 | UIBarStyleDefault 56 | Translucent 57 | 58 | 59 | 60 | UISupportedInterfaceOrientations 61 | 62 | UIInterfaceOrientationPortrait 63 | UIInterfaceOrientationLandscapeLeft 64 | UIInterfaceOrientationLandscapeRight 65 | 66 | UISupportedInterfaceOrientations~ipad 67 | 68 | UIInterfaceOrientationPortrait 69 | UIInterfaceOrientationPortraitUpsideDown 70 | UIInterfaceOrientationLandscapeLeft 71 | UIInterfaceOrientationLandscapeRight 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /PointerInteraction/MiscViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MiscViewController.swift 3 | // PointerInteraction 4 | // 5 | // Created by Tim Roesner on 4/11/20. 6 | // Copyright © 2020 Tim Roesner. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MiscViewController: UIViewController { 12 | @IBOutlet weak var hoverGestureView: UIView! 13 | @IBOutlet weak var hoverLabel: UILabel! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | hoverLabel.alpha = 0.0 18 | 19 | let hoverGesture = UIHoverGestureRecognizer(target: self, action: #selector(handleHover)) 20 | hoverGestureView.addGestureRecognizer(hoverGesture) 21 | } 22 | 23 | @objc private func handleHover(_ gesture: UIHoverGestureRecognizer) { 24 | guard gesture.state == .began || gesture.state == .ended else { return } 25 | let newAlpha: CGFloat = gesture.state == .began ? 1.0 : 0.0 26 | 27 | UIView.animate(withDuration: 0.5) { [weak self] in 28 | self?.hoverLabel.alpha = newAlpha 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PointerInteraction/SVG Support/SVGStrings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGStrings.swift 3 | // PointerInteraction 4 | // 5 | // Created by Tim Roesner on 4/12/20. 6 | // Copyright © 2020 Tim Roesner. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | static let contextIcon: String = "M1.916621,2.20588235 L29.653528,2.20588235 C30.2789041,2.20588235 30.8,1.72380745 30.8,1.09564109 C30.8,0.4820749 30.2789041,0 29.653528,0 L1.916621,0 C1.29130592,0 0.8,0.4820749 0.8,1.09564109 C0.8,1.72380745 1.29130592,2.20588235 1.916621,2.20588235 Z M1.916621,9.70588235 L29.653528,9.70588235 C30.2789041,9.70588235 30.8,9.22378407 30.8,8.59562623 C30.8,7.98209828 30.2789041,7.5 29.653528,7.5 L1.916621,7.5 C1.29130592,7.5 0.8,7.98209828 0.8,8.59562623 C0.8,9.22378407 1.29130592,9.70588235 1.916621,9.70588235 Z M1.916621,17.6470588 L29.653528,17.6470588 C30.2789041,17.6470588 30.8,17.1503838 30.8,16.5368176 C30.8,15.9232514 30.2789041,15.4411765 29.653528,15.4411765 L1.916621,15.4411765 C1.29130592,15.4411765 0.8,15.9232514 0.8,16.5368176 C0.8,17.1503838 1.29130592,17.6470588 1.916621,17.6470588 Z M1.916621,25.5882353 L29.653528,25.5882353 C30.2789041,25.5882353 30.8,25.1061572 30.8,24.4926004 C30.8,23.8790419 30.2789041,23.3823529 29.653528,23.3823529 L1.916621,23.3823529 C1.29130592,23.3823529 0.8,23.8790419 0.8,24.4926004 C0.8,25.1061572 1.29130592,25.5882353 1.916621,25.5882353 Z" 13 | static let paperplaneIcon: String = "M14.9263942,24.822524 C15.7714904,24.822524 16.3700962,24.0948077 16.804375,22.9680048 L24.4925481,2.88509615 C24.7038462,2.34516827 24.8211538,1.86391827 24.8211538,1.46485577 C24.8211538,0.701899038 24.3516827,0.232403846 23.5887019,0.232403846 C23.1896635,0.232403846 22.7084135,0.349783654 22.1685096,0.561057692 L1.97987981,8.29608173 C0.993942308,8.67168269 0.230995192,9.2703125 0.230995192,10.1271394 C0.230995192,11.2069952 1.05262019,11.5708654 2.17942308,11.91125 L8.51769231,13.8362019 C9.26889423,14.0709615 9.67971154,14.047476 10.1961538,13.5779808 L23.0605769,1.54699519 C23.2129808,1.40615385 23.3891827,1.42963942 23.5182692,1.53526442 C23.6355769,1.65264423 23.6473558,1.82870192 23.5064904,1.98129808 L11.5107692,14.9043269 C11.0647356,15.3855529 11.0295192,15.7846394 11.252524,16.5710337 L13.1187981,22.7684615 C13.4709375,23.9539423 13.8347837,24.822524 14.9263942,24.822524 Z" 14 | } 15 | -------------------------------------------------------------------------------- /PointerInteraction/SVG Support/SVGtoBezierPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGPath.swift 3 | // SVGPath 4 | // 5 | // Created by Tim Wood on 1/21/15. 6 | // Copyright (c) 2015 Tim Wood. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreGraphics 11 | 12 | // MARK: UIBezierPath 13 | 14 | public extension UIBezierPath { 15 | convenience init (svgPath: String, offset: CGFloat = 0) { 16 | self.init() 17 | applyCommands(from: SVGPath(svgPath), offset: offset) 18 | } 19 | } 20 | 21 | private extension UIBezierPath { 22 | func applyCommands(from svgPath: SVGPath, offset: CGFloat) { 23 | for command in svgPath.commands { 24 | let point = CGPoint(x: command.point.x - offset, y: command.point.y - offset) 25 | let controlPoint1 = CGPoint(x: command.control1.x - offset, y: command.control1.y - offset) 26 | let controlPoint2 = CGPoint(x: command.control2.x - offset, y: command.control2.y - offset) 27 | 28 | switch command.type { 29 | case .move: move(to: point) 30 | case .line: addLine(to: point) 31 | case .quadCurve: addQuadCurve(to: point, controlPoint: controlPoint1) 32 | case .cubeCurve: addCurve(to: point, controlPoint1: controlPoint1, controlPoint2: controlPoint2) 33 | case .close: close() 34 | } 35 | } 36 | } 37 | } 38 | 39 | // MARK: Enums 40 | fileprivate enum Coordinates { 41 | case absolute 42 | case relative 43 | } 44 | 45 | // MARK: Class 46 | public class SVGPath { 47 | public var commands: [SVGCommand] = [] 48 | private var builder: SVGCommandBuilder = move 49 | private var coords: Coordinates = .absolute 50 | private var increment: Int = 2 51 | private var numbers = "" 52 | 53 | public init (_ string: String) { 54 | for char in string { 55 | switch char { 56 | case "M": use(.absolute, 2, move) 57 | case "m": use(.relative, 2, move) 58 | case "L": use(.absolute, 2, line) 59 | case "l": use(.relative, 2, line) 60 | case "V": use(.absolute, 1, lineVertical) 61 | case "v": use(.relative, 1, lineVertical) 62 | case "H": use(.absolute, 1, lineHorizontal) 63 | case "h": use(.relative, 1, lineHorizontal) 64 | case "Q": use(.absolute, 4, quadBroken) 65 | case "q": use(.relative, 4, quadBroken) 66 | case "T": use(.absolute, 2, quadSmooth) 67 | case "t": use(.relative, 2, quadSmooth) 68 | case "C": use(.absolute, 6, cubeBroken) 69 | case "c": use(.relative, 6, cubeBroken) 70 | case "S": use(.absolute, 4, cubeSmooth) 71 | case "s": use(.relative, 4, cubeSmooth) 72 | case "Z": use(.absolute, 1, close) 73 | case "z": use(.absolute, 1, close) 74 | default: numbers.append(char) 75 | } 76 | } 77 | finishLastCommand() 78 | } 79 | 80 | private func use (_ coords: Coordinates, _ increment: Int, _ builder: @escaping SVGCommandBuilder) { 81 | finishLastCommand() 82 | self.builder = builder 83 | self.coords = coords 84 | self.increment = increment 85 | } 86 | 87 | private func finishLastCommand () { 88 | for command in take(SVGPath.parseNumbers(numbers), increment: increment, coords: coords, last: commands.last, callback: builder) { 89 | commands.append(coords == .relative ? command.relative(to: commands.last) : command) 90 | } 91 | numbers = "" 92 | } 93 | } 94 | 95 | // MARK: Numbers 96 | private let numberSet = CharacterSet(charactersIn: "-.0123456789eE") 97 | private let locale = Locale(identifier: "en_US") 98 | 99 | 100 | public extension SVGPath { 101 | class func parseNumbers (_ numbers: String) -> [CGFloat] { 102 | var all:[String] = [] 103 | var curr = "" 104 | var last = "" 105 | 106 | for char in numbers.unicodeScalars { 107 | let next = String(char) 108 | if next == "-" && last != "" && last != "E" && last != "e" { 109 | if curr.utf16.count > 0 { 110 | all.append(curr) 111 | } 112 | curr = next 113 | } else if numberSet.contains(UnicodeScalar(char.value)!) { 114 | curr += next 115 | } else if curr.utf16.count > 0 { 116 | all.append(curr) 117 | curr = "" 118 | } 119 | last = next 120 | } 121 | 122 | all.append(curr) 123 | 124 | return all.map { CGFloat(truncating: NSDecimalNumber(string: $0, locale: locale)) } 125 | } 126 | } 127 | 128 | // MARK: Commands 129 | public struct SVGCommand { 130 | public var point:CGPoint 131 | public var control1:CGPoint 132 | public var control2:CGPoint 133 | public var type:Kind 134 | 135 | public enum Kind { 136 | case move 137 | case line 138 | case cubeCurve 139 | case quadCurve 140 | case close 141 | } 142 | 143 | public init () { 144 | let point = CGPoint() 145 | self.init(point, point, point, type: .close) 146 | } 147 | 148 | public init (_ x: CGFloat, _ y: CGFloat, type: Kind) { 149 | let point = CGPoint(x: x, y: y) 150 | self.init(point, point, point, type: type) 151 | } 152 | 153 | public init (_ cx: CGFloat, _ cy: CGFloat, _ x: CGFloat, _ y: CGFloat) { 154 | let control = CGPoint(x: cx, y: cy) 155 | self.init(control, control, CGPoint(x: x, y: y), type: .quadCurve) 156 | } 157 | 158 | public init (_ cx1: CGFloat, _ cy1: CGFloat, _ cx2: CGFloat, _ cy2: CGFloat, _ x: CGFloat, _ y: CGFloat) { 159 | self.init(CGPoint(x: cx1, y: cy1), CGPoint(x: cx2, y: cy2), CGPoint(x: x, y: y), type: .cubeCurve) 160 | } 161 | 162 | public init (_ control1: CGPoint, _ control2: CGPoint, _ point: CGPoint, type: Kind) { 163 | self.point = point 164 | self.control1 = control1 165 | self.control2 = control2 166 | self.type = type 167 | } 168 | 169 | fileprivate func relative (to other:SVGCommand?) -> SVGCommand { 170 | if let otherPoint = other?.point { 171 | return SVGCommand(control1 + otherPoint, control2 + otherPoint, point + otherPoint, type: type) 172 | } 173 | return self 174 | } 175 | } 176 | 177 | // MARK: CGPoint helpers 178 | private func +(a:CGPoint, b:CGPoint) -> CGPoint { 179 | return CGPoint(x: a.x + b.x, y: a.y + b.y) 180 | } 181 | 182 | private func -(a:CGPoint, b:CGPoint) -> CGPoint { 183 | return CGPoint(x: a.x - b.x, y: a.y - b.y) 184 | } 185 | 186 | // MARK: Command Builders 187 | private typealias SVGCommandBuilder = ([CGFloat], SVGCommand?, Coordinates) -> SVGCommand 188 | 189 | private func take (_ numbers: [CGFloat], increment: Int, coords: Coordinates, last: SVGCommand?, callback: SVGCommandBuilder) -> [SVGCommand] { 190 | var out: [SVGCommand] = [] 191 | var lastCommand:SVGCommand? = last 192 | 193 | let count = (numbers.count / increment) * increment 194 | var nums:[CGFloat] = [0, 0, 0, 0, 0, 0]; 195 | 196 | for i in stride(from: 0, to: count, by: increment) { 197 | for j in 0 ..< increment { 198 | nums[j] = numbers[i + j] 199 | } 200 | lastCommand = callback(nums, lastCommand, coords) 201 | out.append(lastCommand!) 202 | } 203 | 204 | return out 205 | } 206 | 207 | // MARK: Mm - Move 208 | private func move (_ numbers: [CGFloat], last: SVGCommand?, coords: Coordinates) -> SVGCommand { 209 | return SVGCommand(numbers[0], numbers[1], type: .move) 210 | } 211 | 212 | // MARK: Ll - Line 213 | private func line (_ numbers: [CGFloat], last: SVGCommand?, coords: Coordinates) -> SVGCommand { 214 | return SVGCommand(numbers[0], numbers[1], type: .line) 215 | } 216 | 217 | // MARK: Vv - Vertical Line 218 | private func lineVertical (_ numbers: [CGFloat], last: SVGCommand?, coords: Coordinates) -> SVGCommand { 219 | return SVGCommand(coords == .absolute ? last?.point.x ?? 0 : 0, numbers[0], type: .line) 220 | } 221 | 222 | // MARK: Hh - Horizontal Line 223 | private func lineHorizontal (_ numbers: [CGFloat], last: SVGCommand?, coords: Coordinates) -> SVGCommand { 224 | return SVGCommand(numbers[0], coords == .absolute ? last?.point.y ?? 0 : 0, type: .line) 225 | } 226 | 227 | // MARK: Qq - Quadratic Curve To 228 | private func quadBroken (_ numbers: [CGFloat], last: SVGCommand?, coords: Coordinates) -> SVGCommand { 229 | return SVGCommand(numbers[0], numbers[1], numbers[2], numbers[3]) 230 | } 231 | 232 | // MARK: Tt - Smooth Quadratic Curve To 233 | private func quadSmooth (_ numbers: [CGFloat], last: SVGCommand?, coords: Coordinates) -> SVGCommand { 234 | var lastControl = last?.control1 ?? CGPoint() 235 | let lastPoint = last?.point ?? CGPoint() 236 | if (last?.type ?? .line) != .quadCurve { 237 | lastControl = lastPoint 238 | } 239 | var control = lastPoint - lastControl 240 | if coords == .absolute { 241 | control = control + lastPoint 242 | } 243 | return SVGCommand(control.x, control.y, numbers[0], numbers[1]) 244 | } 245 | 246 | // MARK: Cc - Cubic Curve To 247 | private func cubeBroken (_ numbers: [CGFloat], last: SVGCommand?, coords: Coordinates) -> SVGCommand { 248 | return SVGCommand(numbers[0], numbers[1], numbers[2], numbers[3], numbers[4], numbers[5]) 249 | } 250 | 251 | // MARK: Ss - Smooth Cubic Curve To 252 | private func cubeSmooth (_ numbers: [CGFloat], last: SVGCommand?, coords: Coordinates) -> SVGCommand { 253 | var lastControl = last?.control2 ?? CGPoint() 254 | let lastPoint = last?.point ?? CGPoint() 255 | if (last?.type ?? .line) != .cubeCurve { 256 | lastControl = lastPoint 257 | } 258 | var control = lastPoint - lastControl 259 | if coords == .absolute { 260 | control = control + lastPoint 261 | } 262 | return SVGCommand(control.x, control.y, numbers[0], numbers[1], numbers[2], numbers[3]) 263 | } 264 | 265 | // MARK: Zz - Close Path 266 | private func close (_ numbers: [CGFloat], last: SVGCommand?, coords: Coordinates) -> SVGCommand { 267 | return SVGCommand() 268 | } 269 | -------------------------------------------------------------------------------- /PointerInteraction/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // PointerInteraction 4 | // 5 | // Created by Tim Roesner on 4/11/20. 6 | // Copyright © 2020 Tim Roesner. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /PointerInteraction/UIButtonViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirstViewController.swift 3 | // PointerInteraction 4 | // 5 | // Created by Tim Roesner on 4/11/20. 6 | // Copyright © 2020 Tim Roesner. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UIButtonViewController: UIViewController { 12 | @IBOutlet weak var highlightedButton: UIButton! 13 | @IBOutlet weak var smallLiftButton: UIButton! 14 | @IBOutlet weak var largeLiftButton: UIButton! 15 | @IBOutlet weak var hover1Button: UIButton! 16 | @IBOutlet weak var hover2Button: UIButton! 17 | @IBOutlet weak var hover3Button: UIButton! 18 | 19 | private lazy var allButtons: [UIButton] = [highlightedButton, smallLiftButton, largeLiftButton, hover1Button, hover2Button, hover3Button] 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | navigationController?.navigationBar.prefersLargeTitles = true 24 | 25 | smallLiftButton.layer.cornerRadius = 8 26 | largeLiftButton.layer.cornerRadius = 8 27 | hover3Button.layer.cornerRadius = 8 28 | 29 | setupPointerInteraction() 30 | } 31 | 32 | private func setupPointerInteraction() { 33 | allButtons.forEach { (button) in 34 | button.isPointerInteractionEnabled = true 35 | } 36 | 37 | highlightedButton.pointerStyleProvider = { (button, effect, shape) in 38 | let targetedPreview = UITargetedPreview(view: button) 39 | return UIPointerStyle(effect: .highlight(targetedPreview)) 40 | } 41 | 42 | smallLiftButton.pointerStyleProvider = { (button, effect, shape) in 43 | let targetedPreview = UITargetedPreview(view: button) 44 | return UIPointerStyle(effect: .lift(targetedPreview)) 45 | } 46 | 47 | largeLiftButton.pointerStyleProvider = { (button, effect, shape) in 48 | let targetedPreview = UITargetedPreview(view: button) 49 | return UIPointerStyle(effect: .lift(targetedPreview)) 50 | } 51 | 52 | hover1Button.pointerStyleProvider = { (button, effect, shape) in 53 | let targetedPreview = UITargetedPreview(view: button) 54 | return UIPointerStyle(effect: .hover(targetedPreview)) 55 | } 56 | 57 | hover2Button.pointerStyleProvider = { (button, effect, shape) in 58 | let targetedPreview = UITargetedPreview(view: button) 59 | return UIPointerStyle(effect: .hover(targetedPreview, preferredTintMode: .overlay, prefersShadow: true, prefersScaledContent: false)) 60 | } 61 | 62 | hover3Button.pointerStyleProvider = { (button, effect, shape) in 63 | let targetedPreview = UITargetedPreview(view: button) 64 | return UIPointerStyle(effect: .hover(targetedPreview, preferredTintMode: .underlay, prefersShadow: true, prefersScaledContent: true)) 65 | } 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /PointerInteraction/UIViewViewController+ContextMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewViewController+ContextMenu.swift 3 | // PointerInteraction 4 | // 5 | // Created by Tim Roesner on 4/12/20. 6 | // Copyright © 2020 Tim Roesner. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewViewController: UIContextMenuInteractionDelegate { 12 | func makeContextMenu() -> UIMenu { 13 | let starAction = UIAction(title: "Star", image: UIImage(systemName: "star")) { action in 14 | // Perform star 15 | } 16 | 17 | let shareAction = UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { action in 18 | // Perform share 19 | } 20 | 21 | let deleteAction = UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { action in 22 | // Perform delete 23 | } 24 | 25 | return UIMenu(title: "", children: [starAction, shareAction, deleteAction]) 26 | } 27 | 28 | func contextMenuInteraction(_ interaction: UIContextMenuInteraction, 29 | previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { 30 | guard let view = interaction.view else { return nil } 31 | return UITargetedPreview(view: view) 32 | } 33 | 34 | func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { 35 | return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in 36 | return self.makeContextMenu() 37 | } 38 | } 39 | 40 | func contextMenuInteraction(_ interaction: UIContextMenuInteraction, 41 | willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, 42 | animator: UIContextMenuInteractionCommitAnimating) { 43 | // No op 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PointerInteraction/UIViewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.swift 3 | // PointerInteraction 4 | // 5 | // Created by Tim Roesner on 4/11/20. 6 | // Copyright © 2020 Tim Roesner. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UIViewViewController: UIViewController { 12 | @IBOutlet weak var imageView: UIImageView! 13 | @IBOutlet weak var smallImageView: UIImageView! 14 | @IBOutlet weak var buttonStackView: UIStackView! 15 | @IBOutlet weak var contextMenuButton: UIButton! 16 | @IBOutlet weak var shape1Button: UIButton! 17 | @IBOutlet weak var shape2Button: UIButton! 18 | @IBOutlet weak var shape3Button: UIButton! 19 | @IBOutlet weak var shape4Button: UIButton! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | [imageView, smallImageView].forEach { (imageView) in 25 | guard let imageView = imageView else { return } 26 | imageView.isUserInteractionEnabled = true 27 | imageView.addPointerInteraction(with: self) 28 | } 29 | 30 | let contextMenuInteraction = UIContextMenuInteraction(delegate: self) 31 | contextMenuButton.addInteraction(contextMenuInteraction) 32 | 33 | buttonStackView.arrangedSubviews.forEach { (view) in 34 | guard let button = view as? UIButton else { return } 35 | button.isPointerInteractionEnabled = true 36 | } 37 | setupButtonPointerInteraction() 38 | } 39 | 40 | private func setupButtonPointerInteraction() { 41 | contextMenuButton.pointerStyleProvider = { (button, _, _) in 42 | let targetedPreview = UITargetedPreview(view: button) 43 | return UIPointerStyle(effect: .automatic(targetedPreview)) 44 | } 45 | 46 | shape1Button.pointerStyleProvider = { (button, _, _) in 47 | let pointerRect = CGRect(origin: .zero, size: CGSize(width: 25, height: 25)) 48 | return UIPointerStyle(shape: .roundedRect(pointerRect, radius: UIPointerShape.defaultCornerRadius)) 49 | } 50 | 51 | shape2Button.pointerStyleProvider = { (button, _, _) in 52 | let height = button.titleLabel?.font.lineHeight ?? button.frame.height 53 | return UIPointerStyle(shape: .verticalBeam(length: height)) 54 | } 55 | 56 | shape3Button.pointerStyleProvider = { (button, _, _) in 57 | // Max width will be 100 points 58 | return UIPointerStyle(shape: .horizontalBeam(length: button.frame.width), constrainedAxes: .horizontal) 59 | } 60 | 61 | shape4Button.pointerStyleProvider = { (button, effect, shape) in 62 | // Offset is necessary so that the default pointer and this new pointer shape are algined correctly. 63 | let paperplanePath = UIBezierPath(svgPath: .paperplaneIcon, offset: 10.0) 64 | return UIPointerStyle(shape: .path(paperplanePath)) 65 | } 66 | } 67 | } 68 | 69 | extension UIViewViewController: UIPointerInteractionDelegate { 70 | func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { 71 | guard let view = interaction.view else { return nil } 72 | let targetedPreview = UITargetedPreview(view: view) 73 | return UIPointerStyle(effect: .lift(targetedPreview)) 74 | } 75 | } 76 | 77 | extension UIView { 78 | func addPointerInteraction(with pointerInteractionDelegate: UIPointerInteractionDelegate) { 79 | let pointerInteraction = UIPointerInteraction(delegate: pointerInteractionDelegate) 80 | self.addInteraction(pointerInteraction) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pointer Interactions 2 | 🖱 A sample app to show case the different pointer interactions, effects, and shapes. 3 | 4 | ![SampleApp](https://user-images.githubusercontent.com/13894518/79092788-d8df2580-7d06-11ea-8631-ed02087cf600.png) 5 | 6 | To learn more about the different Pointer Interactions see this blog post: https://blog.timroesner.com/implementing-uipointerinteractions 7 | --------------------------------------------------------------------------------