├── README.md ├── XPQLabelDome.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── xiepanqi.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── xiepanqi.xcuserdatad │ └── xcschemes │ │ ├── XPQLabelDome.xcscheme │ │ └── xcschememanagement.plist │ └── zhangzhiryuu.xcuserdatad │ └── xcschemes │ ├── XPQLabelDome.xcscheme │ └── xcschememanagement.plist ├── XPQLabelDome ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m ├── XPQLabel │ ├── XPQLabel.h │ ├── XPQLabel.m │ ├── XPQLabelPath.h │ ├── XPQLabelPath.mm │ └── XPQPath │ │ ├── XPQPath.cpp │ │ └── XPQPath.h └── main.m └── domeImage ├── alignmentDome.gif ├── animationDome1.gif ├── animationDome2.gif ├── classDiagram.png ├── gestureDome.gif ├── pathDome.gif ├── ratonDome.gif └── setText.png /README.md: -------------------------------------------------------------------------------- 1 | # XPQLabel 2 | 让你的文字动起来!!! 3 | 让你的文字随路径轨迹办法!!! 4 | 5 | XPQLabel能够只需简单的几句代码就让文本以各种轨迹显示和各种酷炫的动画效果。 6 | 7 | ## 类继承关系 8 | 9 | 10 | ![UML](https://github.com/xiepanqi/XPQLabel/blob/master/domeImage/classDiagram.png) 11 | 12 | 13 | ##语言 14 | 主要语言为object-c和c++混编。其中object-c主要负责基本显示和操作,C++主要负责路径的计算。 15 | 16 | ##使用 17 | XPQLabel使用非常简单,只需三步就可以完成使用。 18 | ###第一步,引入头文件 19 | 把XPQLabel文件夹和其中的文件全部拖进工程。引入头文件,#import "XPQLabel.h"。 20 | ###第二步,初始化 21 | ####使用代码初始化 22 | ```ios 23 | XPQLabel *label = [[XPQLabel alloc] init]; 24 | ``` 25 | ####可视化初始化 26 | 只需先拖一个UIView到storyboard或者xib上,再Class属性设置成XPQLabel,然后在与某一对象关联就行。 27 | ###第三步,设置文本 28 | ####设置普通文本 29 | #####代码设置 30 | ```ios 31 | label.font = [UIFont systemFontOfSize:18.0]; 32 | label.textColor = [UIColor blackColor]; 33 | label.text = @"这里是一串普通的文本文字。"; 34 | ``` 35 | #####storyboard或者xib设置 36 | 只需修改面板上的text属性和textColor属性,如下图: 37 | 38 | 39 | ![设置文本](https://github.com/xiepanqi/XPQLabel/blob/master/domeImage/setText.png) 40 | 41 | 42 | ####设置富文本 43 | 富文本只能通过代码设置 44 | ```ios 45 | NSMutableAttributedString *attriString = [[NSMutableAttributedString alloc] initWithString:@"this is attributed string."]; 46 | //把this的字体颜色变为红色 47 | [attriString addAttribute:(NSString *)kCTForegroundColorAttributeName 48 | value:(id)[UIColor redColor].CGColor 49 | range:NSMakeRange(0, 4)]; 50 | //把is变为绿色 51 | [attriString addAttribute:(NSString *)kCTForegroundColorAttributeName 52 | value:(id)[UIColor greenColor].CGColor 53 | range:NSMakeRange(5, 2)]; 54 | //改变attributed的字体 55 | [attriString addAttribute:(NSString *)kCTFontAttributeName value:(id)CFBridgingRelease(CTFontCreateWithName((CFStringRef)[UIFont boldSystemFontOfSize:12].fontName, 20, NULL)) range:NSMakeRange(8, 10)]; 56 | //给string加上下划线 57 | [attriString addAttribute:(NSString *)kCTUnderlineStyleAttributeName 58 | value:(id)[NSNumber numberWithInt:kCTUnderlineStyleDouble] 59 | range:NSMakeRange(19, 6)]; 60 | 61 | label.attributedText = attriString; 62 | ``` 63 | 64 | ##属性设置 65 | 当然,经过上面三部还只能简单的显示,如果需要一些额外的效果就需要设置一些属性了。 66 | ###文本对齐 67 | 文本对齐有两个属性textHorizontalAlignment和textVerticalAlignment,从字面意思就可以看出两个属性分别的作用。这两个属性分别对应下面两个枚举: 68 | ```ios 69 | typedef enum : NSUInteger { 70 | XPQLabelHorizontalAlignmentLeft, // 左对齐 71 | XPQLabelHorizontalAlignmentCenter, // 水平居中 72 | XPQLabelHorizontalAlignmentRight, // 右对齐 73 | } XPQLabelHorizontalAlignment; 74 | 75 | typedef enum : NSUInteger { 76 | XPQLabelVerticalAlignmentUp, // 垂直居上 77 | XPQLabelVerticalAlignmentCenter, // 垂直居中 78 | XPQLabelVerticalAlignmentDown, // 垂直居下 79 | } XPQLabelVerticalAlignment; 80 | ``` 81 | 使用效果如下图: 82 | 83 | 84 | ![对齐效果图](https://github.com/xiepanqi/XPQLabel/blob/master/domeImage/alignmentDome.gif) 85 | 86 | 87 | ###路径 88 | 只需设置这个属性就能让文字沿着指定路径显示。 89 | 路径是XPQLabelPath对象,XPQLabelPath的使用也非常简单。 90 | 先使用XPQLabelPath的pathForBeginPoint方法创建路径起点。 91 | ```ios 92 | XPQLabelPath *path = [XPQLabelPath pathForBeginPoint:CGPointMake(10.0, 10.0)]; 93 | ``` 94 | 95 | 96 | 再使用addLineToPoint:/addArcWithCentrePoint:angle:/addCurveToPoint:anchorPoint:来添加路径。 97 | 98 | 99 | ```ios 100 | // 添加直线 101 | [path addLineToPoint:CGPointMake(250.0, 50.0)]; 102 | // 添加圆曲线 103 | [path addArcWithCentrePoint:CGPointMake(90.0, 70.0) angle:-M_PI]; 104 | // 添加贝塞尔曲线 105 | [path addCurveToPoint:CGPointMake(300.0, 60.0) anchorPoint:CGPointMake(100.0, 0.0)]; 106 | ``` 107 | 最后再把路径赋值给path属性或者使用setPath:rotate:animation:方法 108 | ```ios 109 | // 带旋转和动画 110 | label.path = path; 111 | // 旋转和动画可选择 112 | [label setPath:path rotate:rotate animation:animation]; 113 | ``` 114 | 效果图如下: 115 | 116 | 117 | ![路径效果图](https://github.com/xiepanqi/XPQLabel/blob/master/domeImage/pathDome.gif) 118 | 119 | 120 | ###手势轨迹 121 | 这是一个很酷炫的功能(然而并没什么卵用)。 122 | 设置gesturePathEnable为YES后用手在XPQLabel上滑动,文字会根据手指滑动的轨迹显示,效果图如下: 123 | 124 | 125 | ![手势轨迹效果图](https://github.com/xiepanqi/XPQLabel/blob/master/domeImage/gestureDome.gif) 126 | 127 | 128 | ###入场出场动画 129 | 暂时只实现两种入场出场动画,调用函数分别为 130 | startShowWithDirection:duration:bounce:stepTime: 131 | startHideWithDirection:duration:stepTime: 132 | 133 | 134 | ![动画1](https://github.com/xiepanqi/XPQLabel/blob/master/domeImage/animationDome1.gif) 135 | 136 | 137 | startFixedShowWithTransform: duration:stepTime: 138 | startFixedHideWithTransform:duration:stepTime: 139 | 140 | 141 | ![动画2](https://github.com/xiepanqi/XPQLabel/blob/master/domeImage/animationDome2.gif) 142 | 143 | 144 | > **PS:** 如果感觉写的不错请star下,谢谢。 145 | -------------------------------------------------------------------------------- /XPQLabelDome.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E49D03711BE0A03B00BA51CE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E49D03701BE0A03B00BA51CE /* main.m */; }; 11 | E49D03741BE0A03B00BA51CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E49D03731BE0A03B00BA51CE /* AppDelegate.m */; }; 12 | E49D03771BE0A03B00BA51CE /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E49D03761BE0A03B00BA51CE /* ViewController.m */; }; 13 | E49D037A1BE0A03B00BA51CE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E49D03781BE0A03B00BA51CE /* Main.storyboard */; }; 14 | E49D037C1BE0A03B00BA51CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E49D037B1BE0A03B00BA51CE /* Assets.xcassets */; }; 15 | E49D037F1BE0A03B00BA51CE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E49D037D1BE0A03B00BA51CE /* LaunchScreen.storyboard */; }; 16 | E49D038E1BE0A08300BA51CE /* XPQLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = E49D03881BE0A08300BA51CE /* XPQLabel.m */; }; 17 | E49D038F1BE0A08300BA51CE /* XPQLabelPath.mm in Sources */ = {isa = PBXBuildFile; fileRef = E49D038A1BE0A08300BA51CE /* XPQLabelPath.mm */; }; 18 | E49D03901BE0A08300BA51CE /* XPQPath.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E49D038C1BE0A08300BA51CE /* XPQPath.cpp */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | E49D036C1BE0A03B00BA51CE /* XPQLabelDome.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XPQLabelDome.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | E49D03701BE0A03B00BA51CE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 24 | E49D03721BE0A03B00BA51CE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 25 | E49D03731BE0A03B00BA51CE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 26 | E49D03751BE0A03B00BA51CE /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 27 | E49D03761BE0A03B00BA51CE /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 28 | E49D03791BE0A03B00BA51CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 29 | E49D037B1BE0A03B00BA51CE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | E49D037E1BE0A03B00BA51CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | E49D03801BE0A03B00BA51CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | E49D03871BE0A08300BA51CE /* XPQLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XPQLabel.h; sourceTree = ""; }; 33 | E49D03881BE0A08300BA51CE /* XPQLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XPQLabel.m; sourceTree = ""; }; 34 | E49D03891BE0A08300BA51CE /* XPQLabelPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XPQLabelPath.h; sourceTree = ""; }; 35 | E49D038A1BE0A08300BA51CE /* XPQLabelPath.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = XPQLabelPath.mm; sourceTree = ""; }; 36 | E49D038C1BE0A08300BA51CE /* XPQPath.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = XPQPath.cpp; sourceTree = ""; }; 37 | E49D038D1BE0A08300BA51CE /* XPQPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XPQPath.h; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | E49D03691BE0A03B00BA51CE /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | E49D03631BE0A03B00BA51CE = { 52 | isa = PBXGroup; 53 | children = ( 54 | E49D036E1BE0A03B00BA51CE /* XPQLabelDome */, 55 | E49D036D1BE0A03B00BA51CE /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | E49D036D1BE0A03B00BA51CE /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | E49D036C1BE0A03B00BA51CE /* XPQLabelDome.app */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | E49D036E1BE0A03B00BA51CE /* XPQLabelDome */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | E49D03861BE0A08300BA51CE /* XPQLabel */, 71 | E49D03721BE0A03B00BA51CE /* AppDelegate.h */, 72 | E49D03731BE0A03B00BA51CE /* AppDelegate.m */, 73 | E49D03751BE0A03B00BA51CE /* ViewController.h */, 74 | E49D03761BE0A03B00BA51CE /* ViewController.m */, 75 | E49D03781BE0A03B00BA51CE /* Main.storyboard */, 76 | E49D037B1BE0A03B00BA51CE /* Assets.xcassets */, 77 | E49D037D1BE0A03B00BA51CE /* LaunchScreen.storyboard */, 78 | E49D03801BE0A03B00BA51CE /* Info.plist */, 79 | E49D036F1BE0A03B00BA51CE /* Supporting Files */, 80 | ); 81 | path = XPQLabelDome; 82 | sourceTree = ""; 83 | }; 84 | E49D036F1BE0A03B00BA51CE /* Supporting Files */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | E49D03701BE0A03B00BA51CE /* main.m */, 88 | ); 89 | name = "Supporting Files"; 90 | sourceTree = ""; 91 | }; 92 | E49D03861BE0A08300BA51CE /* XPQLabel */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | E49D03871BE0A08300BA51CE /* XPQLabel.h */, 96 | E49D03881BE0A08300BA51CE /* XPQLabel.m */, 97 | E49D03891BE0A08300BA51CE /* XPQLabelPath.h */, 98 | E49D038A1BE0A08300BA51CE /* XPQLabelPath.mm */, 99 | E49D038B1BE0A08300BA51CE /* XPQPath */, 100 | ); 101 | path = XPQLabel; 102 | sourceTree = ""; 103 | }; 104 | E49D038B1BE0A08300BA51CE /* XPQPath */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | E49D038C1BE0A08300BA51CE /* XPQPath.cpp */, 108 | E49D038D1BE0A08300BA51CE /* XPQPath.h */, 109 | ); 110 | path = XPQPath; 111 | sourceTree = ""; 112 | }; 113 | /* End PBXGroup section */ 114 | 115 | /* Begin PBXNativeTarget section */ 116 | E49D036B1BE0A03B00BA51CE /* XPQLabelDome */ = { 117 | isa = PBXNativeTarget; 118 | buildConfigurationList = E49D03831BE0A03B00BA51CE /* Build configuration list for PBXNativeTarget "XPQLabelDome" */; 119 | buildPhases = ( 120 | E49D03681BE0A03B00BA51CE /* Sources */, 121 | E49D03691BE0A03B00BA51CE /* Frameworks */, 122 | E49D036A1BE0A03B00BA51CE /* Resources */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | ); 128 | name = XPQLabelDome; 129 | productName = XPQLabelDome; 130 | productReference = E49D036C1BE0A03B00BA51CE /* XPQLabelDome.app */; 131 | productType = "com.apple.product-type.application"; 132 | }; 133 | /* End PBXNativeTarget section */ 134 | 135 | /* Begin PBXProject section */ 136 | E49D03641BE0A03B00BA51CE /* Project object */ = { 137 | isa = PBXProject; 138 | attributes = { 139 | LastUpgradeCheck = 0710; 140 | ORGANIZATIONNAME = com.xpq; 141 | TargetAttributes = { 142 | E49D036B1BE0A03B00BA51CE = { 143 | CreatedOnToolsVersion = 7.1; 144 | }; 145 | }; 146 | }; 147 | buildConfigurationList = E49D03671BE0A03B00BA51CE /* Build configuration list for PBXProject "XPQLabelDome" */; 148 | compatibilityVersion = "Xcode 3.2"; 149 | developmentRegion = English; 150 | hasScannedForEncodings = 0; 151 | knownRegions = ( 152 | en, 153 | Base, 154 | ); 155 | mainGroup = E49D03631BE0A03B00BA51CE; 156 | productRefGroup = E49D036D1BE0A03B00BA51CE /* Products */; 157 | projectDirPath = ""; 158 | projectRoot = ""; 159 | targets = ( 160 | E49D036B1BE0A03B00BA51CE /* XPQLabelDome */, 161 | ); 162 | }; 163 | /* End PBXProject section */ 164 | 165 | /* Begin PBXResourcesBuildPhase section */ 166 | E49D036A1BE0A03B00BA51CE /* Resources */ = { 167 | isa = PBXResourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | E49D037F1BE0A03B00BA51CE /* LaunchScreen.storyboard in Resources */, 171 | E49D037C1BE0A03B00BA51CE /* Assets.xcassets in Resources */, 172 | E49D037A1BE0A03B00BA51CE /* Main.storyboard in Resources */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | /* End PBXResourcesBuildPhase section */ 177 | 178 | /* Begin PBXSourcesBuildPhase section */ 179 | E49D03681BE0A03B00BA51CE /* Sources */ = { 180 | isa = PBXSourcesBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | E49D03771BE0A03B00BA51CE /* ViewController.m in Sources */, 184 | E49D03741BE0A03B00BA51CE /* AppDelegate.m in Sources */, 185 | E49D03901BE0A08300BA51CE /* XPQPath.cpp in Sources */, 186 | E49D038E1BE0A08300BA51CE /* XPQLabel.m in Sources */, 187 | E49D038F1BE0A08300BA51CE /* XPQLabelPath.mm in Sources */, 188 | E49D03711BE0A03B00BA51CE /* main.m in Sources */, 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXSourcesBuildPhase section */ 193 | 194 | /* Begin PBXVariantGroup section */ 195 | E49D03781BE0A03B00BA51CE /* Main.storyboard */ = { 196 | isa = PBXVariantGroup; 197 | children = ( 198 | E49D03791BE0A03B00BA51CE /* Base */, 199 | ); 200 | name = Main.storyboard; 201 | sourceTree = ""; 202 | }; 203 | E49D037D1BE0A03B00BA51CE /* LaunchScreen.storyboard */ = { 204 | isa = PBXVariantGroup; 205 | children = ( 206 | E49D037E1BE0A03B00BA51CE /* Base */, 207 | ); 208 | name = LaunchScreen.storyboard; 209 | sourceTree = ""; 210 | }; 211 | /* End PBXVariantGroup section */ 212 | 213 | /* Begin XCBuildConfiguration section */ 214 | E49D03811BE0A03B00BA51CE /* Debug */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | ALWAYS_SEARCH_USER_PATHS = NO; 218 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 219 | CLANG_CXX_LIBRARY = "libc++"; 220 | CLANG_ENABLE_MODULES = YES; 221 | CLANG_ENABLE_OBJC_ARC = YES; 222 | CLANG_WARN_BOOL_CONVERSION = YES; 223 | CLANG_WARN_CONSTANT_CONVERSION = YES; 224 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 225 | CLANG_WARN_EMPTY_BODY = YES; 226 | CLANG_WARN_ENUM_CONVERSION = YES; 227 | CLANG_WARN_INT_CONVERSION = YES; 228 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 229 | CLANG_WARN_UNREACHABLE_CODE = YES; 230 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 231 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 232 | COPY_PHASE_STRIP = NO; 233 | DEBUG_INFORMATION_FORMAT = dwarf; 234 | ENABLE_STRICT_OBJC_MSGSEND = YES; 235 | ENABLE_TESTABILITY = YES; 236 | GCC_C_LANGUAGE_STANDARD = gnu99; 237 | GCC_DYNAMIC_NO_PIC = NO; 238 | GCC_NO_COMMON_BLOCKS = YES; 239 | GCC_OPTIMIZATION_LEVEL = 0; 240 | GCC_PREPROCESSOR_DEFINITIONS = ( 241 | "DEBUG=1", 242 | "$(inherited)", 243 | ); 244 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 245 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 246 | GCC_WARN_UNDECLARED_SELECTOR = YES; 247 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 248 | GCC_WARN_UNUSED_FUNCTION = YES; 249 | GCC_WARN_UNUSED_VARIABLE = YES; 250 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 251 | MTL_ENABLE_DEBUG_INFO = YES; 252 | ONLY_ACTIVE_ARCH = YES; 253 | SDKROOT = iphoneos; 254 | TARGETED_DEVICE_FAMILY = "1,2"; 255 | }; 256 | name = Debug; 257 | }; 258 | E49D03821BE0A03B00BA51CE /* Release */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | ALWAYS_SEARCH_USER_PATHS = NO; 262 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 263 | CLANG_CXX_LIBRARY = "libc++"; 264 | CLANG_ENABLE_MODULES = YES; 265 | CLANG_ENABLE_OBJC_ARC = YES; 266 | CLANG_WARN_BOOL_CONVERSION = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INT_CONVERSION = YES; 272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 273 | CLANG_WARN_UNREACHABLE_CODE = YES; 274 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 275 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 276 | COPY_PHASE_STRIP = NO; 277 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 278 | ENABLE_NS_ASSERTIONS = NO; 279 | ENABLE_STRICT_OBJC_MSGSEND = YES; 280 | GCC_C_LANGUAGE_STANDARD = gnu99; 281 | GCC_NO_COMMON_BLOCKS = YES; 282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 284 | GCC_WARN_UNDECLARED_SELECTOR = YES; 285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 286 | GCC_WARN_UNUSED_FUNCTION = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 289 | MTL_ENABLE_DEBUG_INFO = NO; 290 | SDKROOT = iphoneos; 291 | TARGETED_DEVICE_FAMILY = "1,2"; 292 | VALIDATE_PRODUCT = YES; 293 | }; 294 | name = Release; 295 | }; 296 | E49D03841BE0A03B00BA51CE /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 300 | INFOPLIST_FILE = XPQLabelDome/Info.plist; 301 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 302 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 303 | PRODUCT_BUNDLE_IDENTIFIER = com.xpq.XPQLabelDome; 304 | PRODUCT_NAME = "$(TARGET_NAME)"; 305 | }; 306 | name = Debug; 307 | }; 308 | E49D03851BE0A03B00BA51CE /* Release */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 312 | INFOPLIST_FILE = XPQLabelDome/Info.plist; 313 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 314 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 315 | PRODUCT_BUNDLE_IDENTIFIER = com.xpq.XPQLabelDome; 316 | PRODUCT_NAME = "$(TARGET_NAME)"; 317 | }; 318 | name = Release; 319 | }; 320 | /* End XCBuildConfiguration section */ 321 | 322 | /* Begin XCConfigurationList section */ 323 | E49D03671BE0A03B00BA51CE /* Build configuration list for PBXProject "XPQLabelDome" */ = { 324 | isa = XCConfigurationList; 325 | buildConfigurations = ( 326 | E49D03811BE0A03B00BA51CE /* Debug */, 327 | E49D03821BE0A03B00BA51CE /* Release */, 328 | ); 329 | defaultConfigurationIsVisible = 0; 330 | defaultConfigurationName = Release; 331 | }; 332 | E49D03831BE0A03B00BA51CE /* Build configuration list for PBXNativeTarget "XPQLabelDome" */ = { 333 | isa = XCConfigurationList; 334 | buildConfigurations = ( 335 | E49D03841BE0A03B00BA51CE /* Debug */, 336 | E49D03851BE0A03B00BA51CE /* Release */, 337 | ); 338 | defaultConfigurationIsVisible = 0; 339 | defaultConfigurationName = Release; 340 | }; 341 | /* End XCConfigurationList section */ 342 | }; 343 | rootObject = E49D03641BE0A03B00BA51CE /* Project object */; 344 | } 345 | -------------------------------------------------------------------------------- /XPQLabelDome.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XPQLabelDome.xcodeproj/project.xcworkspace/xcuserdata/xiepanqi.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQLabel/6ec6838c24197ce964dc4e0f71161e512e6204e0/XPQLabelDome.xcodeproj/project.xcworkspace/xcuserdata/xiepanqi.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /XPQLabelDome.xcodeproj/xcuserdata/xiepanqi.xcuserdatad/xcschemes/XPQLabelDome.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /XPQLabelDome.xcodeproj/xcuserdata/xiepanqi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | XPQLabelDome.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | E49D036B1BE0A03B00BA51CE 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /XPQLabelDome.xcodeproj/xcuserdata/zhangzhiryuu.xcuserdatad/xcschemes/XPQLabelDome.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /XPQLabelDome.xcodeproj/xcuserdata/zhangzhiryuu.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | XPQLabelDome.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | E49D036B1BE0A03B00BA51CE 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /XPQLabelDome/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // XPQLabelDome 4 | // 5 | // Created by XPQ on 15/10/28. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /XPQLabelDome/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // XPQLabelDome 4 | // 5 | // Created by XPQ on 15/10/28. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 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 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /XPQLabelDome/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 | } -------------------------------------------------------------------------------- /XPQLabelDome/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 | -------------------------------------------------------------------------------- /XPQLabelDome/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 78 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 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 | 152 | 164 | 176 | 188 | 200 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /XPQLabelDome/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /XPQLabelDome/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // XPQLabelDome 4 | // 5 | // Created by XPQ on 15/10/28. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /XPQLabelDome/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // XPQLabelDome 4 | // 5 | // Created by XPQ on 15/10/28. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "XPQLabel.h" 11 | 12 | @interface ViewController () 13 | @property (weak, nonatomic) IBOutlet XPQLabel *stringLabel; 14 | @property (weak, nonatomic) IBOutlet XPQLabel *attributedLabel; 15 | 16 | @property (weak, nonatomic) IBOutlet UISegmentedControl *pathAnimationSegment; 17 | @property (weak, nonatomic) IBOutlet UISegmentedControl *pathRotateSegment; 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | // Do any additional setup after loading the view, typically from a nib. 26 | 27 | _stringLabel.text = @"这里是一串普\n通的文本文字。"; 28 | 29 | NSMutableAttributedString *attriString = [[NSMutableAttributedString alloc] initWithString:@"this is attributed \nstring."]; 30 | //把this的字体颜色变为红色 31 | [attriString addAttribute:(NSString *)kCTForegroundColorAttributeName 32 | value:(id)[UIColor redColor].CGColor 33 | range:NSMakeRange(0, 4)]; 34 | //把is变为绿色 35 | [attriString addAttribute:(NSString *)kCTForegroundColorAttributeName 36 | value:(id)[UIColor greenColor].CGColor 37 | range:NSMakeRange(5, 2)]; 38 | //改变this的字体,value必须是一个CTFontRef 39 | [attriString addAttribute:(NSString *)kCTFontAttributeName value:(id)CFBridgingRelease(CTFontCreateWithName((CFStringRef)[UIFont boldSystemFontOfSize:12].fontName, 20, NULL)) range:NSMakeRange(8, 10)]; 40 | //给this加上下划线,value可以在指定的枚举中选择 41 | [attriString addAttribute:(NSString *)kCTUnderlineStyleAttributeName 42 | value:(id)[NSNumber numberWithInt:kCTUnderlineStyleDouble] 43 | range:NSMakeRange(19, 6)]; 44 | 45 | _attributedLabel.attributedText = attriString; 46 | } 47 | 48 | - (void)didReceiveMemoryWarning { 49 | [super didReceiveMemoryWarning]; 50 | // Dispose of any resources that can be recreated. 51 | } 52 | 53 | - (IBAction)onHorizontalAlignmentChange:(UISegmentedControl *)sender { 54 | BOOL animation = (self.pathAnimationSegment.selectedSegmentIndex == 0); 55 | // 可以直接通过textHorizontalAlignment属性设置,默认是不带动画。 56 | switch (sender.selectedSegmentIndex) { 57 | case 0: 58 | [self.stringLabel setTextHorizontalAlignment:XPQLabelHorizontalAlignmentLeft animation:animation]; 59 | [self.attributedLabel setTextHorizontalAlignment:XPQLabelHorizontalAlignmentLeft animation:animation]; 60 | break; 61 | 62 | case 1: 63 | [self.stringLabel setTextHorizontalAlignment:XPQLabelHorizontalAlignmentCenter animation:animation]; 64 | [self.attributedLabel setTextHorizontalAlignment:XPQLabelHorizontalAlignmentCenter animation:animation]; 65 | break; 66 | 67 | case 2: 68 | [self.stringLabel setTextHorizontalAlignment:XPQLabelHorizontalAlignmentRight animation:animation]; 69 | [self.attributedLabel setTextHorizontalAlignment:XPQLabelHorizontalAlignmentRight animation:animation]; 70 | break; 71 | default: 72 | break; 73 | } 74 | } 75 | 76 | - (IBAction)onVerticalAlignmentChange:(UISegmentedControl *)sender { 77 | BOOL animation = (self.pathAnimationSegment.selectedSegmentIndex == 0); 78 | // 可以直接通过textVerticalAlignment属性设置,默认是不带动画。 79 | switch (sender.selectedSegmentIndex) { 80 | case 0: 81 | [self.stringLabel setTextVerticalAlignment:XPQLabelVerticalAlignmentUp animation:animation]; 82 | [self.attributedLabel setTextVerticalAlignment:XPQLabelVerticalAlignmentUp animation:animation]; 83 | break; 84 | 85 | case 1: 86 | [self.stringLabel setTextVerticalAlignment:XPQLabelVerticalAlignmentCenter animation:animation]; 87 | [self.attributedLabel setTextVerticalAlignment:XPQLabelVerticalAlignmentCenter animation:animation]; 88 | break; 89 | 90 | case 2: 91 | [self.stringLabel setTextVerticalAlignment:XPQLabelVerticalAlignmentDown animation:animation]; 92 | [self.attributedLabel setTextVerticalAlignment:XPQLabelVerticalAlignmentDown animation:animation]; 93 | break; 94 | 95 | default: 96 | break; 97 | } 98 | } 99 | - (IBAction)onSetPath:(UISegmentedControl *)sender { 100 | BOOL rotate = (self.pathRotateSegment.selectedSegmentIndex == 0); 101 | BOOL animation = (self.pathAnimationSegment.selectedSegmentIndex == 0); 102 | // 可以直接通过path属性设置,默认是带旋转带动画。 103 | switch (sender.selectedSegmentIndex) { 104 | case 0: 105 | [self.stringLabel setPath:nil rotate:rotate animation:animation]; 106 | [self.attributedLabel setPath:nil rotate:rotate animation:animation]; 107 | break; 108 | 109 | case 1: { 110 | XPQLabelPath *path = [XPQLabelPath pathForBeginPoint:CGPointMake(10.0, 10.0)]; 111 | [path addLineToPoint:CGPointMake(250.0, 50.0)]; 112 | [self.stringLabel setPath:path rotate:rotate animation:animation]; 113 | XPQLabelPath *path2 = [XPQLabelPath pathForBeginPoint:CGPointMake(10.0, 50.0)]; 114 | [path2 addLineToPoint:CGPointMake(220.0, 10.0)]; 115 | [self.attributedLabel setPath:path2 rotate:rotate animation:animation]; 116 | } 117 | break; 118 | 119 | case 2: { 120 | XPQLabelPath *path = [XPQLabelPath pathForBeginPoint:CGPointMake(20.0, 70.0)]; 121 | [path addArcWithCentrePoint:CGPointMake(90.0, 70.0) angle:-M_PI]; 122 | [self.stringLabel setPath:path rotate:rotate animation:animation]; 123 | [self.attributedLabel setPath:path rotate:rotate animation:animation]; 124 | } 125 | break; 126 | 127 | case 3: { 128 | XPQLabelPath *path = [XPQLabelPath pathForBeginPoint:CGPointMake(20.0, 60.0)]; 129 | [path addCurveToPoint:CGPointMake(300.0, 60.0) anchorPoint:CGPointMake(100.0, 0.0)]; 130 | [self.stringLabel setPath:path rotate:rotate animation:animation]; 131 | [self.attributedLabel setPath:path rotate:rotate animation:animation]; 132 | } 133 | break; 134 | default: 135 | break; 136 | } 137 | } 138 | 139 | - (IBAction)onEnterAnmation1:(id)sender { 140 | [self.stringLabel startShowWithDirection:XPQLabelAnimationDirectionDown duration:0.5 bounce:0.0 stepTime:0.2]; 141 | [self.attributedLabel startShowWithDirection:XPQLabelAnimationDirectionRight duration:0.5 bounce:0.0 stepTime:0.2]; 142 | } 143 | 144 | - (IBAction)onExitAnmation1:(id)sender { 145 | [self.stringLabel startHideWithDirection:XPQLabelAnimationDirectionRight duration:0.5 stepTime:0.2]; 146 | [self.attributedLabel startHideWithDirection:XPQLabelAnimationDirectionDown duration:0.5 stepTime:0.2]; 147 | } 148 | 149 | - (IBAction)onEnterAnmation2:(id)sender { 150 | CATransform3D transform = CATransform3DScale(CATransform3DIdentity, 0.0, 0.0, 1.0); 151 | [self.stringLabel startFixedShowWithTransform:&transform duration:1.0 stepTime:0.1]; CATransform3DRotate(CATransform3DIdentity, 2 * M_PI, 0.0, 0.0, 1.0); 152 | [self.attributedLabel startFixedShowWithTransform:&transform duration:1.0 stepTime:0.1]; 153 | } 154 | 155 | - (IBAction)onExitAnmation2:(id)sender { 156 | CATransform3D transform = CATransform3DScale(CATransform3DIdentity, 0.0, 0.0, 1.0); 157 | [self.stringLabel startFixedHideWithTransform:&transform duration:1.0 stepTime:0.1]; 158 | [self.attributedLabel startFixedHideWithTransform:&transform duration:1.0 stepTime:0.1]; 159 | } 160 | 161 | - (IBAction)gestureSwitchChanged:(UISwitch *)sender { 162 | self.stringLabel.gesturePathEnable = sender.isOn; 163 | self.attributedLabel.gesturePathEnable = sender.isOn; 164 | } 165 | @end 166 | -------------------------------------------------------------------------------- /XPQLabelDome/XPQLabel/XPQLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // XPQLabel.h 3 | // XPQLabel 4 | // 5 | // Created by XPQ on 15/10/12. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "XPQLabelPath.h" 12 | 13 | typedef enum : NSUInteger { 14 | XPQLabelHorizontalAlignmentLeft, 15 | XPQLabelHorizontalAlignmentCenter, 16 | XPQLabelHorizontalAlignmentRight, 17 | } XPQLabelHorizontalAlignment; 18 | 19 | typedef enum : NSUInteger { 20 | XPQLabelVerticalAlignmentUp, 21 | XPQLabelVerticalAlignmentCenter, 22 | XPQLabelVerticalAlignmentDown, 23 | } XPQLabelVerticalAlignment; 24 | 25 | @interface XPQLabel : UIView 26 | /** 27 | * 要显示的文本。 28 | */ 29 | @property(nonatomic, copy) IBInspectable NSString *text; 30 | /** 31 | * 文本颜色。 32 | */ 33 | @property(nonatomic, strong) IBInspectable UIColor *textColor; 34 | /** 35 | * 文本字体。 36 | */ 37 | @property(nonatomic, strong) UIFont *font; 38 | /** 39 | * 富文本。 40 | */ 41 | @property(nonatomic, copy) NSAttributedString *attributedText; 42 | /** 43 | * 字符层数组。可以通过此数组来控制各个字符来实现你想要的效果。 44 | */ 45 | @property(nonatomic, strong, readonly) NSArray *layerArray; 46 | /** 47 | * @brief 刷新图层,一般用在设置attributedText后修改属性使用,其他地方不需调用。如果attributedText为nil则该函数没有任何效果。 48 | * @param animation YES-有动画效果,NO-无动画效果。 49 | */ 50 | -(void)refreshLayer:(BOOL)animation; 51 | 52 | /** 53 | * 水平方向对齐,默认值为XPQLabelHorizontalAlignmentLeft。改变时没有动画效果,如果需要动画效果可以参考setTextHorizontalAlignment:animation。 54 | */ 55 | @property(nonatomic) XPQLabelHorizontalAlignment textHorizontalAlignment; 56 | /** 57 | * 垂直方向对齐,默认值为XPQLabelVerticalAlignmentCenter。改变时没有动画效果,如果需要动画效果可以参考setTextVerticalAlignment:animation。 58 | */ 59 | @property(nonatomic) XPQLabelVerticalAlignment textVerticalAlignment; 60 | /** 61 | * @brief 设置文本水平对齐方向 62 | * @param textHorizontalAlignment 水平对齐方向。 63 | * @param animation YES-有动画效果,NO-无动画效果。 64 | */ 65 | -(void)setTextHorizontalAlignment:(XPQLabelHorizontalAlignment)textHorizontalAlignment animation:(BOOL)animation; 66 | /** 67 | * @brief 设置文本垂直对齐方向 68 | * @param textVerticalAlignment 垂直对齐方向。 69 | * @param animation YES-有动画效果,NO-无动画效果。 70 | */ 71 | -(void)setTextVerticalAlignment:(XPQLabelVerticalAlignment)textVerticalAlignment animation:(BOOL)animation; 72 | /** 73 | * @brief 两个字符之间的间距。为0时是正常情况下间距,小于0则间距缩小,大于0间距则增加。 74 | */ 75 | @property(nonatomic, assign)CGFloat elementSpacing; 76 | /** 77 | * @brief 行之间的间距。为0时是正常情况下间距,小于0则间距缩小,大于0间距则增加。 78 | */ 79 | @property(nonatomic, assign)CGFloat rowSpacing; 80 | @end 81 | 82 | #pragma mark - 路径分类 83 | @interface XPQLabel (Path) 84 | 85 | /** 86 | * 字符路径。路径将无视换行符(\n)。 87 | */ 88 | @property(nonatomic) XPQLabelPath *path; 89 | 90 | -(void)setPath:(XPQLabelPath *)path rotate:(BOOL)rotate animation:(BOOL)animation; 91 | @end 92 | 93 | 94 | #pragma mark - 手势滑动自定义路径分类 95 | 96 | @interface XPQLabel (gesturePath) 97 | @property (nonatomic, assign) BOOL gesturePathEnable; 98 | @end 99 | 100 | 101 | #pragma mark - 文本动画效果 102 | /** 103 | * 这个分类主要是实现一些动画效果。 104 | */ 105 | @interface XPQLabel (Animation) 106 | /** 107 | * @brief 启动跳动动画 108 | * @param height 跳动幅度 109 | * @param beatTime 跳一次所用时间 110 | * @param banTime 两次跳动之间的禁止时间 111 | * @param stepTime 两个字符之间跳动的间隔时间 112 | */ 113 | -(void)startBeatAnimationWithBeatHeight:(CGFloat)height beatTime:(NSTimeInterval)beatTime banTime:(NSTimeInterval)banTime stepTime:(NSTimeInterval)stepTime; 114 | /** 115 | * @brief 停止跳动动画 116 | */ 117 | -(void)stopBeatAnimation; 118 | 119 | /** 120 | * @brief 启动抖动动画 121 | */ 122 | -(void)startWiggleAnimation; 123 | 124 | /** 125 | * @brief 停止抖动动画 126 | */ 127 | -(void)stopWiggleAnimation; 128 | @end 129 | 130 | 131 | #pragma mark - 文本显示隐藏动画效果 132 | /** 133 | 动画移动方向 134 | */ 135 | typedef enum : NSUInteger { 136 | XPQLabelAnimationDirectionDown, 137 | XPQLabelAnimationDirectionUp, 138 | XPQLabelAnimationDirectionLeft, 139 | XPQLabelAnimationDirectionRight, 140 | } XPQLabelAnimationDirection; 141 | 142 | /** 143 | * 这个分类主要是实现显示和隐藏过程中的动画效果。 144 | */ 145 | @interface XPQLabel (ShowAndHide) 146 | /** 147 | * @brief 启动显示动画(直线移动)。 148 | * @param direction 移动方向。 149 | * @param duration 单个字符移动时间。 150 | * @param bounce 弹性系数,反弹距离为bounce * 字体大小。 151 | * @param stepTime 两个字符之间动画间隔时间。 152 | */ 153 | -(void)startShowWithDirection:(XPQLabelAnimationDirection)direction duration:(NSTimeInterval)duration bounce:(CGFloat)bounce stepTime:(NSTimeInterval)stepTime; 154 | /** 155 | * @brief 停止显示动画(直线移动)。 156 | */ 157 | -(void)stopDropShow; 158 | 159 | /** 160 | * @brief 启动隐藏动画(直线移动)。 161 | * @param direction 移动方向。 162 | * @param duration 单个字符移动时间。 163 | * @param stepTime 两个字符之间动画间隔时间。 164 | */ 165 | -(void)startHideWithDirection:(XPQLabelAnimationDirection)direction duration:(NSTimeInterval)duration stepTime:(NSTimeInterval)stepTime; 166 | /** 167 | * @brief 停止隐藏动画(直线移动)。 168 | */ 169 | -(void)stopDropHide; 170 | 171 | /** 172 | * @brief 启动显示动画(固定位置)。 173 | * @param transform 显示过程中变化值,此值是指开始时的状态。 174 | * @param duration 单个字符持续时间。 175 | * @param stepTime 两个字符之间动画间隔时间。 176 | */ 177 | -(void)startFixedShowWithTransform:(CATransform3D *)transform duration:(NSTimeInterval)duration stepTime:(NSTimeInterval)stepTime; 178 | /** 179 | * @brief 停止显示动画(固定位置)。 180 | */ 181 | -(void)stopFixedShow; 182 | 183 | /** 184 | * @brief 启动隐藏动画(固定位置)。 185 | * @param transform 隐藏过程中变化值,此值是指结束时的状态。 186 | * @param duration 单个字符持续时间。 187 | * @param stepTime 两个字符之间动画间隔时间。 188 | */ 189 | -(void)startFixedHideWithTransform:(CATransform3D *)transform duration:(NSTimeInterval)duration stepTime:(NSTimeInterval)stepTime; 190 | /** 191 | * @brief 停止隐藏动画(固定位置)。 192 | */ 193 | -(void)stopFixedHide; 194 | @end -------------------------------------------------------------------------------- /XPQLabelDome/XPQLabel/XPQLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // XPQLabel.m 3 | // XPQLabel 4 | // 5 | // Created by XPQ on 15/10/12. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #import "XPQLabel.h" 10 | 11 | @interface XPQLabel () { 12 | XPQLabelPath *_path; 13 | 14 | NSMutableArray *_stringArray; 15 | 16 | /// 手势路径使能。 17 | BOOL _gesturePathEnable; 18 | /// 手势路径点坐标数组。 19 | NSMutableArray *_gesturePointArray; 20 | } 21 | @property (nonatomic, strong) NSMutableArray *layerMutableArray; 22 | @end 23 | 24 | @implementation XPQLabel 25 | 26 | -(instancetype)init { 27 | self = [super init]; 28 | if (self) { 29 | [self configSelf]; 30 | } 31 | return self; 32 | } 33 | 34 | -(instancetype)initWithCoder:(NSCoder *)aDecoder { 35 | self = [super initWithCoder:aDecoder]; 36 | if (self) { 37 | [self configSelf]; 38 | } 39 | return self; 40 | } 41 | 42 | -(instancetype)initWithFrame:(CGRect)frame { 43 | self = [super initWithFrame:frame]; 44 | if (self) { 45 | [self configSelf]; 46 | } 47 | return self; 48 | } 49 | 50 | -(void)configSelf { 51 | _layerMutableArray = [NSMutableArray array]; 52 | _textColor = [UIColor blackColor]; 53 | _font = [UIFont systemFontOfSize:17.0]; 54 | _textHorizontalAlignment = XPQLabelHorizontalAlignmentLeft; 55 | _textVerticalAlignment = XPQLabelVerticalAlignmentCenter; 56 | _stringArray = [NSMutableArray array]; 57 | } 58 | 59 | #pragma mark -文本操作 60 | -(void)setText:(NSString *)text { 61 | _text = text; 62 | NSDictionary *attributes = @{NSFontAttributeName:_font, NSForegroundColorAttributeName:_textColor}; 63 | self.attributedText = [[NSAttributedString alloc] initWithString:text attributes:attributes]; 64 | } 65 | 66 | -(void)setFont:(UIFont *)font { 67 | _font = font; 68 | if (_text != nil) { 69 | NSDictionary *attributes = @{NSFontAttributeName:_font, NSForegroundColorAttributeName:_textColor}; 70 | self.attributedText = [[NSAttributedString alloc] initWithString:_text attributes:attributes]; 71 | } 72 | } 73 | 74 | -(void)setTextColor:(UIColor *)textColor { 75 | _textColor = textColor; 76 | if (_text != nil) { 77 | NSDictionary *attributes = @{NSFontAttributeName:_font, NSForegroundColorAttributeName:_textColor}; 78 | self.attributedText = [[NSAttributedString alloc] initWithString:_text attributes:attributes]; 79 | } 80 | } 81 | 82 | #pragma mark -富文本 83 | -(void)setAttributedText:(NSAttributedString *)attributedText { 84 | _attributedText = attributedText; 85 | _text = nil; 86 | [self string2ArrayWithWrap]; 87 | 88 | [self updateLayerArrayCount]; 89 | _layerArray = [NSArray arrayWithArray:self.layerMutableArray]; 90 | } 91 | 92 | /** 93 | * @brief 把字符串在换行符处拆分 94 | */ 95 | -(void)string2ArrayWithWrap { 96 | NSString *string = _attributedText.string; 97 | NSRange range = NSMakeRange(0, 0); 98 | do { 99 | NSUInteger begin = range.location + range.length; 100 | NSRange searchRange = NSMakeRange(begin, string.length - begin); 101 | range = [string rangeOfString:@"\n" options:NSCaseInsensitiveSearch range:searchRange]; 102 | if (range.location == NSNotFound) { 103 | [_stringArray addObject:[_attributedText attributedSubstringFromRange:searchRange]]; 104 | } 105 | else { 106 | [_stringArray addObject:[_attributedText attributedSubstringFromRange:NSMakeRange(begin, range.location - begin)]]; 107 | } 108 | } while (range.location != NSNotFound); 109 | } 110 | 111 | /// 更新图层个数 112 | -(void)updateLayerArrayCount { 113 | NSInteger layerNum = self.layerMutableArray.count; 114 | NSInteger textLength = 0; 115 | // 这样计算字符长度去掉了换行符 116 | for (NSAttributedString *string in _stringArray) { 117 | textLength += string.length; 118 | } 119 | if (layerNum > textLength) { 120 | // 移除多余的layer 121 | NSRange removeRange = NSMakeRange(textLength, layerNum - textLength); 122 | 123 | while (layerNum > textLength) { 124 | CATextLayer *layer = [self.layerMutableArray objectAtIndex:textLength]; 125 | [layer removeFromSuperlayer]; 126 | textLength++; 127 | } 128 | 129 | [self.layerMutableArray removeObjectsInRange:removeRange]; 130 | } 131 | else { 132 | // 添加少了的layer 133 | while (layerNum < textLength) { 134 | CATextLayer *layer = [CATextLayer layer]; 135 | // 缩放因子 136 | layer.contentsScale = [UIScreen mainScreen].scale; 137 | [self.layerMutableArray addObject:layer]; 138 | [self.layer addSublayer:layer]; 139 | layerNum++; 140 | } 141 | } 142 | 143 | [self refreshLayer:NO]; 144 | } 145 | 146 | #pragma mark - 刷新 147 | -(void)refreshLayer:(BOOL)animation { 148 | // 如果设置了路径对齐无效 149 | if (_path != nil) { 150 | return; 151 | } 152 | 153 | if (!animation) { 154 | [CATransaction begin]; 155 | // 关闭隐式动画 156 | [CATransaction setDisableActions:YES]; 157 | } 158 | 159 | [self updateLayerString]; 160 | [self updateLayerBounds]; 161 | 162 | 163 | if (!animation) { 164 | [CATransaction commit]; 165 | } 166 | } 167 | 168 | /// 更新图层上的字符 169 | -(void)updateLayerString { 170 | int layerIndex = 0; 171 | for (NSAttributedString *string in _stringArray) { 172 | for (int i = 0; i < string.length && i < _layerMutableArray.count; i++) { 173 | _layerMutableArray[layerIndex++].string = [string attributedSubstringFromRange:NSMakeRange(i, 1)]; 174 | } 175 | } 176 | } 177 | 178 | /// 更新图层位置和大小 179 | -(void)updateLayerBounds { 180 | int layerIndex = 0; 181 | CGRect layerRect = CGRectMake(0, [self beginY], 0, 0); 182 | for (NSAttributedString *string in _stringArray) { 183 | layerRect.origin.x = [self lineBeginX:string]; 184 | layerRect.origin.y += layerRect.size.height; 185 | layerRect.size.width = 0; 186 | layerRect.size.height = string.size.height; 187 | for (int i = 0; i < string.length; i++) { 188 | CGSize layerSize = ((NSAttributedString *)_layerMutableArray[layerIndex].string).size; 189 | layerRect.origin.x += layerRect.size.width; 190 | layerRect.origin.y += (layerRect.size.height - layerSize.height); 191 | layerRect.size.width = layerSize.width + _elementSpacing; 192 | layerRect.size.height = layerSize.height + _rowSpacing; 193 | 194 | _layerMutableArray[layerIndex].frame = layerRect; 195 | 196 | layerIndex++; 197 | } 198 | } 199 | } 200 | 201 | /// 行X起始坐标 202 | -(CGFloat)lineBeginX:(NSAttributedString *)string { 203 | switch (_textHorizontalAlignment) { 204 | case XPQLabelHorizontalAlignmentLeft: 205 | return 0.0; 206 | 207 | case XPQLabelHorizontalAlignmentCenter: 208 | return (self.bounds.size.width - string.size.width) / 2; 209 | 210 | case XPQLabelHorizontalAlignmentRight: 211 | return self.bounds.size.width - string.size.width; 212 | 213 | default: 214 | return 0.0; 215 | } 216 | } 217 | 218 | /// 字符串Y起始坐标 219 | -(CGFloat)beginY { 220 | switch (_textVerticalAlignment) { 221 | case XPQLabelVerticalAlignmentUp: 222 | return 0.0; 223 | 224 | case XPQLabelVerticalAlignmentCenter: 225 | return (self.bounds.size.height - _attributedText.size.height) / 2; 226 | 227 | case XPQLabelVerticalAlignmentDown: 228 | return self.bounds.size.height - _attributedText.size.height; 229 | 230 | default: 231 | return 0.0; 232 | } 233 | } 234 | 235 | /** 236 | * @brief 通过字符的索引获取对应的layer位置和大小 237 | * @param index 索引 238 | * @return 位置和大小,如果是index无效则返回CGRectMake(0, 0, 0, 0) 239 | */ 240 | -(CGRect)layerRectWithIndex:(NSInteger)index { 241 | if (index < 0 || index >= self.layerMutableArray.count) { 242 | CGPoint basePoint = [self basePoint]; 243 | return CGRectMake(basePoint.x, basePoint.y, 0, 0); 244 | } 245 | else { 246 | CALayer *layer = [self.layerMutableArray objectAtIndex:index]; 247 | return layer.frame; 248 | } 249 | } 250 | 251 | #pragma mark -对齐 252 | -(CGPoint)basePoint { 253 | CGSize stringSize = self.attributedText.size; 254 | CGPoint basePoint = CGPointZero; 255 | switch (self.textHorizontalAlignment) { 256 | case XPQLabelHorizontalAlignmentLeft: { 257 | basePoint.x = 0; 258 | } 259 | break; 260 | case XPQLabelHorizontalAlignmentCenter: { 261 | basePoint.x = (self.bounds.size.width - stringSize.width) / 2; 262 | } 263 | break; 264 | case XPQLabelHorizontalAlignmentRight: { 265 | basePoint.x = self.bounds.size.width - stringSize.width; 266 | } 267 | break; 268 | default: 269 | break; 270 | } 271 | 272 | switch (self.textVerticalAlignment) { 273 | case XPQLabelVerticalAlignmentUp: { 274 | basePoint.y = stringSize.height; 275 | } 276 | break; 277 | case XPQLabelVerticalAlignmentCenter: { 278 | basePoint.y = (self.bounds.size.height + stringSize.height) / 2; 279 | } 280 | break; 281 | case XPQLabelVerticalAlignmentDown: { 282 | basePoint.y = self.bounds.size.height; 283 | } 284 | break; 285 | default: 286 | break; 287 | } 288 | 289 | return basePoint; 290 | } 291 | 292 | -(void)setTextVerticalAlignment:(XPQLabelVerticalAlignment)textVerticalAlignment { 293 | _textVerticalAlignment = textVerticalAlignment; 294 | [self refreshLayer:NO]; 295 | } 296 | 297 | -(void)setTextHorizontalAlignment:(XPQLabelHorizontalAlignment)textHorizontalAlignment { 298 | _textHorizontalAlignment = textHorizontalAlignment; 299 | [self refreshLayer:NO]; 300 | } 301 | 302 | -(void)setTextVerticalAlignment:(XPQLabelVerticalAlignment)textVerticalAlignment animation:(BOOL)animation { 303 | _textVerticalAlignment = textVerticalAlignment; 304 | [self refreshLayer:animation]; 305 | } 306 | 307 | -(void)setTextHorizontalAlignment:(XPQLabelHorizontalAlignment)textHorizontalAlignment animation:(BOOL)animation { 308 | _textHorizontalAlignment = textHorizontalAlignment; 309 | [self refreshLayer:animation]; 310 | } 311 | @end 312 | 313 | 314 | #pragma mark - 文本路径 315 | @implementation XPQLabel (Path) 316 | -(XPQLabelPath *)path { 317 | return _path; 318 | } 319 | 320 | -(void)setPath:(XPQLabelPath *)path { 321 | [self setPath:path rotate:YES animation:YES]; 322 | } 323 | 324 | -(void)setPath:(XPQLabelPath *)path rotate:(BOOL)rotate animation:(BOOL)animation { 325 | _path = path; 326 | if (path == nil) { 327 | if (!animation) { 328 | [CATransaction begin]; 329 | // 关闭隐式动画 330 | [CATransaction setDisableActions:YES]; 331 | } 332 | 333 | for (CALayer *layer in _layerMutableArray) { 334 | layer.transform = CATransform3DIdentity; 335 | } 336 | 337 | if (!animation) { 338 | [CATransaction commit]; 339 | } 340 | [self refreshLayer:animation]; 341 | return; 342 | } 343 | 344 | NSArray *pointArray = [_path getPosTan:1.0]; 345 | double currentLength = 0.0; 346 | 347 | if (!animation) { 348 | [CATransaction begin]; 349 | // 关闭隐式动画 350 | [CATransaction setDisableActions:YES]; 351 | } 352 | 353 | for (int i = 0; i < self.layerArray.count; i++) { 354 | CALayer *layer = self.layerArray[i]; 355 | NSUInteger pointIndex = currentLength + layer.bounds.size.width / 2; 356 | if (pointIndex + 1 < pointArray.count) { 357 | layer.position = pointArray[pointIndex].CGPointValue; 358 | if (rotate) { 359 | CGPoint lastPoint = pointArray[pointIndex - 1].CGPointValue; 360 | CGPoint nextPoint = pointArray[pointIndex + 1].CGPointValue; 361 | CGFloat angle = atan((nextPoint.y - lastPoint.y) / (nextPoint.x - lastPoint.x)); 362 | if (nextPoint.x < lastPoint.x) { 363 | angle += M_PI; 364 | } 365 | layer.transform = CATransform3DRotate(CATransform3DIdentity, angle, 0.0, 0.0, 1.0); 366 | } 367 | else { 368 | layer.transform = CATransform3DIdentity; 369 | } 370 | currentLength += layer.bounds.size.width; 371 | } 372 | else { // 超出路径部分文字按水平直线排列 373 | layer.transform = CATransform3DIdentity; 374 | if (i == 0) { 375 | CGPoint basePoint = [self basePoint]; 376 | layer.frame = CGRectMake(basePoint.x, basePoint.y - layer.frame.size.height, layer.frame.size.width, layer.frame.size.height); 377 | } 378 | else { 379 | CALayer *lastLayer = self.layerArray[i - 1]; 380 | layer.position = CGPointMake(lastLayer.position.x + (lastLayer.frame.size.width + layer.frame.size.width) / 2, lastLayer.position.y); 381 | } 382 | } 383 | } 384 | 385 | 386 | if (!animation) { 387 | [CATransaction commit]; 388 | } 389 | } 390 | @end 391 | 392 | 393 | @implementation XPQLabel (gesturePath) 394 | -(BOOL)gesturePathEnable { 395 | return _gesturePathEnable; 396 | } 397 | 398 | -(void)setGesturePathEnable:(BOOL)gesturePathEnable { 399 | _gesturePathEnable = gesturePathEnable; 400 | if (gesturePathEnable == NO) { 401 | _gesturePointArray = nil; 402 | } 403 | } 404 | 405 | -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 406 | if (_gesturePathEnable) { 407 | _gesturePointArray = [NSMutableArray array]; 408 | } 409 | } 410 | 411 | -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 412 | if (_gesturePathEnable && _gesturePointArray != nil) { 413 | CGPoint point = [touches.anyObject locationInView:self]; 414 | [_gesturePointArray addObject:[NSValue valueWithCGPoint:point]]; 415 | } 416 | } 417 | 418 | -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 419 | if (_gesturePathEnable && _gesturePointArray != nil && _gesturePointArray.count > 0) { 420 | XPQLabelPath *path = [XPQLabelPath pathForBeginPoint:((NSValue *)_gesturePointArray[0]).CGPointValue]; 421 | [path addCustomPoint:_gesturePointArray]; 422 | self.path = path; 423 | } 424 | } 425 | @end 426 | 427 | @implementation XPQLabel (Animation) 428 | //TODO:设置路径后这个分类的动画表现都不好 429 | #pragma mark -跳动动画 430 | -(void)startBeatAnimationWithBeatHeight:(CGFloat)height beatTime:(NSTimeInterval)beatTime banTime:(NSTimeInterval)banTime stepTime:(NSTimeInterval)stepTime { 431 | if (self.layerArray.count == 0) { 432 | return; 433 | } 434 | 435 | CALayer *fristLayer = self.layerArray[0]; 436 | CAAnimation *animation = [self beatAnimationWithY:fristLayer.position.y beatHeight:height beatTime:beatTime cycleTime:beatTime + banTime]; 437 | for (int i = 0; i < self.layerArray.count; i++) { 438 | NSDictionary *dict = @{@"layer":self.layerArray[i], 439 | @"animation":animation}; 440 | [self performSelector:@selector(addAnimation:) withObject:dict afterDelay:i * stepTime]; 441 | } 442 | } 443 | 444 | -(void)addAnimation:(NSDictionary *)dict { 445 | CALayer *layer = dict[@"layer"]; 446 | CAAnimation *animation = dict[@"animation"]; 447 | [layer addAnimation:animation forKey:@"beatAnimation"]; 448 | } 449 | 450 | -(CAAnimation *)beatAnimationWithY:(CGFloat)y beatHeight:(CGFloat)height beatTime:(NSTimeInterval)beatTime cycleTime:(NSTimeInterval)cycleTime { 451 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"]; 452 | animation.fromValue = [NSNumber numberWithFloat:y]; 453 | animation.toValue = [NSNumber numberWithFloat:y - height]; 454 | animation.autoreverses = YES; 455 | animation.duration = beatTime / 2; 456 | 457 | CAAnimationGroup *group = [CAAnimationGroup animation]; 458 | group.animations = @[animation]; 459 | group.duration = MAX(beatTime, cycleTime); 460 | group.repeatCount = FLT_MAX; 461 | 462 | return group; 463 | } 464 | 465 | -(void)stopBeatAnimation { 466 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 467 | for (CALayer *layer in self.layerArray) { 468 | [layer removeAnimationForKey:@"beatAnimation"]; 469 | } 470 | } 471 | 472 | #pragma mark -抖动动画 473 | -(void)startWiggleAnimation { 474 | for (CALayer *layer in self.layerArray) { 475 | [layer addAnimation:[self wiggleAnimationWithPosittion:layer.position] forKey:@"wiggleAnimation"]; 476 | } 477 | } 478 | 479 | -(CAAnimation *)wiggleAnimationWithPosittion:(CGPoint)position { 480 | CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; 481 | rotateAnimation.fromValue = [NSNumber numberWithFloat:-M_PI / 16]; 482 | rotateAnimation.toValue = [NSNumber numberWithFloat:+M_PI / 16]; 483 | rotateAnimation.autoreverses = YES; 484 | rotateAnimation.duration = 0.05; 485 | 486 | CABasicAnimation *moveAnimation1 = [CABasicAnimation animationWithKeyPath:@"position.x"]; 487 | moveAnimation1.toValue = [NSNumber numberWithFloat:position.x + 2]; 488 | moveAnimation1.autoreverses = YES; 489 | moveAnimation1.duration = 0.05; 490 | 491 | CABasicAnimation *moveAnimation2 = [CABasicAnimation animationWithKeyPath:@"position.y"]; 492 | moveAnimation2.toValue = [NSNumber numberWithFloat:position.y + 2]; 493 | moveAnimation2.autoreverses = YES; 494 | moveAnimation2.duration = 0.05; 495 | 496 | CAAnimationGroup *group = [CAAnimationGroup animation]; 497 | group.animations = @[rotateAnimation, moveAnimation1, moveAnimation2]; 498 | group.duration = 0.1; 499 | group.repeatCount = FLT_MAX; 500 | return group; 501 | } 502 | 503 | -(void)stopWiggleAnimation { 504 | for (CALayer *layer in self.layerArray) { 505 | [layer removeAnimationForKey:@"wiggleAnimation"]; 506 | } 507 | } 508 | @end 509 | 510 | 511 | 512 | @implementation XPQLabel (ShowAndHide) 513 | #pragma mark -直线移动动画 514 | -(void)startShowWithDirection:(XPQLabelAnimationDirection)direction duration:(NSTimeInterval)duration bounce:(CGFloat)bounce stepTime:(NSTimeInterval)stepTime { 515 | for (int i = 0; i < self.layerArray.count; i++) { 516 | CALayer *layer = self.layerArray[i]; 517 | [layer removeAllAnimations]; 518 | 519 | CGPoint beginPoint; 520 | CGPoint offsetPoint = layer.position; 521 | switch (direction) { 522 | case XPQLabelAnimationDirectionDown: { 523 | beginPoint = [self convertPoint:CGPointMake(0.0, 0.0) fromView:self.window]; 524 | beginPoint.x = layer.position.x; 525 | beginPoint.y -= layer.bounds.size.height; 526 | offsetPoint.y += (bounce * layer.bounds.size.height); 527 | } 528 | break; 529 | 530 | case XPQLabelAnimationDirectionUp: { 531 | beginPoint = [self convertPoint:CGPointMake(0.0, [UIScreen mainScreen].bounds.size.height) fromView:self.window]; 532 | beginPoint.x = layer.position.x; 533 | beginPoint.y += layer.bounds.size.height; 534 | offsetPoint.y -= (bounce * layer.bounds.size.height); 535 | } 536 | 537 | case XPQLabelAnimationDirectionRight: { 538 | beginPoint = [self convertPoint:CGPointMake(0.0, 0.0) fromView:self.window]; 539 | beginPoint.x -= layer.bounds.size.width; 540 | beginPoint.y = layer.position.y; 541 | offsetPoint.x += (bounce * layer.bounds.size.width); 542 | } 543 | break; 544 | 545 | case XPQLabelAnimationDirectionLeft: { 546 | beginPoint = [self convertPoint:CGPointMake([UIScreen mainScreen].bounds.size.width, 0.0) fromView:self.window]; 547 | beginPoint.x += layer.bounds.size.width; 548 | beginPoint.y = layer.position.y; 549 | offsetPoint.x -= (bounce * layer.bounds.size.width); 550 | } 551 | break; 552 | 553 | default: 554 | break; 555 | } 556 | NSTimeInterval fixedTime = i * stepTime; 557 | if (direction == XPQLabelAnimationDirectionRight) { 558 | fixedTime = (self.layerArray.count - i) * stepTime; 559 | } 560 | CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 561 | animation.values = @[[NSValue valueWithCGPoint:beginPoint], 562 | [NSValue valueWithCGPoint:beginPoint], 563 | [NSValue valueWithCGPoint:layer.position], 564 | [NSValue valueWithCGPoint:offsetPoint]]; 565 | animation.keyTimes = @[@(0.0), 566 | @(fixedTime / (duration + fixedTime + 0.2)), 567 | @((duration + fixedTime) / (duration + fixedTime + 0.2)), 568 | @((duration + fixedTime + 0.1) / (duration + fixedTime + 0.2)), 569 | @(1.0)]; 570 | animation.duration = duration + fixedTime + 0.2; 571 | 572 | [layer addAnimation:animation forKey:@"dropShowAnimation"]; 573 | } 574 | self.hidden = NO; 575 | } 576 | 577 | -(void)stopDropShow { 578 | for (CALayer *layer in self.layerArray) { 579 | [layer removeAnimationForKey:@"dropShowAnimation"]; 580 | } 581 | } 582 | 583 | -(void)startHideWithDirection:(XPQLabelAnimationDirection)direction duration:(NSTimeInterval)duration stepTime:(NSTimeInterval)stepTime { 584 | for (int i = 0; i < self.layerArray.count; i++) { 585 | CALayer *layer = self.layerArray[i]; 586 | CGPoint endPoint; 587 | switch (direction) { 588 | case XPQLabelAnimationDirectionUp: { 589 | endPoint = [self convertPoint:CGPointMake(0.0, 0.0) fromView:self.window]; 590 | endPoint.x = layer.position.x; 591 | endPoint.y -= layer.bounds.size.height; 592 | } 593 | break; 594 | 595 | case XPQLabelAnimationDirectionDown: { 596 | endPoint = [self convertPoint:CGPointMake(0.0, [UIScreen mainScreen].bounds.size.height) fromView:self.window]; 597 | endPoint.x = layer.position.x; 598 | endPoint.y += layer.bounds.size.height; 599 | } 600 | break; 601 | 602 | case XPQLabelAnimationDirectionLeft: { 603 | endPoint = [self convertPoint:CGPointMake(0.0, 0.0) fromView:self.window]; 604 | endPoint.x -= layer.bounds.size.width; 605 | endPoint.y = layer.position.y; 606 | } 607 | break; 608 | 609 | case XPQLabelAnimationDirectionRight: { 610 | endPoint = [self convertPoint:CGPointMake([UIScreen mainScreen].bounds.size.width, 0.0) fromView:self.window]; 611 | endPoint.x += layer.bounds.size.width; 612 | endPoint.y = layer.position.y; 613 | } 614 | break; 615 | default: 616 | break; 617 | } 618 | NSTimeInterval fixedTime = i * stepTime; 619 | if (direction == XPQLabelAnimationDirectionRight) { 620 | fixedTime = (self.layerArray.count - i) * stepTime; 621 | } 622 | CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 623 | animation.values = @[[NSValue valueWithCGPoint:layer.position], 624 | [NSValue valueWithCGPoint:layer.position], 625 | [NSValue valueWithCGPoint:endPoint]]; 626 | animation.keyTimes = @[@(0.0), 627 | @(fixedTime / (duration + fixedTime)), 628 | @(1.0)]; 629 | animation.duration = duration + fixedTime; 630 | animation.removedOnCompletion = NO; 631 | animation.fillMode = kCAFillModeForwards; 632 | 633 | [layer addAnimation:animation forKey:@"dropHideAnimation"]; 634 | if ((direction != XPQLabelAnimationDirectionRight && i == self.layerArray.count - 1) 635 | || (direction == XPQLabelAnimationDirectionRight && i == 0)) { 636 | // 这里之所以不使用代理是为了防止其他分类重写了animationDidStop:finished造成BUG 637 | [self performSelector:@selector(dropHideAnimationEnd) withObject:nil afterDelay:duration + fixedTime]; 638 | } 639 | } 640 | } 641 | 642 | -(void)stopDropHide { 643 | for (CALayer *layer in self.layerArray) { 644 | [layer removeAnimationForKey:@"dropHideAnimation"]; 645 | } 646 | self.hidden = YES; 647 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(dropHideAnimationEnd) object:nil]; 648 | } 649 | 650 | -(void)dropHideAnimationEnd { 651 | self.hidden = YES; 652 | } 653 | 654 | #pragma mark -固定位置动画 655 | -(void)startFixedShowWithTransform:(CATransform3D *)transform duration:(NSTimeInterval)duration stepTime:(NSTimeInterval)stepTime { 656 | self.hidden = NO; 657 | for (int i = 0; i < self.layerArray.count; i++) { 658 | NSTimeInterval fixedTime = i * stepTime; 659 | CALayer *layer = self.layerArray[i]; 660 | [layer removeAllAnimations]; 661 | 662 | CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; 663 | opacityAnimation.values = @[@(0.0), 664 | @(0.0), 665 | @(1.0)]; 666 | opacityAnimation.keyTimes = @[@(0.0), 667 | @(fixedTime / (duration + fixedTime)), 668 | @(1.0)]; 669 | opacityAnimation.duration = duration + fixedTime; 670 | opacityAnimation.removedOnCompletion = NO; 671 | opacityAnimation.fillMode = kCAFillModeForwards; 672 | 673 | if (transform == nil) { 674 | if (i == self.layerArray.count - 1) { 675 | opacityAnimation.delegate = self; 676 | } 677 | [layer addAnimation:opacityAnimation forKey:@"fixedShowAnimation"]; 678 | } 679 | else { 680 | CAKeyframeAnimation *transformAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"]; 681 | transformAnimation.values = @[[NSValue valueWithCATransform3D:*transform], 682 | [NSValue valueWithCATransform3D:*transform], 683 | [NSValue valueWithCATransform3D:layer.transform]]; 684 | transformAnimation.keyTimes = @[@(0.0), 685 | @(fixedTime / (duration + fixedTime)), 686 | @(1.0)]; 687 | transformAnimation.duration = duration + fixedTime; 688 | transformAnimation.removedOnCompletion = NO; 689 | transformAnimation.fillMode = kCAFillModeForwards; 690 | 691 | CAAnimationGroup *group = [CAAnimationGroup animation]; 692 | group.duration = duration + fixedTime; 693 | group.animations = @[opacityAnimation, transformAnimation]; 694 | [layer addAnimation:group forKey:@"fixedShowAnimation"]; 695 | } 696 | } 697 | } 698 | 699 | -(void)stopFixedShow { 700 | for (CALayer *layer in self.layerArray) { 701 | [layer removeAnimationForKey:@"fixedShowAnimation"]; 702 | } 703 | } 704 | 705 | -(void)startFixedHideWithTransform:(CATransform3D *)transform duration:(NSTimeInterval)duration stepTime:(NSTimeInterval)stepTime { 706 | for (int i = 0; i < self.layerArray.count; i++) { 707 | NSTimeInterval fixedTime = i * stepTime; 708 | CALayer *layer = self.layerArray[i]; 709 | 710 | CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; 711 | opacityAnimation.values = @[@(1.0), 712 | @(1.0), 713 | @(0.0)]; 714 | opacityAnimation.keyTimes = @[@(0.0), 715 | @(fixedTime / (duration + fixedTime)), 716 | @(1.0)]; 717 | opacityAnimation.duration = duration + fixedTime; 718 | 719 | if (transform == nil) { 720 | if (i == self.layerArray.count - 1) { 721 | opacityAnimation.delegate = self; 722 | } 723 | opacityAnimation.removedOnCompletion = NO; 724 | opacityAnimation.fillMode = kCAFillModeForwards; 725 | [layer addAnimation:opacityAnimation forKey:@"fixedHideAnimation"]; 726 | } 727 | else { 728 | CAKeyframeAnimation *transformAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"]; 729 | transformAnimation.values = @[[NSValue valueWithCATransform3D:layer.transform], 730 | [NSValue valueWithCATransform3D:layer.transform], 731 | [NSValue valueWithCATransform3D:*transform]]; 732 | transformAnimation.keyTimes = @[@(0.0), 733 | @(fixedTime / (duration + fixedTime)), 734 | @(1.0)]; 735 | transformAnimation.duration = duration + fixedTime; 736 | 737 | CAAnimationGroup *group = [CAAnimationGroup animation]; 738 | group.duration = duration + fixedTime; 739 | group.animations = @[opacityAnimation, transformAnimation]; 740 | group.removedOnCompletion = NO; 741 | group.fillMode = kCAFillModeForwards; 742 | [layer addAnimation:group forKey:@"fixedHideAnimation"]; 743 | } 744 | if (i == self.layerArray.count - 1) { 745 | // 这里之所以不使用代理是为了防止其他分类重写了animationDidStop:finished造成BUG 746 | [self performSelector:@selector(fixedHideAnimationEnd) withObject:nil afterDelay:duration + fixedTime]; 747 | } 748 | } 749 | } 750 | 751 | -(void)stopFixedHide { 752 | for (CALayer *layer in self.layerArray) { 753 | [layer removeAnimationForKey:@"fixedHideAnimation"]; 754 | } 755 | self.hidden = YES; 756 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fixedHideAnimationEnd) object:nil]; 757 | } 758 | 759 | -(void)fixedHideAnimationEnd { 760 | self.hidden = YES; 761 | } 762 | @end 763 | -------------------------------------------------------------------------------- /XPQLabelDome/XPQLabel/XPQLabelPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // XPQLabelPath.h 3 | // XPQLabel 4 | // 5 | // Created by XPQ on 15/10/27. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /** 13 | * @brief 对XPQPath类的OC封装 14 | */ 15 | @interface XPQLabelPath : NSObject 16 | /** 17 | * @brief 构建路径起点。 18 | * @param point 起点位置。 19 | * @return 路径。 20 | */ 21 | +(instancetype)pathForBeginPoint:(CGPoint)point; 22 | 23 | /** 24 | * @brief 移动起点。 25 | * @param point 起点位置。 26 | */ 27 | -(void)moveBeginPoint:(CGPoint)point; 28 | /** 29 | * @brief 添加直线。 30 | * @param point 直线结束点。 31 | */ 32 | -(void)addLineToPoint:(CGPoint)point; 33 | /** 34 | * @brief 添加圆曲线,半径由上一条路径结束点和圆心共同确定。 35 | * @param centrePoint 圆心点。 36 | * @param angle 旋转角度。2π表示一圈。正数为逆时针,负数为顺时针。 37 | */ 38 | -(void)addArcWithCentrePoint:(CGPoint)centrePoint angle:(CGFloat)angle; 39 | /** 40 | * @brief 添加贝塞尔曲线。 41 | * @param point 结束点。 42 | * @param anchorPoint 锚点。 43 | */ 44 | -(void)addCurveToPoint:(CGPoint)point anchorPoint:(CGPoint)anchorPoint; 45 | /** 46 | * @brief 添加自定义曲线。 47 | * @param customPoint 自定义曲线点坐标数组。 48 | */ 49 | -(void)addCustomPoint:(NSArray *)customPoint; 50 | /** 51 | * @brief 获取路径长度。 52 | * @return 路径长度。 53 | */ 54 | -(CGFloat)getLength; 55 | /** 56 | * @brief 获取等距路径点数组。 57 | * @param precision 点与点之间的间距。 58 | * @return 路径点数组。 59 | */ 60 | -(NSArray *)getPosTan:(CGFloat)precision; 61 | 62 | /** 63 | * @brief 强制刷新路径点坐标数组。 64 | */ 65 | -(void)setNeedsUpdate; 66 | 67 | /** 68 | * @brief 深度复制一条路径。 69 | * @return 拷贝出来的对象。 70 | */ 71 | -(XPQLabelPath *)clone; 72 | @end 73 | -------------------------------------------------------------------------------- /XPQLabelDome/XPQLabel/XPQLabelPath.mm: -------------------------------------------------------------------------------- 1 | // 2 | // XPQLabelPath.m 3 | // XPQLabel 4 | // 5 | // Created by XPQ on 15/10/27. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #import "XPQLabelPath.h" 10 | #import "XPQPath.h" 11 | 12 | @interface XPQLabelPath () { 13 | XPQPath* _pathCpp; 14 | } 15 | @end 16 | 17 | @implementation XPQLabelPath 18 | -(instancetype)init { 19 | self = [super init]; 20 | if (self) { 21 | _pathCpp = new XPQPointPath({0.0, 0.0}); 22 | } 23 | return self; 24 | } 25 | 26 | +(instancetype)pathForBeginPoint:(CGPoint)point { 27 | XPQLabelPath *path = [[XPQLabelPath alloc] init]; 28 | path->_pathCpp->setEndPoint({point.x, point.y}); 29 | return path; 30 | } 31 | 32 | -(void)dealloc { 33 | if (_pathCpp != nil) { 34 | delete _pathCpp; 35 | } 36 | } 37 | 38 | -(void)moveBeginPoint:(CGPoint)point { 39 | _pathCpp->setEndPoint({point.x, point.y}); 40 | } 41 | 42 | -(void)addLineToPoint:(CGPoint)point { 43 | _pathCpp->appendPath(new XPQLinePath({point.x, point.y})); 44 | } 45 | 46 | -(void)addArcWithCentrePoint:(CGPoint)centrePoint angle:(CGFloat)angle { 47 | _pathCpp->appendPath(new XPQRoundPath({centrePoint.x, centrePoint.y}, angle)); 48 | } 49 | 50 | -(void)addCurveToPoint:(CGPoint)point anchorPoint:(CGPoint)anchorPoint { 51 | _pathCpp->appendPath(new XPQBezierPath({anchorPoint.x, anchorPoint.y}, {point.x, point.y})); 52 | } 53 | 54 | -(void)addCustomPoint:(NSArray *)customPoint { 55 | std::vector pointVector(customPoint.count); 56 | for (int i = 0; i < customPoint.count; i++) { 57 | CGPoint point = ((NSValue *)customPoint[i]).CGPointValue; 58 | pointVector[i] = {point.x, point.y}; 59 | } 60 | _pathCpp->appendPath(new XPQCustomPath(&pointVector)); 61 | } 62 | 63 | -(CGFloat)getLength { 64 | return _pathCpp->getLength(); 65 | } 66 | 67 | -(NSArray *)getPosTan:(CGFloat)precision { 68 | std::vector *outBuffer = new std::vector(); 69 | NSMutableArray *array = [NSMutableArray array]; 70 | _pathCpp->getPosTan(precision, outBuffer); 71 | for (auto it = outBuffer->begin(); it != outBuffer->end(); ++it) { 72 | CGPoint point = {static_cast((*it).x), static_cast((*it).y)}; 73 | [array addObject:[NSValue valueWithCGPoint:point]]; 74 | } 75 | delete outBuffer; 76 | return array; 77 | } 78 | 79 | -(void)setNeedsUpdate { 80 | _pathCpp->setNeedsUpdate(); 81 | } 82 | 83 | -(XPQLabelPath *)clone { 84 | XPQLabelPath *clone = [[XPQLabelPath alloc] init]; 85 | clone->_pathCpp = _pathCpp->clone(); 86 | return clone; 87 | } 88 | @end 89 | -------------------------------------------------------------------------------- /XPQLabelDome/XPQLabel/XPQPath/XPQPath.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // XPQLabelLine.cpp 3 | // XPQLabel 4 | // 5 | // Created by XPQ on 15/10/21. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #include "XPQPath.h" 10 | #include 11 | 12 | #pragma mark - XPQPath-----路径基类 13 | XPQPath::XPQPath(XPQPoint point) 14 | { 15 | m_lastPath = nullptr; 16 | m_nextPath = nullptr; 17 | m_endPoint = {point.x, point.y}; 18 | m_needsUpdate = true; 19 | m_pointBuffer = nullptr; 20 | } 21 | 22 | XPQPath::~XPQPath() 23 | { 24 | if (m_lastPath != nullptr) { 25 | // 判断上一条路径的下一条路径指的是否还是自己 26 | if (m_lastPath->m_nextPath == this) { 27 | // 防止野指针 28 | m_lastPath->m_nextPath = nullptr; 29 | } 30 | } 31 | if (m_nextPath != nullptr) { 32 | delete m_nextPath; 33 | } 34 | if (m_pointBuffer != nullptr) { 35 | delete m_pointBuffer; 36 | } 37 | } 38 | 39 | bool XPQPath::appendPath(XPQPath *path) 40 | { 41 | if (path->m_lastPath != nullptr) { 42 | return false; 43 | } 44 | 45 | XPQPath *endPath = this; 46 | while (endPath->m_nextPath != nullptr) { 47 | endPath = endPath->m_nextPath; 48 | } 49 | endPath->setNextPath(path); 50 | path->setLastPath(endPath); 51 | path->m_needsUpdate = true; 52 | 53 | return true; 54 | } 55 | 56 | bool XPQPath::removeFrontPath() 57 | { 58 | if (this->m_lastPath == nullptr) { 59 | return false; 60 | } 61 | 62 | this->m_lastPath->m_nextPath = nullptr; 63 | this->m_lastPath = nullptr; 64 | this->m_needsUpdate = true; 65 | 66 | return true; 67 | } 68 | 69 | bool XPQPath::removeBackPath(bool release) 70 | { 71 | if (this->m_nextPath == nullptr) { 72 | return false; 73 | } 74 | 75 | if (release) { 76 | delete this->m_nextPath; 77 | } 78 | else { 79 | this->m_nextPath->m_lastPath = nullptr; 80 | this->m_nextPath->m_needsUpdate = true; 81 | } 82 | 83 | this->m_nextPath = nullptr; 84 | return true; 85 | } 86 | 87 | double XPQPath::getLength(bool isTotal) 88 | { 89 | double length = getSelfLength(); 90 | if (isTotal && m_nextPath != nullptr) { 91 | length += m_nextPath->getLength(true); 92 | } 93 | return length; 94 | } 95 | 96 | void XPQPath::getPosTan(double precision, std::vector *outBuffer) 97 | { 98 | if (m_needsUpdate) { 99 | if (m_lastPath != nullptr) { 100 | updatePosTan(precision); 101 | } 102 | else { 103 | if (m_pointBuffer != nullptr) { 104 | delete m_pointBuffer; 105 | } 106 | m_pointBuffer = new std::vector(0); 107 | } 108 | m_needsUpdate = false; 109 | } 110 | 111 | outBuffer->insert(outBuffer->end(), m_pointBuffer->begin(), m_pointBuffer->end()); 112 | 113 | if (this->m_nextPath != nullptr) { 114 | this->m_nextPath->getPosTan(precision, outBuffer); 115 | } 116 | } 117 | 118 | void XPQPath::setNeedsUpdate() 119 | { 120 | for (XPQPath *path = this; path != nullptr; path = getNextPath()) { 121 | path->m_needsUpdate = true; 122 | } 123 | } 124 | 125 | //XPQPath* XPQPath::clone(bool needsUpdate) 126 | //{ 127 | // XPQPath *clone = new XPQPath(m_endPoint); 128 | // clone->m_needsUpdate = needsUpdate | m_needsUpdate; 129 | // clone->m_pointBuffer = new std::vector(*m_pointBuffer); 130 | // if (m_nextPath != nullptr) { 131 | // clone->appendPath(m_nextPath->clone(false)); 132 | // } 133 | // return clone; 134 | //} 135 | 136 | double XPQPath::pointSpace(XPQPoint point1, XPQPoint point2) 137 | { 138 | double xSpace = point1.x - point2.x; 139 | double ySpace = point1.y - point2.y; 140 | return sqrt(xSpace * xSpace + ySpace * ySpace); 141 | } 142 | 143 | //double XPQPath::getSelfLength() 144 | //{ 145 | // if (m_needsUpdate) { 146 | // m_length = 0.0; 147 | // } 148 | // return m_length; 149 | //} 150 | // 151 | //void XPQPath::updatePosTan(double precision) 152 | //{ 153 | // if (m_pointBuffer != nullptr) { 154 | // delete m_pointBuffer; 155 | // } 156 | // 157 | // 158 | // if (m_lastPath != nullptr) { 159 | // m_pointBuffer = new std::vector(1); 160 | // (*m_pointBuffer)[0].x = getLastPath()->m_endPoint.x; 161 | // (*m_pointBuffer)[0].y = getLastPath()->m_endPoint.y; 162 | // } 163 | //} 164 | 165 | 166 | #pragma mark - XPQPointPath-----点 167 | XPQPointPath::XPQPointPath(XPQPoint point) : XPQPath(point) 168 | { 169 | 170 | } 171 | 172 | XPQPointPath* XPQPointPath::clone(bool needsUpdate) 173 | { 174 | XPQPointPath *clone = new XPQPointPath(m_endPoint); 175 | clone->m_needsUpdate = needsUpdate | m_needsUpdate; 176 | clone->m_pointBuffer = new std::vector(*m_pointBuffer); 177 | if (getNextPath() != nullptr) { 178 | clone->appendPath(getNextPath()->clone(false)); 179 | } 180 | return clone; 181 | } 182 | 183 | double XPQPointPath::getSelfLength() 184 | { 185 | return 0.0; 186 | } 187 | 188 | void XPQPointPath::updatePosTan(double precision) 189 | { 190 | if (m_pointBuffer == nullptr) { 191 | m_pointBuffer = new std::vector(1); 192 | } 193 | (*m_pointBuffer)[0] = m_endPoint; 194 | } 195 | 196 | #pragma mark - XPQLinePath-----直线 197 | XPQLinePath::XPQLinePath(XPQPoint point) : XPQPath(point) 198 | { 199 | 200 | } 201 | 202 | XPQLinePath* XPQLinePath::clone(bool needsUpdate) 203 | { 204 | XPQLinePath *clone = new XPQLinePath(m_endPoint); 205 | clone->m_needsUpdate = needsUpdate | m_needsUpdate; 206 | clone->m_pointBuffer = new std::vector(*m_pointBuffer); 207 | if (getNextPath() != nullptr) { 208 | clone->appendPath(getNextPath()->clone(false)); 209 | } 210 | return clone; 211 | } 212 | 213 | double XPQLinePath::getSelfLength() 214 | { 215 | if (getLastPath() == nullptr) { 216 | return 0.0; 217 | } 218 | 219 | if (m_needsUpdate) { 220 | m_length = pointSpace(m_endPoint, getLastPath()->getEndPoint()); 221 | } 222 | 223 | return m_length; 224 | } 225 | 226 | void XPQLinePath::updatePosTan(double precision) 227 | { 228 | double length = getSelfLength(); 229 | int pointCount = static_cast(length / precision); 230 | double xSpike = (m_endPoint.x - getLastPath()->getEndPoint().x) / length * precision; 231 | double ySpike = (m_endPoint.y - getLastPath()->getEndPoint().y) / length * precision; 232 | 233 | // 重新精确大小分配outBuffer的内存,提升内存使用率 234 | if (m_pointBuffer != nullptr) { 235 | delete m_pointBuffer; 236 | } 237 | m_pointBuffer = new std::vector(pointCount); 238 | 239 | for (int i = 0; i < pointCount; i++) { 240 | (*m_pointBuffer)[i].x = getLastPath()->getEndPoint().x + i * xSpike; 241 | (*m_pointBuffer)[i].y = getLastPath()->getEndPoint().y + i * ySpike; 242 | } 243 | } 244 | 245 | 246 | #pragma mark - XPQRoundPath-----圆 247 | XPQRoundPath::XPQRoundPath(XPQPoint centrePoint, double angle) : XPQPath(centrePoint) 248 | { 249 | m_angle = angle; 250 | m_centrePoint = centrePoint; 251 | } 252 | 253 | XPQRoundPath* XPQRoundPath::clone(bool needsUpdate) 254 | { 255 | XPQRoundPath *clone = new XPQRoundPath(m_centrePoint, m_angle); 256 | clone->m_needsUpdate = needsUpdate | m_needsUpdate; 257 | clone->m_pointBuffer = new std::vector(*m_pointBuffer); 258 | if (getNextPath() != nullptr) { 259 | clone->appendPath(getNextPath()->clone(false)); 260 | } 261 | return clone; 262 | } 263 | 264 | void XPQRoundPath::setLastPath(XPQPath *lastPath) 265 | { 266 | XPQPath::setLastPath(lastPath); 267 | if (lastPath == nullptr) { 268 | m_radii = 0.0; 269 | m_beginAngle = 0.0; 270 | m_endPoint.x = m_centrePoint.x; 271 | m_endPoint.y = m_centrePoint.y; 272 | } 273 | else 274 | { 275 | m_radii = pointSpace(m_endPoint, getLastPath()->getEndPoint()); 276 | m_beginAngle = atan((lastPath->getEndPoint().x - m_centrePoint.x) / (lastPath->getEndPoint().y - m_centrePoint.y)); 277 | m_endPoint.x = m_centrePoint.x + m_radii * sin(m_beginAngle + m_angle); 278 | m_endPoint.y = m_centrePoint.y + m_radii * cos(m_beginAngle + m_angle); 279 | } 280 | } 281 | 282 | double XPQRoundPath::getSelfLength() 283 | { 284 | if (getLastPath() == nullptr) { 285 | return 0.0; 286 | } 287 | 288 | if (m_needsUpdate) { 289 | m_length = fabs(m_angle) * m_radii; 290 | } 291 | 292 | return m_length; 293 | } 294 | 295 | void XPQRoundPath::updatePosTan(double precision) 296 | { 297 | double length = getSelfLength(); 298 | int pointCount = static_cast(length / precision); 299 | double angleSpike = m_angle / pointCount; 300 | 301 | // 重新精确大小分配outBuffer的内存,提升内存使用率 302 | if (m_pointBuffer != nullptr) { 303 | delete m_pointBuffer; 304 | } 305 | m_pointBuffer = new std::vector(pointCount); 306 | 307 | for (int i = 0; i < pointCount; i++) { 308 | (*m_pointBuffer)[i].x = m_centrePoint.x + m_radii * sin(m_beginAngle + i * angleSpike); 309 | (*m_pointBuffer)[i].y = m_centrePoint.y + m_radii * cos(m_beginAngle + i * angleSpike); 310 | } 311 | } 312 | 313 | 314 | #pragma mark - XPQBezierPath-----贝塞尔曲线 315 | XPQBezierPath::XPQBezierPath(XPQPoint anchorPoint, XPQPoint endPoint) : XPQPath(endPoint) 316 | { 317 | m_anchorPoint = anchorPoint; 318 | } 319 | 320 | XPQBezierPath* XPQBezierPath::clone(bool needsUpdate) 321 | { 322 | XPQBezierPath *clone = new XPQBezierPath(m_anchorPoint, m_endPoint); 323 | clone->m_needsUpdate = needsUpdate | m_needsUpdate; 324 | clone->m_pointBuffer = new std::vector(*m_pointBuffer); 325 | if (getNextPath() != nullptr) { 326 | clone->appendPath(getNextPath()->clone(false)); 327 | } 328 | return clone; 329 | } 330 | 331 | void XPQBezierPath::setLastPath(XPQPath *lastPath) 332 | { 333 | XPQPath::setLastPath(lastPath); 334 | 335 | if (lastPath == nullptr) { 336 | 337 | } 338 | else { 339 | m_ax = lastPath->getEndPoint().x - 2 * m_anchorPoint.x + m_endPoint.x; 340 | m_ay = lastPath->getEndPoint().y - 2 * m_anchorPoint.y + m_endPoint.y; 341 | m_bx = 2 * m_anchorPoint.x - 2 * lastPath->getEndPoint().x; 342 | m_by = 2 * m_anchorPoint.y - 2 * lastPath->getEndPoint().y; 343 | 344 | m_A = 4 * (m_ax * m_ax + m_ay * m_ay); 345 | m_B = 4 * (m_ax * m_bx + m_ay * m_by); 346 | m_C = m_bx * m_bx + m_by * m_by; 347 | } 348 | } 349 | 350 | //长度函数 351 | /* 352 | L(t) = Integrate[s[t], t] 353 | 354 | L(t_) = ((2*Sqrt[A]*(2*A*t*Sqrt[C + t*(B + A*t)] + B*(-Sqrt[C] + Sqrt[C + t*(B + A*t)])) + (B^2 - 4*A*C) (Log[B + 2*Sqrt[A]*Sqrt[C]] - Log[B + 2*A*t + 2 Sqrt[A]*Sqrt[C + t*(B + A*t)]])) /(8* A^(3/2))); 355 | */ 356 | double XPQBezierPath::getSelfLength() 357 | { 358 | if (getLastPath() == nullptr) { 359 | return 0.0; 360 | } 361 | 362 | if (m_needsUpdate) { 363 | double temp1 = sqrt(m_A + m_B + m_C); 364 | double temp2 = (2 * m_A * temp1 + m_B * (temp1 - sqrt(m_C))); 365 | double temp3 = log(m_B + 2 * sqrt(m_A) * sqrt(m_C)); 366 | double temp4 = log(m_B + 2 * m_A + 2 * sqrt(m_A) * temp1); 367 | double temp5 = 2 * sqrt(m_A) * temp2; 368 | double temp6 = (m_B * m_B - 4 * m_A * m_C) * (temp3 - temp4); 369 | 370 | m_length = (temp5 + temp6) / (8 * pow(m_A, 1.5)); 371 | } 372 | 373 | return m_length; 374 | } 375 | 376 | void XPQBezierPath::updatePosTan(double precision) 377 | { 378 | double length = getSelfLength(); 379 | int pointCount = static_cast(length / precision); 380 | 381 | // 重新精确大小分配outBuffer的内存,提升内存使用率 382 | if (m_pointBuffer != nullptr) { 383 | delete m_pointBuffer; 384 | } 385 | m_pointBuffer = new std::vector(pointCount); 386 | 387 | for (int i = 0; i < pointCount; i++) { 388 | double t = (double)i / pointCount; 389 | //如果按照线形增长,此时对应的曲线长度 390 | double l = t * length; 391 | //根据L函数的反函数,求得l对应的t值 392 | t = invertLength(t, l); 393 | 394 | //根据贝塞尔曲线函数,求得取得此时的x,y坐标 395 | (*m_pointBuffer)[i].x = (1 - t) * (1 - t) * getLastPath()->getEndPoint().x 396 | + 2 * (1 - t) * t * m_anchorPoint.x 397 | + t * t * m_endPoint.x; 398 | (*m_pointBuffer)[i].y = (1 - t) * (1 - t) * getLastPath()->getEndPoint().y 399 | + 2 * (1 - t) * t * m_anchorPoint.y 400 | + t * t * m_endPoint.y; 401 | } 402 | } 403 | 404 | //长度函数反函数,使用牛顿切线法求解 405 | /* 406 | X(n+1) = Xn - F(Xn)/F'(Xn) 407 | */ 408 | double XPQBezierPath::invertLength(double t, double l) 409 | { 410 | double t1 = t, t2; 411 | 412 | do 413 | { 414 | t2 = t1 - (getBezierLength(t1) - l) / speed(t1); 415 | if(fabs(t1 - t2) < 0.0001) break; 416 | t1 = t2; 417 | } while(true); 418 | 419 | return t2; 420 | } 421 | 422 | //速度函数 423 | /* 424 | s(t_) = Sqrt[A*t*t+B*t+C] 425 | */ 426 | double XPQBezierPath::speed(double t) 427 | { 428 | if (t > 1.0) { 429 | t = 1.0; 430 | } 431 | else if (t <= 0.0) { 432 | t = 0.0; 433 | } 434 | return sqrt(m_A * t * t + m_B * t + m_C); 435 | } 436 | 437 | //长度函数 438 | /* 439 | L(t) = Integrate[s[t], t] 440 | 441 | L(t_) = ((2*Sqrt[A]*(2*A*t*Sqrt[C + t*(B + A*t)] + B*(-Sqrt[C] + Sqrt[C + t*(B + A*t)])) + (B^2 - 4*A*C) (Log[B + 2*Sqrt[A]*Sqrt[C]] - Log[B + 2*A*t + 2 Sqrt[A]*Sqrt[C + t*(B + A*t)]])) /(8* A^(3/2))); 442 | */ 443 | double XPQBezierPath::getBezierLength(double t) 444 | { 445 | if (t > 1.0) { 446 | t = 1.0; 447 | } 448 | else if (t <= 0.0) { 449 | return 0.0; 450 | } 451 | 452 | double temp1 = sqrt(m_C + t * (m_B + m_A * t)); 453 | double temp2 = (2 * m_A * t * temp1 + m_B * (temp1 - sqrt(m_C))); 454 | double temp3 = log(m_B + 2 * sqrt(m_A) * sqrt(m_C)); 455 | double temp4 = log(m_B + 2 * m_A * t + 2 * sqrt(m_A) * temp1); 456 | double temp5 = 2 * sqrt(m_A) * temp2; 457 | double temp6 = (m_B * m_B - 4 * m_A * m_C) * (temp3 - temp4); 458 | 459 | return (temp5 + temp6) / (8 * pow(m_A, 1.5)); 460 | } 461 | 462 | 463 | #pragma mark - XPQCustomPath-----自定义曲线 464 | XPQCustomPath::XPQCustomPath(std::vector *customPoint) : XPQPath(*customPoint->end()) 465 | { 466 | m_customPoint = new std::vector(*customPoint); 467 | } 468 | 469 | XPQCustomPath::~XPQCustomPath() 470 | { 471 | if (m_customPoint != nullptr) { 472 | delete m_customPoint; 473 | } 474 | } 475 | 476 | XPQCustomPath* XPQCustomPath::clone(bool needsUpdate) 477 | { 478 | XPQCustomPath *clone = new XPQCustomPath(m_customPoint); 479 | clone->m_needsUpdate = needsUpdate | m_needsUpdate; 480 | clone->m_pointBuffer = new std::vector(*m_pointBuffer); 481 | if (getNextPath() != nullptr) { 482 | clone->appendPath(getNextPath()->clone(false)); 483 | } 484 | return clone; 485 | } 486 | 487 | double XPQCustomPath::getSelfLength() 488 | { 489 | if (m_needsUpdate) { 490 | if (m_customPoint == nullptr || m_customPoint->size() == 0) { 491 | m_length = 0.0; 492 | } 493 | else { 494 | if (getLastPath() == nullptr) { 495 | m_length = 0.0; 496 | } 497 | else { 498 | m_length = pointSpace(getLastPath()->getEndPoint(), (*m_customPoint)[0]); 499 | } 500 | 501 | for (int i = 1; i < m_customPoint->size(); i++) { 502 | m_length += pointSpace((*m_customPoint)[i - 1], (*m_customPoint)[i]); 503 | } 504 | } 505 | } 506 | 507 | return m_length; 508 | } 509 | 510 | void XPQCustomPath::updatePosTan(double precision) 511 | { 512 | // 重新精确大小分配outBuffer的内存,提升内存使用率 513 | if (m_pointBuffer == nullptr) { 514 | m_pointBuffer = new std::vector(); 515 | } 516 | else { 517 | m_pointBuffer->clear(); 518 | } 519 | 520 | if (m_customPoint != nullptr && m_customPoint->size() > 0) { 521 | double offset = 0.0; 522 | if (getLastPath() != nullptr) { 523 | offset = calcSegmentPoint(getLastPath()->getEndPoint(), (*m_customPoint)[0], precision, offset, m_pointBuffer); 524 | } 525 | for (int i = 1; i < m_customPoint->size(); i++) { 526 | offset = calcSegmentPoint((*m_customPoint)[i - 1], (*m_customPoint)[i], precision, offset, m_pointBuffer); 527 | } 528 | } 529 | } 530 | 531 | double XPQCustomPath::calcSegmentPoint(XPQPoint point1, XPQPoint point2, double precision, double offset, std::vector *outBuffer) 532 | { 533 | _LIBCPP_ASSERT(offset < precision, "偏移值 > 精度值"); 534 | double length = pointSpace(point1, point2); 535 | if (length + offset < precision) { 536 | return length + offset; 537 | } 538 | 539 | double xSpace = point2.x - point1.x; 540 | double ySpace = point2.y - point1.y; 541 | double calcLength = 0.0; 542 | for (calcLength = precision - offset; calcLength < length; calcLength += precision) { 543 | double scale = calcLength / length; 544 | XPQPoint point = {point1.x + xSpace * scale, point1.y + ySpace * scale}; 545 | outBuffer->push_back(point); 546 | } 547 | return length - (calcLength - precision); 548 | } -------------------------------------------------------------------------------- /XPQLabelDome/XPQLabel/XPQPath/XPQPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // XPQLabelLine.hpp 3 | // XPQLabel 4 | // 5 | // Created by XPQ on 15/10/21. 6 | // Copyright © 2015年 com.xpq. All rights reserved. 7 | // 8 | 9 | #ifndef XPQLabelPath_h 10 | #define XPQLabelPath_h 11 | 12 | #include 13 | #include 14 | 15 | struct XPQPoint { 16 | double x; 17 | double y; 18 | }; 19 | 20 | #pragma mark - 路径基类 21 | class XPQPath { 22 | public: 23 | XPQPath(XPQPoint point); 24 | XPQPath(XPQPath *other); 25 | virtual ~XPQPath(); 26 | 27 | /** 28 | * @brief 在路径链结尾附加一个路径链。必须要附加路径链起点才能调用成功。 29 | * @param path 要附加的路径。 30 | * @return false-附加失败,true-附加成功。 31 | */ 32 | bool appendPath(XPQPath *path); 33 | 34 | /** 35 | * @brief 移除前面的路径,使自身成为起点。 36 | * @return 如果本身就为起点返回false,反之返回true。 37 | */ 38 | bool removeFrontPath(); 39 | 40 | /** 41 | * @brief 移除后续路径。 42 | * @param release 是否要释放移除的路径 43 | * @return 如果没有附加路径则会返回false,反之返回true。 44 | */ 45 | bool removeBackPath(bool release = true); 46 | 47 | /** 48 | * @brief 获取长度。 49 | * @param isTotal true-包括后续路径;false-只有此路径。 50 | * @return 路径长度。 51 | */ 52 | double getLength(bool isTotal = false); 53 | /** 54 | * @brief 获取路径的点坐标数组。 55 | * @param precision 精度值,两点之间的距离。 56 | * @param outBuffer 接收点坐标的容器。 57 | */ 58 | void getPosTan(double precision, std::vector *outBuffer); 59 | /** 60 | * @brief 路径点数组强制刷新。 61 | */ 62 | void setNeedsUpdate(); 63 | /** 64 | * @brief 深度复制一条路径,包括后续路径。不过没有复制前面的路径,所以拷贝出来的对象是起点。 65 | * @param needsUpdate 拷贝后是否需要刷新路径点数组。 66 | * @return 拷贝出来的对象。 67 | */ 68 | virtual XPQPath *clone(bool needsUpdate = true) = 0; 69 | 70 | /** 71 | * @brief 计算两点之间的距离。 72 | * @return 点距。 73 | */ 74 | double pointSpace(XPQPoint point1, XPQPoint point2); 75 | 76 | XPQPoint getEndPoint() {return m_endPoint;}; 77 | void setEndPoint(XPQPoint endPoint) {m_endPoint = endPoint; setNeedsUpdate();}; 78 | 79 | protected: 80 | // 子类只需重写下面两个方法就行 81 | virtual double getSelfLength() = 0; 82 | virtual void updatePosTan(double precision) = 0; 83 | // 属性方法也可重写 84 | virtual XPQPath* getLastPath() { return m_lastPath; }; 85 | virtual void setLastPath(XPQPath *lastPath) { m_lastPath = lastPath; setNeedsUpdate(); }; 86 | virtual XPQPath* getNextPath() { return m_nextPath; }; 87 | virtual void setNextPath(XPQPath *nextPath) { m_nextPath = nextPath; }; 88 | 89 | protected: 90 | bool m_needsUpdate; 91 | double m_length; 92 | /// 路径结束点 93 | XPQPoint m_endPoint; 94 | std::vector *m_pointBuffer; 95 | 96 | private: 97 | /// 上一条路径,如果为null则表示此对象是起点。 98 | XPQPath *m_lastPath; 99 | /// 下一条路径 100 | XPQPath *m_nextPath; 101 | }; 102 | 103 | #pragma mark - 点 104 | class XPQPointPath : public XPQPath 105 | { 106 | public: 107 | XPQPointPath(XPQPoint point); 108 | virtual XPQPointPath *clone(bool needsUpdate = true); 109 | 110 | protected: 111 | virtual double getSelfLength(); 112 | virtual void updatePosTan(double precision); 113 | }; 114 | 115 | #pragma mark - 直线 116 | class XPQLinePath : public XPQPath 117 | { 118 | public: 119 | XPQLinePath(XPQPoint point); 120 | virtual XPQLinePath *clone(bool needsUpdate = true); 121 | 122 | protected: 123 | virtual double getSelfLength(); 124 | virtual void updatePosTan(double precision); 125 | }; 126 | 127 | #pragma mark - 圆 128 | class XPQRoundPath : public XPQPath 129 | { 130 | public: 131 | /** 132 | * @brief 圆曲线构造函数。 133 | * @param centrePoint 圆心。圆半径由圆心和上一条路径结束点共同决定,如果上一条路径为空则半径为0. 134 | * @param angle 路径旋转弧度,2π为一圈,正数为逆时针,负数为顺时针。 135 | */ 136 | XPQRoundPath(XPQPoint centrePoint, double angle); 137 | virtual XPQRoundPath *clone(bool needsUpdate = true); 138 | 139 | protected: 140 | virtual void setLastPath(XPQPath *lastPath); 141 | virtual double getSelfLength(); 142 | virtual void updatePosTan(double precision); 143 | 144 | private: 145 | /// 旋转弧度 146 | double m_angle; 147 | /// 开始弧度 148 | double m_beginAngle; 149 | /// 半径 150 | double m_radii; 151 | /// 圆心 152 | XPQPoint m_centrePoint; 153 | }; 154 | 155 | #pragma mark - 贝塞尔曲线 156 | class XPQBezierPath : public XPQPath 157 | { 158 | public: 159 | XPQBezierPath(XPQPoint anchorPoint, XPQPoint endPoint); 160 | virtual XPQBezierPath *clone(bool needsUpdate = true); 161 | 162 | protected: 163 | virtual void setLastPath(XPQPath *lastPath); 164 | virtual double getSelfLength(); 165 | virtual void updatePosTan(double precision); 166 | 167 | private: 168 | /// 锚点 169 | XPQPoint m_anchorPoint; 170 | private: 171 | ///长度函数反函数,使用牛顿切线法求解 172 | double invertLength(double t, double l); 173 | double speed(double t); 174 | double getBezierLength(double t); 175 | 176 | private: 177 | /// 下面都是求值过程中的中间变量 178 | int m_ax; 179 | int m_ay; 180 | int m_bx; 181 | int m_by; 182 | 183 | double m_A; 184 | double m_B; 185 | double m_C; 186 | }; 187 | 188 | #pragma mark - 自定义曲线 189 | class XPQCustomPath : public XPQPath 190 | { 191 | public: 192 | XPQCustomPath(std::vector *customPoint); 193 | virtual ~XPQCustomPath(); 194 | virtual XPQCustomPath *clone(bool needsUpdate = true); 195 | 196 | protected: 197 | virtual double getSelfLength(); 198 | virtual void updatePosTan(double precision); 199 | 200 | private: 201 | std::vector *m_customPoint; 202 | 203 | double calcSegmentPoint(XPQPoint point1, XPQPoint point2, double precision, double offset, std::vector *outBuffer); 204 | }; 205 | 206 | #endif /* XPQLabelPath_h */ 207 | -------------------------------------------------------------------------------- /XPQLabelDome/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // XPQLabelDome 4 | // 5 | // Created by RHC on 15/10/28. 6 | // Copyright © 2015年 com.xpq. 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 | -------------------------------------------------------------------------------- /domeImage/alignmentDome.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQLabel/6ec6838c24197ce964dc4e0f71161e512e6204e0/domeImage/alignmentDome.gif -------------------------------------------------------------------------------- /domeImage/animationDome1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQLabel/6ec6838c24197ce964dc4e0f71161e512e6204e0/domeImage/animationDome1.gif -------------------------------------------------------------------------------- /domeImage/animationDome2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQLabel/6ec6838c24197ce964dc4e0f71161e512e6204e0/domeImage/animationDome2.gif -------------------------------------------------------------------------------- /domeImage/classDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQLabel/6ec6838c24197ce964dc4e0f71161e512e6204e0/domeImage/classDiagram.png -------------------------------------------------------------------------------- /domeImage/gestureDome.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQLabel/6ec6838c24197ce964dc4e0f71161e512e6204e0/domeImage/gestureDome.gif -------------------------------------------------------------------------------- /domeImage/pathDome.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQLabel/6ec6838c24197ce964dc4e0f71161e512e6204e0/domeImage/pathDome.gif -------------------------------------------------------------------------------- /domeImage/ratonDome.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQLabel/6ec6838c24197ce964dc4e0f71161e512e6204e0/domeImage/ratonDome.gif -------------------------------------------------------------------------------- /domeImage/setText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQLabel/6ec6838c24197ce964dc4e0f71161e512e6204e0/domeImage/setText.png --------------------------------------------------------------------------------