├── .gitignore ├── LICENSE ├── README.md ├── Sequence 1.gif ├── TurtleBezierPath demo ├── TurtleBezierPath demo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── TurtleBezierPath demo │ ├── Base.lproj │ │ ├── Main_iPad.storyboard │ │ └── Main_iPhone.storyboard │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── RoundedUISlider.h │ ├── RoundedUISlider.m │ ├── TurtleAppDelegate.h │ ├── TurtleAppDelegate.m │ ├── TurtleBezierPath demo-Info.plist │ ├── TurtleBezierPath demo-Prefix.pch │ ├── TurtleBezierPath+DemoPaths.h │ ├── TurtleBezierPath+DemoPaths.m │ ├── TurtleCanvasView.h │ ├── TurtleCanvasView.m │ ├── TurtleDemoState.h │ ├── TurtleDemoState.m │ ├── TurtleViewController.h │ ├── TurtleViewController.m │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m └── TurtleBezierPath demoTests │ ├── TurtleBezierPath demoTests-Info.plist │ ├── TurtleBezierPath_demoTests.m │ └── en.lproj │ └── InfoPlist.strings ├── TurtleBezierPath.podspec └── TurtleBezierPath ├── TurtleBezierPath.h └── TurtleBezierPath.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Nigel Timothy Barber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TurtleBezierPath 2 | =================== 3 | 4 | `TurtleBezierPath` is a `UIBezierPath` subclass for Turtle Graphics: a simple, intuitive drawing system developed my Seymour Papert for the Logo programming language. 5 | 6 | The Turtle is a drawing cursor that follows the commands: `home`, `turn`, `forward`, `leftArc`, `rightArc`, `up` and `down`. As the Turtle moves, it draws a path in its wake. Many shapes are much easier to draw this way. 7 | 8 | Demo app 9 | ------------------ 10 | 11 | The app requires iOS 7 and allows you to explore Turtle Graphics drawing with simple controls. It's fun too! 12 | 13 | ![Turtle Graphics!](https://github.com/mindbrix/TurtleBezierPath/blob/f738542fd89f3eaac3a4e677908fe196bb16b8e4/Sequence%201.gif) 14 | 15 | Installation 16 | ------------------ 17 | Simply add `TurtleBezierPath.h` and `TurtleBezierPath.m` to your project, or add it as a **Cocoapod** named `TurtleBezierPath`. 18 | 19 | `TurtleBezierPath` will work with both ARC and MRC projects. 20 | 21 | @interface 22 | ----- 23 | Both `NSCoding` and `NSCopying` are fully supported. 24 | 25 | 26 | `@property( nonatomic, assign ) CGFloat bearing;` // The compass bearing of the Turtle in **degrees** 27 | 28 | * 0 - North 29 | * 90 - East 30 | * 180 - South 31 | * 270 - West 32 | 33 | 34 | `@property( nonatomic, assign ) BOOL penUp;` // When `YES` the Turtle will move but not draw 35 | 36 | 37 | `-(CGRect)boundsWithStroke;` // The bounds rect of the path including the stroke width 38 | 39 | 40 | `-(void)home;` // Move the Turtle to ( 0, 0 ) and set the bearing to 0 *degrees* 41 | 42 | 43 | `-(void)forward:(CGFloat)distance;` // Move the Turtle forward **distance** points 44 | 45 | 46 | `-(void)turn:(CGFloat)angle;` // Add **angle** degrees to the Turtle's bearing 47 | 48 | 49 | `-(void)leftArc:(CGFloat)radius turn:(CGFloat)angle;` // Move the Turtle **angle** degrees in an anti-clockwise arc of **radius** points 50 | 51 | 52 | `-(void)rightArc:(CGFloat)radius turn:(CGFloat)angle;` // Move the Turtle **angle** degrees in a clockwise arc of **radius** points 53 | 54 | 55 | `-(void)down;` // Move the pen down 56 | 57 | 58 | `-(void)up;` // Move the pen up 59 | 60 | 61 | `-(void)centreInBounds:(CGRect)bounds;` // Transform the path so that the home position is in the centre of the bounds 62 | 63 | 64 | 65 | @end 66 | ----- 67 | -------------------------------------------------------------------------------- /Sequence 1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mindbrix/TurtleBezierPath/df3203b27f8e2a10a1ece95256d261d8ec401d3e/Sequence 1.gif -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A64AA09318559841006A2736 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A64AA09218559841006A2736 /* Foundation.framework */; }; 11 | A64AA09518559841006A2736 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A64AA09418559841006A2736 /* CoreGraphics.framework */; }; 12 | A64AA09718559841006A2736 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A64AA09618559841006A2736 /* UIKit.framework */; }; 13 | A64AA09D18559841006A2736 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A64AA09B18559841006A2736 /* InfoPlist.strings */; }; 14 | A64AA09F18559841006A2736 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A64AA09E18559841006A2736 /* main.m */; }; 15 | A64AA0A318559841006A2736 /* TurtleAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A64AA0A218559841006A2736 /* TurtleAppDelegate.m */; }; 16 | A64AA0A618559841006A2736 /* Main_iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A64AA0A418559841006A2736 /* Main_iPhone.storyboard */; }; 17 | A64AA0A918559841006A2736 /* Main_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A64AA0A718559841006A2736 /* Main_iPad.storyboard */; }; 18 | A64AA0AC18559841006A2736 /* TurtleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A64AA0AB18559841006A2736 /* TurtleViewController.m */; }; 19 | A64AA0AE18559841006A2736 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A64AA0AD18559841006A2736 /* Images.xcassets */; }; 20 | A64AA0B518559841006A2736 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A64AA0B418559841006A2736 /* XCTest.framework */; }; 21 | A64AA0B618559841006A2736 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A64AA09218559841006A2736 /* Foundation.framework */; }; 22 | A64AA0B718559841006A2736 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A64AA09618559841006A2736 /* UIKit.framework */; }; 23 | A64AA0BF18559841006A2736 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A64AA0BD18559841006A2736 /* InfoPlist.strings */; }; 24 | A64AA0C118559841006A2736 /* TurtleBezierPath_demoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A64AA0C018559841006A2736 /* TurtleBezierPath_demoTests.m */; }; 25 | A64AA0CF1855BA41006A2736 /* TurtleBezierPath.m in Sources */ = {isa = PBXBuildFile; fileRef = A64AA0CE1855BA41006A2736 /* TurtleBezierPath.m */; }; 26 | A64AA0D918565F8A006A2736 /* TurtleCanvasView.m in Sources */ = {isa = PBXBuildFile; fileRef = A64AA0D818565F8A006A2736 /* TurtleCanvasView.m */; }; 27 | A655667F18574FCC0066C4CF /* RoundedUISlider.m in Sources */ = {isa = PBXBuildFile; fileRef = A655667E18574FCC0066C4CF /* RoundedUISlider.m */; }; 28 | A6985CD21858FD7D00AF0F19 /* TurtleDemoState.m in Sources */ = {isa = PBXBuildFile; fileRef = A6985CD11858FD7D00AF0F19 /* TurtleDemoState.m */; }; 29 | A6985CE1185A48BE00AF0F19 /* TurtleBezierPath+DemoPaths.m in Sources */ = {isa = PBXBuildFile; fileRef = A6985CE0185A48BE00AF0F19 /* TurtleBezierPath+DemoPaths.m */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | A64AA0B818559841006A2736 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = A64AA08718559841006A2736 /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = A64AA08E18559841006A2736; 38 | remoteInfo = "TurtleBezierPath demo"; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | A64AA08F18559841006A2736 /* TurtleBezierPath demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TurtleBezierPath demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | A64AA09218559841006A2736 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 45 | A64AA09418559841006A2736 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 46 | A64AA09618559841006A2736 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 47 | A64AA09A18559841006A2736 /* TurtleBezierPath demo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TurtleBezierPath demo-Info.plist"; sourceTree = ""; }; 48 | A64AA09C18559841006A2736 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 49 | A64AA09E18559841006A2736 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 50 | A64AA0A018559841006A2736 /* TurtleBezierPath demo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TurtleBezierPath demo-Prefix.pch"; sourceTree = ""; }; 51 | A64AA0A118559841006A2736 /* TurtleAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TurtleAppDelegate.h; sourceTree = ""; }; 52 | A64AA0A218559841006A2736 /* TurtleAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TurtleAppDelegate.m; sourceTree = ""; }; 53 | A64AA0A518559841006A2736 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main_iPhone.storyboard; sourceTree = ""; }; 54 | A64AA0A818559841006A2736 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main_iPad.storyboard; sourceTree = ""; }; 55 | A64AA0AA18559841006A2736 /* TurtleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TurtleViewController.h; sourceTree = ""; }; 56 | A64AA0AB18559841006A2736 /* TurtleViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TurtleViewController.m; sourceTree = ""; }; 57 | A64AA0AD18559841006A2736 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 58 | A64AA0B318559841006A2736 /* TurtleBezierPath demoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "TurtleBezierPath demoTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | A64AA0B418559841006A2736 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 60 | A64AA0BC18559841006A2736 /* TurtleBezierPath demoTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TurtleBezierPath demoTests-Info.plist"; sourceTree = ""; }; 61 | A64AA0BE18559841006A2736 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 62 | A64AA0C018559841006A2736 /* TurtleBezierPath_demoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TurtleBezierPath_demoTests.m; sourceTree = ""; }; 63 | A64AA0CD1855BA41006A2736 /* TurtleBezierPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TurtleBezierPath.h; path = ../../TurtleBezierPath/TurtleBezierPath.h; sourceTree = ""; }; 64 | A64AA0CE1855BA41006A2736 /* TurtleBezierPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TurtleBezierPath.m; path = ../../TurtleBezierPath/TurtleBezierPath.m; sourceTree = ""; }; 65 | A64AA0D718565F8A006A2736 /* TurtleCanvasView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TurtleCanvasView.h; sourceTree = ""; }; 66 | A64AA0D818565F8A006A2736 /* TurtleCanvasView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TurtleCanvasView.m; sourceTree = ""; }; 67 | A655667D18574FCC0066C4CF /* RoundedUISlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoundedUISlider.h; sourceTree = ""; }; 68 | A655667E18574FCC0066C4CF /* RoundedUISlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoundedUISlider.m; sourceTree = ""; }; 69 | A6985CD01858FD7D00AF0F19 /* TurtleDemoState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TurtleDemoState.h; sourceTree = ""; }; 70 | A6985CD11858FD7D00AF0F19 /* TurtleDemoState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TurtleDemoState.m; sourceTree = ""; }; 71 | A6985CDF185A48BE00AF0F19 /* TurtleBezierPath+DemoPaths.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TurtleBezierPath+DemoPaths.h"; sourceTree = ""; }; 72 | A6985CE0185A48BE00AF0F19 /* TurtleBezierPath+DemoPaths.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "TurtleBezierPath+DemoPaths.m"; sourceTree = ""; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | A64AA08C18559841006A2736 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | A64AA09518559841006A2736 /* CoreGraphics.framework in Frameworks */, 81 | A64AA09718559841006A2736 /* UIKit.framework in Frameworks */, 82 | A64AA09318559841006A2736 /* Foundation.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | A64AA0B018559841006A2736 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | A64AA0B518559841006A2736 /* XCTest.framework in Frameworks */, 91 | A64AA0B718559841006A2736 /* UIKit.framework in Frameworks */, 92 | A64AA0B618559841006A2736 /* Foundation.framework in Frameworks */, 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | /* End PBXFrameworksBuildPhase section */ 97 | 98 | /* Begin PBXGroup section */ 99 | A64AA08618559841006A2736 = { 100 | isa = PBXGroup; 101 | children = ( 102 | A64AA09818559841006A2736 /* TurtleBezierPath demo */, 103 | A64AA0BA18559841006A2736 /* TurtleBezierPath demoTests */, 104 | A64AA09118559841006A2736 /* Frameworks */, 105 | A64AA09018559841006A2736 /* Products */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | A64AA09018559841006A2736 /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | A64AA08F18559841006A2736 /* TurtleBezierPath demo.app */, 113 | A64AA0B318559841006A2736 /* TurtleBezierPath demoTests.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | A64AA09118559841006A2736 /* Frameworks */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | A64AA09218559841006A2736 /* Foundation.framework */, 122 | A64AA09418559841006A2736 /* CoreGraphics.framework */, 123 | A64AA09618559841006A2736 /* UIKit.framework */, 124 | A64AA0B418559841006A2736 /* XCTest.framework */, 125 | ); 126 | name = Frameworks; 127 | sourceTree = ""; 128 | }; 129 | A64AA09818559841006A2736 /* TurtleBezierPath demo */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | A6985CCF1858D4FE00AF0F19 /* TurtleBezierPath */, 133 | A64AA0A118559841006A2736 /* TurtleAppDelegate.h */, 134 | A64AA0A218559841006A2736 /* TurtleAppDelegate.m */, 135 | A64AA0A418559841006A2736 /* Main_iPhone.storyboard */, 136 | A64AA0A718559841006A2736 /* Main_iPad.storyboard */, 137 | A64AA0AA18559841006A2736 /* TurtleViewController.h */, 138 | A64AA0AB18559841006A2736 /* TurtleViewController.m */, 139 | A64AA0AD18559841006A2736 /* Images.xcassets */, 140 | A64AA09918559841006A2736 /* Supporting Files */, 141 | A64AA0D718565F8A006A2736 /* TurtleCanvasView.h */, 142 | A64AA0D818565F8A006A2736 /* TurtleCanvasView.m */, 143 | A655667D18574FCC0066C4CF /* RoundedUISlider.h */, 144 | A655667E18574FCC0066C4CF /* RoundedUISlider.m */, 145 | A6985CD01858FD7D00AF0F19 /* TurtleDemoState.h */, 146 | A6985CD11858FD7D00AF0F19 /* TurtleDemoState.m */, 147 | A6985CDF185A48BE00AF0F19 /* TurtleBezierPath+DemoPaths.h */, 148 | A6985CE0185A48BE00AF0F19 /* TurtleBezierPath+DemoPaths.m */, 149 | ); 150 | path = "TurtleBezierPath demo"; 151 | sourceTree = ""; 152 | }; 153 | A64AA09918559841006A2736 /* Supporting Files */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | A64AA09A18559841006A2736 /* TurtleBezierPath demo-Info.plist */, 157 | A64AA09B18559841006A2736 /* InfoPlist.strings */, 158 | A64AA09E18559841006A2736 /* main.m */, 159 | A64AA0A018559841006A2736 /* TurtleBezierPath demo-Prefix.pch */, 160 | ); 161 | name = "Supporting Files"; 162 | sourceTree = ""; 163 | }; 164 | A64AA0BA18559841006A2736 /* TurtleBezierPath demoTests */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | A64AA0C018559841006A2736 /* TurtleBezierPath_demoTests.m */, 168 | A64AA0BB18559841006A2736 /* Supporting Files */, 169 | ); 170 | path = "TurtleBezierPath demoTests"; 171 | sourceTree = ""; 172 | }; 173 | A64AA0BB18559841006A2736 /* Supporting Files */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | A64AA0BC18559841006A2736 /* TurtleBezierPath demoTests-Info.plist */, 177 | A64AA0BD18559841006A2736 /* InfoPlist.strings */, 178 | ); 179 | name = "Supporting Files"; 180 | sourceTree = ""; 181 | }; 182 | A6985CCF1858D4FE00AF0F19 /* TurtleBezierPath */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | A64AA0CD1855BA41006A2736 /* TurtleBezierPath.h */, 186 | A64AA0CE1855BA41006A2736 /* TurtleBezierPath.m */, 187 | ); 188 | name = TurtleBezierPath; 189 | sourceTree = ""; 190 | }; 191 | /* End PBXGroup section */ 192 | 193 | /* Begin PBXNativeTarget section */ 194 | A64AA08E18559841006A2736 /* TurtleBezierPath demo */ = { 195 | isa = PBXNativeTarget; 196 | buildConfigurationList = A64AA0C418559841006A2736 /* Build configuration list for PBXNativeTarget "TurtleBezierPath demo" */; 197 | buildPhases = ( 198 | A64AA08B18559841006A2736 /* Sources */, 199 | A64AA08C18559841006A2736 /* Frameworks */, 200 | A64AA08D18559841006A2736 /* Resources */, 201 | ); 202 | buildRules = ( 203 | ); 204 | dependencies = ( 205 | ); 206 | name = "TurtleBezierPath demo"; 207 | productName = "TurtleBezierPath demo"; 208 | productReference = A64AA08F18559841006A2736 /* TurtleBezierPath demo.app */; 209 | productType = "com.apple.product-type.application"; 210 | }; 211 | A64AA0B218559841006A2736 /* TurtleBezierPath demoTests */ = { 212 | isa = PBXNativeTarget; 213 | buildConfigurationList = A64AA0C718559841006A2736 /* Build configuration list for PBXNativeTarget "TurtleBezierPath demoTests" */; 214 | buildPhases = ( 215 | A64AA0AF18559841006A2736 /* Sources */, 216 | A64AA0B018559841006A2736 /* Frameworks */, 217 | A64AA0B118559841006A2736 /* Resources */, 218 | ); 219 | buildRules = ( 220 | ); 221 | dependencies = ( 222 | A64AA0B918559841006A2736 /* PBXTargetDependency */, 223 | ); 224 | name = "TurtleBezierPath demoTests"; 225 | productName = "TurtleBezierPath demoTests"; 226 | productReference = A64AA0B318559841006A2736 /* TurtleBezierPath demoTests.xctest */; 227 | productType = "com.apple.product-type.bundle.unit-test"; 228 | }; 229 | /* End PBXNativeTarget section */ 230 | 231 | /* Begin PBXProject section */ 232 | A64AA08718559841006A2736 /* Project object */ = { 233 | isa = PBXProject; 234 | attributes = { 235 | CLASSPREFIX = Turtle; 236 | LastUpgradeCheck = 0500; 237 | ORGANIZATIONNAME = "Nigel Barber"; 238 | TargetAttributes = { 239 | A64AA0B218559841006A2736 = { 240 | TestTargetID = A64AA08E18559841006A2736; 241 | }; 242 | }; 243 | }; 244 | buildConfigurationList = A64AA08A18559841006A2736 /* Build configuration list for PBXProject "TurtleBezierPath demo" */; 245 | compatibilityVersion = "Xcode 3.2"; 246 | developmentRegion = English; 247 | hasScannedForEncodings = 0; 248 | knownRegions = ( 249 | en, 250 | Base, 251 | ); 252 | mainGroup = A64AA08618559841006A2736; 253 | productRefGroup = A64AA09018559841006A2736 /* Products */; 254 | projectDirPath = ""; 255 | projectRoot = ""; 256 | targets = ( 257 | A64AA08E18559841006A2736 /* TurtleBezierPath demo */, 258 | A64AA0B218559841006A2736 /* TurtleBezierPath demoTests */, 259 | ); 260 | }; 261 | /* End PBXProject section */ 262 | 263 | /* Begin PBXResourcesBuildPhase section */ 264 | A64AA08D18559841006A2736 /* Resources */ = { 265 | isa = PBXResourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | A64AA0A918559841006A2736 /* Main_iPad.storyboard in Resources */, 269 | A64AA0AE18559841006A2736 /* Images.xcassets in Resources */, 270 | A64AA0A618559841006A2736 /* Main_iPhone.storyboard in Resources */, 271 | A64AA09D18559841006A2736 /* InfoPlist.strings in Resources */, 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | A64AA0B118559841006A2736 /* Resources */ = { 276 | isa = PBXResourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | A64AA0BF18559841006A2736 /* InfoPlist.strings in Resources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXResourcesBuildPhase section */ 284 | 285 | /* Begin PBXSourcesBuildPhase section */ 286 | A64AA08B18559841006A2736 /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | A64AA0D918565F8A006A2736 /* TurtleCanvasView.m in Sources */, 291 | A64AA0AC18559841006A2736 /* TurtleViewController.m in Sources */, 292 | A655667F18574FCC0066C4CF /* RoundedUISlider.m in Sources */, 293 | A64AA0A318559841006A2736 /* TurtleAppDelegate.m in Sources */, 294 | A6985CD21858FD7D00AF0F19 /* TurtleDemoState.m in Sources */, 295 | A64AA09F18559841006A2736 /* main.m in Sources */, 296 | A64AA0CF1855BA41006A2736 /* TurtleBezierPath.m in Sources */, 297 | A6985CE1185A48BE00AF0F19 /* TurtleBezierPath+DemoPaths.m in Sources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | A64AA0AF18559841006A2736 /* Sources */ = { 302 | isa = PBXSourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | A64AA0C118559841006A2736 /* TurtleBezierPath_demoTests.m in Sources */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | /* End PBXSourcesBuildPhase section */ 310 | 311 | /* Begin PBXTargetDependency section */ 312 | A64AA0B918559841006A2736 /* PBXTargetDependency */ = { 313 | isa = PBXTargetDependency; 314 | target = A64AA08E18559841006A2736 /* TurtleBezierPath demo */; 315 | targetProxy = A64AA0B818559841006A2736 /* PBXContainerItemProxy */; 316 | }; 317 | /* End PBXTargetDependency section */ 318 | 319 | /* Begin PBXVariantGroup section */ 320 | A64AA09B18559841006A2736 /* InfoPlist.strings */ = { 321 | isa = PBXVariantGroup; 322 | children = ( 323 | A64AA09C18559841006A2736 /* en */, 324 | ); 325 | name = InfoPlist.strings; 326 | sourceTree = ""; 327 | }; 328 | A64AA0A418559841006A2736 /* Main_iPhone.storyboard */ = { 329 | isa = PBXVariantGroup; 330 | children = ( 331 | A64AA0A518559841006A2736 /* Base */, 332 | ); 333 | name = Main_iPhone.storyboard; 334 | sourceTree = ""; 335 | }; 336 | A64AA0A718559841006A2736 /* Main_iPad.storyboard */ = { 337 | isa = PBXVariantGroup; 338 | children = ( 339 | A64AA0A818559841006A2736 /* Base */, 340 | ); 341 | name = Main_iPad.storyboard; 342 | sourceTree = ""; 343 | }; 344 | A64AA0BD18559841006A2736 /* InfoPlist.strings */ = { 345 | isa = PBXVariantGroup; 346 | children = ( 347 | A64AA0BE18559841006A2736 /* en */, 348 | ); 349 | name = InfoPlist.strings; 350 | sourceTree = ""; 351 | }; 352 | /* End PBXVariantGroup section */ 353 | 354 | /* Begin XCBuildConfiguration section */ 355 | A64AA0C218559841006A2736 /* Debug */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ALWAYS_SEARCH_USER_PATHS = NO; 359 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 360 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 361 | CLANG_CXX_LIBRARY = "libc++"; 362 | CLANG_ENABLE_MODULES = YES; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_WARN_BOOL_CONVERSION = YES; 365 | CLANG_WARN_CONSTANT_CONVERSION = YES; 366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 371 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 372 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 373 | COPY_PHASE_STRIP = NO; 374 | GCC_C_LANGUAGE_STANDARD = gnu99; 375 | GCC_DYNAMIC_NO_PIC = NO; 376 | GCC_OPTIMIZATION_LEVEL = 0; 377 | GCC_PREPROCESSOR_DEFINITIONS = ( 378 | "DEBUG=1", 379 | "$(inherited)", 380 | ); 381 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 384 | GCC_WARN_UNDECLARED_SELECTOR = YES; 385 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 386 | GCC_WARN_UNUSED_FUNCTION = YES; 387 | GCC_WARN_UNUSED_VARIABLE = YES; 388 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 389 | ONLY_ACTIVE_ARCH = YES; 390 | SDKROOT = iphoneos; 391 | TARGETED_DEVICE_FAMILY = "1,2"; 392 | }; 393 | name = Debug; 394 | }; 395 | A64AA0C318559841006A2736 /* Release */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 400 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 401 | CLANG_CXX_LIBRARY = "libc++"; 402 | CLANG_ENABLE_MODULES = YES; 403 | CLANG_ENABLE_OBJC_ARC = YES; 404 | CLANG_WARN_BOOL_CONVERSION = YES; 405 | CLANG_WARN_CONSTANT_CONVERSION = YES; 406 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 407 | CLANG_WARN_EMPTY_BODY = YES; 408 | CLANG_WARN_ENUM_CONVERSION = YES; 409 | CLANG_WARN_INT_CONVERSION = YES; 410 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 411 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 412 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 413 | COPY_PHASE_STRIP = YES; 414 | ENABLE_NS_ASSERTIONS = NO; 415 | GCC_C_LANGUAGE_STANDARD = gnu99; 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 423 | SDKROOT = iphoneos; 424 | TARGETED_DEVICE_FAMILY = "1,2"; 425 | VALIDATE_PRODUCT = YES; 426 | }; 427 | name = Release; 428 | }; 429 | A64AA0C518559841006A2736 /* Debug */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 433 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 434 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 435 | GCC_PREFIX_HEADER = "TurtleBezierPath demo/TurtleBezierPath demo-Prefix.pch"; 436 | INFOPLIST_FILE = "TurtleBezierPath demo/TurtleBezierPath demo-Info.plist"; 437 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | WRAPPER_EXTENSION = app; 440 | }; 441 | name = Debug; 442 | }; 443 | A64AA0C618559841006A2736 /* Release */ = { 444 | isa = XCBuildConfiguration; 445 | buildSettings = { 446 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 447 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 448 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 449 | GCC_PREFIX_HEADER = "TurtleBezierPath demo/TurtleBezierPath demo-Prefix.pch"; 450 | INFOPLIST_FILE = "TurtleBezierPath demo/TurtleBezierPath demo-Info.plist"; 451 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | WRAPPER_EXTENSION = app; 454 | }; 455 | name = Release; 456 | }; 457 | A64AA0C818559841006A2736 /* Debug */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 461 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/TurtleBezierPath demo.app/TurtleBezierPath demo"; 462 | FRAMEWORK_SEARCH_PATHS = ( 463 | "$(SDKROOT)/Developer/Library/Frameworks", 464 | "$(inherited)", 465 | "$(DEVELOPER_FRAMEWORKS_DIR)", 466 | ); 467 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 468 | GCC_PREFIX_HEADER = "TurtleBezierPath demo/TurtleBezierPath demo-Prefix.pch"; 469 | GCC_PREPROCESSOR_DEFINITIONS = ( 470 | "DEBUG=1", 471 | "$(inherited)", 472 | ); 473 | INFOPLIST_FILE = "TurtleBezierPath demoTests/TurtleBezierPath demoTests-Info.plist"; 474 | PRODUCT_NAME = "$(TARGET_NAME)"; 475 | TEST_HOST = "$(BUNDLE_LOADER)"; 476 | WRAPPER_EXTENSION = xctest; 477 | }; 478 | name = Debug; 479 | }; 480 | A64AA0C918559841006A2736 /* Release */ = { 481 | isa = XCBuildConfiguration; 482 | buildSettings = { 483 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 484 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/TurtleBezierPath demo.app/TurtleBezierPath demo"; 485 | FRAMEWORK_SEARCH_PATHS = ( 486 | "$(SDKROOT)/Developer/Library/Frameworks", 487 | "$(inherited)", 488 | "$(DEVELOPER_FRAMEWORKS_DIR)", 489 | ); 490 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 491 | GCC_PREFIX_HEADER = "TurtleBezierPath demo/TurtleBezierPath demo-Prefix.pch"; 492 | INFOPLIST_FILE = "TurtleBezierPath demoTests/TurtleBezierPath demoTests-Info.plist"; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | TEST_HOST = "$(BUNDLE_LOADER)"; 495 | WRAPPER_EXTENSION = xctest; 496 | }; 497 | name = Release; 498 | }; 499 | /* End XCBuildConfiguration section */ 500 | 501 | /* Begin XCConfigurationList section */ 502 | A64AA08A18559841006A2736 /* Build configuration list for PBXProject "TurtleBezierPath demo" */ = { 503 | isa = XCConfigurationList; 504 | buildConfigurations = ( 505 | A64AA0C218559841006A2736 /* Debug */, 506 | A64AA0C318559841006A2736 /* Release */, 507 | ); 508 | defaultConfigurationIsVisible = 0; 509 | defaultConfigurationName = Release; 510 | }; 511 | A64AA0C418559841006A2736 /* Build configuration list for PBXNativeTarget "TurtleBezierPath demo" */ = { 512 | isa = XCConfigurationList; 513 | buildConfigurations = ( 514 | A64AA0C518559841006A2736 /* Debug */, 515 | A64AA0C618559841006A2736 /* Release */, 516 | ); 517 | defaultConfigurationIsVisible = 0; 518 | defaultConfigurationName = Release; 519 | }; 520 | A64AA0C718559841006A2736 /* Build configuration list for PBXNativeTarget "TurtleBezierPath demoTests" */ = { 521 | isa = XCConfigurationList; 522 | buildConfigurations = ( 523 | A64AA0C818559841006A2736 /* Debug */, 524 | A64AA0C918559841006A2736 /* Release */, 525 | ); 526 | defaultConfigurationIsVisible = 0; 527 | defaultConfigurationName = Release; 528 | }; 529 | /* End XCConfigurationList section */ 530 | }; 531 | rootObject = A64AA08718559841006A2736 /* Project object */; 532 | } 533 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/Base.lproj/Main_iPad.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 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/Base.lproj/Main_iPhone.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 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/RoundedUISlider.h: -------------------------------------------------------------------------------- 1 | // 2 | // RoundedUISlider.h 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 10/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RoundedUISlider : UISlider 12 | 13 | @property( nonatomic, assign ) CGFloat rounding; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/RoundedUISlider.m: -------------------------------------------------------------------------------- 1 | // 2 | // RoundedUISlider.m 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 10/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import "RoundedUISlider.h" 10 | 11 | @implementation RoundedUISlider 12 | 13 | - (id)initWithFrame:(CGRect)frame 14 | { 15 | self = [super initWithFrame:frame]; 16 | if (self) { 17 | // Initialization code 18 | } 19 | return self; 20 | } 21 | 22 | 23 | -(float)value 24 | { 25 | return roundToStep( super.value, self.rounding ); 26 | } 27 | 28 | 29 | 30 | #pragma mark - Maths 31 | 32 | static inline float roundToStep( float number, float step ) 33 | { 34 | return ( step != 0.0f ) ? floorf( number / step ) * step : number; 35 | } 36 | 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleAppDelegate.h 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TurtleAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleAppDelegate.m 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import "TurtleAppDelegate.h" 10 | 11 | @implementation TurtleAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleBezierPath demo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | uk.co.mindbrix.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main_iPhone 29 | UIMainStoryboardFile~ipad 30 | Main_iPad 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleBezierPath demo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleBezierPath+DemoPaths.h: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleBezierPath+DemoPaths.h 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 12/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import "TurtleBezierPath.h" 10 | 11 | @interface TurtleBezierPath (DemoPaths) 12 | 13 | +(TurtleBezierPath *)demoPatternPath; 14 | +(TurtleBezierPath *)pointerPath; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleBezierPath+DemoPaths.m: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleBezierPath+DemoPaths.m 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 12/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import "TurtleBezierPath+DemoPaths.h" 10 | 11 | @implementation TurtleBezierPath (DemoPaths) 12 | 13 | 14 | #pragma mark - Demo pattern 15 | 16 | +(TurtleBezierPath *)demoPatternPath 17 | { 18 | TurtleBezierPath *turtlePath = [ TurtleBezierPath new ]; 19 | 20 | [ turtlePath home ]; 21 | [ turtlePath up ]; 22 | [ turtlePath forward:100.0f ]; 23 | [ turtlePath down ]; 24 | 25 | for( NSInteger i = 0; i < 400; i++ ) 26 | { 27 | CGFloat scale = 1 - (CGFloat)i / 400.0f; 28 | 29 | [ turtlePath rightArc:20.0f * scale turn:200.0f ]; 30 | [ turtlePath turn:-180.0f ]; 31 | } 32 | 33 | turtlePath.lineWidth = 1.0f; 34 | 35 | TurtleBezierPath *clone = [ turtlePath copy ]; 36 | 37 | NSData *cloneData = [ NSKeyedArchiver archivedDataWithRootObject:clone ]; 38 | TurtleBezierPath *unarchivedPath = [ NSKeyedUnarchiver unarchiveObjectWithData:cloneData ]; 39 | 40 | return unarchivedPath; 41 | } 42 | 43 | +(TurtleBezierPath *)pointerPath 44 | { 45 | TurtleBezierPath *path = [ TurtleBezierPath new ]; 46 | 47 | CGFloat scale = 2.0f; 48 | 49 | path.lineCapStyle = kCGLineCapRound; 50 | path.lineWidth = 2.0 * scale; 51 | 52 | [ path home ]; 53 | [ path forward:0.01f ]; 54 | 55 | [ path up ]; 56 | 57 | [ path home ]; 58 | [ path forward: 20.0f * scale ]; 59 | [ path turn:180.0f ]; 60 | [ path down ]; 61 | [ path leftArc:40.f * scale turn:30.0f ]; 62 | 63 | [ path up ]; 64 | 65 | [ path home ]; 66 | [ path forward: 20.0f * scale ]; 67 | [ path turn:180.0f ]; 68 | [ path down ]; 69 | [ path rightArc:40.f * scale turn:30.0f ]; 70 | 71 | return path; 72 | } 73 | 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleCanvasView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleCanvasView.h 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TurtleBezierPath.h" 12 | 13 | 14 | @interface TurtleCanvasView : UIView 15 | 16 | @property( nonatomic, strong ) UIColor *fillColour; 17 | @property( nonatomic, strong ) TurtleBezierPath *path; 18 | @property( nonatomic, strong ) UIColor *strokeColour; 19 | 20 | -(id)initWithPath:(TurtleBezierPath *)path; 21 | -(void)positionOnPath:(TurtleBezierPath *)path; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleCanvasView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleCanvasView.m 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import "TurtleCanvasView.h" 10 | 11 | 12 | @implementation TurtleCanvasView 13 | 14 | - (id)initWithFrame:(CGRect)frame 15 | { 16 | self = [super initWithFrame:frame]; 17 | 18 | if (self) 19 | { 20 | [ self setDefaults ]; 21 | } 22 | return self; 23 | } 24 | 25 | -(id)initWithPath:(TurtleBezierPath *)path 26 | { 27 | self = [super initWithFrame:[ path boundsForView ]]; 28 | 29 | if (self) 30 | { 31 | [ self setDefaults ]; 32 | self.path = path; 33 | } 34 | return self; 35 | } 36 | 37 | -(void)setDefaults 38 | { 39 | // Initialization code 40 | self.backgroundColor = [ UIColor whiteColor ]; 41 | self.strokeColour = [ UIColor blackColor ]; 42 | } 43 | 44 | -(void)positionOnPath:(TurtleBezierPath *)path 45 | { 46 | self.center = path.currentPoint; 47 | self.transform = CGAffineTransformMakeRotation( path.bearing * M_PI / 180.0f ); 48 | } 49 | 50 | 51 | #pragma mark - Properties 52 | 53 | -(void)setFillColour:(UIColor *)fillColour 54 | { 55 | _fillColour = fillColour; 56 | 57 | [ self setNeedsDisplay ]; 58 | } 59 | 60 | -(void)setFrame:(CGRect)frame 61 | { 62 | [ super setFrame:frame ]; 63 | 64 | [ self setNeedsDisplay ]; 65 | } 66 | 67 | -(void)setPath:(TurtleBezierPath *)path 68 | { 69 | _path = path; 70 | 71 | [ self setNeedsDisplay ]; 72 | } 73 | 74 | -(void)setStrokeColour:(UIColor *)strokeColour 75 | { 76 | _strokeColour = strokeColour; 77 | 78 | [ self setNeedsDisplay ]; 79 | } 80 | 81 | 82 | #pragma mark - drawRect 83 | 84 | - (void)drawRect:(CGRect)rect 85 | { 86 | if( self.path ) 87 | { 88 | TurtleBezierPath *drawPath = [ self.path copy ]; 89 | [ drawPath centreInBounds:self.bounds ]; 90 | 91 | if( self.fillColour ) 92 | { 93 | [ self.fillColour setFill ]; 94 | [ drawPath fill ]; 95 | } 96 | 97 | if( self.strokeColour ) 98 | { 99 | [ self.strokeColour set ]; 100 | [ drawPath stroke ]; 101 | } 102 | } 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleDemoState.h: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleDemoState.h 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 11/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TurtleBezierPath.h" 12 | 13 | @interface TurtleDemoState : NSObject 14 | 15 | @property( nonatomic, readonly ) NSInteger index; 16 | @property( nonatomic, strong, readonly ) TurtleBezierPath *path; 17 | @property( nonatomic, strong, readonly ) TurtleBezierPath *previewPath; 18 | @property( nonatomic, readonly ) CGFloat value0; 19 | @property( nonatomic, readonly ) CGFloat value1; 20 | 21 | -(instancetype)initWithIndex:(NSInteger)index path:(TurtleBezierPath *)path previewPath:(TurtleBezierPath *)previewPath value0:(NSInteger)value0 value1:(NSInteger)value1; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleDemoState.m: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleDemoState.m 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 11/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import "TurtleDemoState.h" 10 | 11 | @implementation TurtleDemoState 12 | 13 | -(instancetype)initWithIndex:(NSInteger)index path:(TurtleBezierPath *)path previewPath:(TurtleBezierPath *)previewPath value0:(NSInteger)value0 value1:(NSInteger)value1 14 | { 15 | self = [ super init ]; 16 | 17 | if( self ) 18 | { 19 | _index = index; 20 | _path = path; 21 | _previewPath = previewPath; 22 | _value0 = value0; 23 | _value1 = value1; 24 | } 25 | 26 | return self; 27 | } 28 | 29 | 30 | -(BOOL)isEqual:(id)object 31 | { 32 | TurtleDemoState *aState = object; 33 | 34 | return ( self.index == aState.index && 35 | self.value0 == aState.value0 && 36 | self.value1 == aState.value1 && 37 | [ self.path isEqual:aState.path ] && 38 | [ self.previewPath isEqual:aState.previewPath ]); 39 | } 40 | 41 | -(NSString *)description 42 | { 43 | return [ NSString stringWithFormat:@"index = %ld, path = %@, previewPath = %@, value0 = %g, value1 = %g", (long)self.index, self.path, self.previewPath, self.value0, self.value1 ]; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleViewController.h 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TurtleViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/TurtleViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleViewController.m 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import "TurtleViewController.h" 10 | 11 | #import "RoundedUISlider.h" 12 | 13 | #import "TurtleCanvasView.h" 14 | #import "TurtleDemoState.h" 15 | #import "TurtleBezierPath.h" 16 | #import "TurtleBezierPath+DemoPaths.h" 17 | 18 | 19 | @interface TurtleViewController () 20 | 21 | @property( nonatomic, strong ) TurtleDemoState *clearState; 22 | @property( nonatomic, strong ) UISegmentedControl *commandControl; 23 | @property( nonatomic, strong ) UILabel *commandLabel; 24 | @property( nonatomic, strong ) TurtleCanvasView *canvasView; 25 | 26 | @property( nonatomic, assign ) TurtleDemoState *currentState; 27 | 28 | @property( nonatomic, strong ) TurtleCanvasView *pointerView; 29 | 30 | @property( nonatomic, strong ) TurtleBezierPath *path; 31 | @property( nonatomic, strong ) TurtleBezierPath *previewPath; 32 | 33 | @property( nonatomic, strong ) NSTimer *undoTimer; 34 | @property( nonatomic, strong ) TurtleDemoState *undoComparisonState; 35 | 36 | @property( nonatomic, strong ) RoundedUISlider *valueSlider0; 37 | @property( nonatomic, strong ) RoundedUISlider *valueSlider1; 38 | 39 | @end 40 | 41 | 42 | @implementation TurtleViewController 43 | 44 | - (BOOL)canBecomeFirstResponder { 45 | return YES; 46 | } 47 | 48 | - (void)viewDidLoad 49 | { 50 | [super viewDidLoad]; 51 | // Do any additional setup after loading the view, typically from a nib. 52 | 53 | [ self initDemoApp ]; 54 | } 55 | 56 | -(void)viewWillAppear:(BOOL)animated 57 | { 58 | [ super viewWillAppear:animated ]; 59 | 60 | [ self becomeFirstResponder ]; 61 | 62 | [ self layoutViews ]; 63 | } 64 | 65 | - (void)viewWillDisappear:(BOOL)animated 66 | { 67 | // No longer need to receive the shake gesture event since the view is gone 68 | [self resignFirstResponder]; 69 | 70 | [super viewWillDisappear:animated]; 71 | } 72 | 73 | 74 | -(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 75 | { 76 | [ self layoutViews ]; 77 | } 78 | 79 | #pragma mark - Init 80 | 81 | -(void)initDemoApp 82 | { 83 | self.canvasView = [[ TurtleCanvasView alloc ] initWithFrame:self.view.bounds ]; 84 | self.canvasView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 85 | [ self.view addSubview:self.canvasView ]; 86 | 87 | UIScreenEdgePanGestureRecognizer *undoSwipe = [[ UIScreenEdgePanGestureRecognizer alloc ] initWithTarget:self action:@selector(performUndo:)]; 88 | undoSwipe.edges = UIRectEdgeLeft; 89 | [ self.canvasView addGestureRecognizer:undoSwipe ]; 90 | 91 | UIScreenEdgePanGestureRecognizer *redoSwipe = [[ UIScreenEdgePanGestureRecognizer alloc ] initWithTarget:self action:@selector(performRedo:)]; 92 | redoSwipe.edges = UIRectEdgeRight; 93 | [ self.canvasView addGestureRecognizer:redoSwipe ]; 94 | 95 | CGFloat fontScale = ([[ UIDevice currentDevice ] userInterfaceIdiom ] == UIUserInterfaceIdiomPad ) ? 1.5f : 1.0f; 96 | 97 | self.pointerView = [[ TurtleCanvasView alloc ] initWithPath:[ TurtleBezierPath pointerPath ]]; 98 | self.pointerView.backgroundColor = [ UIColor clearColor ]; 99 | self.pointerView.strokeColour = [ UIColor redColor ]; 100 | [ self.view addSubview:self.pointerView ]; 101 | 102 | self.commandControl = [[ UISegmentedControl alloc ] initWithItems:@[ @"forward", @"turn", @"leftArc", @"rightArc", @"up" ]]; 103 | [ self.commandControl addTarget:self action:@selector(commmandSelected:) forControlEvents:UIControlEventValueChanged ]; 104 | [ self.commandControl setTitleTextAttributes:@{ NSFontAttributeName : [ UIFont fontWithName:@"HelveticaNeue-Medium" size:13.0f * fontScale ]} forState:UIControlStateNormal ]; 105 | [ self.view addSubview:self.commandControl ]; 106 | 107 | self.commandLabel = [ UILabel new ]; 108 | self.commandLabel.backgroundColor = [ UIColor clearColor ]; 109 | self.commandLabel.font = [ UIFont fontWithName:@"Menlo-Regular" size:17.0f * fontScale ]; 110 | self.commandLabel.textAlignment = NSTextAlignmentCenter; 111 | self.commandLabel.textColor = self.commandControl.tintColor; 112 | self.commandLabel.text = @""; 113 | [ self.view addSubview:self.commandLabel ]; 114 | 115 | self.valueSlider0 = [ RoundedUISlider new ]; 116 | [ self.valueSlider0 addTarget:self action:@selector(sliderValueChanged0:) forControlEvents:UIControlEventValueChanged ]; 117 | [ self.view addSubview:self.valueSlider0 ]; 118 | 119 | self.valueSlider1 = [ RoundedUISlider new ]; 120 | [ self.valueSlider1 addTarget:self action:@selector(sliderValueChanged1:) forControlEvents:UIControlEventValueChanged ]; 121 | [ self.view addSubview:self.valueSlider1 ]; 122 | 123 | [ self selectCommmandAtIndex: -1 ]; 124 | 125 | [ self initPath ]; 126 | 127 | self.clearState = self.currentState; 128 | 129 | [ self reset ]; 130 | } 131 | 132 | -(void)reset 133 | { 134 | self.currentState = self.undoComparisonState = self.clearState; 135 | 136 | [ self resetUndoTimer ]; 137 | [ self.undoManager removeAllActions ]; 138 | } 139 | 140 | -(void)initPath 141 | { 142 | self.path = [ TurtleBezierPath new ]; 143 | [ self.path home ]; 144 | self.path.lineWidth = 2.0f; 145 | self.path.lineCapStyle = kCGLineCapRound; 146 | self.previewPath = [ self.path copy ]; 147 | } 148 | 149 | #pragma mark - Layout 150 | 151 | -(void)layoutViews 152 | { 153 | self.commandLabel.frame = CGRectMake( 0.0f, 20.0f, self.view.bounds.size.width, self.commandLabel.font.pointSize * 1.5f ); 154 | 155 | self.valueSlider0.frame = self.valueSlider1.frame = CGRectMake( 0.0, 0.0, self.commandControl.bounds.size.width, self.valueSlider0.bounds.size.height ); 156 | 157 | CGFloat originY = self.view.bounds.size.height; 158 | 159 | for( UIView *view in @[ self.valueSlider1, self.valueSlider0, self.commandControl ]) 160 | { 161 | view.frame = CGRectIntegral( CGRectMake(( self.view.bounds.size.width - view.bounds.size.width ) / 2.0f, originY - view.bounds.size.height, view.bounds.size.width, view.bounds.size.height )); 162 | 163 | originY = view.frame.origin.y - view.frame.size.height * 0.2f; 164 | } 165 | 166 | [ self positionPointer ]; 167 | } 168 | 169 | #pragma mark - UIResponder 170 | 171 | - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event 172 | { 173 | if (motion == UIEventSubtypeMotionShake) 174 | { 175 | [ self reset ]; 176 | } 177 | } 178 | 179 | 180 | #pragma mark - Controls 181 | 182 | -(void)commmandSelected:(id)sender 183 | { 184 | [ self resetUndoTimer ]; 185 | 186 | [ self selectCommmandAtIndex:self.commandControl.selectedSegmentIndex ]; 187 | } 188 | 189 | -(void)sliderValueChanged0:(id)sender 190 | { 191 | [ self resetUndoTimer ]; 192 | 193 | [ self updateCommandForIndex:self.commandControl.selectedSegmentIndex ]; 194 | } 195 | 196 | -(void)sliderValueChanged1:(id)sender 197 | { 198 | [ self resetUndoTimer ]; 199 | 200 | [ self updateCommandForIndex:self.commandControl.selectedSegmentIndex ]; 201 | } 202 | 203 | -(void)setupSlidersForIndex:(NSInteger)index 204 | { 205 | self.valueSlider0.hidden = !( index >= 0 && index < 4 ); 206 | self.valueSlider1.hidden = !( index > 1 && index < 4 ); 207 | 208 | CGFloat maximumLength = MIN( self.view.bounds.size.width, self.view.bounds.size.height ) / 2.0f; 209 | 210 | self.valueSlider0.value = 0.0f; 211 | self.valueSlider1.value = ( index > 1 ) ? 90.0f : 0.0f; 212 | self.valueSlider0.maximumValue = ( index == 1 ) ? 360.0f : maximumLength; 213 | self.valueSlider1.maximumValue = 360.0f; 214 | 215 | CGFloat lengthRounding = ( index == 0 ) ? 10.0f : 5.0f; 216 | self.valueSlider0.rounding = ( index == 1 ) ? 5.0f : lengthRounding; 217 | self.valueSlider1.rounding = 5.0f; 218 | } 219 | 220 | 221 | #pragma mark - Properties 222 | 223 | -(TurtleDemoState *)currentState 224 | { 225 | return [[ TurtleDemoState alloc ] initWithIndex:self.commandControl.selectedSegmentIndex path:self.path previewPath:self.previewPath value0:self.valueSlider0.value value1:self.valueSlider1.value ]; 226 | } 227 | 228 | -(void)setCurrentState:(TurtleDemoState *)currentState 229 | { 230 | self.commandControl.selectedSegmentIndex = currentState.index; 231 | self.path = currentState.path; 232 | self.previewPath = currentState.previewPath; 233 | [ self setupSlidersForIndex:currentState.index ]; 234 | self.valueSlider0.value = currentState.value0; 235 | self.valueSlider1.value = currentState.value1; 236 | 237 | [ self showState ]; 238 | } 239 | 240 | 241 | #pragma mark - Undo 242 | 243 | -(void)setUndoState:(TurtleDemoState *)newState 244 | { 245 | if( ![ newState.previewPath isEqual:self.undoComparisonState.previewPath ]) 246 | { 247 | [ self.undoManager registerUndoWithTarget:self selector:@selector(setUndoState:) object:self.undoComparisonState ]; 248 | 249 | self.currentState = self.undoComparisonState = newState; 250 | } 251 | } 252 | 253 | -(void)performRedo:(UIScreenEdgePanGestureRecognizer *)redoSwipe 254 | { 255 | if( redoSwipe.state == UIGestureRecognizerStateBegan ) 256 | { 257 | [ self.undoManager redo ]; 258 | } 259 | } 260 | 261 | -(void)performUndo:(UIScreenEdgePanGestureRecognizer *)undoSwipe 262 | { 263 | if( undoSwipe.state == UIGestureRecognizerStateBegan ) 264 | { 265 | [ self.undoManager undo ]; 266 | } 267 | } 268 | 269 | 270 | #pragma mark - Undo Timer 271 | 272 | -(void)resetUndoTimer 273 | { 274 | if( self.undoTimer ) 275 | { 276 | [ self.undoTimer invalidate ]; 277 | } 278 | 279 | self.undoTimer = [ NSTimer scheduledTimerWithTimeInterval:0.25f target:self selector:@selector(undoTimerFired) userInfo:nil repeats:NO ]; 280 | [[ NSRunLoop currentRunLoop ] addTimer:self.undoTimer forMode:NSRunLoopCommonModes ]; 281 | } 282 | 283 | -(void)undoTimerFired 284 | { 285 | self.undoTimer = nil; 286 | 287 | [ self setUndoState:self.currentState ]; 288 | } 289 | 290 | 291 | #pragma mark - Pointer 292 | 293 | -(void)positionPointer 294 | { 295 | TurtleBezierPath *pointerPath = [ self.previewPath copy ]; 296 | [ pointerPath centreInBounds:self.view.bounds ]; 297 | [ self.pointerView positionOnPath:pointerPath ]; 298 | 299 | self.pointerView.alpha = ( pointerPath.penUp ) ? 0.333f : 1.0f; 300 | } 301 | 302 | -(void)selectCommmandAtIndex:(NSInteger)index 303 | { 304 | self.path = self.previewPath; 305 | [ self setUndoState:[ self currentState ]]; 306 | 307 | [ self setupSlidersForIndex:index ]; 308 | 309 | [ self updateCommandForIndex:index ]; 310 | } 311 | 312 | -(void)updateCommandForIndex:(NSInteger)index 313 | { 314 | self.previewPath = [ self.path copy ]; 315 | 316 | [ self drawCommandForIndex:index value0:self.valueSlider0.value value1:self.valueSlider1.value ontoPath:self.previewPath ]; 317 | 318 | [ self showState ]; 319 | } 320 | 321 | -(void)showState 322 | { 323 | NSInteger index = self.commandControl.selectedSegmentIndex; 324 | 325 | NSString *commandTitle = ( index >= 0 ) ? [ self.commandControl titleForSegmentAtIndex:index ] : nil; 326 | self.commandLabel.text = [ self commandStringForIndex:index title:commandTitle value0:self.valueSlider0.value value1:self.valueSlider1.value ]; 327 | 328 | self.canvasView.path = self.previewPath; 329 | 330 | [ self positionPointer ]; 331 | 332 | NSString *downUp = ( self.previewPath.penUp ) ? @"down" : @"up"; 333 | [ self.commandControl setTitle:downUp forSegmentAtIndex:4 ]; 334 | } 335 | 336 | 337 | #pragma mark - Turtle Commands 338 | 339 | -(NSString *)commandStringForIndex:(NSInteger)index title:(NSString *)title value0:(CGFloat)value0 value1:(CGFloat)value1 340 | { 341 | if( index < 0 ) 342 | { 343 | return nil; 344 | } 345 | else if( index < 2 ) 346 | { 347 | return [ NSString stringWithFormat:@"[ path %@:%g ];", title, value0 ]; 348 | } 349 | else if( index < 4 ) 350 | { 351 | return [ NSString stringWithFormat:@"[ path %@:%g turn:%g ];", title, value0, value1 ]; 352 | } 353 | else 354 | { 355 | return [ NSString stringWithFormat:@"[ path %@ ];", title ]; 356 | } 357 | } 358 | 359 | -(void)drawCommandForIndex:(NSInteger)index value0:(CGFloat)value0 value1:(CGFloat)value1 ontoPath:(TurtleBezierPath *)path 360 | { 361 | if( index == 0 && value0 > 0.0f ) 362 | { 363 | [ path forward:value0 ]; 364 | } 365 | else if( index == 1 && value0 > 0.0f ) 366 | { 367 | [ path turn:value0 ]; 368 | } 369 | else if( index == 2 && value0 > 0.0f && value1 > 0.0f ) 370 | { 371 | [ path leftArc:value0 turn:value1 ]; 372 | } 373 | else if( index == 3 && value0 > 0.0f && value1 > 0.0f ) 374 | { 375 | [ path rightArc:value0 turn:value1 ]; 376 | } 377 | else if( index == 4 ) 378 | { 379 | if( path.penUp ) 380 | { 381 | [ path down ]; 382 | } 383 | else 384 | { 385 | [ path up ]; 386 | } 387 | } 388 | } 389 | 390 | @end 391 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TurtleAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([TurtleAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demoTests/TurtleBezierPath demoTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | uk.co.mindbrix.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demoTests/TurtleBezierPath_demoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleBezierPath_demoTests.m 3 | // TurtleBezierPath demoTests 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TurtleBezierPath_demoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TurtleBezierPath_demoTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /TurtleBezierPath demo/TurtleBezierPath demoTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /TurtleBezierPath.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'TurtleBezierPath' 3 | s.version = '1.0' 4 | s.license = 'MIT' 5 | s.summary = 'UIBezierPath subclass for Turtle Graphics' 6 | s.homepage = 'https://github.com/mindbrix/TurtleBezierPath' 7 | s.authors = 'Nigel Timothy Barber' 8 | s.source = { :git => 'https://github.com/mindbrix/TurtleBezierPath.git', :tag => '1.0' } 9 | s.source_files = 'TurtleBezierPath' 10 | s.requires_arc = false 11 | s.ios.deployment_target = '5.0' 12 | end -------------------------------------------------------------------------------- /TurtleBezierPath/TurtleBezierPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleBezierPath.h 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TurtleBezierPath : UIBezierPath 12 | 13 | @property( nonatomic, assign ) CGFloat bearing; 14 | @property( nonatomic, assign ) BOOL penUp; 15 | 16 | -(CGRect)boundsWithStroke; 17 | -(CGRect)boundsForView; 18 | 19 | -(void)home; 20 | -(void)forward:(CGFloat)distance; 21 | -(void)turn:(CGFloat)angle; 22 | -(void)leftArc:(CGFloat)radius turn:(CGFloat)angle; 23 | -(void)rightArc:(CGFloat)radius turn:(CGFloat)angle; 24 | -(void)down; 25 | -(void)up; 26 | 27 | -(void)centreInBounds:(CGRect)bounds; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /TurtleBezierPath/TurtleBezierPath.m: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleBezierPath.m 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import "TurtleBezierPath.h" 10 | 11 | @implementation TurtleBezierPath 12 | 13 | 14 | #pragma mark - NSCoding 15 | 16 | -(void)encodeWithCoder:(NSCoder *)aCoder 17 | { 18 | [ super encodeWithCoder:aCoder ]; 19 | 20 | [ aCoder encodeFloat:self.bearing forKey:@"bearing" ]; 21 | [ aCoder encodeBool:self.penUp forKey:@"penUp" ]; 22 | } 23 | 24 | - (id)initWithCoder:(NSCoder *)aDecoder 25 | { 26 | if( self = [ super initWithCoder:aDecoder ]) 27 | { 28 | self.bearing = [ aDecoder decodeFloatForKey:@"bearing" ]; 29 | self.penUp = [ aDecoder decodeBoolForKey:@"penUp" ]; 30 | } 31 | 32 | return self; 33 | } 34 | 35 | 36 | #pragma mark - NSCopying 37 | 38 | -(id)copyWithZone:(NSZone *)zone 39 | { 40 | TurtleBezierPath *clone = [[ TurtleBezierPath allocWithZone:zone ] init ]; 41 | clone.CGPath = self.CGPath; 42 | clone.lineCapStyle = self.lineCapStyle; 43 | clone.lineJoinStyle = self.lineJoinStyle; 44 | clone.lineWidth = self.lineWidth; 45 | clone.miterLimit = self.miterLimit; 46 | clone.flatness = self.flatness; 47 | clone.usesEvenOddFillRule = self.usesEvenOddFillRule; 48 | 49 | CGFloat phase; 50 | NSInteger count; 51 | [ self getLineDash:nil count:&count phase:&phase ]; 52 | CGFloat *lineDash = malloc( count * sizeof( CGFloat )); 53 | [ self getLineDash:lineDash count:&count phase:&phase ]; 54 | [ clone setLineDash:lineDash count:count phase:phase ]; 55 | free( lineDash ); 56 | 57 | clone.bearing = self.bearing; 58 | clone.penUp = self.penUp; 59 | 60 | return clone; 61 | } 62 | 63 | 64 | #pragma mark - Private methods 65 | 66 | -(void)arc:(CGFloat)radius turn:(CGFloat)angle clockwise:(BOOL)clockwise 67 | { 68 | CGFloat radiusTurn = ( clockwise ) ? 90.0f : -90.0f; 69 | CGFloat cgAngleBias = ( clockwise ) ? 180.0f : 0.0f; 70 | angle = ( clockwise ) ? angle : -angle; 71 | 72 | CGPoint centre = [ self toCartesian:radius bearing:self.bearing + radiusTurn origin:self.currentPoint ]; 73 | 74 | CGFloat cgStartAngle = cgAngleBias + self.bearing; 75 | CGFloat cgEndAngle = cgAngleBias + ( self.bearing + angle ); 76 | 77 | self.bearing += angle; 78 | CGPoint endPoint = [ self toCartesian:radius bearing:( self.bearing -radiusTurn ) origin:centre ]; 79 | 80 | if( self.penUp ) 81 | { 82 | [ self moveToPoint:endPoint ]; 83 | } 84 | else 85 | { 86 | [ self addArcWithCenter:centre radius:radius startAngle:radians( cgStartAngle ) endAngle:radians( cgEndAngle ) clockwise:clockwise ]; 87 | } 88 | } 89 | 90 | 91 | #pragma mark - Public methods 92 | 93 | -(CGRect)boundsWithStroke 94 | { 95 | return CGRectIntegral( CGRectInset( self.bounds, -self.lineWidth * 0.5f, -self.lineWidth * 0.5f )); 96 | } 97 | 98 | -(CGRect)boundsForView 99 | { 100 | CGRect bounds = self.boundsWithStroke; 101 | CGFloat maxWidth = MAX( fabsf( CGRectGetMinX( bounds )), fabsf( CGRectGetMaxX( bounds ))); 102 | CGFloat maxHeight = MAX( fabsf( CGRectGetMinY( bounds )), fabsf( CGRectGetMaxY( bounds ))); 103 | 104 | return CGRectMake( 0.0f, 0.0f, maxWidth * 2.0f, maxHeight * 2.0f ); 105 | } 106 | 107 | -(BOOL)isEqual:(TurtleBezierPath *)aPath 108 | { 109 | return [[ NSKeyedArchiver archivedDataWithRootObject:self ] isEqualToData:[ NSKeyedArchiver archivedDataWithRootObject:aPath ]]; 110 | } 111 | 112 | -(void)home 113 | { 114 | [ self moveToPoint:CGPointZero ]; 115 | self.bearing = 0.0f; 116 | } 117 | 118 | -(void)forward:(CGFloat)distance 119 | { 120 | CGPoint endPoint = [ self toCartesian:distance bearing:self.bearing origin:self.currentPoint ]; 121 | 122 | if( self.penUp ) 123 | { 124 | [ self moveToPoint:endPoint ]; 125 | } 126 | else 127 | { 128 | [ self addLineToPoint:endPoint ]; 129 | } 130 | } 131 | 132 | -(void)turn:(CGFloat)angle 133 | { 134 | self.bearing += angle; 135 | } 136 | 137 | -(void)leftArc:(CGFloat)radius turn:(CGFloat)angle 138 | { 139 | [ self arc:radius turn:angle clockwise:NO ]; 140 | } 141 | 142 | -(void)rightArc:(CGFloat)radius turn:(CGFloat)angle 143 | { 144 | [ self arc:radius turn:angle clockwise:YES ]; 145 | } 146 | 147 | -(void)down 148 | { 149 | self.penUp = NO; 150 | } 151 | 152 | -(void)up 153 | { 154 | self.penUp = YES; 155 | } 156 | 157 | -(void)centreInBounds:(CGRect)bounds 158 | { 159 | [ self applyTransform:CGAffineTransformMakeTranslation( bounds.size.width / 2.0f, bounds.size.height / 2.0f )]; 160 | } 161 | 162 | 163 | #pragma mark - Maths 164 | 165 | static inline CGFloat radians (CGFloat degrees) {return degrees * M_PI / 180.0;} 166 | 167 | -(CGPoint)toCartesian:(CGFloat)radius bearing:(CGFloat)bearing origin:(CGPoint)origin 168 | { 169 | CGFloat bearingInRadians = radians( bearing ); 170 | 171 | CGPoint vector = CGPointMake( radius * sinf( bearingInRadians ), -radius * cosf( bearingInRadians )); 172 | 173 | return CGPointMake( origin.x + vector.x, origin.y + vector.y ); 174 | } 175 | 176 | 177 | @end 178 | --------------------------------------------------------------------------------