├── .github └── FUNDING.yml ├── .swiftlint.yml ├── DrawUI.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ ├── WorkspaceSettings.xcsettings │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ ├── xcbaselines │ └── C58025A824EA3F150043130D.xcbaseline │ │ ├── 6AE7C7C6-6793-4BE0-997E-EEF04D460DBA.plist │ │ └── Info.plist │ └── xcschemes │ ├── DrawUI.xcscheme │ ├── DrawUIExample.xcscheme │ └── DrawUITests.xcscheme └── DrawUIExample ├── AppDelegate.swift ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── DebugView.swift ├── Info.plist ├── SceneDelegate.swift └── Tabs ├── BaseViewController.swift ├── BezierViewController.swift ├── ClippedBezierViewController.swift └── DebugViewController.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: adamwulf 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: # rule identifiers to exclude from running 2 | - trailing_comma 3 | - force_cast 4 | - identifier_name 5 | - multiple_closures_with_trailing_closure 6 | - cyclomatic_complexity 7 | - function_body_length 8 | - type_body_length 9 | - file_length 10 | - function_parameter_count 11 | - weak_delegate 12 | - xctfail_message 13 | - force_try 14 | - closure_parameter_position 15 | opt_in_rules: # some rules are only opt-in 16 | - empty_count 17 | - operator_usage_whitespace 18 | 19 | line_length: 20 | warning: 140 21 | error: 500 22 | ignores_comments: true 23 | ignores_interpolated_strings: true 24 | 25 | excluded: # paths to ignore during linting. Takes precedence over `included`. 26 | - Pods 27 | - Contributed 28 | 29 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown) 30 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C5094CE925FEB6770001AE2A /* BezierViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5094CE825FEB6770001AE2A /* BezierViewController.swift */; }; 11 | C5094CEF25FEB6FB0001AE2A /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5094CEE25FEB6FB0001AE2A /* BaseViewController.swift */; }; 12 | C524927226AE2E6D00479E00 /* Inkable in Frameworks */ = {isa = PBXBuildFile; productRef = C524927126AE2E6D00479E00 /* Inkable */; }; 13 | C5C159C424E9AF4E00B52B4B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C159C324E9AF4E00B52B4B /* AppDelegate.swift */; }; 14 | C5C159C624E9AF4E00B52B4B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C159C524E9AF4E00B52B4B /* SceneDelegate.swift */; }; 15 | C5C159C824E9AF4E00B52B4B /* DebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C159C724E9AF4E00B52B4B /* DebugViewController.swift */; }; 16 | C5C159CB24E9AF4E00B52B4B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C5C159C924E9AF4E00B52B4B /* Main.storyboard */; }; 17 | C5C159CD24E9AF4E00B52B4B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5C159CC24E9AF4E00B52B4B /* Assets.xcassets */; }; 18 | C5C159D024E9AF4E00B52B4B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C5C159CE24E9AF4E00B52B4B /* LaunchScreen.storyboard */; }; 19 | C5C159E724E9BC6500B52B4B /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C159E624E9BC6500B52B4B /* DebugView.swift */; }; 20 | C5EBA802261ABABE000E29D3 /* ClippedBezierViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5EBA801261ABABE000E29D3 /* ClippedBezierViewController.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXCopyFilesBuildPhase section */ 24 | C5C159DE24E9AFA000B52B4B /* CopyFiles */ = { 25 | isa = PBXCopyFilesBuildPhase; 26 | buildActionMask = 2147483647; 27 | dstPath = ""; 28 | dstSubfolderSpec = 10; 29 | files = ( 30 | ); 31 | runOnlyForDeploymentPostprocessing = 0; 32 | }; 33 | /* End PBXCopyFilesBuildPhase section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | C5094CE825FEB6770001AE2A /* BezierViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BezierViewController.swift; sourceTree = ""; }; 37 | C5094CEE25FEB6FB0001AE2A /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; 38 | C5C159C124E9AF4E00B52B4B /* DrawUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DrawUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | C5C159C324E9AF4E00B52B4B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | C5C159C524E9AF4E00B52B4B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 41 | C5C159C724E9AF4E00B52B4B /* DebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugViewController.swift; sourceTree = ""; }; 42 | C5C159CA24E9AF4E00B52B4B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43 | C5C159CC24E9AF4E00B52B4B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | C5C159CF24E9AF4E00B52B4B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 45 | C5C159D124E9AF4E00B52B4B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | C5C159E624E9BC6500B52B4B /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = ""; }; 47 | C5EBA801261ABABE000E29D3 /* ClippedBezierViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClippedBezierViewController.swift; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | C5C159BE24E9AF4E00B52B4B /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | C524927226AE2E6D00479E00 /* Inkable in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | C5094CE725FEB6690001AE2A /* Tabs */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | C5094CEE25FEB6FB0001AE2A /* BaseViewController.swift */, 66 | C5C159C724E9AF4E00B52B4B /* DebugViewController.swift */, 67 | C5094CE825FEB6770001AE2A /* BezierViewController.swift */, 68 | C5EBA801261ABABE000E29D3 /* ClippedBezierViewController.swift */, 69 | ); 70 | path = Tabs; 71 | sourceTree = ""; 72 | }; 73 | C5C159A424E9AF1C00B52B4B = { 74 | isa = PBXGroup; 75 | children = ( 76 | C5C159C224E9AF4E00B52B4B /* DrawUIExample */, 77 | C5C159AF24E9AF1C00B52B4B /* Products */, 78 | C5C159D724E9AF6100B52B4B /* Frameworks */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | C5C159AF24E9AF1C00B52B4B /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | C5C159C124E9AF4E00B52B4B /* DrawUIExample.app */, 86 | ); 87 | name = Products; 88 | sourceTree = ""; 89 | }; 90 | C5C159C224E9AF4E00B52B4B /* DrawUIExample */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | C5094CE725FEB6690001AE2A /* Tabs */, 94 | C5C159C324E9AF4E00B52B4B /* AppDelegate.swift */, 95 | C5C159C524E9AF4E00B52B4B /* SceneDelegate.swift */, 96 | C5C159E624E9BC6500B52B4B /* DebugView.swift */, 97 | C5C159C924E9AF4E00B52B4B /* Main.storyboard */, 98 | C5C159CC24E9AF4E00B52B4B /* Assets.xcassets */, 99 | C5C159CE24E9AF4E00B52B4B /* LaunchScreen.storyboard */, 100 | C5C159D124E9AF4E00B52B4B /* Info.plist */, 101 | ); 102 | path = DrawUIExample; 103 | sourceTree = ""; 104 | }; 105 | C5C159D724E9AF6100B52B4B /* Frameworks */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | ); 109 | name = Frameworks; 110 | sourceTree = ""; 111 | }; 112 | /* End PBXGroup section */ 113 | 114 | /* Begin PBXNativeTarget section */ 115 | C5C159C024E9AF4E00B52B4B /* DrawUIExample */ = { 116 | isa = PBXNativeTarget; 117 | buildConfigurationList = C5C159D224E9AF4E00B52B4B /* Build configuration list for PBXNativeTarget "DrawUIExample" */; 118 | buildPhases = ( 119 | C5C159BD24E9AF4E00B52B4B /* Sources */, 120 | C5C159BE24E9AF4E00B52B4B /* Frameworks */, 121 | C5C159BF24E9AF4E00B52B4B /* Resources */, 122 | C5C159DE24E9AFA000B52B4B /* CopyFiles */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | ); 128 | name = DrawUIExample; 129 | packageProductDependencies = ( 130 | C524927126AE2E6D00479E00 /* Inkable */, 131 | ); 132 | productName = DrawUIExample; 133 | productReference = C5C159C124E9AF4E00B52B4B /* DrawUIExample.app */; 134 | productType = "com.apple.product-type.application"; 135 | }; 136 | /* End PBXNativeTarget section */ 137 | 138 | /* Begin PBXProject section */ 139 | C5C159A524E9AF1C00B52B4B /* Project object */ = { 140 | isa = PBXProject; 141 | attributes = { 142 | LastSwiftUpdateCheck = 1200; 143 | LastUpgradeCheck = 1200; 144 | TargetAttributes = { 145 | C5C159C024E9AF4E00B52B4B = { 146 | CreatedOnToolsVersion = 12.0; 147 | }; 148 | }; 149 | }; 150 | buildConfigurationList = C5C159A824E9AF1C00B52B4B /* Build configuration list for PBXProject "DrawUI" */; 151 | compatibilityVersion = "Xcode 9.3"; 152 | developmentRegion = en; 153 | hasScannedForEncodings = 0; 154 | knownRegions = ( 155 | en, 156 | Base, 157 | ); 158 | mainGroup = C5C159A424E9AF1C00B52B4B; 159 | packageReferences = ( 160 | C524926E26AE2E0000479E00 /* XCRemoteSwiftPackageReference "Inkable" */, 161 | ); 162 | productRefGroup = C5C159AF24E9AF1C00B52B4B /* Products */; 163 | projectDirPath = ""; 164 | projectRoot = ""; 165 | targets = ( 166 | C5C159C024E9AF4E00B52B4B /* DrawUIExample */, 167 | ); 168 | }; 169 | /* End PBXProject section */ 170 | 171 | /* Begin PBXResourcesBuildPhase section */ 172 | C5C159BF24E9AF4E00B52B4B /* Resources */ = { 173 | isa = PBXResourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | C5C159D024E9AF4E00B52B4B /* LaunchScreen.storyboard in Resources */, 177 | C5C159CD24E9AF4E00B52B4B /* Assets.xcassets in Resources */, 178 | C5C159CB24E9AF4E00B52B4B /* Main.storyboard in Resources */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | /* End PBXResourcesBuildPhase section */ 183 | 184 | /* Begin PBXSourcesBuildPhase section */ 185 | C5C159BD24E9AF4E00B52B4B /* Sources */ = { 186 | isa = PBXSourcesBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | C5094CE925FEB6770001AE2A /* BezierViewController.swift in Sources */, 190 | C5C159E724E9BC6500B52B4B /* DebugView.swift in Sources */, 191 | C5094CEF25FEB6FB0001AE2A /* BaseViewController.swift in Sources */, 192 | C5C159C824E9AF4E00B52B4B /* DebugViewController.swift in Sources */, 193 | C5C159C424E9AF4E00B52B4B /* AppDelegate.swift in Sources */, 194 | C5C159C624E9AF4E00B52B4B /* SceneDelegate.swift in Sources */, 195 | C5EBA802261ABABE000E29D3 /* ClippedBezierViewController.swift in Sources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXSourcesBuildPhase section */ 200 | 201 | /* Begin PBXVariantGroup section */ 202 | C5C159C924E9AF4E00B52B4B /* Main.storyboard */ = { 203 | isa = PBXVariantGroup; 204 | children = ( 205 | C5C159CA24E9AF4E00B52B4B /* Base */, 206 | ); 207 | name = Main.storyboard; 208 | sourceTree = ""; 209 | }; 210 | C5C159CE24E9AF4E00B52B4B /* LaunchScreen.storyboard */ = { 211 | isa = PBXVariantGroup; 212 | children = ( 213 | C5C159CF24E9AF4E00B52B4B /* Base */, 214 | ); 215 | name = LaunchScreen.storyboard; 216 | sourceTree = ""; 217 | }; 218 | /* End PBXVariantGroup section */ 219 | 220 | /* Begin XCBuildConfiguration section */ 221 | C5C159B424E9AF1C00B52B4B /* Debug */ = { 222 | isa = XCBuildConfiguration; 223 | buildSettings = { 224 | ALWAYS_SEARCH_USER_PATHS = NO; 225 | CLANG_ANALYZER_NONNULL = YES; 226 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 227 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 228 | CLANG_CXX_LIBRARY = "libc++"; 229 | CLANG_ENABLE_MODULES = YES; 230 | CLANG_ENABLE_OBJC_ARC = YES; 231 | CLANG_ENABLE_OBJC_WEAK = YES; 232 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 233 | CLANG_WARN_BOOL_CONVERSION = YES; 234 | CLANG_WARN_COMMA = YES; 235 | CLANG_WARN_CONSTANT_CONVERSION = YES; 236 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 237 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 238 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 239 | CLANG_WARN_EMPTY_BODY = YES; 240 | CLANG_WARN_ENUM_CONVERSION = YES; 241 | CLANG_WARN_INFINITE_RECURSION = YES; 242 | CLANG_WARN_INT_CONVERSION = YES; 243 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 245 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 248 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 249 | CLANG_WARN_STRICT_PROTOTYPES = YES; 250 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 251 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 252 | CLANG_WARN_UNREACHABLE_CODE = YES; 253 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 254 | COPY_PHASE_STRIP = NO; 255 | CURRENT_PROJECT_VERSION = 1; 256 | DEBUG_INFORMATION_FORMAT = dwarf; 257 | ENABLE_STRICT_OBJC_MSGSEND = YES; 258 | ENABLE_TESTABILITY = YES; 259 | GCC_C_LANGUAGE_STANDARD = gnu11; 260 | GCC_DYNAMIC_NO_PIC = NO; 261 | GCC_NO_COMMON_BLOCKS = YES; 262 | GCC_OPTIMIZATION_LEVEL = 0; 263 | GCC_PREPROCESSOR_DEFINITIONS = ( 264 | "DEBUG=1", 265 | "$(inherited)", 266 | ); 267 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 268 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 269 | GCC_WARN_UNDECLARED_SELECTOR = YES; 270 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 271 | GCC_WARN_UNUSED_FUNCTION = YES; 272 | GCC_WARN_UNUSED_VARIABLE = YES; 273 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 274 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 275 | MTL_FAST_MATH = YES; 276 | ONLY_ACTIVE_ARCH = YES; 277 | OTHER_LDFLAGS = ( 278 | "-lstdc++", 279 | "-ObjC", 280 | ); 281 | SDKROOT = iphoneos; 282 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 283 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 284 | VERSIONING_SYSTEM = "apple-generic"; 285 | VERSION_INFO_PREFIX = ""; 286 | }; 287 | name = Debug; 288 | }; 289 | C5C159B524E9AF1C00B52B4B /* Release */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | ALWAYS_SEARCH_USER_PATHS = NO; 293 | CLANG_ANALYZER_NONNULL = YES; 294 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 295 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 296 | CLANG_CXX_LIBRARY = "libc++"; 297 | CLANG_ENABLE_MODULES = YES; 298 | CLANG_ENABLE_OBJC_ARC = YES; 299 | CLANG_ENABLE_OBJC_WEAK = YES; 300 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 301 | CLANG_WARN_BOOL_CONVERSION = YES; 302 | CLANG_WARN_COMMA = YES; 303 | CLANG_WARN_CONSTANT_CONVERSION = YES; 304 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 305 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 306 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 307 | CLANG_WARN_EMPTY_BODY = YES; 308 | CLANG_WARN_ENUM_CONVERSION = YES; 309 | CLANG_WARN_INFINITE_RECURSION = YES; 310 | CLANG_WARN_INT_CONVERSION = YES; 311 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 312 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 313 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 314 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 315 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 316 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 317 | CLANG_WARN_STRICT_PROTOTYPES = YES; 318 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 319 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 320 | CLANG_WARN_UNREACHABLE_CODE = YES; 321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 322 | COPY_PHASE_STRIP = NO; 323 | CURRENT_PROJECT_VERSION = 1; 324 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 325 | ENABLE_NS_ASSERTIONS = NO; 326 | ENABLE_STRICT_OBJC_MSGSEND = YES; 327 | GCC_C_LANGUAGE_STANDARD = gnu11; 328 | GCC_NO_COMMON_BLOCKS = YES; 329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 336 | MTL_ENABLE_DEBUG_INFO = NO; 337 | MTL_FAST_MATH = YES; 338 | OTHER_LDFLAGS = ( 339 | "-lstdc++", 340 | "-ObjC", 341 | ); 342 | SDKROOT = iphoneos; 343 | SWIFT_COMPILATION_MODE = wholemodule; 344 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 345 | VALIDATE_PRODUCT = YES; 346 | VERSIONING_SYSTEM = "apple-generic"; 347 | VERSION_INFO_PREFIX = ""; 348 | }; 349 | name = Release; 350 | }; 351 | C5C159D324E9AF4E00B52B4B /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 355 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 356 | CODE_SIGN_STYLE = Automatic; 357 | DEVELOPMENT_TEAM = MU27A4SCKC; 358 | INFOPLIST_FILE = DrawUIExample/Info.plist; 359 | LD_RUNPATH_SEARCH_PATHS = ( 360 | "$(inherited)", 361 | "@executable_path/Frameworks", 362 | ); 363 | PRODUCT_BUNDLE_IDENTIFIER = com.milestonemade.DrawUIExample; 364 | PRODUCT_NAME = "$(TARGET_NAME)"; 365 | SWIFT_VERSION = 5.0; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | }; 368 | name = Debug; 369 | }; 370 | C5C159D424E9AF4E00B52B4B /* Release */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 374 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 375 | CODE_SIGN_STYLE = Automatic; 376 | DEVELOPMENT_TEAM = MU27A4SCKC; 377 | INFOPLIST_FILE = DrawUIExample/Info.plist; 378 | LD_RUNPATH_SEARCH_PATHS = ( 379 | "$(inherited)", 380 | "@executable_path/Frameworks", 381 | ); 382 | PRODUCT_BUNDLE_IDENTIFIER = com.milestonemade.DrawUIExample; 383 | PRODUCT_NAME = "$(TARGET_NAME)"; 384 | SWIFT_VERSION = 5.0; 385 | TARGETED_DEVICE_FAMILY = "1,2"; 386 | }; 387 | name = Release; 388 | }; 389 | /* End XCBuildConfiguration section */ 390 | 391 | /* Begin XCConfigurationList section */ 392 | C5C159A824E9AF1C00B52B4B /* Build configuration list for PBXProject "DrawUI" */ = { 393 | isa = XCConfigurationList; 394 | buildConfigurations = ( 395 | C5C159B424E9AF1C00B52B4B /* Debug */, 396 | C5C159B524E9AF1C00B52B4B /* Release */, 397 | ); 398 | defaultConfigurationIsVisible = 0; 399 | defaultConfigurationName = Release; 400 | }; 401 | C5C159D224E9AF4E00B52B4B /* Build configuration list for PBXNativeTarget "DrawUIExample" */ = { 402 | isa = XCConfigurationList; 403 | buildConfigurations = ( 404 | C5C159D324E9AF4E00B52B4B /* Debug */, 405 | C5C159D424E9AF4E00B52B4B /* Release */, 406 | ); 407 | defaultConfigurationIsVisible = 0; 408 | defaultConfigurationName = Release; 409 | }; 410 | /* End XCConfigurationList section */ 411 | 412 | /* Begin XCRemoteSwiftPackageReference section */ 413 | C524926E26AE2E0000479E00 /* XCRemoteSwiftPackageReference "Inkable" */ = { 414 | isa = XCRemoteSwiftPackageReference; 415 | repositoryURL = "https://github.com/adamwulf/Inkable.git"; 416 | requirement = { 417 | branch = main; 418 | kind = branch; 419 | }; 420 | }; 421 | /* End XCRemoteSwiftPackageReference section */ 422 | 423 | /* Begin XCSwiftPackageProductDependency section */ 424 | C524927126AE2E6D00479E00 /* Inkable */ = { 425 | isa = XCSwiftPackageProductDependency; 426 | package = C524926E26AE2E0000479E00 /* XCRemoteSwiftPackageReference "Inkable" */; 427 | productName = Inkable; 428 | }; 429 | /* End XCSwiftPackageProductDependency section */ 430 | }; 431 | rootObject = C5C159A524E9AF1C00B52B4B /* Project object */; 432 | } 433 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "ClippingBezier", 6 | "repositoryURL": "https://github.com/adamwulf/ClippingBezier.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "9cde6dd3396ad59d3636e4c6e3f55847de48bf30", 10 | "version": "1.2.0" 11 | } 12 | }, 13 | { 14 | "package": "Inkable", 15 | "repositoryURL": "https://github.com/adamwulf/Inkable.git", 16 | "state": { 17 | "branch": "main", 18 | "revision": "3aa32e10b609d3cc2a550e03d29381971311bc51", 19 | "version": null 20 | } 21 | }, 22 | { 23 | "package": "MMSwiftToolbox", 24 | "repositoryURL": "https://github.com/adamwulf/MMSwiftToolbox.git", 25 | "state": { 26 | "branch": "main", 27 | "revision": "bcca4a637e512d41772582ef9e602c6df876a585", 28 | "version": null 29 | } 30 | }, 31 | { 32 | "package": "PerformanceBezier", 33 | "repositoryURL": "https://github.com/adamwulf/PerformanceBezier.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "5737bfcfe7b69ea79a02853f75ae838892f5db51", 37 | "version": "1.3.0" 38 | } 39 | } 40 | ] 41 | }, 42 | "version": 1 43 | } 44 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/xcshareddata/xcbaselines/C58025A824EA3F150043130D.xcbaseline/6AE7C7C6-6793-4BE0-997E-EEF04D460DBA.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | TouchPathTests 8 | 9 | testMeasureTouchEvents() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.0051962 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | TouchPointStreamTests 21 | 22 | testMeasureTouchEvents() 23 | 24 | com.apple.XCTPerformanceMetric_WallClockTime 25 | 26 | baselineAverage 27 | 0.0051869 28 | baselineIntegrationDisplayName 29 | Local Baseline 30 | 31 | 32 | testStreamsMatch2() 33 | 34 | com.apple.XCTPerformanceMetric_WallClockTime 35 | 36 | baselineAverage 37 | 1.1521 38 | baselineIntegrationDisplayName 39 | Local Baseline 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/xcshareddata/xcbaselines/C58025A824EA3F150043130D.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 6AE7C7C6-6793-4BE0-997E-EEF04D460DBA 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 400 13 | cpuCount 14 | 1 15 | cpuKind 16 | 8-Core Intel Core i9 17 | cpuSpeedInMHz 18 | 2400 19 | logicalCPUCoresPerPackage 20 | 16 21 | modelCode 22 | MacBookPro16,1 23 | physicalCPUCoresPerPackage 24 | 8 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPad11,7 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/xcshareddata/xcschemes/DrawUI.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/xcshareddata/xcschemes/DrawUIExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /DrawUI.xcodeproj/xcshareddata/xcschemes/DrawUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 67 | 68 | 74 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /DrawUIExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DrawUIExample 4 | // 5 | // Created by Adam Wulf on 8/16/20. 6 | // 7 | 8 | import UIKit 9 | import Inkable 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, 15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, 23 | configurationForConnecting connectingSceneSession: UISceneSession, 24 | options: UIScene.ConnectionOptions) -> UISceneConfiguration { 25 | // Called when a new scene session is being created. 26 | // Use this method to select a configuration to create the new scene with. 27 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 28 | } 29 | 30 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 31 | // Called when the user discards a scene session. 32 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 33 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /DrawUIExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /DrawUIExample/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 | -------------------------------------------------------------------------------- /DrawUIExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DrawUIExample/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 | -------------------------------------------------------------------------------- /DrawUIExample/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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /DrawUIExample/DebugView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DebugView.swift 3 | // DrawUIExample 4 | // 5 | // Created by Adam Wulf on 8/16/20. 6 | // 7 | 8 | import UIKit 9 | import Inkable 10 | 11 | class DebugView: UIView { 12 | var originalStrokes: [Polyline] = [] 13 | var smoothStrokes: [UIBezierPath] = [] 14 | private var deltas: [PolylineStream.Delta]? 15 | 16 | override public func layoutSubviews() { 17 | setNeedsDisplay() 18 | super.layoutSubviews() 19 | } 20 | 21 | func add(deltas: [PolylineStream.Delta]) { 22 | if self.deltas == nil { 23 | self.deltas = [] 24 | } 25 | self.deltas?.append(contentsOf: deltas) 26 | } 27 | 28 | func reset() { 29 | originalStrokes = [] 30 | smoothStrokes = [] 31 | deltas = [] 32 | setNeedsDisplay() 33 | } 34 | 35 | override func draw(_ rect: CGRect) { 36 | for stroke in originalStrokes { 37 | for event in stroke.points.flatMap({ $0.touchPoint.events }) { 38 | var radius: CGFloat = 2 39 | if event.isUpdate { 40 | radius = 1 41 | if !event.expectsUpdate { 42 | UIColor.red.setFill() 43 | } else { 44 | UIColor.green.setFill() 45 | } 46 | } else if event.isPrediction { 47 | UIColor.blue.setFill() 48 | } else { 49 | if !event.expectsUpdate { 50 | UIColor.red.setFill() 51 | } else { 52 | UIColor.green.setFill() 53 | } 54 | } 55 | UIBezierPath(ovalIn: CGRect(origin: event.location, size: CGSize.zero).expand(by: radius)).fill() 56 | } 57 | 58 | UIColor.red.setStroke() 59 | 60 | let path = UIBezierPath() 61 | for point in stroke.points { 62 | if point.event.phase == .began { 63 | path.move(to: point.location) 64 | } else { 65 | path.addLine(to: point.location) 66 | } 67 | } 68 | path.stroke() 69 | } 70 | 71 | for path in smoothStrokes { 72 | UIColor.green.setStroke() 73 | 74 | path.stroke() 75 | } 76 | 77 | if let deltas = deltas { 78 | func draw(stroke: Polyline, indexSet: IndexSet?) { 79 | UIColor.red.setStroke() 80 | if let indexSet = indexSet { 81 | for index in indexSet { 82 | if index < stroke.points.count { 83 | let point = stroke.points[index] 84 | let path = UIBezierPath(arcCenter: point.event.location, 85 | radius: 4, 86 | startAngle: 0, 87 | endAngle: CGFloat(Double.pi * 2), 88 | clockwise: true) 89 | path.stroke() 90 | } else { 91 | let point = stroke.points.last! 92 | UIBezierPath(rect: CGRect(origin: point.event.location, size: CGSize.zero).expand(by: 4)).stroke() 93 | } 94 | } 95 | } else { 96 | for point in stroke.points { 97 | let path = UIBezierPath(arcCenter: point.event.location, 98 | radius: 4, 99 | startAngle: 0, 100 | endAngle: CGFloat(Double.pi * 2), 101 | clockwise: true) 102 | path.stroke() 103 | } 104 | } 105 | } 106 | 107 | for delta in deltas { 108 | switch delta { 109 | case .addedPolyline(let polylineIndex): 110 | draw(stroke: originalStrokes[polylineIndex], indexSet: nil) 111 | case .updatedPolyline(let polylineIndex, let indexSet): 112 | draw(stroke: originalStrokes[polylineIndex], indexSet: indexSet) 113 | default: 114 | break 115 | } 116 | } 117 | } 118 | 119 | deltas?.removeAll() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /DrawUIExample/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 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /DrawUIExample/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // DrawUIExample 4 | // 5 | // Created by Adam Wulf on 8/16/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 15 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 16 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 17 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 18 | guard (scene as? UIWindowScene) != nil else { return } 19 | } 20 | 21 | func sceneDidDisconnect(_ scene: UIScene) { 22 | // Called as the scene is being released by the system. 23 | // This occurs shortly after the scene enters the background, or when its session is discarded. 24 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 25 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 26 | } 27 | 28 | func sceneDidBecomeActive(_ scene: UIScene) { 29 | // Called when the scene has moved from an inactive state to an active state. 30 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 31 | } 32 | 33 | func sceneWillResignActive(_ scene: UIScene) { 34 | // Called when the scene will move from an active state to an inactive state. 35 | // This may occur due to temporary interruptions (ex. an incoming phone call). 36 | } 37 | 38 | func sceneWillEnterForeground(_ scene: UIScene) { 39 | // Called as the scene transitions from the background to the foreground. 40 | // Use this method to undo the changes made on entering the background. 41 | } 42 | 43 | func sceneDidEnterBackground(_ scene: UIScene) { 44 | // Called as the scene transitions from the foreground to the background. 45 | // Use this method to save data, release shared resources, and store enough scene-specific state information 46 | // to restore the scene back to its current state. 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /DrawUIExample/Tabs/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // DrawUIExample 4 | // 5 | // Created by Adam Wulf on 3/14/21. 6 | // 7 | 8 | import UIKit 9 | import Inkable 10 | 11 | class BaseViewController: UIViewController, UIDocumentPickerDelegate { 12 | 13 | var allEvents: [DrawEvent] = [] 14 | 15 | let touchEventStream = TouchEventStream() 16 | 17 | required init?(coder: NSCoder) { 18 | super.init(coder: coder) 19 | 20 | touchEventStream.addConsumer { (updatedEvents) in 21 | self.allEvents.append(contentsOf: updatedEvents) 22 | } 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | let exportButton = UIButton() 29 | exportButton.setTitle("Export", for: .normal) 30 | exportButton.setTitleColor(.systemBlue, for: .normal) 31 | view.addSubview(exportButton) 32 | exportButton.translatesAutoresizingMaskIntoConstraints = false 33 | exportButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true 34 | exportButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true 35 | exportButton.addTarget(self, action: #selector(didRequestExport), for: .touchUpInside) 36 | 37 | let importButton = UIButton() 38 | importButton.setTitle("Import", for: .normal) 39 | importButton.setTitleColor(.systemBlue, for: .normal) 40 | view.addSubview(importButton) 41 | importButton.translatesAutoresizingMaskIntoConstraints = false 42 | importButton.topAnchor.constraint(equalTo: exportButton.bottomAnchor, constant: 20).isActive = true 43 | importButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true 44 | importButton.addTarget(self, action: #selector(didRequestImport), for: .touchUpInside) 45 | 46 | let clearButton = UIButton() 47 | clearButton.setTitle("Clear", for: .normal) 48 | clearButton.setTitleColor(.systemBlue, for: .normal) 49 | view.addSubview(clearButton) 50 | clearButton.translatesAutoresizingMaskIntoConstraints = false 51 | clearButton.topAnchor.constraint(equalTo: importButton.bottomAnchor, constant: 20).isActive = true 52 | clearButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true 53 | clearButton.addTarget(self, action: #selector(didRequestClear), for: .touchUpInside) 54 | } 55 | 56 | // MARK: - UIDocumentPickerDelegate 57 | 58 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { 59 | for url in urls { 60 | guard let data = try? Data(contentsOf: url) else { continue } 61 | let decoder = JSONDecoder() 62 | guard let events = try? decoder.decode(Array.self, from: data) else { continue } 63 | importEvents(events) 64 | } 65 | } 66 | 67 | func importEvents(_ events: [DrawEvent]) { 68 | let existingIdentifiers = allEvents.map({ $0.identifier }) 69 | let filtered = events.filter({ !existingIdentifiers.contains($0.identifier) }) 70 | allEvents += filtered 71 | touchEventStream.process(events: filtered) 72 | } 73 | } 74 | 75 | extension BaseViewController { 76 | 77 | @objc func didRequestExport(_ sender: UIView) { 78 | let tmpDirURL = FileManager.default.temporaryDirectory.appendingPathComponent("events").appendingPathExtension("json") 79 | let jsonEncoder = JSONEncoder() 80 | jsonEncoder.outputFormatting = [.withoutEscapingSlashes, .prettyPrinted] 81 | 82 | if let json = try? jsonEncoder.encode(allEvents) { 83 | do { 84 | try json.write(to: tmpDirURL) 85 | 86 | let sharevc = UIActivityViewController(activityItems: [tmpDirURL], applicationActivities: nil) 87 | sharevc.popoverPresentationController?.sourceView = sender 88 | present(sharevc, animated: true, completion: nil) 89 | } catch { 90 | // ignore 91 | } 92 | } 93 | } 94 | 95 | @objc func didRequestImport(_ sender: UIView) { 96 | let picker = UIDocumentPickerViewController(documentTypes: ["public.json", "public.text"], in: .import) 97 | picker.delegate = self 98 | present(picker, animated: true, completion: nil) 99 | } 100 | 101 | @objc func didRequestClear(_ sender: UIView) { 102 | allEvents = [] 103 | touchEventStream.reset() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /DrawUIExample/Tabs/BezierViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BezierViewController.swift 3 | // DrawUIExample 4 | // 5 | // Created by Adam Wulf on 3/14/21. 6 | // 7 | 8 | import UIKit 9 | import Inkable 10 | import MMSwiftToolbox 11 | 12 | class BezierViewController: BaseViewController { 13 | 14 | enum ToolIndex: Int { 15 | case pen = 0 16 | case marker = 1 17 | case highlighter = 2 18 | case eraser = 3 19 | 20 | var style: AttributesStream.ToolStyle { 21 | switch self { 22 | case .pen: 23 | return AttributesStream.ToolStyle(width: 1.5, color: .black) 24 | case .marker: 25 | return AttributesStream.ToolStyle(width: 2.5, color: .blue) 26 | case .highlighter: 27 | return AttributesStream.ToolStyle(width: 8, color: UIColor.green.withAlphaComponent(0.5)) 28 | case .eraser: 29 | return AttributesStream.ToolStyle(width: 40, color: nil) 30 | } 31 | } 32 | } 33 | 34 | let touchPathStream = TouchPathStream() 35 | let lineStream = PolylineStream() 36 | let savitzkyGolay = NaiveSavitzkyGolay() 37 | let bezierStream = BezierStream(smoother: AntigrainSmoother()) 38 | let attributeStream = AttributesStream() 39 | @IBOutlet var pathView: BezierView! 40 | @IBOutlet var toolPicker: UISegmentedControl! 41 | 42 | required init?(coder: NSCoder) { 43 | super.init(coder: coder) 44 | 45 | touchEventStream.addConsumer(touchPathStream) 46 | touchPathStream.addConsumer(lineStream) 47 | lineStream.addConsumer(savitzkyGolay) 48 | savitzkyGolay.addConsumer(bezierStream) 49 | bezierStream.addConsumer(attributeStream) 50 | } 51 | 52 | override func viewDidLoad() { 53 | super.viewDidLoad() 54 | 55 | attributeStream.addConsumer(pathView) 56 | pathView?.addGestureRecognizer(touchEventStream.gesture) 57 | } 58 | 59 | // MARK: - Actions 60 | 61 | @IBAction func toolDidChange(_ sender: Any) { 62 | guard 63 | let tool = BezierViewController.ToolIndex(rawValue: toolPicker.selectedSegmentIndex) 64 | else { 65 | return 66 | } 67 | touchEventStream.process(events: [ToolEvent(style: tool.style)]) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /DrawUIExample/Tabs/ClippedBezierViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClippedBezierViewController.swift 3 | // DrawUIExample 4 | // 5 | // Created by Adam Wulf on 4/4/21. 6 | // 7 | 8 | import UIKit 9 | import Inkable 10 | import MMSwiftToolbox 11 | 12 | class ClippedBezierViewController: BaseViewController { 13 | 14 | enum ToolIndex: Int { 15 | case pen = 0 16 | case marker = 1 17 | case highlighter = 2 18 | case eraser = 3 19 | 20 | var style: AttributesStream.ToolStyle { 21 | switch self { 22 | case .pen: 23 | return AttributesStream.ToolStyle(width: 1.5, color: .black) 24 | case .marker: 25 | return AttributesStream.ToolStyle(width: 2.5, color: .blue) 26 | case .highlighter: 27 | return AttributesStream.ToolStyle(width: 8, color: UIColor.green.withAlphaComponent(0.5)) 28 | case .eraser: 29 | return AttributesStream.ToolStyle(width: 40, color: nil) 30 | } 31 | } 32 | } 33 | 34 | let touchPathStream = TouchPathStream() 35 | let lineStream = PolylineStream() 36 | let savitzkyGolay = NaiveSavitzkyGolay() 37 | let bezierStream = BezierStream(smoother: AntigrainSmoother()) 38 | let attributeStream = AttributesStream() 39 | let clippedStream = ClippedBezierStream() 40 | @IBOutlet var pathView: ClippedBezierView! 41 | @IBOutlet var toolPicker: UISegmentedControl! 42 | 43 | required init?(coder: NSCoder) { 44 | super.init(coder: coder) 45 | 46 | touchEventStream.addConsumer(touchPathStream) 47 | touchPathStream.addConsumer(lineStream) 48 | lineStream.addConsumer(savitzkyGolay) 49 | savitzkyGolay.addConsumer(bezierStream) 50 | bezierStream.addConsumer(attributeStream) 51 | attributeStream.addConsumer(clippedStream) 52 | } 53 | 54 | override func viewDidLoad() { 55 | super.viewDidLoad() 56 | 57 | clippedStream.addConsumer(pathView) 58 | pathView?.addGestureRecognizer(touchEventStream.gesture) 59 | } 60 | 61 | // MARK: - Actions 62 | 63 | @IBAction func toolDidChange(_ sender: Any) { 64 | guard 65 | let tool = BezierViewController.ToolIndex(rawValue: toolPicker.selectedSegmentIndex) 66 | else { 67 | return 68 | } 69 | touchEventStream.process(events: [ToolEvent(style: tool.style)]) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /DrawUIExample/Tabs/DebugViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DebugViewController.swift 3 | // DrawUIExample 4 | // 5 | // Created by Adam Wulf on 8/16/20. 6 | // 7 | 8 | import UIKit 9 | import Inkable 10 | 11 | class DebugViewController: BaseViewController { 12 | 13 | let touchPathStream = TouchPathStream() 14 | let lineStream = PolylineStream() 15 | let bezierStream = BezierStream(smoother: AntigrainSmoother()) 16 | let attributeStream = AttributesStream() 17 | @IBOutlet var debugView: DebugView! 18 | 19 | let savitzkyGolay = NaiveSavitzkyGolay() 20 | let douglasPeucker = NaiveDouglasPeucker() 21 | let pointDistance = NaivePointDistance() 22 | 23 | required init?(coder: NSCoder) { 24 | super.init(coder: coder) 25 | 26 | touchEventStream.addConsumer { (updatedEvents) in 27 | self.allEvents.append(contentsOf: updatedEvents) 28 | } 29 | touchEventStream.addConsumer(touchPathStream) 30 | touchPathStream.addConsumer(lineStream) 31 | var strokeOutput = PolylineStream.Produces(lines: [], deltas: []) 32 | lineStream.addConsumer { (input) in 33 | strokeOutput = input 34 | } 35 | lineStream.addConsumer(douglasPeucker) 36 | douglasPeucker.addConsumer(pointDistance) 37 | pointDistance.addConsumer(savitzkyGolay) 38 | savitzkyGolay.addConsumer(bezierStream) 39 | bezierStream.addConsumer(attributeStream) 40 | attributeStream.addConsumer { (bezierOutput) in 41 | self.debugView?.smoothStrokes = bezierOutput.paths 42 | self.debugView?.originalStrokes = strokeOutput.lines 43 | self.debugView?.add(deltas: strokeOutput.deltas) 44 | self.debugView?.setNeedsDisplay() 45 | } 46 | } 47 | 48 | override func viewDidLoad() { 49 | super.viewDidLoad() 50 | 51 | debugView?.addGestureRecognizer(touchEventStream.gesture) 52 | } 53 | 54 | @objc override func didRequestClear(_ sender: UIView) { 55 | self.debugView?.reset() 56 | super.didRequestClear(sender) 57 | } 58 | } 59 | --------------------------------------------------------------------------------