├── .github └── FUNDING.yml ├── .gitignore ├── Example ├── THLabelExample.xcodeproj │ └── project.pbxproj └── THLabelExample │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Constants.h │ ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── LaunchScreen.storyboard │ ├── Main.storyboard │ ├── THLabelExample-Info.plist │ ├── THLabelExample-Prefix.pch │ ├── TableViewController.h │ ├── TableViewController.m │ ├── ViewController.h │ ├── ViewController.m │ ├── en.lproj │ └── InfoPlist.strings │ └── main.m ├── LICENSE.md ├── README.md ├── RELEASE NOTES.md ├── THLabel.podspec ├── THLabel.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── THLabel.xcscheme ├── THLabel.xcworkspace └── contents.xcworkspacedata ├── THLabel ├── Info.plist ├── THLabel.h └── THLabel.m └── screenshot.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: tobihagemann 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /Example/THLabelExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7428883717C557170011060B /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7428883617C557170011060B /* CoreText.framework */; }; 11 | 74338E6216830723001B12C9 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74338E6116830723001B12C9 /* UIKit.framework */; }; 12 | 74338E6416830723001B12C9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74338E6316830723001B12C9 /* Foundation.framework */; }; 13 | 74338E6616830723001B12C9 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74338E6516830723001B12C9 /* CoreGraphics.framework */; }; 14 | 74338E6C16830723001B12C9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 74338E6A16830723001B12C9 /* InfoPlist.strings */; }; 15 | 74338E6E16830723001B12C9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 74338E6D16830723001B12C9 /* main.m */; }; 16 | 74338E7216830723001B12C9 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 74338E7116830723001B12C9 /* AppDelegate.m */; }; 17 | 74338E7B16830723001B12C9 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 74338E7A16830723001B12C9 /* ViewController.m */; }; 18 | 74338E92168307E8001B12C9 /* THLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 74338E91168307E8001B12C9 /* THLabel.m */; }; 19 | 7454A23D1D8CA71600EDC9FA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7454A23C1D8CA71600EDC9FA /* LaunchScreen.storyboard */; }; 20 | 74699B4619BF0EDA0099B0FF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 74699B4519BF0EDA0099B0FF /* Images.xcassets */; }; 21 | 74699B4919BF21CE0099B0FF /* TableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 74699B4819BF21CE0099B0FF /* TableViewController.m */; }; 22 | 74B737E319BF0E1D0098444F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74B737E219BF0E1D0098444F /* Main.storyboard */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 7428883617C557170011060B /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; 27 | 74338E5D16830723001B12C9 /* THLabelExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = THLabelExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 74338E6116830723001B12C9 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 29 | 74338E6316830723001B12C9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 30 | 74338E6516830723001B12C9 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 31 | 74338E6916830723001B12C9 /* THLabelExample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "THLabelExample-Info.plist"; sourceTree = ""; }; 32 | 74338E6B16830723001B12C9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 33 | 74338E6D16830723001B12C9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 34 | 74338E6F16830723001B12C9 /* THLabelExample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "THLabelExample-Prefix.pch"; sourceTree = ""; }; 35 | 74338E7016830723001B12C9 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 36 | 74338E7116830723001B12C9 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 37 | 74338E7916830723001B12C9 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 38 | 74338E7A16830723001B12C9 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 39 | 74338E90168307E8001B12C9 /* THLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THLabel.h; sourceTree = ""; }; 40 | 74338E91168307E8001B12C9 /* THLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = THLabel.m; sourceTree = ""; }; 41 | 7454A23C1D8CA71600EDC9FA /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 42 | 74699B4519BF0EDA0099B0FF /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 43 | 74699B4719BF21CE0099B0FF /* TableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableViewController.h; sourceTree = ""; }; 44 | 74699B4819BF21CE0099B0FF /* TableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TableViewController.m; sourceTree = ""; }; 45 | 74699B4A19BF237C0099B0FF /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; 46 | 74B737E219BF0E1D0098444F /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 74338E5A16830723001B12C9 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | 74338E6216830723001B12C9 /* UIKit.framework in Frameworks */, 55 | 74338E6416830723001B12C9 /* Foundation.framework in Frameworks */, 56 | 74338E6616830723001B12C9 /* CoreGraphics.framework in Frameworks */, 57 | 7428883717C557170011060B /* CoreText.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 74338E5216830723001B12C9 = { 65 | isa = PBXGroup; 66 | children = ( 67 | 74338E8F168307E8001B12C9 /* THLabel */, 68 | 74338E6716830723001B12C9 /* THLabelExample */, 69 | 74338E6016830723001B12C9 /* Frameworks */, 70 | 74338E5E16830723001B12C9 /* Products */, 71 | ); 72 | sourceTree = ""; 73 | }; 74 | 74338E5E16830723001B12C9 /* Products */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 74338E5D16830723001B12C9 /* THLabelExample.app */, 78 | ); 79 | name = Products; 80 | sourceTree = ""; 81 | }; 82 | 74338E6016830723001B12C9 /* Frameworks */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 74338E6116830723001B12C9 /* UIKit.framework */, 86 | 74338E6316830723001B12C9 /* Foundation.framework */, 87 | 74338E6516830723001B12C9 /* CoreGraphics.framework */, 88 | 7428883617C557170011060B /* CoreText.framework */, 89 | ); 90 | name = Frameworks; 91 | sourceTree = ""; 92 | }; 93 | 74338E6716830723001B12C9 /* THLabelExample */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 74699B4A19BF237C0099B0FF /* Constants.h */, 97 | 74338E7016830723001B12C9 /* AppDelegate.h */, 98 | 74338E7116830723001B12C9 /* AppDelegate.m */, 99 | 74338E7916830723001B12C9 /* ViewController.h */, 100 | 74338E7A16830723001B12C9 /* ViewController.m */, 101 | 74699B4719BF21CE0099B0FF /* TableViewController.h */, 102 | 74699B4819BF21CE0099B0FF /* TableViewController.m */, 103 | 74B737E219BF0E1D0098444F /* Main.storyboard */, 104 | 7454A23C1D8CA71600EDC9FA /* LaunchScreen.storyboard */, 105 | 74699B4519BF0EDA0099B0FF /* Images.xcassets */, 106 | 74338E6816830723001B12C9 /* Supporting Files */, 107 | ); 108 | path = THLabelExample; 109 | sourceTree = ""; 110 | }; 111 | 74338E6816830723001B12C9 /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 74338E6916830723001B12C9 /* THLabelExample-Info.plist */, 115 | 74338E6A16830723001B12C9 /* InfoPlist.strings */, 116 | 74338E6D16830723001B12C9 /* main.m */, 117 | 74338E6F16830723001B12C9 /* THLabelExample-Prefix.pch */, 118 | ); 119 | name = "Supporting Files"; 120 | sourceTree = ""; 121 | }; 122 | 74338E8F168307E8001B12C9 /* THLabel */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 74338E90168307E8001B12C9 /* THLabel.h */, 126 | 74338E91168307E8001B12C9 /* THLabel.m */, 127 | ); 128 | name = THLabel; 129 | path = ../THLabel; 130 | sourceTree = ""; 131 | }; 132 | /* End PBXGroup section */ 133 | 134 | /* Begin PBXNativeTarget section */ 135 | 74338E5C16830723001B12C9 /* THLabelExample */ = { 136 | isa = PBXNativeTarget; 137 | buildConfigurationList = 74338E8416830723001B12C9 /* Build configuration list for PBXNativeTarget "THLabelExample" */; 138 | buildPhases = ( 139 | 74338E5916830723001B12C9 /* Sources */, 140 | 74338E5A16830723001B12C9 /* Frameworks */, 141 | 74338E5B16830723001B12C9 /* Resources */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = THLabelExample; 148 | productName = THLabelExample; 149 | productReference = 74338E5D16830723001B12C9 /* THLabelExample.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 74338E5416830723001B12C9 /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | LastUpgradeCheck = 1030; 159 | ORGANIZATIONNAME = tobiha.de; 160 | }; 161 | buildConfigurationList = 74338E5716830723001B12C9 /* Build configuration list for PBXProject "THLabelExample" */; 162 | compatibilityVersion = "Xcode 3.2"; 163 | developmentRegion = en; 164 | hasScannedForEncodings = 0; 165 | knownRegions = ( 166 | en, 167 | Base, 168 | ); 169 | mainGroup = 74338E5216830723001B12C9; 170 | productRefGroup = 74338E5E16830723001B12C9 /* Products */; 171 | projectDirPath = ""; 172 | projectRoot = ""; 173 | targets = ( 174 | 74338E5C16830723001B12C9 /* THLabelExample */, 175 | ); 176 | }; 177 | /* End PBXProject section */ 178 | 179 | /* Begin PBXResourcesBuildPhase section */ 180 | 74338E5B16830723001B12C9 /* Resources */ = { 181 | isa = PBXResourcesBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | 74699B4619BF0EDA0099B0FF /* Images.xcassets in Resources */, 185 | 74338E6C16830723001B12C9 /* InfoPlist.strings in Resources */, 186 | 7454A23D1D8CA71600EDC9FA /* LaunchScreen.storyboard in Resources */, 187 | 74B737E319BF0E1D0098444F /* Main.storyboard in Resources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXResourcesBuildPhase section */ 192 | 193 | /* Begin PBXSourcesBuildPhase section */ 194 | 74338E5916830723001B12C9 /* Sources */ = { 195 | isa = PBXSourcesBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | 74338E6E16830723001B12C9 /* main.m in Sources */, 199 | 74338E7216830723001B12C9 /* AppDelegate.m in Sources */, 200 | 74338E7B16830723001B12C9 /* ViewController.m in Sources */, 201 | 74699B4919BF21CE0099B0FF /* TableViewController.m in Sources */, 202 | 74338E92168307E8001B12C9 /* THLabel.m in Sources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXSourcesBuildPhase section */ 207 | 208 | /* Begin PBXVariantGroup section */ 209 | 74338E6A16830723001B12C9 /* InfoPlist.strings */ = { 210 | isa = PBXVariantGroup; 211 | children = ( 212 | 74338E6B16830723001B12C9 /* en */, 213 | ); 214 | name = InfoPlist.strings; 215 | sourceTree = ""; 216 | }; 217 | /* End PBXVariantGroup section */ 218 | 219 | /* Begin XCBuildConfiguration section */ 220 | 74338E8216830723001B12C9 /* Debug */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 226 | CLANG_CXX_LIBRARY = "libc++"; 227 | CLANG_ENABLE_OBJC_ARC = YES; 228 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 229 | CLANG_WARN_BOOL_CONVERSION = YES; 230 | CLANG_WARN_COMMA = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INFINITE_RECURSION = YES; 236 | CLANG_WARN_INT_CONVERSION = YES; 237 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 238 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 239 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 240 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 241 | CLANG_WARN_STRICT_PROTOTYPES = YES; 242 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 243 | CLANG_WARN_UNREACHABLE_CODE = YES; 244 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 245 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 246 | COPY_PHASE_STRIP = NO; 247 | ENABLE_STRICT_OBJC_MSGSEND = YES; 248 | ENABLE_TESTABILITY = YES; 249 | GCC_C_LANGUAGE_STANDARD = gnu99; 250 | GCC_DYNAMIC_NO_PIC = NO; 251 | GCC_NO_COMMON_BLOCKS = YES; 252 | GCC_OPTIMIZATION_LEVEL = 0; 253 | GCC_PREPROCESSOR_DEFINITIONS = ( 254 | "DEBUG=1", 255 | "$(inherited)", 256 | ); 257 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 265 | ONLY_ACTIVE_ARCH = YES; 266 | SDKROOT = iphoneos; 267 | TARGETED_DEVICE_FAMILY = "1,2"; 268 | }; 269 | name = Debug; 270 | }; 271 | 74338E8316830723001B12C9 /* Release */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 276 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 277 | CLANG_CXX_LIBRARY = "libc++"; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 280 | CLANG_WARN_BOOL_CONVERSION = YES; 281 | CLANG_WARN_COMMA = YES; 282 | CLANG_WARN_CONSTANT_CONVERSION = YES; 283 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 284 | CLANG_WARN_EMPTY_BODY = YES; 285 | CLANG_WARN_ENUM_CONVERSION = YES; 286 | CLANG_WARN_INFINITE_RECURSION = YES; 287 | CLANG_WARN_INT_CONVERSION = YES; 288 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 289 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 290 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 291 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 292 | CLANG_WARN_STRICT_PROTOTYPES = YES; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 297 | COPY_PHASE_STRIP = YES; 298 | ENABLE_STRICT_OBJC_MSGSEND = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu99; 300 | GCC_NO_COMMON_BLOCKS = YES; 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 308 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 309 | SDKROOT = iphoneos; 310 | TARGETED_DEVICE_FAMILY = "1,2"; 311 | VALIDATE_PRODUCT = YES; 312 | }; 313 | name = Release; 314 | }; 315 | 74338E8516830723001B12C9 /* Debug */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 319 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 320 | GCC_PREFIX_HEADER = "THLabelExample/THLabelExample-Prefix.pch"; 321 | INFOPLIST_FILE = "THLabelExample/THLabelExample-Info.plist"; 322 | PRODUCT_BUNDLE_IDENTIFIER = "de.tobiha.$(PRODUCT_NAME:rfc1034identifier)"; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | WRAPPER_EXTENSION = app; 325 | }; 326 | name = Debug; 327 | }; 328 | 74338E8616830723001B12C9 /* Release */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 332 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 333 | GCC_PREFIX_HEADER = "THLabelExample/THLabelExample-Prefix.pch"; 334 | INFOPLIST_FILE = "THLabelExample/THLabelExample-Info.plist"; 335 | PRODUCT_BUNDLE_IDENTIFIER = "de.tobiha.$(PRODUCT_NAME:rfc1034identifier)"; 336 | PRODUCT_NAME = "$(TARGET_NAME)"; 337 | WRAPPER_EXTENSION = app; 338 | }; 339 | name = Release; 340 | }; 341 | /* End XCBuildConfiguration section */ 342 | 343 | /* Begin XCConfigurationList section */ 344 | 74338E5716830723001B12C9 /* Build configuration list for PBXProject "THLabelExample" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | 74338E8216830723001B12C9 /* Debug */, 348 | 74338E8316830723001B12C9 /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | 74338E8416830723001B12C9 /* Build configuration list for PBXNativeTarget "THLabelExample" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | 74338E8516830723001B12C9 /* Debug */, 357 | 74338E8616830723001B12C9 /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | /* End XCConfigurationList section */ 363 | }; 364 | rootObject = 74338E5416830723001B12C9 /* Project object */; 365 | } 366 | -------------------------------------------------------------------------------- /Example/THLabelExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // THLabelExample 4 | // 5 | // Created by Tobias Hagemann on 12/20/12. 6 | // Copyright (c) 2012 tobiha.de. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic, strong) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/THLabelExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // THLabelExample 4 | // 5 | // Created by Tobias Hagemann on 12/20/12. 6 | // Copyright (c) 2012 tobiha.de. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/THLabelExample/Constants.h: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.h 3 | // THLabelExample 4 | // 5 | // Created by Tobias Hagemann on 09/09/14. 6 | // Copyright (c) 2014 tobiha.de. All rights reserved. 7 | // 8 | 9 | #define kShadowColor1 [UIColor blackColor] 10 | #define kShadowColor2 [UIColor colorWithWhite:0.0 alpha:0.75] 11 | #define kShadowOffset CGSizeMake(0.0, UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? 4.0 : 2.0) 12 | #define kShadowBlur (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? 10.0 : 5.0) 13 | #define kInnerShadowOffset CGSizeMake(0.0, UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? 2.0 : 1.0) 14 | #define kInnerShadowBlur (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? 4.0 : 2.0) 15 | 16 | #define kStrokeColor [UIColor blackColor] 17 | #define kStrokeSize (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? 6.0 : 3.0) 18 | 19 | #define kGradientStartColor [UIColor colorWithRed:255.0 / 255.0 green:193.0 / 255.0 blue:127.0 / 255.0 alpha:1.0] 20 | #define kGradientEndColor [UIColor colorWithRed:255.0 / 255.0 green:163.0 / 255.0 blue:64.0 / 255.0 alpha:1.0] 21 | -------------------------------------------------------------------------------- /Example/THLabelExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | }, 98 | "properties" : { 99 | "pre-rendered" : true 100 | } 101 | } -------------------------------------------------------------------------------- /Example/THLabelExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/THLabelExample/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Example/THLabelExample/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 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | -------------------------------------------------------------------------------- /Example/THLabelExample/THLabelExample-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.0 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/THLabelExample/THLabelExample-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'THLabelExample' target in the 'THLabelExample' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_7_0 8 | #warning "This project uses features only available in iOS SDK 7.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #import "Constants.h" 15 | #endif 16 | -------------------------------------------------------------------------------- /Example/THLabelExample/TableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.h 3 | // THLabelExample 4 | // 5 | // Created by Tobias Hagemann on 09/09/14. 6 | // Copyright (c) 2014 tobiha.de. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/THLabelExample/TableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.m 3 | // THLabelExample 4 | // 5 | // Created by Tobias Hagemann on 09/09/14. 6 | // Copyright (c) 2014 tobiha.de. All rights reserved. 7 | // 8 | 9 | #import "TableViewController.h" 10 | #import "THLabel.h" 11 | 12 | @interface TableViewCell : UITableViewCell 13 | @property (nonatomic, weak) IBOutlet THLabel *customTextLabel; 14 | @end 15 | 16 | @implementation TableViewCell 17 | @end 18 | 19 | @implementation TableViewController 20 | 21 | #pragma mark - UITableViewDataSource 22 | 23 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 24 | return 100; 25 | } 26 | 27 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 28 | static NSString *CellIdentifier = @"Cell"; 29 | TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 30 | cell.customTextLabel.textColor = [UIColor whiteColor]; 31 | cell.customTextLabel.strokeColor = kStrokeColor; 32 | cell.customTextLabel.strokeSize = kStrokeSize; 33 | cell.customTextLabel.gradientStartColor = kGradientStartColor; 34 | cell.customTextLabel.gradientEndColor = kGradientEndColor; 35 | return cell; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Example/THLabelExample/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // THLabelExample 4 | // 5 | // Created by Tobias Hagemann on 12/20/12. 6 | // Copyright (c) 2012 tobiha.de. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/THLabelExample/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // THLabelExample 4 | // 5 | // Created by Tobias Hagemann on 12/20/12. 6 | // Copyright (c) 2012 tobiha.de. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "THLabel.h" 11 | 12 | @interface ViewController () 13 | @property (nonatomic, weak) IBOutlet THLabel *label1; 14 | @property (nonatomic, weak) IBOutlet THLabel *label2; 15 | @property (nonatomic, weak) IBOutlet THLabel *label3; 16 | @property (nonatomic, weak) IBOutlet THLabel *label4; 17 | @property (nonatomic, weak) IBOutlet THLabel *label5; 18 | @property (nonatomic, weak) IBOutlet THLabel *label6; 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | 26 | // Demonstrate shadow blur. 27 | self.label1.shadowColor = kShadowColor1; 28 | self.label1.shadowOffset = kShadowOffset; 29 | self.label1.shadowBlur = kShadowBlur; 30 | 31 | // Demonstrate inner shadow. 32 | self.label2.innerShadowColor = kShadowColor1; 33 | self.label2.innerShadowOffset = kInnerShadowOffset; 34 | self.label2.innerShadowBlur = kInnerShadowBlur; 35 | 36 | // Demonstrate stroke. 37 | self.label3.strokeColor = kStrokeColor; 38 | self.label3.strokeSize = kStrokeSize; 39 | 40 | // Demonstrate fill gradient. 41 | self.label4.gradientStartColor = kGradientStartColor; 42 | self.label4.gradientEndColor = kGradientEndColor; 43 | 44 | // Demonstrate fade truncating. 45 | self.label5.fadeTruncatingMode = THLabelFadeTruncatingModeTail; 46 | 47 | // Demonstrate everything. 48 | self.label6.shadowColor = kShadowColor2; 49 | self.label6.shadowOffset = kShadowOffset; 50 | self.label6.shadowBlur = kShadowBlur; 51 | self.label6.innerShadowColor = kShadowColor2; 52 | self.label6.innerShadowOffset = kInnerShadowOffset; 53 | self.label6.innerShadowBlur = kInnerShadowBlur; 54 | self.label6.strokeColor = kStrokeColor; 55 | self.label6.strokeSize = kStrokeSize; 56 | self.label6.gradientStartColor = kGradientStartColor; 57 | self.label6.gradientEndColor = kGradientEndColor; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Example/THLabelExample/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/THLabelExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // THLabelExample 4 | // 5 | // Created by Tobias Hagemann on 12/20/12. 6 | // Copyright (c) 2012 tobiha.de. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | THLabel 2 | 3 | Version 1.4.10, January 10th, 2020 4 | 5 | Copyright (c) 2012-2020 Tobias Hagemann, tobiha.de 6 | 7 | This software is provided 'as-is', without any express or implied 8 | warranty. In no event will the authors be held liable for any damages 9 | arising from the use of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it 13 | freely, subject to the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software 17 | in a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 3. This notice may not be removed or altered from any source distribution. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THLabel 2 | 3 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/THLabel.svg)](https://img.shields.io/cocoapods/v/THLabel.svg) 4 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | [![Platform](https://img.shields.io/cocoapods/p/THLabel.svg?style=flat)](http://cocoadocs.org/docsets/THLabel) 6 | [![Twitter](https://img.shields.io/badge/twitter-@tobihagemann-blue.svg?style=flat)](https://twitter.com/tobihagemann) 7 | 8 | THLabel is a subclass of UILabel, which additionally allows shadow blur, inner shadow, stroke text and fill gradient. 9 | 10 | ![THLabel screenshot](https://raw.githubusercontent.com/tobihagemann/THLabel/master/screenshot.png "THLabel screenshot") 11 | 12 | ## Requirements 13 | 14 | * iOS 4.0 or higher (below iOS 7.0 is untested though) 15 | * ARC enabled 16 | 17 | ## Installation 18 | 19 | The easiest way to use THLabel in your app is via [CocoaPods](http://cocoapods.org/ "CocoaPods"). 20 | 21 | 1. Add the following line in the project's Podfile file: `pod 'THLabel', '~> 1.4.0'` 22 | 2. Run the command `pod install` from the Podfile folder directory. 23 | 24 | ### Manual Installation 25 | 26 | 1. Add `CoreText.framework` to your *Link Binary with Libraries* list. 27 | 2. Drag these files into your project: `THLabel.h`, `THLabel.m` 28 | 29 | ## IBDesignable / IBInspectable 30 | 31 | There is an `ibdesignable` branch, if you're interested in this feature. It has been removed from the `master` branch, because IBDesignable and IBInspectable cause problems with CocoaPods, if you're not using `use_frameworks!` in your Podfile. 32 | 33 | ## Usage 34 | 35 | You can create THLabels programmatically, or create them in Interface Builder by dragging an ordinary UILabel into your view and setting its *Custom Class* to THLabel. With Xcode 6 you can set most of the properties within Interface Builder, which will preview your changes immediately. 36 | 37 | ## Properties 38 | 39 | ### Spacing 40 | 41 | ```objective-c 42 | @property CGFloat letterSpacing; 43 | @property CGFloat lineSpacing; 44 | ``` 45 | 46 | You can modify letter spacing of the text (also known as kerning) by changing the `letterSpacing` property. The default value is `0.0`. A positive value will separate the characters, whereas a negative value will make them closer. 47 | 48 | Modify line spacing of the text (also known as leading) by changing the `lineSpacing` property. The default value is `0.0`. Only positive values will have an effect. 49 | 50 | ### Shadow Blur 51 | 52 | ```objective-c 53 | @property CGFloat shadowBlur; 54 | ``` 55 | 56 | Additionally to UILabel's `shadowColor` and `shadowOffset`, you can set a shadow blur to soften the shadow. 57 | 58 | ### Inner Shadow 59 | 60 | ```objective-c 61 | @property CGFloat innerShadowBlur; 62 | @property CGSize innerShadowOffset; 63 | @property UIColor *innerShadowColor; 64 | ``` 65 | 66 | The inner shadow has similar properties as UILabel's shadow, once again additionally with a shadow blur. If an inner shadow and a stroke are overlapping, it will appear beneath the stroke. 67 | 68 | ### Stroke Text 69 | 70 | ```objective-c 71 | @property CGFloat strokeSize; 72 | @property UIColor *strokeColor; 73 | @property THLabelStrokePosition strokePosition; 74 | ``` 75 | 76 | You can set an outer, centered or inner stroke by setting the `strokePosition` property. Default value is `THLabelStrokePositionOutside`. Other options are `THLabelStrokePositionCenter` and `THLabelStrokePositionInside`. 77 | 78 | ### Fill Gradient 79 | 80 | ```objective-c 81 | @property UIColor *gradientStartColor; 82 | @property UIColor *gradientEndColor; 83 | @property NSArray *gradientColors; 84 | @property CGPoint gradientStartPoint; 85 | @property CGPoint gradientEndPoint; 86 | ``` 87 | 88 | The gradient can consist of multiple colors, which have to be saved as UIColor objects in the `gradientColors` array. For more convenience, `gradientStartColor` and `gradientEndColor` will fill up the array accordingly. 89 | 90 | The starting and ending points of the gradient are in the range 0 to 1, where (0, 0) is the top-left and (1, 1) the bottom-right of the text. The default value for `gradientStartPoint` is (0.5, 0.2) and for `gradientEndPoint` it is (0.5, 0.8). 91 | 92 | ### Fade Truncating 93 | 94 | ```objective-c 95 | @property THLabelFadeTruncatingMode fadeTruncatingMode; 96 | ``` 97 | 98 | You can fade in/out your label by setting the `fadeTruncatingMode` property. Default value is `THLabelFadeTruncatingModeNone`. The options are `THLabelFadeTruncatingModeTail`, `THLabelFadeTruncatingModeHead` and `THLabelFadeTruncatingModeHeadAndTail`. 99 | 100 | ### Text Insets / Padding 101 | 102 | ```objective-c 103 | @property UIEdgeInsets textInsets; 104 | @property BOOL automaticallyAdjustTextInsets; 105 | ``` 106 | 107 | Effects like stroke and shadow can't be drawn outside of the bounds of the label view. You may need to set text insets to move a bit away from the edge so that the effects don't get clipped. This will be automatically done if you set `automaticallyAdjustTextInsets` to `YES`, which is also the default value. 108 | 109 | ## Notes 110 | 111 | THLabel respects (unlike UILabel) the `contentMode` property, which is used for vertical alignment. The `textAlignment` still has the higher priority, when it comes to horizontal alignment. 112 | 113 | However THLabel doesn't respect the `numberOfLines` property, because Core Text doesn't support it natively. If you would like to have multiple lines, set `lineBreakMode` to `NSLineBreakByWordWrapping`. 114 | 115 | THLabels are slower to draw than UILabels, so be aware of that. 116 | 117 | ## Credits 118 | 119 | Original source and inspiration from: 120 | 121 | - FXLabel by Nick Lockwood, https://github.com/nicklockwood/FXLabel 122 | - KSLabel by Kai Schweiger, https://github.com/vigorouscoding/KSLabel 123 | - GTMFadeTruncatingLabel by Google, https://github.com/google/google-toolbox-for-mac/tree/master/iPhone 124 | 125 | Big thanks to Jason Miller for showing me sample code of his implementation using Core Text! It inspired me to dig deeper and move away from drawing with NSAttributedString on iOS 7, which caused a lot of problems. 126 | 127 | ## License 128 | 129 | Distributed under the permissive zlib license. See the LICENSE file for more info. 130 | 131 | ## Contact 132 | 133 | Tobias Hagemann 134 | 135 | - https://tobiha.de/ 136 | - tobias.hagemann@gmail.com 137 | - https://twitter.com/tobihagemann 138 | -------------------------------------------------------------------------------- /RELEASE NOTES.md: -------------------------------------------------------------------------------- 1 | Version 1.4.10 2 | 3 | - Fixed layout for RTL mode, kudos to @morozkin. 4 | 5 | Version 1.4.9 6 | 7 | - Fixed warnings and showing incorrect fonts on iOS 13, kudos to @sochalewski. 8 | 9 | Version 1.4.8 10 | 11 | - Fixed memory related crash. 12 | 13 | Version 1.4.7 14 | 15 | - Set maximum width to preferredMaxLayoutWidth for intrinsicContentSize. 16 | - Fixed warning. 17 | 18 | Version 1.4.6 19 | 20 | - Removed support for IB_DESIGNABLE and IBInspectable, until it doesn't cause problems with CocoaPods. Please us `ibdesignable` branch, if you're interested in this feature and not using CocoaPods. 21 | - Fixed bug regarding gradients introduced in last version. 22 | 23 | Version 1.4.5 24 | 25 | - Added support for IB_DESIGNABLE and IBInspectable, only available with Xcode 6. 26 | - Added lineSpacing property. 27 | 28 | Version 1.4.4 29 | 30 | - Fixed memory leak. 31 | 32 | Version 1.4.3 33 | 34 | - Forcing clipsToBounds to YES, because of potential drawing issues. 35 | 36 | Version 1.4.2 37 | 38 | - Fixed unexpected truncation on iOS device. 39 | 40 | Version 1.4.1 41 | 42 | - Fixed crash, when text is nil. 43 | 44 | Version 1.4 45 | 46 | - Added logic for sizeThatFits and intrinsicContentSize. 47 | - Added letterSpacing and automaticallyAdjustTextInsets properties. 48 | 49 | Version 1.3.1 50 | 51 | - Fixed memory leak. 52 | - Updated example and screenshot. 53 | 54 | Version 1.3 55 | 56 | - Added fadeTruncatingMode property. 57 | - Fixed overlapping non-opaque strokes. 58 | 59 | Version 1.2 60 | 61 | - Added innerShadowBlur, innerShadowOffset and innerShadowColor properties. 62 | 63 | Version 1.1.7 64 | 65 | - Fixed drawing, when frame is too small. 66 | 67 | Version 1.1.6 68 | 69 | - Fixed memory related crash. 70 | 71 | Version 1.1.5 72 | 73 | - Fixed text alignment for multi-line texts. 74 | 75 | Version 1.1.4 76 | 77 | - iOS 5 compatibilty restored. iOS 4 should also be compatible, but it's untested! 78 | 79 | Version 1.1.3 80 | 81 | - Fixed potential memory leaks. 82 | 83 | Version 1.1.2 84 | 85 | - Fixed memory related crash. 86 | 87 | Version 1.1.1 88 | 89 | - Fixed crash, which was caused by a premature release of a CFStringRef. 90 | - Fixed crash, when text is nil. 91 | 92 | Version 1.1 93 | 94 | - Complete overhaul using Core Text. This means `CoreText.framework` is now required. Don't forget to add it to your project, if you're updating. 95 | - This also fixes all problems with iOS 7 and should still be compatible with iOS 4 (untested yet, iOS 5 works). 96 | 97 | Version 1.0.7 98 | 99 | - Fixes regarding iOS 7 beta 5. Only tested on Simulator though. 100 | 101 | Version 1.0.6 102 | 103 | - Minor refactorings, because iOS Base SDK 6.0 or higher is required. 104 | 105 | Version 1.0.5 106 | 107 | - Fixed usage of compiler macros and backward compatibility. 108 | 109 | Version 1.0.4 110 | 111 | - iOS 7 compatibility: Breaks former look & feel of strokes though as described in README. 112 | 113 | Version 1.0.3 114 | 115 | - Fixed bug with text insets not applying correctly on stroke. 116 | 117 | Version 1.0.2 118 | 119 | - Fixed character spacing for centered stroke. 120 | 121 | Version 1.0.1 122 | 123 | - Added strokePosition property. 124 | 125 | Version 1.0 126 | 127 | - Initial release. 128 | -------------------------------------------------------------------------------- /THLabel.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'THLabel' 3 | s.version = '1.4.10' 4 | s.summary = 'THLabel is a subclass of UILabel, which additionally allows shadow blur, inner shadow, stroke text and fill gradient.' 5 | s.homepage = 'https://github.com/tobihagemann/THLabel' 6 | s.screenshots = 'https://raw.githubusercontent.com/tobihagemann/THLabel/master/screenshot.png' 7 | s.license = 'zlib' 8 | s.author = { 'Tobias Hagemann' => 'tobias.hagemann@gmail.com' } 9 | s.source = { :git => 'https://github.com/tobihagemann/THLabel.git', :tag => s.version.to_s } 10 | s.platform = :ios, '4.0' 11 | s.source_files = 'THLabel' 12 | s.framework = 'CoreText' 13 | s.requires_arc = true 14 | end 15 | -------------------------------------------------------------------------------- /THLabel.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 90BCAD051C5EF9C600A4D1F6 /* THLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = 90BCAD041C5EF9C600A4D1F6 /* THLabel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 90BCAD0E1C5EFA4800A4D1F6 /* THLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 90BCAD0C1C5EFA4800A4D1F6 /* THLabel.m */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXFileReference section */ 15 | 90BCAD011C5EF9C600A4D1F6 /* THLabel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = THLabel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 16 | 90BCAD041C5EF9C600A4D1F6 /* THLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = THLabel.h; sourceTree = ""; }; 17 | 90BCAD061C5EF9C600A4D1F6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 18 | 90BCAD0C1C5EFA4800A4D1F6 /* THLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = THLabel.m; sourceTree = ""; }; 19 | /* End PBXFileReference section */ 20 | 21 | /* Begin PBXFrameworksBuildPhase section */ 22 | 90BCACFD1C5EF9C600A4D1F6 /* Frameworks */ = { 23 | isa = PBXFrameworksBuildPhase; 24 | buildActionMask = 2147483647; 25 | files = ( 26 | ); 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXFrameworksBuildPhase section */ 30 | 31 | /* Begin PBXGroup section */ 32 | 90BCACF71C5EF9C500A4D1F6 = { 33 | isa = PBXGroup; 34 | children = ( 35 | 90BCAD031C5EF9C600A4D1F6 /* THLabel */, 36 | 90BCAD021C5EF9C600A4D1F6 /* Products */, 37 | ); 38 | sourceTree = ""; 39 | }; 40 | 90BCAD021C5EF9C600A4D1F6 /* Products */ = { 41 | isa = PBXGroup; 42 | children = ( 43 | 90BCAD011C5EF9C600A4D1F6 /* THLabel.framework */, 44 | ); 45 | name = Products; 46 | sourceTree = ""; 47 | }; 48 | 90BCAD031C5EF9C600A4D1F6 /* THLabel */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | 90BCAD041C5EF9C600A4D1F6 /* THLabel.h */, 52 | 90BCAD0C1C5EFA4800A4D1F6 /* THLabel.m */, 53 | 90BCAD061C5EF9C600A4D1F6 /* Info.plist */, 54 | ); 55 | path = THLabel; 56 | sourceTree = ""; 57 | }; 58 | /* End PBXGroup section */ 59 | 60 | /* Begin PBXHeadersBuildPhase section */ 61 | 90BCACFE1C5EF9C600A4D1F6 /* Headers */ = { 62 | isa = PBXHeadersBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 90BCAD051C5EF9C600A4D1F6 /* THLabel.h in Headers */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXHeadersBuildPhase section */ 70 | 71 | /* Begin PBXNativeTarget section */ 72 | 90BCAD001C5EF9C600A4D1F6 /* THLabel */ = { 73 | isa = PBXNativeTarget; 74 | buildConfigurationList = 90BCAD091C5EF9C600A4D1F6 /* Build configuration list for PBXNativeTarget "THLabel" */; 75 | buildPhases = ( 76 | 90BCACFC1C5EF9C600A4D1F6 /* Sources */, 77 | 90BCACFD1C5EF9C600A4D1F6 /* Frameworks */, 78 | 90BCACFE1C5EF9C600A4D1F6 /* Headers */, 79 | 90BCACFF1C5EF9C600A4D1F6 /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = THLabel; 86 | productName = THLabel; 87 | productReference = 90BCAD011C5EF9C600A4D1F6 /* THLabel.framework */; 88 | productType = "com.apple.product-type.framework"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | 90BCACF81C5EF9C500A4D1F6 /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastUpgradeCheck = 1030; 97 | ORGANIZATIONNAME = de.tobiha.THLabelExample; 98 | TargetAttributes = { 99 | 90BCAD001C5EF9C600A4D1F6 = { 100 | CreatedOnToolsVersion = 7.2; 101 | }; 102 | }; 103 | }; 104 | buildConfigurationList = 90BCACFB1C5EF9C500A4D1F6 /* Build configuration list for PBXProject "THLabel" */; 105 | compatibilityVersion = "Xcode 3.2"; 106 | developmentRegion = en; 107 | hasScannedForEncodings = 0; 108 | knownRegions = ( 109 | en, 110 | Base, 111 | ); 112 | mainGroup = 90BCACF71C5EF9C500A4D1F6; 113 | productRefGroup = 90BCAD021C5EF9C600A4D1F6 /* Products */; 114 | projectDirPath = ""; 115 | projectRoot = ""; 116 | targets = ( 117 | 90BCAD001C5EF9C600A4D1F6 /* THLabel */, 118 | ); 119 | }; 120 | /* End PBXProject section */ 121 | 122 | /* Begin PBXResourcesBuildPhase section */ 123 | 90BCACFF1C5EF9C600A4D1F6 /* Resources */ = { 124 | isa = PBXResourcesBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | /* End PBXResourcesBuildPhase section */ 131 | 132 | /* Begin PBXSourcesBuildPhase section */ 133 | 90BCACFC1C5EF9C600A4D1F6 /* Sources */ = { 134 | isa = PBXSourcesBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | 90BCAD0E1C5EFA4800A4D1F6 /* THLabel.m in Sources */, 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | /* End PBXSourcesBuildPhase section */ 142 | 143 | /* Begin XCBuildConfiguration section */ 144 | 90BCAD071C5EF9C600A4D1F6 /* Debug */ = { 145 | isa = XCBuildConfiguration; 146 | buildSettings = { 147 | ALWAYS_SEARCH_USER_PATHS = NO; 148 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 149 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 150 | CLANG_CXX_LIBRARY = "libc++"; 151 | CLANG_ENABLE_MODULES = YES; 152 | CLANG_ENABLE_OBJC_ARC = YES; 153 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 154 | CLANG_WARN_BOOL_CONVERSION = YES; 155 | CLANG_WARN_COMMA = YES; 156 | CLANG_WARN_CONSTANT_CONVERSION = YES; 157 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 158 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 159 | CLANG_WARN_EMPTY_BODY = YES; 160 | CLANG_WARN_ENUM_CONVERSION = YES; 161 | CLANG_WARN_INFINITE_RECURSION = YES; 162 | CLANG_WARN_INT_CONVERSION = YES; 163 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 164 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 165 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 166 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 167 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 168 | CLANG_WARN_STRICT_PROTOTYPES = YES; 169 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 170 | CLANG_WARN_UNREACHABLE_CODE = YES; 171 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 172 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 173 | COPY_PHASE_STRIP = NO; 174 | CURRENT_PROJECT_VERSION = 1.4.10; 175 | DEBUG_INFORMATION_FORMAT = dwarf; 176 | ENABLE_STRICT_OBJC_MSGSEND = YES; 177 | ENABLE_TESTABILITY = YES; 178 | GCC_C_LANGUAGE_STANDARD = gnu99; 179 | GCC_DYNAMIC_NO_PIC = NO; 180 | GCC_NO_COMMON_BLOCKS = YES; 181 | GCC_OPTIMIZATION_LEVEL = 0; 182 | GCC_PREPROCESSOR_DEFINITIONS = ( 183 | "DEBUG=1", 184 | "$(inherited)", 185 | ); 186 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 187 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 188 | GCC_WARN_UNDECLARED_SELECTOR = YES; 189 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 190 | GCC_WARN_UNUSED_FUNCTION = YES; 191 | GCC_WARN_UNUSED_VARIABLE = YES; 192 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 193 | MTL_ENABLE_DEBUG_INFO = YES; 194 | ONLY_ACTIVE_ARCH = YES; 195 | SDKROOT = iphoneos; 196 | TARGETED_DEVICE_FAMILY = "1,2"; 197 | VERSIONING_SYSTEM = "apple-generic"; 198 | VERSION_INFO_PREFIX = ""; 199 | }; 200 | name = Debug; 201 | }; 202 | 90BCAD081C5EF9C600A4D1F6 /* Release */ = { 203 | isa = XCBuildConfiguration; 204 | buildSettings = { 205 | ALWAYS_SEARCH_USER_PATHS = NO; 206 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 207 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 208 | CLANG_CXX_LIBRARY = "libc++"; 209 | CLANG_ENABLE_MODULES = YES; 210 | CLANG_ENABLE_OBJC_ARC = YES; 211 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 212 | CLANG_WARN_BOOL_CONVERSION = YES; 213 | CLANG_WARN_COMMA = YES; 214 | CLANG_WARN_CONSTANT_CONVERSION = YES; 215 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 216 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 217 | CLANG_WARN_EMPTY_BODY = YES; 218 | CLANG_WARN_ENUM_CONVERSION = YES; 219 | CLANG_WARN_INFINITE_RECURSION = YES; 220 | CLANG_WARN_INT_CONVERSION = YES; 221 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 222 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 223 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 224 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 225 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 226 | CLANG_WARN_STRICT_PROTOTYPES = YES; 227 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 228 | CLANG_WARN_UNREACHABLE_CODE = YES; 229 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 230 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 231 | COPY_PHASE_STRIP = NO; 232 | CURRENT_PROJECT_VERSION = 1.4.10; 233 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 234 | ENABLE_NS_ASSERTIONS = NO; 235 | ENABLE_STRICT_OBJC_MSGSEND = YES; 236 | GCC_C_LANGUAGE_STANDARD = gnu99; 237 | GCC_NO_COMMON_BLOCKS = YES; 238 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 239 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 240 | GCC_WARN_UNDECLARED_SELECTOR = YES; 241 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 242 | GCC_WARN_UNUSED_FUNCTION = YES; 243 | GCC_WARN_UNUSED_VARIABLE = YES; 244 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 245 | MTL_ENABLE_DEBUG_INFO = NO; 246 | SDKROOT = iphoneos; 247 | TARGETED_DEVICE_FAMILY = "1,2"; 248 | VALIDATE_PRODUCT = YES; 249 | VERSIONING_SYSTEM = "apple-generic"; 250 | VERSION_INFO_PREFIX = ""; 251 | }; 252 | name = Release; 253 | }; 254 | 90BCAD0A1C5EF9C600A4D1F6 /* Debug */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 258 | DEFINES_MODULE = YES; 259 | DYLIB_COMPATIBILITY_VERSION = 1; 260 | DYLIB_CURRENT_VERSION = 1; 261 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 262 | INFOPLIST_FILE = THLabel/Info.plist; 263 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 264 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 265 | PRODUCT_BUNDLE_IDENTIFIER = "de.tobiha.$(PRODUCT_NAME:rfc1034identifier)"; 266 | PRODUCT_NAME = "$(TARGET_NAME)"; 267 | SKIP_INSTALL = YES; 268 | }; 269 | name = Debug; 270 | }; 271 | 90BCAD0B1C5EF9C600A4D1F6 /* Release */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 275 | DEFINES_MODULE = YES; 276 | DYLIB_COMPATIBILITY_VERSION = 1; 277 | DYLIB_CURRENT_VERSION = 1; 278 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 279 | INFOPLIST_FILE = THLabel/Info.plist; 280 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 281 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 282 | PRODUCT_BUNDLE_IDENTIFIER = "de.tobiha.$(PRODUCT_NAME:rfc1034identifier)"; 283 | PRODUCT_NAME = "$(TARGET_NAME)"; 284 | SKIP_INSTALL = YES; 285 | }; 286 | name = Release; 287 | }; 288 | /* End XCBuildConfiguration section */ 289 | 290 | /* Begin XCConfigurationList section */ 291 | 90BCACFB1C5EF9C500A4D1F6 /* Build configuration list for PBXProject "THLabel" */ = { 292 | isa = XCConfigurationList; 293 | buildConfigurations = ( 294 | 90BCAD071C5EF9C600A4D1F6 /* Debug */, 295 | 90BCAD081C5EF9C600A4D1F6 /* Release */, 296 | ); 297 | defaultConfigurationIsVisible = 0; 298 | defaultConfigurationName = Release; 299 | }; 300 | 90BCAD091C5EF9C600A4D1F6 /* Build configuration list for PBXNativeTarget "THLabel" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | 90BCAD0A1C5EF9C600A4D1F6 /* Debug */, 304 | 90BCAD0B1C5EF9C600A4D1F6 /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | defaultConfigurationName = Release; 308 | }; 309 | /* End XCConfigurationList section */ 310 | }; 311 | rootObject = 90BCACF81C5EF9C500A4D1F6 /* Project object */; 312 | } 313 | -------------------------------------------------------------------------------- /THLabel.xcodeproj/xcshareddata/xcschemes/THLabel.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /THLabel.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /THLabel/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.4.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /THLabel/THLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // THLabel.h 3 | // 4 | // Version 1.4.10 5 | // 6 | // Created by Tobias Hagemann on 11/25/12. 7 | // Copyright (c) 2012-2020 tobiha.de. All rights reserved. 8 | // 9 | // Original source and inspiration from: 10 | // FXLabel by Nick Lockwood, 11 | // https://github.com/nicklockwood/FXLabel 12 | // KSLabel by Kai Schweiger, 13 | // https://github.com/vigorouscoding/KSLabel 14 | // GTMFadeTruncatingLabel by Google, 15 | // https://github.com/google/google-toolbox-for-mac/tree/master/iPhone 16 | // 17 | // Big thanks to Jason Miller for showing me sample code of his implementation 18 | // using Core Text! It inspired me to dig deeper and move away from drawing 19 | // with NSAttributedString on iOS 7, which caused a lot of problems. 20 | // 21 | // Distributed under the permissive zlib license 22 | // Get the latest version from here: 23 | // 24 | // https://github.com/tobihagemann/THLabel 25 | // 26 | // This software is provided 'as-is', without any express or implied 27 | // warranty. In no event will the authors be held liable for any damages 28 | // arising from the use of this software. 29 | // 30 | // Permission is granted to anyone to use this software for any purpose, 31 | // including commercial applications, and to alter it and redistribute it 32 | // freely, subject to the following restrictions: 33 | // 34 | // 1. The origin of this software must not be misrepresented; you must not 35 | // claim that you wrote the original software. If you use this software 36 | // in a product, an acknowledgment in the product documentation would be 37 | // appreciated but is not required. 38 | // 39 | // 2. Altered source versions must be plainly marked as such, and must not be 40 | // misrepresented as being the original software. 41 | // 42 | // 3. This notice may not be removed or altered from any source distribution. 43 | // 44 | 45 | #import 46 | 47 | //! Project version number for THLabel. 48 | FOUNDATION_EXPORT double THLabelVersionNumber; 49 | 50 | //! Project version string for THLabel. 51 | FOUNDATION_EXPORT const unsigned char THLabelVersionString[]; 52 | 53 | // In this header, you should import all the public headers of your framework using statements like #import 54 | 55 | 56 | typedef NS_ENUM(NSInteger, THLabelStrokePosition) { 57 | THLabelStrokePositionOutside, 58 | THLabelStrokePositionCenter, 59 | THLabelStrokePositionInside 60 | }; 61 | 62 | typedef NS_OPTIONS(NSUInteger, THLabelFadeTruncatingMode) { 63 | THLabelFadeTruncatingModeNone = 0, 64 | THLabelFadeTruncatingModeTail = 1 << 0, 65 | THLabelFadeTruncatingModeHead = 1 << 1, 66 | THLabelFadeTruncatingModeHeadAndTail = THLabelFadeTruncatingModeHead | THLabelFadeTruncatingModeTail 67 | }; 68 | 69 | @interface THLabel : UILabel 70 | 71 | @property (nonatomic, assign) CGFloat letterSpacing; 72 | @property (nonatomic, assign) CGFloat lineSpacing; 73 | 74 | @property (nonatomic, assign) CGFloat shadowBlur; 75 | 76 | @property (nonatomic, assign) CGFloat innerShadowBlur; 77 | @property (nonatomic, assign) CGSize innerShadowOffset; 78 | @property (nonatomic, strong) UIColor *innerShadowColor; 79 | 80 | @property (nonatomic, assign) CGFloat strokeSize; 81 | @property (nonatomic, strong) UIColor *strokeColor; 82 | @property (nonatomic, assign) THLabelStrokePosition strokePosition; 83 | 84 | @property (nonatomic, strong) UIColor *gradientStartColor; 85 | @property (nonatomic, strong) UIColor *gradientEndColor; 86 | @property (nonatomic, copy) NSArray *gradientColors; 87 | @property (nonatomic, assign) CGPoint gradientStartPoint; 88 | @property (nonatomic, assign) CGPoint gradientEndPoint; 89 | 90 | @property (nonatomic, assign) THLabelFadeTruncatingMode fadeTruncatingMode; 91 | 92 | @property (nonatomic, assign) UIEdgeInsets textInsets; 93 | @property (nonatomic, assign) BOOL automaticallyAdjustTextInsets; 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /THLabel/THLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // THLabel.m 3 | // 4 | // Version 1.4.10 5 | // 6 | // Created by Tobias Hagemann on 11/25/12. 7 | // Copyright (c) 2012-2020 tobiha.de. All rights reserved. 8 | // 9 | // Original source and inspiration from: 10 | // FXLabel by Nick Lockwood, 11 | // https://github.com/nicklockwood/FXLabel 12 | // KSLabel by Kai Schweiger, 13 | // https://github.com/vigorouscoding/KSLabel 14 | // GTMFadeTruncatingLabel by Google, 15 | // https://github.com/google/google-toolbox-for-mac/tree/master/iPhone 16 | // 17 | // Big thanks to Jason Miller for showing me sample code of his implementation 18 | // using Core Text! It inspired me to dig deeper and move away from drawing 19 | // with NSAttributedString on iOS 7, which caused a lot of problems. 20 | // 21 | // Distributed under the permissive zlib license 22 | // Get the latest version from here: 23 | // 24 | // https://github.com/tobihagemann/THLabel 25 | // 26 | // This software is provided 'as-is', without any express or implied 27 | // warranty. In no event will the authors be held liable for any damages 28 | // arising from the use of this software. 29 | // 30 | // Permission is granted to anyone to use this software for any purpose, 31 | // including commercial applications, and to alter it and redistribute it 32 | // freely, subject to the following restrictions: 33 | // 34 | // 1. The origin of this software must not be misrepresented; you must not 35 | // claim that you wrote the original software. If you use this software 36 | // in a product, an acknowledgment in the product documentation would be 37 | // appreciated but is not required. 38 | // 39 | // 2. Altered source versions must be plainly marked as such, and must not be 40 | // misrepresented as being the original software. 41 | // 42 | // 3. This notice may not be removed or altered from any source distribution. 43 | // 44 | 45 | 46 | #import 47 | #if !__has_feature(objc_arc) 48 | #error This class requires automatic reference counting. 49 | #endif 50 | 51 | 52 | #import 53 | 54 | #import "THLabel.h" 55 | 56 | @implementation THLabel 57 | 58 | - (instancetype)initWithFrame:(CGRect)frame { 59 | if (self = [super initWithFrame:frame]) { 60 | self.backgroundColor = [UIColor clearColor]; 61 | [self setDefaults]; 62 | } 63 | return self; 64 | } 65 | 66 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 67 | if (self = [super initWithCoder:aDecoder]) { 68 | [self setDefaults]; 69 | } 70 | return self; 71 | } 72 | 73 | - (void)setDefaults { 74 | self.clipsToBounds = YES; 75 | self.letterSpacing = 0.0; 76 | self.gradientStartPoint = CGPointMake(0.5, 0.2); 77 | self.gradientEndPoint = CGPointMake(0.5, 0.8); 78 | self.automaticallyAdjustTextInsets = YES; 79 | } 80 | 81 | - (BOOL)hasShadow { 82 | return self.shadowColor && ![self.shadowColor isEqual:[UIColor clearColor]] && (self.shadowBlur > 0.0 || !CGSizeEqualToSize(self.shadowOffset, CGSizeZero)); 83 | } 84 | 85 | - (BOOL)hasInnerShadow { 86 | return self.innerShadowColor && ![self.innerShadowColor isEqual:[UIColor clearColor]] && (self.innerShadowBlur > 0.0 || !CGSizeEqualToSize(self.innerShadowOffset, CGSizeZero)); 87 | } 88 | 89 | - (BOOL)hasStroke { 90 | return self.strokeSize > 0.0 && ![self.strokeColor isEqual:[UIColor clearColor]]; 91 | } 92 | 93 | - (BOOL)hasGradient { 94 | return self.gradientColors.count > 1; 95 | } 96 | 97 | - (BOOL)hasFadeTruncating { 98 | return self.fadeTruncatingMode != THLabelFadeTruncatingModeNone; 99 | } 100 | 101 | - (CGSize)sizeThatFits:(CGSize)size { 102 | return [self intrinsicContentSize]; 103 | } 104 | 105 | - (CGSize)intrinsicContentSize { 106 | if (!self.text || [self.text isEqualToString:@""]) { 107 | return CGSizeZero; 108 | } 109 | 110 | CGRect textRect; 111 | CTFrameRef frameRef = [self frameRefFromSize:CGSizeMake(self.preferredMaxLayoutWidth, CGFLOAT_MAX) textRectOutput:&textRect]; 112 | CFRelease(frameRef); 113 | 114 | return CGSizeMake(ceilf(CGRectGetWidth(textRect) + self.textInsets.left + self.textInsets.right), 115 | ceilf(CGRectGetHeight(textRect) + self.textInsets.top + self.textInsets.bottom)); 116 | } 117 | 118 | #pragma mark - Accessors and Mutators 119 | 120 | - (void)setShadowBlur:(CGFloat)shadowBlur { 121 | _shadowBlur = fmaxf(shadowBlur, 0.0); 122 | } 123 | 124 | - (UIColor *)gradientStartColor { 125 | return self.gradientColors.count ? self.gradientColors.firstObject : nil; 126 | } 127 | 128 | - (void)setGradientStartColor:(UIColor *)gradientStartColor { 129 | if (gradientStartColor == nil) { 130 | self.gradientColors = nil; 131 | } else if (self.gradientColors.count < 2) { 132 | self.gradientColors = @[gradientStartColor, gradientStartColor]; 133 | } else if (![self.gradientColors.firstObject isEqual:gradientStartColor]) { 134 | NSMutableArray *colors = [self.gradientColors mutableCopy]; 135 | colors[0] = gradientStartColor; 136 | self.gradientColors = colors; 137 | } 138 | } 139 | 140 | - (UIColor *)gradientEndColor { 141 | return self.gradientColors.lastObject; 142 | } 143 | 144 | - (void)setGradientEndColor:(UIColor *)gradientEndColor { 145 | if (gradientEndColor == nil) { 146 | self.gradientColors = nil; 147 | } else if (self.gradientColors.count < 2) { 148 | self.gradientColors = @[gradientEndColor, gradientEndColor]; 149 | } else if (![self.gradientColors.lastObject isEqual:gradientEndColor]) { 150 | NSMutableArray *colors = [self.gradientColors mutableCopy]; 151 | colors[colors.count - 1] = gradientEndColor; 152 | self.gradientColors = colors; 153 | } 154 | } 155 | 156 | - (void)setGradientColors:(NSArray *)gradientColors { 157 | if (self.gradientColors != gradientColors) { 158 | _gradientColors = [gradientColors copy]; 159 | [self setNeedsDisplay]; 160 | } 161 | } 162 | 163 | - (void)setTextInsets:(UIEdgeInsets)textInsets { 164 | if (!UIEdgeInsetsEqualToEdgeInsets(self.textInsets, textInsets)) { 165 | _textInsets = textInsets; 166 | [self setNeedsDisplay]; 167 | } 168 | } 169 | 170 | - (CGFloat)strokeSizeDependentOnStrokePosition { 171 | switch (self.strokePosition) { 172 | case THLabelStrokePositionCenter: 173 | return self.strokeSize; 174 | 175 | default: 176 | // Stroke width times 2, because CG draws a centered stroke. We cut the rest into halves. 177 | return self.strokeSize * 2.0; 178 | } 179 | } 180 | 181 | #pragma mark - Drawing 182 | 183 | - (void)drawRect:(CGRect)rect { 184 | // Don't draw anything, if there is no text. 185 | if (!self.text || [self.text isEqualToString:@""]) { 186 | return; 187 | } 188 | 189 | // ------- 190 | // Determine what has to be drawn. 191 | // ------- 192 | 193 | BOOL hasShadow = [self hasShadow]; 194 | BOOL hasInnerShadow = [self hasInnerShadow]; 195 | BOOL hasStroke = [self hasStroke]; 196 | BOOL hasGradient = [self hasGradient]; 197 | BOOL hasFadeTruncating = [self hasFadeTruncating]; 198 | BOOL needsMask = hasGradient || (hasStroke && self.strokePosition == THLabelStrokePositionInside) || hasInnerShadow; 199 | 200 | // ------- 201 | // Step 1: Begin new drawing context, where we will apply all our styles. 202 | // ------- 203 | 204 | UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0); 205 | CGContextRef context = UIGraphicsGetCurrentContext(); 206 | CGImageRef alphaMask = NULL; 207 | CGRect textRect; 208 | CTFrameRef frameRef = [self frameRefFromSize:self.bounds.size textRectOutput:&textRect]; 209 | 210 | // Invert everything, because CG works with an inverted coordinate system. 211 | CGContextTranslateCTM(context, 0.0, CGRectGetHeight(rect)); 212 | CGContextScaleCTM(context, 1.0, -1.0); 213 | 214 | // ------- 215 | // Step 2: Prepare mask. 216 | // ------- 217 | 218 | if (needsMask) { 219 | CGContextSaveGState(context); 220 | 221 | // Draw alpha mask. 222 | if (hasStroke) { 223 | // Text needs invisible stroke for consistent character glyph widths. 224 | CGContextSetTextDrawingMode(context, kCGTextFillStroke); 225 | CGContextSetLineWidth(context, [self strokeSizeDependentOnStrokePosition]); 226 | CGContextSetLineJoin(context, kCGLineJoinRound); 227 | [[UIColor clearColor] setStroke]; 228 | } else { 229 | CGContextSetTextDrawingMode(context, kCGTextFill); 230 | } 231 | 232 | [[UIColor whiteColor] setFill]; 233 | CTFrameDraw(frameRef, context); 234 | 235 | // Save alpha mask. 236 | alphaMask = CGBitmapContextCreateImage(context); 237 | 238 | // Clear the content. 239 | CGContextClearRect(context, rect); 240 | 241 | CGContextRestoreGState(context); 242 | } 243 | 244 | // ------- 245 | // Step 3: Draw text normally, or with gradient. 246 | // ------- 247 | 248 | CGContextSaveGState(context); 249 | 250 | if (!hasGradient) { 251 | // Draw text. 252 | if (hasStroke) { 253 | // Text needs invisible stroke for consistent character glyph widths. 254 | CGContextSetTextDrawingMode(context, kCGTextFillStroke); 255 | CGContextSetLineWidth(context, [self strokeSizeDependentOnStrokePosition]); 256 | CGContextSetLineJoin(context, kCGLineJoinRound); 257 | [[UIColor clearColor] setStroke]; 258 | } else { 259 | CGContextSetTextDrawingMode(context, kCGTextFill); 260 | } 261 | 262 | CTFrameDraw(frameRef, context); 263 | } else { 264 | // Clip the current context to alpha mask. 265 | CGContextClipToMask(context, rect, alphaMask); 266 | 267 | // Invert back to draw the gradient correctly. 268 | CGContextTranslateCTM(context, 0.0, CGRectGetHeight(rect)); 269 | CGContextScaleCTM(context, 1.0, -1.0); 270 | 271 | // Get gradient colors as CGColor. 272 | NSMutableArray *gradientColors = [NSMutableArray arrayWithCapacity:self.gradientColors.count]; 273 | for (UIColor *color in self.gradientColors) { 274 | [gradientColors addObject:(__bridge id)color.CGColor]; 275 | } 276 | 277 | // Create gradient. 278 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 279 | CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)gradientColors, NULL); 280 | CGPoint startPoint = CGPointMake(textRect.origin.x + self.gradientStartPoint.x * CGRectGetWidth(textRect), 281 | textRect.origin.y + self.gradientStartPoint.y * CGRectGetHeight(textRect)); 282 | CGPoint endPoint = CGPointMake(textRect.origin.x + self.gradientEndPoint.x * CGRectGetWidth(textRect), 283 | textRect.origin.y + self.gradientEndPoint.y * CGRectGetHeight(textRect)); 284 | 285 | // Draw gradient. 286 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); 287 | 288 | // Clean up. 289 | CGColorSpaceRelease(colorSpace); 290 | CGGradientRelease(gradient); 291 | } 292 | 293 | CGContextRestoreGState(context); 294 | 295 | // ------- 296 | // Step 4: Draw inner shadow. 297 | // ------- 298 | 299 | if (hasInnerShadow) { 300 | CGContextSaveGState(context); 301 | 302 | // Clip the current context to alpha mask. 303 | CGContextClipToMask(context, rect, alphaMask); 304 | 305 | // Invert to draw the inner shadow correctly. 306 | CGContextTranslateCTM(context, 0.0, CGRectGetHeight(rect)); 307 | CGContextScaleCTM(context, 1.0, -1.0); 308 | 309 | // Draw inner shadow. 310 | CGImageRef shadowImage = [self inverseMaskFromAlphaMask:alphaMask withRect:rect]; 311 | CGContextSetShadowWithColor(context, self.innerShadowOffset, self.innerShadowBlur, self.innerShadowColor.CGColor); 312 | CGContextSetBlendMode(context, kCGBlendModeDarken); 313 | CGContextDrawImage(context, rect, shadowImage); 314 | 315 | // Clean up. 316 | CGImageRelease(shadowImage); 317 | 318 | CGContextRestoreGState(context); 319 | } 320 | 321 | // ------- 322 | // Step 5: Draw stroke. 323 | // ------- 324 | 325 | if (hasStroke) { 326 | CGContextSaveGState(context); 327 | 328 | CGContextSetTextDrawingMode(context, kCGTextStroke); 329 | 330 | CGImageRef image = NULL; 331 | 332 | if (self.strokePosition == THLabelStrokePositionOutside) { 333 | // Create an image from the text. 334 | image = CGBitmapContextCreateImage(context); 335 | } else if (self.strokePosition == THLabelStrokePositionInside) { 336 | // Clip the current context to alpha mask. 337 | CGContextClipToMask(context, rect, alphaMask); 338 | } 339 | 340 | // Draw stroke. 341 | CGImageRef strokeImage = [self strokeImageWithRect:rect frameRef:frameRef strokeSize:[self strokeSizeDependentOnStrokePosition] strokeColor:self.strokeColor]; 342 | CGContextDrawImage(context, rect, strokeImage); 343 | 344 | if (self.strokePosition == THLabelStrokePositionOutside) { 345 | // Draw the saved image over half of the stroke. 346 | CGContextDrawImage(context, rect, image); 347 | } 348 | 349 | // Clean up. 350 | CGImageRelease(strokeImage); 351 | CGImageRelease(image); 352 | 353 | CGContextRestoreGState(context); 354 | } 355 | 356 | // ------- 357 | // Step 6: Draw shadow. 358 | // ------- 359 | 360 | if (hasShadow) { 361 | CGContextSaveGState(context); 362 | 363 | // Create an image from the text. 364 | CGImageRef image = CGBitmapContextCreateImage(context); 365 | 366 | // Clear the content. 367 | CGContextClearRect(context, rect); 368 | 369 | // Set shadow attributes. 370 | CGContextSetShadowWithColor(context, self.shadowOffset, self.shadowBlur, self.shadowColor.CGColor); 371 | 372 | // Draw the saved image, which throws off a shadow. 373 | CGContextDrawImage(context, rect, image); 374 | 375 | // Clean up. 376 | CGImageRelease(image); 377 | 378 | CGContextRestoreGState(context); 379 | } 380 | 381 | // ------- 382 | // Step 7: Draw fade truncating. 383 | // ------- 384 | 385 | if (hasFadeTruncating) { 386 | CGContextSaveGState(context); 387 | 388 | // Create an image from the text. 389 | CGImageRef image = CGBitmapContextCreateImage(context); 390 | 391 | // Clear the content. 392 | CGContextClearRect(context, rect); 393 | 394 | // Clip the current context to linear gradient mask. 395 | CGImageRef linearGradientImage = [self linearGradientImageWithRect:rect fadeHead:self.fadeTruncatingMode & THLabelFadeTruncatingModeHead fadeTail:self.fadeTruncatingMode & THLabelFadeTruncatingModeTail]; 396 | CGContextClipToMask(context, self.bounds, linearGradientImage); 397 | 398 | // Draw the saved image, which is clipped by the linear gradient mask. 399 | CGContextDrawImage(context, rect, image); 400 | 401 | // Clean up. 402 | CGImageRelease(linearGradientImage); 403 | CGImageRelease(image); 404 | 405 | CGContextRestoreGState(context); 406 | } 407 | 408 | // ------- 409 | // Step 8: End drawing context and finally draw the text with all styles. 410 | // ------- 411 | 412 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 413 | UIGraphicsEndImageContext(); 414 | [image drawInRect:rect]; 415 | 416 | // ------- 417 | // Clean up. 418 | // ------- 419 | 420 | if (needsMask) { 421 | CGImageRelease(alphaMask); 422 | } 423 | 424 | CFRelease(frameRef); 425 | } 426 | 427 | - (CTFrameRef)frameRefFromSize:(CGSize)size textRectOutput:(CGRect *)textRectOutput CF_RETURNS_RETAINED { 428 | // Set up font. 429 | CTFontRef fontRef = CTFontCreateWithFontDescriptor((__bridge CTFontDescriptorRef)self.font.fontDescriptor, self.font.pointSize, NULL); 430 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_6_0 431 | CTTextAlignment alignment = NSTextAlignmentToCTTextAlignment(self.textAlignment); 432 | #else 433 | CTTextAlignment alignment = NSTextAlignmentToCTTextAlignment ? NSTextAlignmentToCTTextAlignment(self.textAlignment) : [self CTTextAlignmentFromNSTextAlignment:self.textAlignment]; 434 | #endif 435 | CTLineBreakMode lineBreakMode = (CTLineBreakMode)self.lineBreakMode; 436 | CGFloat lineSpacing = self.lineSpacing; 437 | CTParagraphStyleSetting paragraphStyleSettings[] = { 438 | {kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment}, 439 | {kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode}, 440 | {kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing} 441 | }; 442 | CTParagraphStyleRef paragraphStyleRef = CTParagraphStyleCreate(paragraphStyleSettings, 3); 443 | CFNumberRef kernRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &_letterSpacing); 444 | 445 | // Set up attributed string. 446 | CFStringRef keys[] = {kCTFontAttributeName, kCTParagraphStyleAttributeName, kCTForegroundColorAttributeName, kCTKernAttributeName}; 447 | CFTypeRef values[] = {fontRef, paragraphStyleRef, self.textColor.CGColor, kernRef}; 448 | CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys, (const void **)&values, sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 449 | CFRelease(fontRef); 450 | CFRelease(paragraphStyleRef); 451 | CFRelease(kernRef); 452 | 453 | CFStringRef stringRef = (CFStringRef)CFBridgingRetain(self.text); 454 | CFAttributedStringRef attributedStringRef = CFAttributedStringCreate(kCFAllocatorDefault, stringRef, attributes); 455 | CFRelease(stringRef); 456 | CFRelease(attributes); 457 | 458 | // Set up frame. 459 | CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString(attributedStringRef); 460 | CFRelease(attributedStringRef); 461 | 462 | if (self.automaticallyAdjustTextInsets) { 463 | self.textInsets = [self fittingTextInsets]; 464 | } 465 | CGRect contentRect = [self contentRectFromSize:size withInsets:self.textInsets]; 466 | CGRect textRect = [self textRectFromContentRect:contentRect framesetterRef:framesetterRef]; 467 | if (textRectOutput) { 468 | *textRectOutput = textRect; 469 | } 470 | CGMutablePathRef pathRef = CGPathCreateMutable(); 471 | CGPathAddRect(pathRef, NULL, textRect); 472 | 473 | CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, self.text.length), pathRef, NULL); 474 | CFRelease(framesetterRef); 475 | CGPathRelease(pathRef); 476 | return frameRef; 477 | } 478 | 479 | // Workaround for < iOS 6. 480 | - (CTTextAlignment)CTTextAlignmentFromNSTextAlignment:(NSTextAlignment)nsTextAlignment { 481 | switch (nsTextAlignment) { 482 | case NSTextAlignmentLeft: 483 | return kCTTextAlignmentLeft; 484 | case NSTextAlignmentCenter: 485 | return kCTTextAlignmentCenter; 486 | case NSTextAlignmentRight: 487 | return kCTTextAlignmentRight; 488 | case NSTextAlignmentJustified: 489 | return kCTTextAlignmentJustified; 490 | case NSTextAlignmentNatural: 491 | return kCTTextAlignmentNatural; 492 | default: 493 | return 0; 494 | } 495 | } 496 | 497 | - (CGRect)contentRectFromSize:(CGSize)size withInsets:(UIEdgeInsets)insets { 498 | CGRect contentRect = CGRectMake(0.0, 0.0, size.width, size.height); 499 | 500 | // Apply insets. 501 | contentRect.origin.x += insets.left; 502 | contentRect.origin.y += insets.top; 503 | contentRect.size.width -= insets.left + insets.right; 504 | contentRect.size.height -= insets.top + insets.bottom; 505 | 506 | return contentRect; 507 | } 508 | 509 | - (CGRect)textRectFromContentRect:(CGRect)contentRect framesetterRef:(CTFramesetterRef)framesetterRef { 510 | CGSize suggestedTextRectSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetterRef, CFRangeMake(0, self.text.length), NULL, contentRect.size, NULL); 511 | if (CGSizeEqualToSize(suggestedTextRectSize, CGSizeZero)) { 512 | suggestedTextRectSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetterRef, CFRangeMake(0, self.text.length), NULL, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), NULL); 513 | } 514 | CGRect textRect = CGRectMake(0.0, 0.0, ceilf(suggestedTextRectSize.width), ceilf(suggestedTextRectSize.height)); 515 | 516 | // Horizontal alignment. 517 | switch (self.textAlignment) { 518 | case NSTextAlignmentCenter: 519 | textRect.origin.x = floorf(CGRectGetMinX(contentRect) + (CGRectGetWidth(contentRect) - CGRectGetWidth(textRect)) / 2.0); 520 | break; 521 | 522 | case NSTextAlignmentRight: 523 | textRect.origin.x = floorf(CGRectGetMinX(contentRect) + CGRectGetWidth(contentRect) - CGRectGetWidth(textRect)); 524 | break; 525 | 526 | case NSTextAlignmentNatural: 527 | if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft) { 528 | textRect.origin.x = floorf(CGRectGetMinX(contentRect) + CGRectGetWidth(contentRect) - CGRectGetWidth(textRect)); 529 | } else { 530 | textRect.origin.x = floorf(CGRectGetMinX(contentRect)); 531 | } 532 | break; 533 | 534 | default: 535 | textRect.origin.x = floorf(CGRectGetMinX(contentRect)); 536 | break; 537 | } 538 | 539 | // Vertical alignment. Top and bottom are upside down, because of inverted drawing. 540 | switch (self.contentMode) { 541 | case UIViewContentModeTop: 542 | case UIViewContentModeTopLeft: 543 | case UIViewContentModeTopRight: 544 | textRect.origin.y = floorf(CGRectGetMinY(contentRect) + CGRectGetHeight(contentRect) - CGRectGetHeight(textRect)); 545 | break; 546 | 547 | case UIViewContentModeBottom: 548 | case UIViewContentModeBottomLeft: 549 | case UIViewContentModeBottomRight: 550 | textRect.origin.y = floorf(CGRectGetMinY(contentRect)); 551 | break; 552 | 553 | default: 554 | textRect.origin.y = floorf(CGRectGetMinY(contentRect) + floorf((CGRectGetHeight(contentRect) - CGRectGetHeight(textRect)) / 2.0)); 555 | break; 556 | } 557 | 558 | return textRect; 559 | } 560 | 561 | - (UIEdgeInsets)fittingTextInsets { 562 | BOOL hasShadow = [self hasShadow]; 563 | BOOL hasStroke = [self hasStroke]; 564 | UIEdgeInsets edgeInsets = UIEdgeInsetsZero; 565 | 566 | if (hasStroke) { 567 | switch (self.strokePosition) { 568 | case THLabelStrokePositionOutside: 569 | edgeInsets = UIEdgeInsetsMake(self.strokeSize, self.strokeSize, self.strokeSize, self.strokeSize); 570 | break; 571 | 572 | case THLabelStrokePositionCenter: 573 | edgeInsets = UIEdgeInsetsMake(self.strokeSize / 2.0, self.strokeSize / 2.0, self.strokeSize / 2.0, self.strokeSize / 2.0); 574 | break; 575 | 576 | default: 577 | break; 578 | } 579 | } 580 | 581 | if (hasShadow) { 582 | edgeInsets.top = fmaxf(edgeInsets.top + self.shadowBlur + self.shadowOffset.height, edgeInsets.top); 583 | edgeInsets.left = fmaxf(edgeInsets.left + self.shadowBlur + self.shadowOffset.width, edgeInsets.left); 584 | edgeInsets.bottom = fmaxf(edgeInsets.bottom + self.shadowBlur - self.shadowOffset.height, edgeInsets.bottom); 585 | edgeInsets.right = fmaxf(edgeInsets.right + self.shadowBlur - self.shadowOffset.width, edgeInsets.right); 586 | } 587 | 588 | return edgeInsets; 589 | } 590 | 591 | #pragma mark - Image Functions 592 | 593 | - (CGImageRef)inverseMaskFromAlphaMask:(CGImageRef)alphaMask withRect:(CGRect)rect CF_RETURNS_RETAINED { 594 | // Create context. 595 | UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0); 596 | CGContextRef context = UIGraphicsGetCurrentContext(); 597 | 598 | // Fill rect, clip to alpha mask and clear. 599 | [[UIColor whiteColor] setFill]; 600 | UIRectFill(rect); 601 | CGContextClipToMask(context, rect, alphaMask); 602 | CGContextClearRect(context, rect); 603 | 604 | // Return image. 605 | CGImageRef image = CGBitmapContextCreateImage(context); 606 | UIGraphicsEndImageContext(); 607 | return image; 608 | } 609 | 610 | - (CGImageRef)strokeImageWithRect:(CGRect)rect frameRef:(CTFrameRef)frameRef strokeSize:(CGFloat)strokeSize strokeColor:(UIColor *)strokeColor CF_RETURNS_RETAINED { 611 | // Create context. 612 | UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0); 613 | CGContextRef context = UIGraphicsGetCurrentContext(); 614 | 615 | CGContextSetTextDrawingMode(context, kCGTextStroke); 616 | 617 | // Draw clipping mask. 618 | CGContextSetLineWidth(context, strokeSize); 619 | CGContextSetLineJoin(context, kCGLineJoinRound); 620 | [[UIColor whiteColor] setStroke]; 621 | CTFrameDraw(frameRef, context); 622 | 623 | // Save clipping mask. 624 | CGImageRef clippingMask = CGBitmapContextCreateImage(context); 625 | 626 | // Clear the content. 627 | CGContextClearRect(context, rect); 628 | 629 | // Draw stroke. 630 | CGContextClipToMask(context, rect, clippingMask); 631 | CGContextTranslateCTM(context, 0.0, CGRectGetHeight(rect)); 632 | CGContextScaleCTM(context, 1.0, -1.0); 633 | [strokeColor setFill]; 634 | UIRectFill(rect); 635 | 636 | // Clean up and return image. 637 | CGImageRelease(clippingMask); 638 | CGImageRef image = CGBitmapContextCreateImage(context); 639 | UIGraphicsEndImageContext(); 640 | return image; 641 | } 642 | 643 | - (CGImageRef)linearGradientImageWithRect:(CGRect)rect fadeHead:(BOOL)fadeHead fadeTail:(BOOL)fadeTail CF_RETURNS_RETAINED { 644 | // Create an opaque context. 645 | UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0.0); 646 | CGContextRef context = UIGraphicsGetCurrentContext(); 647 | 648 | // White background will mask opaque, black gradient will mask transparent. 649 | [[UIColor whiteColor] setFill]; 650 | UIRectFill(rect); 651 | 652 | // Create gradient from white to black. 653 | CGFloat locs[2] = {0.0, 1.0}; 654 | CGFloat components[4] = {1.0, 1.0, 0.0, 1.0}; 655 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); 656 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locs, 2); 657 | 658 | // Draw head and/or tail gradient. 659 | CGFloat fadeWidth = fminf(CGRectGetHeight(rect) * 2.0, floorf(CGRectGetWidth(rect) / 4.0)); 660 | CGFloat minX = CGRectGetMinX(rect); 661 | CGFloat maxX = CGRectGetMaxX(rect); 662 | if (fadeTail) { 663 | CGFloat startX = maxX - fadeWidth; 664 | CGPoint startPoint = CGPointMake(startX, CGRectGetMidY(rect)); 665 | CGPoint endPoint = CGPointMake(maxX, CGRectGetMidY(rect)); 666 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0); 667 | } 668 | if (fadeHead) { 669 | CGFloat startX = minX + fadeWidth; 670 | CGPoint startPoint = CGPointMake(startX, CGRectGetMidY(rect)); 671 | CGPoint endPoint = CGPointMake(minX, CGRectGetMidY(rect)); 672 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0); 673 | } 674 | 675 | // Clean up and return image. 676 | CGColorSpaceRelease(colorSpace); 677 | CGGradientRelease(gradient); 678 | CGImageRef image = CGBitmapContextCreateImage(context); 679 | UIGraphicsEndImageContext(); 680 | return image; 681 | } 682 | 683 | @end 684 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobihagemann/THLabel/1541d0fd4c36890d3395d4ae3ab7910fc4727e94/screenshot.png --------------------------------------------------------------------------------