├── GradientBezierLine.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── zhanggy.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── zhanggy.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── GradientBezierLine ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── GradientLine.h ├── GradientLine.m ├── GradientRingView.h ├── GradientRingView.m ├── Info.plist ├── NextVC.h ├── NextVC.m ├── ViewController.h ├── ViewController.m └── main.m └── README.md /GradientBezierLine.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 154EFE7B1E76A68D00405ECF /* GradientLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 154EFE7A1E76A68D00405ECF /* GradientLine.m */; }; 11 | 1554D0001E10CE8D000FC274 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1554CFFF1E10CE8D000FC274 /* main.m */; }; 12 | 1554D0031E10CE8D000FC274 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1554D0021E10CE8D000FC274 /* AppDelegate.m */; }; 13 | 1554D0061E10CE8D000FC274 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1554D0051E10CE8D000FC274 /* ViewController.m */; }; 14 | 1554D0091E10CE8D000FC274 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1554D0071E10CE8D000FC274 /* Main.storyboard */; }; 15 | 1554D00B1E10CE8D000FC274 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1554D00A1E10CE8D000FC274 /* Assets.xcassets */; }; 16 | 1554D00E1E10CE8D000FC274 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1554D00C1E10CE8D000FC274 /* LaunchScreen.storyboard */; }; 17 | 15BFAA23208F24FB00664992 /* NextVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BFAA22208F24FB00664992 /* NextVC.m */; }; 18 | 15C35EAC21C9E520006B1CA3 /* GradientRingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 15C35EAA21C9E51F006B1CA3 /* GradientRingView.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 154EFE791E76A68D00405ECF /* GradientLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GradientLine.h; sourceTree = ""; }; 23 | 154EFE7A1E76A68D00405ECF /* GradientLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GradientLine.m; sourceTree = ""; }; 24 | 1554CFFB1E10CE8D000FC274 /* GradientBezierLine.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GradientBezierLine.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 1554CFFF1E10CE8D000FC274 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 26 | 1554D0011E10CE8D000FC274 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 27 | 1554D0021E10CE8D000FC274 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 28 | 1554D0041E10CE8D000FC274 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 29 | 1554D0051E10CE8D000FC274 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 30 | 1554D0081E10CE8D000FC274 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | 1554D00A1E10CE8D000FC274 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | 1554D00D1E10CE8D000FC274 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 33 | 1554D00F1E10CE8D000FC274 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | 15BFAA21208F24FB00664992 /* NextVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NextVC.h; sourceTree = ""; }; 35 | 15BFAA22208F24FB00664992 /* NextVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NextVC.m; sourceTree = ""; }; 36 | 15C35EAA21C9E51F006B1CA3 /* GradientRingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GradientRingView.m; sourceTree = ""; }; 37 | 15C35EAB21C9E520006B1CA3 /* GradientRingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GradientRingView.h; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | 1554CFF81E10CE8D000FC274 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | 154EFE781E76A67E00405ECF /* dev */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | 154EFE791E76A68D00405ECF /* GradientLine.h */, 55 | 154EFE7A1E76A68D00405ECF /* GradientLine.m */, 56 | ); 57 | name = dev; 58 | sourceTree = ""; 59 | }; 60 | 1554CFF21E10CE8D000FC274 = { 61 | isa = PBXGroup; 62 | children = ( 63 | 1554CFFD1E10CE8D000FC274 /* GradientBezierLine */, 64 | 1554CFFC1E10CE8D000FC274 /* Products */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | 1554CFFC1E10CE8D000FC274 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 1554CFFB1E10CE8D000FC274 /* GradientBezierLine.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | 1554CFFD1E10CE8D000FC274 /* GradientBezierLine */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 154EFE781E76A67E00405ECF /* dev */, 80 | 1554D0011E10CE8D000FC274 /* AppDelegate.h */, 81 | 1554D0021E10CE8D000FC274 /* AppDelegate.m */, 82 | 1554D0041E10CE8D000FC274 /* ViewController.h */, 83 | 1554D0051E10CE8D000FC274 /* ViewController.m */, 84 | 15BFAA21208F24FB00664992 /* NextVC.h */, 85 | 15BFAA22208F24FB00664992 /* NextVC.m */, 86 | 15C35EAB21C9E520006B1CA3 /* GradientRingView.h */, 87 | 15C35EAA21C9E51F006B1CA3 /* GradientRingView.m */, 88 | 1554D0071E10CE8D000FC274 /* Main.storyboard */, 89 | 1554D00A1E10CE8D000FC274 /* Assets.xcassets */, 90 | 1554D00C1E10CE8D000FC274 /* LaunchScreen.storyboard */, 91 | 1554D00F1E10CE8D000FC274 /* Info.plist */, 92 | 1554CFFE1E10CE8D000FC274 /* Supporting Files */, 93 | ); 94 | path = GradientBezierLine; 95 | sourceTree = ""; 96 | }; 97 | 1554CFFE1E10CE8D000FC274 /* Supporting Files */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 1554CFFF1E10CE8D000FC274 /* main.m */, 101 | ); 102 | name = "Supporting Files"; 103 | sourceTree = ""; 104 | }; 105 | /* End PBXGroup section */ 106 | 107 | /* Begin PBXNativeTarget section */ 108 | 1554CFFA1E10CE8D000FC274 /* GradientBezierLine */ = { 109 | isa = PBXNativeTarget; 110 | buildConfigurationList = 1554D0121E10CE8D000FC274 /* Build configuration list for PBXNativeTarget "GradientBezierLine" */; 111 | buildPhases = ( 112 | 1554CFF71E10CE8D000FC274 /* Sources */, 113 | 1554CFF81E10CE8D000FC274 /* Frameworks */, 114 | 1554CFF91E10CE8D000FC274 /* Resources */, 115 | ); 116 | buildRules = ( 117 | ); 118 | dependencies = ( 119 | ); 120 | name = GradientBezierLine; 121 | productName = GradientBezierLine; 122 | productReference = 1554CFFB1E10CE8D000FC274 /* GradientBezierLine.app */; 123 | productType = "com.apple.product-type.application"; 124 | }; 125 | /* End PBXNativeTarget section */ 126 | 127 | /* Begin PBXProject section */ 128 | 1554CFF31E10CE8D000FC274 /* Project object */ = { 129 | isa = PBXProject; 130 | attributes = { 131 | LastUpgradeCheck = 0920; 132 | ORGANIZATIONNAME = Ive; 133 | TargetAttributes = { 134 | 1554CFFA1E10CE8D000FC274 = { 135 | CreatedOnToolsVersion = 8.1; 136 | ProvisioningStyle = Automatic; 137 | }; 138 | }; 139 | }; 140 | buildConfigurationList = 1554CFF61E10CE8D000FC274 /* Build configuration list for PBXProject "GradientBezierLine" */; 141 | compatibilityVersion = "Xcode 3.2"; 142 | developmentRegion = English; 143 | hasScannedForEncodings = 0; 144 | knownRegions = ( 145 | en, 146 | Base, 147 | ); 148 | mainGroup = 1554CFF21E10CE8D000FC274; 149 | productRefGroup = 1554CFFC1E10CE8D000FC274 /* Products */; 150 | projectDirPath = ""; 151 | projectRoot = ""; 152 | targets = ( 153 | 1554CFFA1E10CE8D000FC274 /* GradientBezierLine */, 154 | ); 155 | }; 156 | /* End PBXProject section */ 157 | 158 | /* Begin PBXResourcesBuildPhase section */ 159 | 1554CFF91E10CE8D000FC274 /* Resources */ = { 160 | isa = PBXResourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | 1554D00E1E10CE8D000FC274 /* LaunchScreen.storyboard in Resources */, 164 | 1554D00B1E10CE8D000FC274 /* Assets.xcassets in Resources */, 165 | 1554D0091E10CE8D000FC274 /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXSourcesBuildPhase section */ 172 | 1554CFF71E10CE8D000FC274 /* Sources */ = { 173 | isa = PBXSourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | 1554D0061E10CE8D000FC274 /* ViewController.m in Sources */, 177 | 1554D0031E10CE8D000FC274 /* AppDelegate.m in Sources */, 178 | 154EFE7B1E76A68D00405ECF /* GradientLine.m in Sources */, 179 | 15BFAA23208F24FB00664992 /* NextVC.m in Sources */, 180 | 15C35EAC21C9E520006B1CA3 /* GradientRingView.m in Sources */, 181 | 1554D0001E10CE8D000FC274 /* main.m in Sources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXSourcesBuildPhase section */ 186 | 187 | /* Begin PBXVariantGroup section */ 188 | 1554D0071E10CE8D000FC274 /* Main.storyboard */ = { 189 | isa = PBXVariantGroup; 190 | children = ( 191 | 1554D0081E10CE8D000FC274 /* Base */, 192 | ); 193 | name = Main.storyboard; 194 | sourceTree = ""; 195 | }; 196 | 1554D00C1E10CE8D000FC274 /* LaunchScreen.storyboard */ = { 197 | isa = PBXVariantGroup; 198 | children = ( 199 | 1554D00D1E10CE8D000FC274 /* Base */, 200 | ); 201 | name = LaunchScreen.storyboard; 202 | sourceTree = ""; 203 | }; 204 | /* End PBXVariantGroup section */ 205 | 206 | /* Begin XCBuildConfiguration section */ 207 | 1554D0101E10CE8D000FC274 /* Debug */ = { 208 | isa = XCBuildConfiguration; 209 | buildSettings = { 210 | ALWAYS_SEARCH_USER_PATHS = NO; 211 | CLANG_ANALYZER_NONNULL = YES; 212 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 213 | CLANG_CXX_LIBRARY = "libc++"; 214 | CLANG_ENABLE_MODULES = YES; 215 | CLANG_ENABLE_OBJC_ARC = YES; 216 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 217 | CLANG_WARN_BOOL_CONVERSION = YES; 218 | CLANG_WARN_COMMA = YES; 219 | CLANG_WARN_CONSTANT_CONVERSION = YES; 220 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 221 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 222 | CLANG_WARN_EMPTY_BODY = YES; 223 | CLANG_WARN_ENUM_CONVERSION = YES; 224 | CLANG_WARN_INFINITE_RECURSION = YES; 225 | CLANG_WARN_INT_CONVERSION = YES; 226 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 227 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 228 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 229 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 230 | CLANG_WARN_STRICT_PROTOTYPES = YES; 231 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 232 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 233 | CLANG_WARN_UNREACHABLE_CODE = YES; 234 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 235 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 236 | COPY_PHASE_STRIP = NO; 237 | DEBUG_INFORMATION_FORMAT = dwarf; 238 | ENABLE_STRICT_OBJC_MSGSEND = YES; 239 | ENABLE_TESTABILITY = YES; 240 | GCC_C_LANGUAGE_STANDARD = gnu99; 241 | GCC_DYNAMIC_NO_PIC = NO; 242 | GCC_NO_COMMON_BLOCKS = YES; 243 | GCC_OPTIMIZATION_LEVEL = 0; 244 | GCC_PREPROCESSOR_DEFINITIONS = ( 245 | "DEBUG=1", 246 | "$(inherited)", 247 | ); 248 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 249 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 250 | GCC_WARN_UNDECLARED_SELECTOR = YES; 251 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 252 | GCC_WARN_UNUSED_FUNCTION = YES; 253 | GCC_WARN_UNUSED_VARIABLE = YES; 254 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 255 | MTL_ENABLE_DEBUG_INFO = YES; 256 | ONLY_ACTIVE_ARCH = YES; 257 | SDKROOT = iphoneos; 258 | TARGETED_DEVICE_FAMILY = "1,2"; 259 | }; 260 | name = Debug; 261 | }; 262 | 1554D0111E10CE8D000FC274 /* Release */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_ANALYZER_NONNULL = YES; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 268 | CLANG_CXX_LIBRARY = "libc++"; 269 | CLANG_ENABLE_MODULES = YES; 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 272 | CLANG_WARN_BOOL_CONVERSION = YES; 273 | CLANG_WARN_COMMA = YES; 274 | CLANG_WARN_CONSTANT_CONVERSION = YES; 275 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 276 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 277 | CLANG_WARN_EMPTY_BODY = YES; 278 | CLANG_WARN_ENUM_CONVERSION = YES; 279 | CLANG_WARN_INFINITE_RECURSION = YES; 280 | CLANG_WARN_INT_CONVERSION = YES; 281 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 285 | CLANG_WARN_STRICT_PROTOTYPES = YES; 286 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 287 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 288 | CLANG_WARN_UNREACHABLE_CODE = YES; 289 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 290 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 291 | COPY_PHASE_STRIP = NO; 292 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 293 | ENABLE_NS_ASSERTIONS = NO; 294 | ENABLE_STRICT_OBJC_MSGSEND = YES; 295 | GCC_C_LANGUAGE_STANDARD = gnu99; 296 | GCC_NO_COMMON_BLOCKS = YES; 297 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 299 | GCC_WARN_UNDECLARED_SELECTOR = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 301 | GCC_WARN_UNUSED_FUNCTION = YES; 302 | GCC_WARN_UNUSED_VARIABLE = YES; 303 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 304 | MTL_ENABLE_DEBUG_INFO = NO; 305 | SDKROOT = iphoneos; 306 | TARGETED_DEVICE_FAMILY = "1,2"; 307 | VALIDATE_PRODUCT = YES; 308 | }; 309 | name = Release; 310 | }; 311 | 1554D0131E10CE8D000FC274 /* Debug */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 315 | INFOPLIST_FILE = GradientBezierLine/Info.plist; 316 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 317 | PRODUCT_BUNDLE_IDENTIFIER = Ive.GradientBezierLine; 318 | PRODUCT_NAME = "$(TARGET_NAME)"; 319 | }; 320 | name = Debug; 321 | }; 322 | 1554D0141E10CE8D000FC274 /* Release */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 326 | INFOPLIST_FILE = GradientBezierLine/Info.plist; 327 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 328 | PRODUCT_BUNDLE_IDENTIFIER = Ive.GradientBezierLine; 329 | PRODUCT_NAME = "$(TARGET_NAME)"; 330 | }; 331 | name = Release; 332 | }; 333 | /* End XCBuildConfiguration section */ 334 | 335 | /* Begin XCConfigurationList section */ 336 | 1554CFF61E10CE8D000FC274 /* Build configuration list for PBXProject "GradientBezierLine" */ = { 337 | isa = XCConfigurationList; 338 | buildConfigurations = ( 339 | 1554D0101E10CE8D000FC274 /* Debug */, 340 | 1554D0111E10CE8D000FC274 /* Release */, 341 | ); 342 | defaultConfigurationIsVisible = 0; 343 | defaultConfigurationName = Release; 344 | }; 345 | 1554D0121E10CE8D000FC274 /* Build configuration list for PBXNativeTarget "GradientBezierLine" */ = { 346 | isa = XCConfigurationList; 347 | buildConfigurations = ( 348 | 1554D0131E10CE8D000FC274 /* Debug */, 349 | 1554D0141E10CE8D000FC274 /* Release */, 350 | ); 351 | defaultConfigurationIsVisible = 0; 352 | defaultConfigurationName = Release; 353 | }; 354 | /* End XCConfigurationList section */ 355 | }; 356 | rootObject = 1554CFF31E10CE8D000FC274 /* Project object */; 357 | } 358 | -------------------------------------------------------------------------------- /GradientBezierLine.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GradientBezierLine.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GradientBezierLine.xcodeproj/project.xcworkspace/xcuserdata/zhanggy.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyzhanggy/GradientBezierLine/1b0ccafb8a9fdef3bced8d2eee79cc59539771e8/GradientBezierLine.xcodeproj/project.xcworkspace/xcuserdata/zhanggy.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /GradientBezierLine.xcodeproj/xcuserdata/zhanggy.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /GradientBezierLine.xcodeproj/xcuserdata/zhanggy.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | GradientBezierLine.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | GradientBezierLine.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /GradientBezierLine/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // GradientBezierLine 4 | // 5 | 6 | 7 | #import 8 | 9 | @interface AppDelegate : UIResponder 10 | 11 | @property (strong, nonatomic) UIWindow *window; 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /GradientBezierLine/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // GradientBezierLine 4 | 5 | 6 | #import "AppDelegate.h" 7 | 8 | @interface AppDelegate () 9 | 10 | @end 11 | 12 | @implementation AppDelegate 13 | 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 16 | // Override point for customization after application launch. 17 | return YES; 18 | } 19 | 20 | 21 | - (void)applicationWillResignActive:(UIApplication *)application { 22 | // 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. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | 27 | - (void)applicationDidEnterBackground:(UIApplication *)application { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | 38 | - (void)applicationDidBecomeActive:(UIApplication *)application { 39 | // 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. 40 | } 41 | 42 | 43 | - (void)applicationWillTerminate:(UIApplication *)application { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /GradientBezierLine/Assets.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" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /GradientBezierLine/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 | 27 | 28 | -------------------------------------------------------------------------------- /GradientBezierLine/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 | 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 | 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 | -------------------------------------------------------------------------------- /GradientBezierLine/GradientLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // GradientLine.h 3 | // GradientBezierLine 4 | // 5 | 6 | #import 7 | 8 | @interface GradientLine : NSObject 9 | - (instancetype)initWithStartPoint:(CGPoint)startPoint controlPoint:(CGPoint)controlPoint endPoint:(CGPoint)endPoint; 10 | - (UIImage*)gradientLineWithStartColor:(UIColor *)startColor endColor:(UIColor *)endColor size:(CGSize)size; 11 | - (CGFloat)lengthWithT:(CGFloat)t; 12 | @end 13 | -------------------------------------------------------------------------------- /GradientBezierLine/GradientLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // GradientLine.m 3 | // GradientBezierLine 4 | // 5 | 6 | #import "GradientLine.h" 7 | 8 | @interface GradientLine () { 9 | 10 | CGPoint _startPoint; 11 | CGPoint _controlPoint; 12 | CGPoint _endPoint; 13 | 14 | float _quadraticEquationA; 15 | float _quadraticEquationB; 16 | 17 | CGFloat _lineWidth; 18 | CGPoint _vertexPoint; 19 | } 20 | 21 | @end 22 | 23 | @implementation GradientLine 24 | - (instancetype)initWithStartPoint:(CGPoint)startPoint controlPoint:(CGPoint)controlPoint endPoint:(CGPoint)endPoint { 25 | if (self = [super init]) { 26 | _startPoint = startPoint; 27 | _controlPoint = controlPoint; 28 | _endPoint = endPoint; 29 | } 30 | return self; 31 | } 32 | - (UIImage*)gradientLineWithStartColor:(UIColor *)startColor endColor:(UIColor *)endColor size:(CGSize)size { 33 | 34 | _lineWidth = 5; 35 | 36 | CGFloat startR; 37 | CGFloat startG; 38 | CGFloat startB; 39 | CGFloat startA; 40 | [startColor getRed:&startR green:&startG blue:&startB alpha:&startA]; 41 | 42 | CGFloat endR; 43 | CGFloat endG; 44 | CGFloat endB; 45 | CGFloat endA; 46 | [endColor getRed:&endR green:&endG blue:&endB alpha:&endA]; 47 | 48 | 49 | CAShapeLayer *layer = [self lineLayerWithStartPoint:_startPoint controlPoint:_controlPoint endPoint:_endPoint size:size]; 50 | 51 | float scale = [UIScreen mainScreen].scale; 52 | // 分配内存 53 | const int imageWidth = layer.bounds.size.width * scale; 54 | const int imageHeight = layer.bounds.size.height * scale; 55 | size_t bytesPerRow = imageWidth * 4; 56 | uint32_t* rgbImageBuf = (uint32_t*)calloc(imageWidth * imageHeight, sizeof(UInt32)); 57 | 58 | // 创建context 59 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 60 | CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); 61 | CGContextTranslateCTM(context,0,imageHeight); 62 | CGContextScaleCTM(context, 1, -1); 63 | CGContextScaleCTM(context, scale,scale); 64 | 65 | 66 | [layer renderInContext:context]; 67 | 68 | 69 | // 遍历像素 70 | int pixelNum = imageWidth * imageHeight; 71 | uint32_t* pCurPtr = rgbImageBuf; 72 | 73 | for (int i = 0; i < pixelNum; i++, pCurPtr++) { 74 | int x = i%imageWidth; 75 | int y = i/imageWidth; 76 | if (*pCurPtr != 0xFFFFFFFF && *pCurPtr != 0x00000000) { 77 | uint8_t* ptr = (uint8_t*)pCurPtr; 78 | float t = [self tAtPoint:CGPointMake(x/scale, y/scale)]; 79 | if (t == -1) { 80 | ptr[0] = 0; 81 | ptr[1] = 0 ; 82 | ptr[2] = 0 ; 83 | ptr[3] = 0 ; 84 | } else { 85 | float v = (ptr[3]*1.0)/255.0; 86 | CGFloat r = (endR - startR) * t + startR; 87 | CGFloat g = (endG - startG) * t + startG; 88 | CGFloat b = (endB - startB) * t + startB; 89 | CGFloat a = (endA - startA) * t + startA; 90 | 91 | ptr[0] = a * 255 * v; 92 | ptr[1] = b * 255 ; 93 | ptr[2] = g * 255 ; 94 | ptr[3] = r * 255 ; 95 | } 96 | } 97 | } 98 | 99 | // 将内存转成image 100 | CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight,ProviderReleaseData); 101 | CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace,kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider,NULL, true, kCGRenderingIntentDefault); 102 | 103 | CGDataProviderRelease(dataProvider); 104 | 105 | UIImage* resultUIImage = [UIImage imageWithCGImage:imageRef scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]; 106 | 107 | // 释放 108 | CGImageRelease(imageRef); 109 | CGContextRelease(context); 110 | CGColorSpaceRelease(colorSpace); 111 | 112 | // free(rgbImageBuf) 创建dataProvider时已提供释放函数,这里不用free 113 | return resultUIImage; 114 | 115 | } 116 | 117 | void ProviderReleaseData (void *info, const void *data, size_t size) 118 | { 119 | free((void*)data); 120 | } 121 | 122 | - (CAShapeLayer *)lineLayerWithStartPoint:(CGPoint)startPoint controlPoint:(CGPoint)controlPoint endPoint:(CGPoint)endPoint size:(CGSize)size{ 123 | UIBezierPath *path = [UIBezierPath bezierPath]; 124 | [path moveToPoint:startPoint]; 125 | [path addQuadCurveToPoint:endPoint controlPoint:controlPoint]; 126 | 127 | CAShapeLayer *layer = [[CAShapeLayer alloc] init]; 128 | layer.bounds = CGRectMake(0, 0, size.width, size.height); 129 | layer.path = path.CGPath; 130 | layer.lineWidth = _lineWidth; 131 | layer.strokeColor = [UIColor redColor].CGColor; 132 | layer.fillColor = [UIColor clearColor].CGColor; 133 | 134 | return layer; 135 | } 136 | 137 | #pragma mark - 计算 t 值 138 | - (float)tAtPoint:(CGPoint)point { 139 | return [self quadraticEquationWithPoint:point]; 140 | } 141 | 142 | #pragma mark - 解方程 143 | #pragma mark ---一元二次方程 144 | - (float)quadraticEquationWithPoint:(CGPoint)point { 145 | // 两个方向上都算一下,取一下最优的 146 | float baseOnXT = [self baseOnXWithPoint:point]; 147 | float baseOnYT = [self baseOnYWithPoint:point]; 148 | float t = [self betterRWithRs:@[@(baseOnXT),@(baseOnYT)] targetPoint:point]; 149 | if (t == -1) { 150 | // 如果不在线上,可以认为是顶点位置。从两个方向上的顶点位置取一个最优的。 151 | float xVertex = [self tForYAtVertexPoint]; 152 | float yVertex = [self tForYAtVertexPoint]; 153 | t = [self betterRWithRs:@[@(xVertex),@(yVertex)] targetPoint:point]; 154 | } 155 | return t; 156 | } 157 | // 根据 x 计算 t 158 | - (float)baseOnXWithPoint:(CGPoint)point { 159 | float a = _startPoint.x - 2 * _controlPoint.x + _endPoint.x; 160 | float b = 2 * _controlPoint.x - 2 * _startPoint.x; 161 | float c = _startPoint.x - point.x; 162 | float condition = pow(b, 2) - 4 * a * c; 163 | if (a != 0 ) { 164 | if (condition >= 0) { 165 | NSArray *r = [self quadraticEquationWithA:a b:b c:c]; 166 | if (r && r.count > 0) { 167 | float t = [self betterRWithRs:r targetPoint:point]; 168 | return t; 169 | } 170 | } 171 | } else { 172 | // 一元一次方程求解 173 | float t = (-c)/b; 174 | return t; 175 | } 176 | return -1; 177 | } 178 | 179 | // 根据 y 计算 t 180 | - (float)baseOnYWithPoint:(CGPoint)point { 181 | float a = _startPoint.y - 2 * _controlPoint.y + _endPoint.y; 182 | float b = 2 * _controlPoint.y - 2 * _startPoint.y; 183 | float c = _startPoint.y - point.y; 184 | float condition = pow(b, 2) - 4 * a * c; 185 | if ( a != 0) { 186 | if (condition >= 0) { 187 | NSArray *r = [self quadraticEquationWithA:a b:b c:c]; 188 | if (r && r.count > 0) { 189 | float t = [self betterRWithRs:r targetPoint:point]; 190 | return t; 191 | } 192 | } 193 | } else { 194 | // 一元一次方程求解 195 | float t = (-c)/b; 196 | return t; 197 | } 198 | 199 | return -1; 200 | } 201 | // 筛选结果 202 | - (float)betterRWithRs:(NSArray *)rs targetPoint:(CGPoint)point{ 203 | CGFloat distance = NSNotFound; 204 | NSInteger betterIndex = -1; 205 | for (NSInteger i = 0; i < rs.count; i ++) { 206 | float t = [[rs objectAtIndex:i] floatValue]; 207 | if (t == -1) { 208 | continue; 209 | } 210 | CGFloat x = [self xAtT:t]; 211 | CGFloat y = [self yAtT:t]; 212 | if (distance == NSNotFound) { 213 | distance = [self distanceWithPoint:CGPointMake(x, y) point1:point]; 214 | betterIndex = i; 215 | 216 | } else { 217 | if (distance > [self distanceWithPoint:CGPointMake(x, y) point1:point]) { 218 | distance = [self distanceWithPoint:CGPointMake(x, y) point1:point]; 219 | betterIndex = i; 220 | } 221 | } 222 | 223 | } 224 | if (betterIndex == -1) { 225 | return -1; 226 | } 227 | float t = [rs[betterIndex] floatValue]; 228 | if (t >= 1) { 229 | if ([self isNearbyTargetPoint:_endPoint x:point.x y:point.y]) { 230 | return 1; 231 | } else { 232 | return -1; 233 | } 234 | } 235 | 236 | if (t <= 0) { 237 | if ([self isNearbyTargetPoint:_startPoint x:point.x y:point.y]) { 238 | return 0; 239 | } else { 240 | return -1; 241 | } 242 | } 243 | return [rs[betterIndex] floatValue]; 244 | } 245 | 246 | // 一元二次方程的求根公式 247 | - (NSArray *)quadraticEquationWithA:(float)a b:(float)b c:(float)c { 248 | float condition = pow(b, 2) - 4 * a * c; 249 | if (condition >= 0) { 250 | float r1 = (-sqrtf(condition) - b)/(2 * a); 251 | float r2 = (sqrtf(condition) - b)/(2 * a); 252 | return @[@(r1),@(r2)]; 253 | } 254 | return nil; 255 | 256 | } 257 | 258 | // 计算两个点的距离 259 | - (float)distanceWithPoint:(CGPoint)point point1:(CGPoint)point1 { 260 | return sqrt(pow(point.x - point1.x, 2) + pow(point.y - point1.y, 2)); 261 | } 262 | 263 | // 判断两个点是否接近,参考值为线宽 264 | - (BOOL)isNearbyTargetPoint:(CGPoint)targetPoint x:(float)x y:(float)y{ 265 | return fabs(x - targetPoint.x) <= ceil(_lineWidth) && fabs(y - targetPoint.y) <= ceil(_lineWidth) ; 266 | } 267 | #pragma mark - 贝塞尔曲线的相关计算公式 268 | #pragma mark ---计算点的速度 269 | - (CGFloat)speedAtT:(CGFloat)t { 270 | CGFloat xSpeed = [self xSpeedAtT:t]; 271 | CGFloat ySpeed = [self ySpeedAtT:t]; 272 | CGFloat speed = sqrt(pow(xSpeed, 2) + pow(ySpeed, 2)); 273 | return speed; 274 | } 275 | 276 | - (CGFloat)xSpeedAtT:(CGFloat)t { 277 | return 2 * (_startPoint.x + _endPoint.x - 2 * _controlPoint.x) * t + 2 * (_controlPoint.x - _startPoint.x);; 278 | } 279 | 280 | - (CGFloat)ySpeedAtT:(CGFloat)t { 281 | return 2 * (_startPoint.y + _endPoint.y - 2 * _controlPoint.y) * t + 2 * (_controlPoint.y - _startPoint.y); 282 | } 283 | 284 | #pragma mark ---根据T计算点的位置 285 | - (CGFloat)xAtT:(CGFloat)t { 286 | CGFloat x = pow((1-t), 2) * _startPoint.x + 2 * (1-t)* t * _controlPoint.x + pow(t, 2) * _endPoint.x; 287 | return x; 288 | } 289 | 290 | - (CGFloat)yAtT:(CGFloat)t { 291 | CGFloat y = pow((1-t), 2) * _startPoint.y + 2 * (1-t) * t * _controlPoint.y + pow(t, 2) * _endPoint.y; 292 | return y; 293 | } 294 | #pragma mark ---计算顶点相关 295 | - (CGPoint)yVertexPoint { 296 | CGFloat t = [self tForYAtVertexPoint]; 297 | if (t >= 0 && t <= 1) { 298 | CGFloat x = [self xAtT:t]; 299 | CGFloat y = [self yAtT:t]; 300 | 301 | return CGPointMake(x, y); 302 | } 303 | return CGPointZero; 304 | } 305 | 306 | - (CGPoint)XVertexPoint { 307 | CGFloat t = [self tForXAtVertexPoint]; 308 | if (t >= 0 && t <= 1) { 309 | CGFloat x = [self xAtT:t]; 310 | CGFloat y = [self yAtT:t]; 311 | return CGPointMake(x, y); 312 | } 313 | return CGPointZero; 314 | } 315 | 316 | - (CGFloat)tForYAtVertexPoint { 317 | CGFloat t = (_startPoint.y - _controlPoint.y)/(_startPoint.y + _endPoint.y - 2 * _controlPoint.y); 318 | return t; 319 | } 320 | - (CGFloat)tForXAtVertexPoint { 321 | CGFloat t = (_startPoint.x - _controlPoint.x)/(_startPoint.x + _endPoint.x - 2 * _controlPoint.x); 322 | return t; 323 | } 324 | #pragma mark ---曲线长度 325 | - (CGFloat)lengthWithT:(CGFloat)t { 326 | NSInteger totalStep = 1000; 327 | 328 | NSInteger stepCounts = (NSInteger)(totalStep * t); 329 | 330 | if(stepCounts & 1) stepCounts++; 331 | 332 | if(stepCounts==0) return 0.0; 333 | 334 | NSInteger halfCounts = stepCounts/2; 335 | CGFloat sum1=0.0, sum2=0.0; 336 | CGFloat dStep = (t * 1.0)/stepCounts; 337 | for(NSInteger i=0; i 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | IB_DESIGNABLE 7 | @interface GradientRingView : UIView 8 | @property (nonatomic, assign) IBInspectable CGFloat lineWidth; 9 | @end 10 | 11 | NS_ASSUME_NONNULL_END 12 | -------------------------------------------------------------------------------- /GradientBezierLine/GradientRingView.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | #import "GradientRingView.h" 4 | 5 | @implementation GradientRingView 6 | - (void)drawRect:(CGRect)rect { 7 | CGFloat width = CGRectGetWidth(rect); 8 | CGFloat height = CGRectGetHeight(rect); 9 | CGFloat diameter = MIN(width, height); 10 | 11 | // 裁剪圆环 12 | UIBezierPath *bPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, diameter,diameter)]; 13 | bPath.usesEvenOddFillRule = YES; 14 | CGFloat lineWidth = MAX(2, _lineWidth); 15 | UIBezierPath *bsPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(lineWidth, lineWidth, diameter - lineWidth * 2, diameter - lineWidth * 2)]; 16 | [bPath appendPath:bsPath]; 17 | [bPath addClip]; 18 | 19 | CGFloat arcStep = (M_PI *2) / 360; 20 | BOOL clocklwise = NO; 21 | CGFloat x = CGRectGetWidth(rect) / 2; 22 | CGFloat y = CGRectGetHeight(rect) / 2; 23 | CGFloat radius = MIN(x, y) / 2; 24 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 25 | 26 | CGContextSetLineWidth(ctx, radius*2); 27 | for (CGFloat i = 0; i < 360; i+=1) { 28 | UIColor* c = [UIColor colorWithHue:i/360 saturation:1. brightness:1. alpha:1]; 29 | CGContextSetStrokeColorWithColor(ctx, c.CGColor); 30 | CGFloat startAngle = i * arcStep; 31 | CGFloat endAngle = startAngle + arcStep + 0.02; 32 | CGContextAddArc(ctx, x, y, radius, startAngle, endAngle, clocklwise); 33 | CGContextStrokePath(ctx); 34 | } 35 | } 36 | @end 37 | -------------------------------------------------------------------------------- /GradientBezierLine/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /GradientBezierLine/NextVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // NextVC.h 3 | // GradientBezierLine 4 | // 5 | // Created by zhanggy on 2018/4/24. 6 | // Copyright © 2018年 Ive. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NextVC : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /GradientBezierLine/NextVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // NextVC.m 3 | // GradientBezierLine 4 | // 5 | // Created by zhanggy on 2018/4/24. 6 | // Copyright © 2018年 Ive. All rights reserved. 7 | // 8 | 9 | #import "NextVC.h" 10 | #import "GradientLine.h" 11 | 12 | @interface NextVC (){ 13 | CGPoint _startPoint ; 14 | CGPoint _controlPoint1 ; 15 | CGPoint _middlePoint ; 16 | CGPoint _controlPoint2; 17 | CGPoint _endPoint; 18 | BOOL _shouldAddPointView; 19 | } 20 | @property (nonatomic, strong) UIImageView *imageView ; 21 | @property (nonatomic, strong) UIImageView *imageViewNext; 22 | 23 | @property (nonatomic, strong) UIButton *clearButton; 24 | @property (nonatomic, strong) NSMutableArray *pointViewArray; 25 | @property (weak, nonatomic) IBOutlet UILabel *desLabel; 26 | @end 27 | 28 | @implementation NextVC 29 | 30 | - (void)viewDidLoad { 31 | [super viewDidLoad]; 32 | // Do any additional setup after loading the view. 33 | self.view.backgroundColor = [UIColor whiteColor]; 34 | _startPoint = CGPointZero; 35 | _endPoint = CGPointZero; 36 | _controlPoint1 = CGPointZero; 37 | _controlPoint2 = CGPointZero; 38 | _middlePoint = CGPointZero; 39 | _shouldAddPointView = YES; 40 | 41 | [self.view addSubview:self.imageView]; 42 | [self.view addSubview:self.imageViewNext]; 43 | [self.view addSubview:self.clearButton]; 44 | [self.view addSubview:self.desLabel]; 45 | } 46 | 47 | - (void)clearUp { 48 | self.imageView.image = nil; 49 | self.imageViewNext.image = nil; 50 | _startPoint = CGPointZero; 51 | _endPoint = CGPointZero; 52 | _controlPoint1 = CGPointZero; 53 | _controlPoint2 = CGPointZero; 54 | _middlePoint = CGPointZero; 55 | _shouldAddPointView = YES; 56 | 57 | _desLabel.text = @"选择起始点"; 58 | } 59 | 60 | -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 61 | CGPoint point = [[touches anyObject] locationInView:self.view]; 62 | if (_shouldAddPointView) { 63 | [self addPointViewWithPoint:point]; 64 | } 65 | 66 | if (CGPointEqualToPoint(_startPoint, CGPointZero)) { 67 | _startPoint = point; 68 | _desLabel.text = @"选择中间点"; 69 | return; 70 | } 71 | 72 | if (CGPointEqualToPoint(_middlePoint, CGPointZero)) { 73 | _middlePoint = point; 74 | _desLabel.text = @"选择控制点"; 75 | return; 76 | } 77 | 78 | // 控制点为一对关于中间点对称的点,不然做出来的曲线会有明显的转折点。 79 | if (CGPointEqualToPoint(_controlPoint1, CGPointZero)) { 80 | _controlPoint1 = point; 81 | // _controlPoint2 是 _controlPoint1 关于 _middlePoint 的对称点 82 | _controlPoint2 = CGPointMake(2 * _middlePoint.x - _controlPoint1.x, 2 * _middlePoint.y - _controlPoint1.y); 83 | [self addPointViewWithPoint:_controlPoint2]; 84 | _desLabel.text = @"选择结束点"; 85 | return; 86 | } 87 | 88 | if (CGPointEqualToPoint(_endPoint, CGPointZero)) { 89 | _endPoint = point; 90 | GradientLine * line1 = [[GradientLine alloc] initWithStartPoint:_startPoint controlPoint:_controlPoint1 endPoint:_middlePoint]; 91 | GradientLine * line2 = [[GradientLine alloc] initWithStartPoint:_middlePoint controlPoint:_controlPoint2 endPoint:_endPoint]; 92 | 93 | UIColor *startColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:1]; 94 | UIColor *endColor = [UIColor colorWithRed:0 green:1 blue:0 alpha:1]; 95 | 96 | CGFloat lineLength1 = [line1 lengthWithT:1.0] ; 97 | CGFloat lineLength2 = [line2 lengthWithT:1.0]; 98 | CGFloat totalLineLength = lineLength1 + lineLength2; 99 | 100 | UIColor *middleColor = [UIColor colorWithRed:1 - (lineLength1/totalLineLength) green:lineLength1/totalLineLength blue:0 alpha:1]; 101 | 102 | self.imageView.image = [line1 gradientLineWithStartColor:startColor endColor:middleColor size:self.imageView.frame.size]; 103 | 104 | self.imageViewNext.image = [line2 gradientLineWithStartColor:middleColor endColor:endColor size:self.imageView.frame.size]; 105 | 106 | _desLabel.text = [NSString stringWithFormat:@"线段总长度为:%f",totalLineLength]; 107 | _shouldAddPointView = NO; 108 | 109 | [_pointViewArray makeObjectsPerformSelector:@selector(removeFromSuperview)]; 110 | } 111 | } 112 | 113 | - (void)addPointViewWithPoint:(CGPoint)point { 114 | UIView *pointView = [[UIView alloc] initWithFrame:CGRectMake(point.x - 5, point.y - 5, 10, 10)]; 115 | pointView.layer.cornerRadius = 5; 116 | pointView.clipsToBounds = YES; 117 | pointView.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.3]; 118 | [self.view addSubview:pointView]; 119 | [self.pointViewArray addObject:pointView]; 120 | } 121 | #pragma mark - setter && getter 122 | 123 | - (UIButton *)clearButton { 124 | if (!_clearButton) { 125 | _clearButton = [UIButton buttonWithType:UIButtonTypeSystem]; 126 | _clearButton.frame = CGRectMake(20, CGRectGetHeight(self.view.bounds) - 60, CGRectGetWidth(self.view.bounds) - 40, 40); 127 | _clearButton.backgroundColor = [UIColor lightGrayColor]; 128 | [_clearButton setTitle:@"clear" forState:UIControlStateNormal]; 129 | [_clearButton addTarget:self action:@selector(clearUp) forControlEvents:UIControlEventTouchUpInside]; 130 | } 131 | return _clearButton; 132 | } 133 | 134 | - (UIImageView *)imageView { 135 | if (!_imageView) { 136 | _imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; 137 | } 138 | return _imageView; 139 | } 140 | - (UIImageView *)imageViewNext { 141 | if (!_imageViewNext) { 142 | _imageViewNext = [[UIImageView alloc] initWithFrame:self.view.bounds]; 143 | } 144 | return _imageViewNext; 145 | } 146 | 147 | - (NSMutableArray *)pointViewArray { 148 | if (!_pointViewArray) { 149 | _pointViewArray = [NSMutableArray array]; 150 | } 151 | return _pointViewArray; 152 | } 153 | 154 | @end 155 | -------------------------------------------------------------------------------- /GradientBezierLine/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // GradientBezierLine 4 | 5 | 6 | #import 7 | 8 | @interface ViewController : UIViewController 9 | 10 | 11 | @end 12 | 13 | -------------------------------------------------------------------------------- /GradientBezierLine/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // GradientBezierLine 4 | 5 | 6 | #import "ViewController.h" 7 | #import "GradientLine.h" 8 | 9 | @interface ViewController () { 10 | CGPoint _startPoint ; 11 | CGPoint _endPoint ; 12 | CGPoint _controlPoint ; 13 | BOOL _shouldAddPointView; 14 | } 15 | @property (nonatomic, strong) UIImageView *imageView ; 16 | 17 | @property (nonatomic, strong) UIButton *clearButton; 18 | @property (nonatomic, strong) NSMutableArray *pointViewArray; 19 | @property (weak, nonatomic) IBOutlet UILabel *desLabel; 20 | 21 | @end 22 | 23 | @implementation ViewController 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | self.view.backgroundColor = [UIColor whiteColor]; 28 | _startPoint = CGPointZero; 29 | _endPoint = CGPointZero; 30 | _controlPoint = CGPointZero; 31 | _shouldAddPointView = YES; 32 | 33 | [self.view addSubview:self.imageView]; 34 | [self.view addSubview:self.clearButton]; 35 | [self.view addSubview:self.desLabel]; 36 | } 37 | - (void)clearUp { 38 | _shouldAddPointView = YES; 39 | self.imageView.image = nil; 40 | _startPoint = CGPointZero; 41 | _endPoint = CGPointZero; 42 | _controlPoint = CGPointZero; 43 | [_pointViewArray makeObjectsPerformSelector:@selector(removeFromSuperview)]; 44 | _desLabel.text = @"连选三个点生成贝塞尔曲线"; 45 | } 46 | 47 | -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 48 | CGPoint point = [[touches anyObject] locationInView:self.view]; 49 | if (_shouldAddPointView) { 50 | [self addPointViewWithPoint:point]; 51 | } 52 | 53 | if (CGPointEqualToPoint(_startPoint, CGPointZero)) { 54 | _startPoint = point; 55 | return; 56 | } 57 | 58 | if (CGPointEqualToPoint(_controlPoint, CGPointZero)) { 59 | _controlPoint = point; 60 | return; 61 | } 62 | 63 | if (CGPointEqualToPoint(_endPoint, CGPointZero)) { 64 | _endPoint = point; 65 | GradientLine * line = [[GradientLine alloc] initWithStartPoint:_startPoint controlPoint:_controlPoint endPoint:_endPoint]; 66 | self.imageView.image = [line gradientLineWithStartColor:[UIColor redColor] endColor:[UIColor greenColor] size:self.imageView.frame.size]; 67 | _desLabel.text = [NSString stringWithFormat:@"连选三个点生成贝塞尔曲线\n长度为:%f",[line lengthWithT:1.0]]; 68 | _shouldAddPointView = NO; 69 | } 70 | } 71 | 72 | - (void)addPointViewWithPoint:(CGPoint)point { 73 | UIView *pointView = [[UIView alloc] initWithFrame:CGRectMake(point.x - 5, point.y - 5, 10, 10)]; 74 | pointView.layer.cornerRadius = 5; 75 | pointView.clipsToBounds = YES; 76 | pointView.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.3]; 77 | [self.view addSubview:pointView]; 78 | [self.pointViewArray addObject:pointView]; 79 | } 80 | #pragma mark - setter && getter 81 | 82 | - (UIButton *)clearButton { 83 | if (!_clearButton) { 84 | _clearButton = [UIButton buttonWithType:UIButtonTypeSystem]; 85 | _clearButton.frame = CGRectMake(20, CGRectGetHeight(self.view.bounds) - 60, CGRectGetWidth(self.view.bounds) - 40, 40); 86 | _clearButton.backgroundColor = [UIColor lightGrayColor]; 87 | [_clearButton setTitle:@"clear" forState:UIControlStateNormal]; 88 | [_clearButton addTarget:self action:@selector(clearUp) forControlEvents:UIControlEventTouchUpInside]; 89 | } 90 | return _clearButton; 91 | } 92 | 93 | - (UIImageView *)imageView { 94 | if (!_imageView) { 95 | _imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; 96 | } 97 | return _imageView; 98 | } 99 | - (NSMutableArray *)pointViewArray { 100 | if (!_pointViewArray) { 101 | _pointViewArray = [NSMutableArray array]; 102 | } 103 | return _pointViewArray; 104 | } 105 | @end 106 | -------------------------------------------------------------------------------- /GradientBezierLine/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // GradientBezierLine 4 | // 5 | // Created by 张桂杨 on 2016/12/26. 6 | // Copyright © 2016年 Ive. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS 沿曲线线性渐变的贝塞尔曲线 2 | 3 | iOS原生的渐变只支持线性的渐变,但有的时候我们需要沿曲线进行渐变。 4 | 先看下垂直线性渐变与沿曲线线性渐变的区别 5 | ![垂直线性渐变:颜色最亮的地方在曲线的最低点](http://upload-images.jianshu.io/upload_images/1681985-f5bea9e1d9f408f1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/500) 6 | ![沿曲线线性渐变:颜色最亮的地方在起点](http://upload-images.jianshu.io/upload_images/1681985-ba5176d179f77972.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/500) 7 | 8 | 那么先来分析一下这个问题: 9 | 10 | 1. 怎样绘制曲线? 11 | 对于贝塞尔曲线的绘制,系统提供了一系列的方法,同时我们已可以通过公式计算出一条贝塞尔曲线。 12 | 13 | 2. 如何保证颜色渐变? 14 | 找到曲线上的点,计算出每一个点的色值。 15 | 16 | 只要解决上面的问题就可以画出一条沿曲线线性渐变的贝塞尔曲线,曲线画起来还是比较简单的,但是这样计算出每一点的色值是一件比较麻烦的事情。 17 | 18 | #### 1、贝塞尔曲线 19 | 这里先介绍一下贝塞尔曲线的一些东西,以二次贝塞尔曲线为例,先来动态感受一下绘制过程 20 | 21 | ![二次贝塞尔曲线](http://upload-images.jianshu.io/upload_images/1681985-ccdeca82b531935b.gif?imageMogr2/auto-orient/strip) 22 | 23 | 一条二次贝塞尔曲线需要三个点,A:起点 ;B:控制点;C:终点 24 | 25 | ![](http://upload-images.jianshu.io/upload_images/1681985-99b2322cd565b4c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520) 26 | 27 | 然后在AB上取点E,在BC上取点F 。使AD:AB = BE:BC 28 | 29 | ![第一次取点](http://upload-images.jianshu.io/upload_images/1681985-b7817ce6361f8792.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520) 30 | 31 | 在DE上取点F,使DF:DE = AD:AB = BE:BC 32 | 33 | ![第二次取点](http://upload-images.jianshu.io/upload_images/1681985-9bbc8768a9703891.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520) 34 | 35 | F点就是贝塞尔曲线上的一个点,以此类推,取点一系列的点之后在ABC之间就产生了一条贝塞尔曲线 36 | 37 | ![贝塞尔曲线](http://upload-images.jianshu.io/upload_images/1681985-205360f03149489d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520) 38 | 39 | 可以看出贝塞尔曲线上的每个点是有规律的,二次贝塞尔曲线的方程为 40 | P0:起点;P1:控制点; P2:终点 ;t:百分比 41 | 42 | ![二次贝塞尔曲线的方程](http://upload-images.jianshu.io/upload_images/1681985-7ca7bcf2f8f69db2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 43 | 用OC表达的话就是这样的 44 | ``` 45 | CGFloat x = pow((1-t), 2) * _startPoint.x + 2 * (1-t) * t * _controlPoint.x + pow(t, 2) * _endPoint.x; 46 | CGFloat y = pow((1-t), 2) * _startPoint.y + 2 * (1-t) * t * _controlPoint.y + pow(t, 2) * _endPoint.y; 47 | ``` 48 | #### 2、渐变色 49 | 既然已经一颗贝塞尔曲线的方程,那就可以操作曲线上的每一个点了,那怎么设置每一个点的颜色的。 50 | ###### 1、取点 51 | 对于取点还有一个问题需要注意,由于贝塞尔曲线并不是匀速变化的,所有如果均匀分割 t 来进行取点的话,取出来的点是不均匀的。不均匀的点会造成有的地方缺失点,形成空白。所以需要对 t 进行修正,取出间隔均匀的点。 52 | 53 | ![均匀间隔的 t ](http://upload-images.jianshu.io/upload_images/1681985-c44f1330c9988e71.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520) 54 | 55 | 想要均匀的点,就需要线计算出曲线长度,接下来就使用[辛普森积分法](http://en.wikipedia.org/wiki/Simpson's_rule)来计算曲线的长度。这个求的是二次贝塞尔曲线的长度,如果需更高次的曲线,可修改一下修改。 56 | ``` 57 | //曲线长度 58 | - (CGFloat)lengthWithT:(CGFloat)t{ 59 |     NSInteger totalStep = 1000; 60 |      61 |     NSInteger stepCounts = (NSInteger)(totalStep * t); 62 |      63 |     if(stepCounts & 1) stepCounts++; 64 |      65 |     if(stepCounts==0) return 0.0; 66 |      67 |     NSInteger halfCounts = stepCounts/2; 68 |     CGFloat sum1=0.0, sum2=0.0; 69 |     CGFloat dStep = (t * 1.0)/stepCounts; 70 |     for(NSInteger i=0; i= 0) { 163 | NSArray *r = [self quadraticEquationWithA:a b:b c:c]; 164 | if (r && r.count > 0) { 165 | float t = [self betterRWithRs:r targetPoint:point]; 166 | return t; 167 | } 168 | } 169 | } else { 170 | // 一元一次方程求解 171 | float t = (-c)/b; 172 | return t; 173 | } 174 | return -1; 175 | } 176 | 177 | // 根据 y 计算 t 178 | - (float)baseOnYWithPoint:(CGPoint)point { 179 | float a = _startPoint.y - 2 * _controlPoint.y + _endPoint.y; 180 | float b = 2 * _controlPoint.y - 2 * _startPoint.y; 181 | float c = _startPoint.y - point.y; 182 | float condition = pow(b, 2) - 4 * a * c; 183 | if ( a != 0) { 184 | if (condition >= 0) { 185 | NSArray *r = [self quadraticEquationWithA:a b:b c:c]; 186 | if (r && r.count > 0) { 187 | float t = [self betterRWithRs:r targetPoint:point]; 188 | return t; 189 | } 190 | } 191 | } else { 192 | // 一元一次方程求解 193 | float t = (-c)/b; 194 | return t; 195 | } 196 | 197 | return -1; 198 | } 199 | ``` 200 | 201 | 这里会有两个方程,一个是以x为参数,一个以y为参数。这两个方程都会用到。为什么要用两个方程?因为有的点通过x或者y 并不能解得结果,比如说顶点附近的点,通过点做 x 轴的 垂线,可能与曲线并不会交点,也就意味着不会有解。在这里为了准确度,在x方向和y方向都做了计算,然后取最优的点。 202 | 203 | 当曲线的顶点比较陡的话,可能通过上面的计算并不会有解。那么这种情况就认为这个点就是顶点附近的点,然后计算出x方面和y方向的顶点值,取最优解。 204 | 205 | ``` 206 | - (float)quadraticEquationWithPoint:(CGPoint)point { 207 | // 两个方向上都算一下,取一下最优的 208 | float baseOnXT = [self baseOnXWithPoint:point]; 209 | float baseOnYT = [self baseOnYWithPoint:point]; 210 | float t = [self betterRWithRs:@[@(baseOnXT),@(baseOnYT)] targetPoint:point]; 211 | if (t == -1) { 212 | // 如果不在线上,可以认为是顶点位置。从两个方向上的顶点位置取一个最优的。 213 | float xVertex = [self tForYAtVertexPoint]; 214 | float yVertex = [self tForYAtVertexPoint]; 215 | t = [self betterRWithRs:@[@(xVertex),@(yVertex)] targetPoint:point]; 216 | } 217 | return t; 218 | } 219 | ``` 220 | 221 | 222 | 对于一元二次方程,是会有两个根的情况的,所以对于解出来的结果需要进行比对,找到与目标点最接近的t值 223 | 224 | ``` 225 | // 筛选结果 226 | - (float)betterRWithRs:(NSArray *)rs targetPoint:(CGPoint)point{ 227 | CGFloat distance = NSNotFound; 228 | NSInteger betterIndex = 0; 229 | for (NSInteger i = 0; i < rs.count; i ++) { 230 | float t = [[rs objectAtIndex:i] floatValue]; 231 | CGFloat x = [self xAtT:t]; 232 | CGFloat y = [self yAtT:t]; 233 | if (distance == NSNotFound) { 234 | distance = [self distanceWithPoint:CGPointMake(x, y) point1:point]; 235 | betterIndex = i; 236 | 237 | } else { 238 | if (distance > [self distanceWithPoint:CGPointMake(x, y) point1:point]) { 239 | distance = [self distanceWithPoint:CGPointMake(x, y) point1:point]; 240 | betterIndex = i; 241 | } 242 | } 243 | 244 | } 245 | float t = [rs[betterIndex] floatValue]; 246 | if (t >= 1) { 247 | if ([self isNearbyTargetPoint:_endPoint x:point.x y:point.y]) { 248 | return 1; 249 | } else { 250 | return -1; 251 | } 252 | } 253 | 254 | if (t <= 0) { 255 | if ([self isNearbyTargetPoint:_startPoint x:point.x y:point.y]) { 256 | return 0; 257 | } else { 258 | return -1; 259 | } 260 | } 261 | return [rs[betterIndex] floatValue]; 262 | } 263 | ``` 264 | 265 | 可以先看下效果。整体来说效果还是理想的,并且也支持了线宽的问题。 266 | 267 | ![渐变曲线](http://upload-images.jianshu.io/upload_images/1681985-a936b1c1775106e8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520) 268 | --------------------------------------------------------------------------------