├── .gitignore ├── .slather.yml ├── .travis.yml ├── LICENSE ├── NSAttributedString+CCLFormat.podspec ├── NSAttributedStringFormat.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── NSAttributedStringFormat.xcscheme ├── NSAttributedStringFormat ├── Info.plist ├── NSAttributedString+CCLFormat.h ├── NSAttributedString+CCLFormat.m └── NSAttributedStringFormat.h ├── NSAttributedStringFormatTests ├── Info.plist ├── NSAttributedString+CCLFormatTests.m └── NSAttributedStringFormatTests.m ├── README.md └── xcconfigs ├── LICENSE ├── README.md ├── UniversalFramework_Base.xcconfig ├── UniversalFramework_Framework.xcconfig └── UniversalFramework_Test.xcconfig /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | Podfile.lock 3 | NSAttributedString+CCLFormat.xcworkspace/ 4 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | coverage_service: coveralls 2 | xcodeproj: Tests/NSAttributedString+CCLFormat.xcodeproj 3 | source_directory: ./ 4 | ignore: 5 | - Tests/* 6 | - "*/Pods/*" 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode7 3 | script: 4 | - set -o pipefail && xcodebuild -project NSAttributedStringFormat.xcodeproj -scheme NSAttributedStringFormat test -sdk macosx | xcpretty -c 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Cocode LTD. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /NSAttributedString+CCLFormat.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'NSAttributedString+CCLFormat' 3 | spec.version = '1.2.0' 4 | spec.license = 'BSD' 5 | spec.summary = 'Attributed string extension for creating attributed strings from a format string.' 6 | spec.description = 'Attributed string extension for creating attributed strings by using a given format as a template into which the remaining argument values are substitued.' 7 | spec.homepage = 'https://github.com/cocodelabs/NSAttributedString-CCLFormat' 8 | spec.authors = { 'Kyle Fuller' => 'kyle@fuller.li' } 9 | spec.social_media_url = 'https://twitter.com/kylefuller' 10 | spec.source = { :git => 'https://github.com/cocodelabs/NSAttributedString-CCLFormat.git', :tag => spec.version.to_s } 11 | 12 | spec.requires_arc = true 13 | 14 | spec.osx.deployment_target = '10.6' 15 | spec.ios.deployment_target = '4.3' 16 | spec.watchos.deployment_target = '2.0' 17 | spec.tvos.deployment_target = '9.0' 18 | 19 | spec.source_files = 'NSAttributedStringFormat/NSAttributedString+CCLFormat.{h,m}' 20 | end 21 | 22 | -------------------------------------------------------------------------------- /NSAttributedStringFormat.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 279E6DC21BB8855B0043DDC9 /* NSAttributedStringFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 279E6DC11BB8855B0043DDC9 /* NSAttributedStringFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 279E6DC91BB8855B0043DDC9 /* NSAttributedStringFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 279E6DBE1BB8855B0043DDC9 /* NSAttributedStringFormat.framework */; settings = {ASSET_TAGS = (); }; }; 12 | 279E6DD91BB8857A0043DDC9 /* NSAttributedString+CCLFormatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 279E6DD81BB8857A0043DDC9 /* NSAttributedString+CCLFormatTests.m */; settings = {ASSET_TAGS = (); }; }; 13 | 279E6DDB1BB885AF0043DDC9 /* NSAttributedString+CCLFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 279E6DDA1BB885AF0043DDC9 /* NSAttributedString+CCLFormat.m */; settings = {ASSET_TAGS = (); }; }; 14 | 279E6DDD1BB885BF0043DDC9 /* NSAttributedString+CCLFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 279E6DDC1BB885BF0043DDC9 /* NSAttributedString+CCLFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | 279E6DCA1BB8855B0043DDC9 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = 279E6DB51BB8855B0043DDC9 /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = 279E6DBD1BB8855B0043DDC9; 23 | remoteInfo = NSAttributedStringFormat; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 279E6DBE1BB8855B0043DDC9 /* NSAttributedStringFormat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NSAttributedStringFormat.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 279E6DC11BB8855B0043DDC9 /* NSAttributedStringFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSAttributedStringFormat.h; sourceTree = ""; }; 30 | 279E6DC31BB8855B0043DDC9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 279E6DC81BB8855B0043DDC9 /* NSAttributedStringFormatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NSAttributedStringFormatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 279E6DCF1BB8855B0043DDC9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 279E6DD81BB8857A0043DDC9 /* NSAttributedString+CCLFormatTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+CCLFormatTests.m"; sourceTree = ""; }; 34 | 279E6DDA1BB885AF0043DDC9 /* NSAttributedString+CCLFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+CCLFormat.m"; sourceTree = ""; }; 35 | 279E6DDC1BB885BF0043DDC9 /* NSAttributedString+CCLFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+CCLFormat.h"; sourceTree = ""; }; 36 | 279E6DE11BB8A4CB0043DDC9 /* UniversalFramework_Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UniversalFramework_Base.xcconfig; sourceTree = ""; }; 37 | 279E6DE21BB8A4CB0043DDC9 /* UniversalFramework_Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UniversalFramework_Framework.xcconfig; sourceTree = ""; }; 38 | 279E6DE31BB8A4CB0043DDC9 /* UniversalFramework_Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UniversalFramework_Test.xcconfig; sourceTree = ""; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | 279E6DBA1BB8855B0043DDC9 /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | 279E6DC51BB8855B0043DDC9 /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | 279E6DC91BB8855B0043DDC9 /* NSAttributedStringFormat.framework in Frameworks */, 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | 279E6DB41BB8855B0043DDC9 = { 61 | isa = PBXGroup; 62 | children = ( 63 | 279E6DDE1BB8A4CB0043DDC9 /* xcconfigs */, 64 | 279E6DC01BB8855B0043DDC9 /* NSAttributedStringFormat */, 65 | 279E6DCC1BB8855B0043DDC9 /* NSAttributedStringFormatTests */, 66 | 279E6DBF1BB8855B0043DDC9 /* Products */, 67 | ); 68 | indentWidth = 4; 69 | sourceTree = ""; 70 | tabWidth = 4; 71 | }; 72 | 279E6DBF1BB8855B0043DDC9 /* Products */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 279E6DBE1BB8855B0043DDC9 /* NSAttributedStringFormat.framework */, 76 | 279E6DC81BB8855B0043DDC9 /* NSAttributedStringFormatTests.xctest */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | 279E6DC01BB8855B0043DDC9 /* NSAttributedStringFormat */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 279E6DC11BB8855B0043DDC9 /* NSAttributedStringFormat.h */, 85 | 279E6DDC1BB885BF0043DDC9 /* NSAttributedString+CCLFormat.h */, 86 | 279E6DDA1BB885AF0043DDC9 /* NSAttributedString+CCLFormat.m */, 87 | 279E6DC31BB8855B0043DDC9 /* Info.plist */, 88 | ); 89 | path = NSAttributedStringFormat; 90 | sourceTree = ""; 91 | }; 92 | 279E6DCC1BB8855B0043DDC9 /* NSAttributedStringFormatTests */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 279E6DD81BB8857A0043DDC9 /* NSAttributedString+CCLFormatTests.m */, 96 | 279E6DCF1BB8855B0043DDC9 /* Info.plist */, 97 | ); 98 | path = NSAttributedStringFormatTests; 99 | sourceTree = ""; 100 | }; 101 | 279E6DDE1BB8A4CB0043DDC9 /* xcconfigs */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 279E6DE11BB8A4CB0043DDC9 /* UniversalFramework_Base.xcconfig */, 105 | 279E6DE21BB8A4CB0043DDC9 /* UniversalFramework_Framework.xcconfig */, 106 | 279E6DE31BB8A4CB0043DDC9 /* UniversalFramework_Test.xcconfig */, 107 | ); 108 | path = xcconfigs; 109 | sourceTree = ""; 110 | }; 111 | /* End PBXGroup section */ 112 | 113 | /* Begin PBXHeadersBuildPhase section */ 114 | 279E6DBB1BB8855B0043DDC9 /* Headers */ = { 115 | isa = PBXHeadersBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | 279E6DDD1BB885BF0043DDC9 /* NSAttributedString+CCLFormat.h in Headers */, 119 | 279E6DC21BB8855B0043DDC9 /* NSAttributedStringFormat.h in Headers */, 120 | ); 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | /* End PBXHeadersBuildPhase section */ 124 | 125 | /* Begin PBXNativeTarget section */ 126 | 279E6DBD1BB8855B0043DDC9 /* NSAttributedStringFormat */ = { 127 | isa = PBXNativeTarget; 128 | buildConfigurationList = 279E6DD21BB8855B0043DDC9 /* Build configuration list for PBXNativeTarget "NSAttributedStringFormat" */; 129 | buildPhases = ( 130 | 279E6DB91BB8855B0043DDC9 /* Sources */, 131 | 279E6DBA1BB8855B0043DDC9 /* Frameworks */, 132 | 279E6DBB1BB8855B0043DDC9 /* Headers */, 133 | 279E6DBC1BB8855B0043DDC9 /* Resources */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = NSAttributedStringFormat; 140 | productName = NSAttributedStringFormat; 141 | productReference = 279E6DBE1BB8855B0043DDC9 /* NSAttributedStringFormat.framework */; 142 | productType = "com.apple.product-type.framework"; 143 | }; 144 | 279E6DC71BB8855B0043DDC9 /* NSAttributedStringFormatTests */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = 279E6DD51BB8855B0043DDC9 /* Build configuration list for PBXNativeTarget "NSAttributedStringFormatTests" */; 147 | buildPhases = ( 148 | 279E6DC41BB8855B0043DDC9 /* Sources */, 149 | 279E6DC51BB8855B0043DDC9 /* Frameworks */, 150 | 279E6DC61BB8855B0043DDC9 /* Resources */, 151 | ); 152 | buildRules = ( 153 | ); 154 | dependencies = ( 155 | 279E6DCB1BB8855B0043DDC9 /* PBXTargetDependency */, 156 | ); 157 | name = NSAttributedStringFormatTests; 158 | productName = NSAttributedStringFormatTests; 159 | productReference = 279E6DC81BB8855B0043DDC9 /* NSAttributedStringFormatTests.xctest */; 160 | productType = "com.apple.product-type.bundle.unit-test"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | 279E6DB51BB8855B0043DDC9 /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastUpgradeCheck = 0700; 169 | ORGANIZATIONNAME = Cocode; 170 | TargetAttributes = { 171 | 279E6DBD1BB8855B0043DDC9 = { 172 | CreatedOnToolsVersion = 7.0; 173 | }; 174 | 279E6DC71BB8855B0043DDC9 = { 175 | CreatedOnToolsVersion = 7.0; 176 | }; 177 | }; 178 | }; 179 | buildConfigurationList = 279E6DB81BB8855B0043DDC9 /* Build configuration list for PBXProject "NSAttributedStringFormat" */; 180 | compatibilityVersion = "Xcode 3.2"; 181 | developmentRegion = English; 182 | hasScannedForEncodings = 0; 183 | knownRegions = ( 184 | en, 185 | ); 186 | mainGroup = 279E6DB41BB8855B0043DDC9; 187 | productRefGroup = 279E6DBF1BB8855B0043DDC9 /* Products */; 188 | projectDirPath = ""; 189 | projectRoot = ""; 190 | targets = ( 191 | 279E6DBD1BB8855B0043DDC9 /* NSAttributedStringFormat */, 192 | 279E6DC71BB8855B0043DDC9 /* NSAttributedStringFormatTests */, 193 | ); 194 | }; 195 | /* End PBXProject section */ 196 | 197 | /* Begin PBXResourcesBuildPhase section */ 198 | 279E6DBC1BB8855B0043DDC9 /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | 279E6DC61BB8855B0043DDC9 /* Resources */ = { 206 | isa = PBXResourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXResourcesBuildPhase section */ 213 | 214 | /* Begin PBXSourcesBuildPhase section */ 215 | 279E6DB91BB8855B0043DDC9 /* Sources */ = { 216 | isa = PBXSourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | 279E6DDB1BB885AF0043DDC9 /* NSAttributedString+CCLFormat.m in Sources */, 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | 279E6DC41BB8855B0043DDC9 /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 279E6DD91BB8857A0043DDC9 /* NSAttributedString+CCLFormatTests.m in Sources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXSourcesBuildPhase section */ 232 | 233 | /* Begin PBXTargetDependency section */ 234 | 279E6DCB1BB8855B0043DDC9 /* PBXTargetDependency */ = { 235 | isa = PBXTargetDependency; 236 | target = 279E6DBD1BB8855B0043DDC9 /* NSAttributedStringFormat */; 237 | targetProxy = 279E6DCA1BB8855B0043DDC9 /* PBXContainerItemProxy */; 238 | }; 239 | /* End PBXTargetDependency section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | 279E6DD01BB8855B0043DDC9 /* Debug */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_WARN_BOOL_CONVERSION = YES; 251 | CLANG_WARN_CONSTANT_CONVERSION = YES; 252 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 253 | CLANG_WARN_EMPTY_BODY = YES; 254 | CLANG_WARN_ENUM_CONVERSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_UNREACHABLE_CODE = YES; 258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 259 | COPY_PHASE_STRIP = NO; 260 | CURRENT_PROJECT_VERSION = 1; 261 | DEBUG_INFORMATION_FORMAT = dwarf; 262 | ENABLE_STRICT_OBJC_MSGSEND = YES; 263 | ENABLE_TESTABILITY = YES; 264 | GCC_C_LANGUAGE_STANDARD = gnu99; 265 | GCC_DYNAMIC_NO_PIC = NO; 266 | GCC_NO_COMMON_BLOCKS = YES; 267 | GCC_OPTIMIZATION_LEVEL = 0; 268 | GCC_PREPROCESSOR_DEFINITIONS = ( 269 | "DEBUG=1", 270 | "$(inherited)", 271 | ); 272 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 273 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 274 | GCC_WARN_UNDECLARED_SELECTOR = YES; 275 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 276 | GCC_WARN_UNUSED_FUNCTION = YES; 277 | GCC_WARN_UNUSED_VARIABLE = YES; 278 | MACOSX_DEPLOYMENT_TARGET = 10.10; 279 | MTL_ENABLE_DEBUG_INFO = YES; 280 | ONLY_ACTIVE_ARCH = YES; 281 | SDKROOT = macosx; 282 | VERSIONING_SYSTEM = "apple-generic"; 283 | VERSION_INFO_PREFIX = ""; 284 | }; 285 | name = Debug; 286 | }; 287 | 279E6DD11BB8855B0043DDC9 /* Release */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | ALWAYS_SEARCH_USER_PATHS = NO; 291 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 292 | CLANG_CXX_LIBRARY = "libc++"; 293 | CLANG_ENABLE_MODULES = YES; 294 | CLANG_ENABLE_OBJC_ARC = YES; 295 | CLANG_WARN_BOOL_CONVERSION = YES; 296 | CLANG_WARN_CONSTANT_CONVERSION = YES; 297 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 298 | CLANG_WARN_EMPTY_BODY = YES; 299 | CLANG_WARN_ENUM_CONVERSION = YES; 300 | CLANG_WARN_INT_CONVERSION = YES; 301 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 302 | CLANG_WARN_UNREACHABLE_CODE = YES; 303 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 304 | COPY_PHASE_STRIP = NO; 305 | CURRENT_PROJECT_VERSION = 1; 306 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 307 | ENABLE_NS_ASSERTIONS = NO; 308 | ENABLE_STRICT_OBJC_MSGSEND = YES; 309 | GCC_C_LANGUAGE_STANDARD = gnu99; 310 | GCC_NO_COMMON_BLOCKS = YES; 311 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 312 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 313 | GCC_WARN_UNDECLARED_SELECTOR = YES; 314 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 315 | GCC_WARN_UNUSED_FUNCTION = YES; 316 | GCC_WARN_UNUSED_VARIABLE = YES; 317 | MACOSX_DEPLOYMENT_TARGET = 10.10; 318 | MTL_ENABLE_DEBUG_INFO = NO; 319 | SDKROOT = macosx; 320 | VERSIONING_SYSTEM = "apple-generic"; 321 | VERSION_INFO_PREFIX = ""; 322 | }; 323 | name = Release; 324 | }; 325 | 279E6DD31BB8855B0043DDC9 /* Debug */ = { 326 | isa = XCBuildConfiguration; 327 | baseConfigurationReference = 279E6DE21BB8A4CB0043DDC9 /* UniversalFramework_Framework.xcconfig */; 328 | buildSettings = { 329 | COMBINE_HIDPI_IMAGES = YES; 330 | DEFINES_MODULE = YES; 331 | DYLIB_COMPATIBILITY_VERSION = 1; 332 | DYLIB_CURRENT_VERSION = 1; 333 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 334 | FRAMEWORK_VERSION = A; 335 | INFOPLIST_FILE = NSAttributedStringFormat/Info.plist; 336 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 337 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 338 | PRODUCT_BUNDLE_IDENTIFIER = org.cocode.NSAttributedStringFormat; 339 | PRODUCT_NAME = "$(TARGET_NAME)"; 340 | SKIP_INSTALL = YES; 341 | }; 342 | name = Debug; 343 | }; 344 | 279E6DD41BB8855B0043DDC9 /* Release */ = { 345 | isa = XCBuildConfiguration; 346 | baseConfigurationReference = 279E6DE21BB8A4CB0043DDC9 /* UniversalFramework_Framework.xcconfig */; 347 | buildSettings = { 348 | COMBINE_HIDPI_IMAGES = YES; 349 | DEFINES_MODULE = YES; 350 | DYLIB_COMPATIBILITY_VERSION = 1; 351 | DYLIB_CURRENT_VERSION = 1; 352 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 353 | FRAMEWORK_VERSION = A; 354 | INFOPLIST_FILE = NSAttributedStringFormat/Info.plist; 355 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 356 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 357 | PRODUCT_BUNDLE_IDENTIFIER = org.cocode.NSAttributedStringFormat; 358 | PRODUCT_NAME = "$(TARGET_NAME)"; 359 | SKIP_INSTALL = YES; 360 | }; 361 | name = Release; 362 | }; 363 | 279E6DD61BB8855B0043DDC9 /* Debug */ = { 364 | isa = XCBuildConfiguration; 365 | baseConfigurationReference = 279E6DE31BB8A4CB0043DDC9 /* UniversalFramework_Test.xcconfig */; 366 | buildSettings = { 367 | COMBINE_HIDPI_IMAGES = YES; 368 | INFOPLIST_FILE = NSAttributedStringFormatTests/Info.plist; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 370 | PRODUCT_BUNDLE_IDENTIFIER = org.cocode.NSAttributedStringFormatTests; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | }; 373 | name = Debug; 374 | }; 375 | 279E6DD71BB8855B0043DDC9 /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | baseConfigurationReference = 279E6DE31BB8A4CB0043DDC9 /* UniversalFramework_Test.xcconfig */; 378 | buildSettings = { 379 | COMBINE_HIDPI_IMAGES = YES; 380 | INFOPLIST_FILE = NSAttributedStringFormatTests/Info.plist; 381 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 382 | PRODUCT_BUNDLE_IDENTIFIER = org.cocode.NSAttributedStringFormatTests; 383 | PRODUCT_NAME = "$(TARGET_NAME)"; 384 | }; 385 | name = Release; 386 | }; 387 | /* End XCBuildConfiguration section */ 388 | 389 | /* Begin XCConfigurationList section */ 390 | 279E6DB81BB8855B0043DDC9 /* Build configuration list for PBXProject "NSAttributedStringFormat" */ = { 391 | isa = XCConfigurationList; 392 | buildConfigurations = ( 393 | 279E6DD01BB8855B0043DDC9 /* Debug */, 394 | 279E6DD11BB8855B0043DDC9 /* Release */, 395 | ); 396 | defaultConfigurationIsVisible = 0; 397 | defaultConfigurationName = Release; 398 | }; 399 | 279E6DD21BB8855B0043DDC9 /* Build configuration list for PBXNativeTarget "NSAttributedStringFormat" */ = { 400 | isa = XCConfigurationList; 401 | buildConfigurations = ( 402 | 279E6DD31BB8855B0043DDC9 /* Debug */, 403 | 279E6DD41BB8855B0043DDC9 /* Release */, 404 | ); 405 | defaultConfigurationIsVisible = 0; 406 | defaultConfigurationName = Release; 407 | }; 408 | 279E6DD51BB8855B0043DDC9 /* Build configuration list for PBXNativeTarget "NSAttributedStringFormatTests" */ = { 409 | isa = XCConfigurationList; 410 | buildConfigurations = ( 411 | 279E6DD61BB8855B0043DDC9 /* Debug */, 412 | 279E6DD71BB8855B0043DDC9 /* Release */, 413 | ); 414 | defaultConfigurationIsVisible = 0; 415 | defaultConfigurationName = Release; 416 | }; 417 | /* End XCConfigurationList section */ 418 | }; 419 | rootObject = 279E6DB51BB8855B0043DDC9 /* Project object */; 420 | } 421 | -------------------------------------------------------------------------------- /NSAttributedStringFormat.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NSAttributedStringFormat.xcodeproj/xcshareddata/xcschemes/NSAttributedStringFormat.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /NSAttributedStringFormat/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.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 Cocode. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /NSAttributedStringFormat/NSAttributedString+CCLFormat.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /** Attributed string extension for creating attributed strings from a format string. */ 4 | @interface NSAttributedString (CCLFormat) 5 | 6 | /** Returns an attributed string created by using a given format string as a template into which the remaining argument values are substituted. 7 | @param format A format string. This value must not be nil. 8 | @param ... A comma-separated list of arguments to substitute into format. 9 | @return An attributed string created by using format as a template into which the remaining argument values are substituted. 10 | */ 11 | + (instancetype)attributedStringWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2); 12 | 13 | /** Returns an attributed string created by using a given format string as a template into which the remaining argument values are substituted. 14 | @param attributes A dictionary of attributes for the format string. May be nil. 15 | @param format A format string. This value must not be nil. 16 | @param arguments list of arguments to substitute into format. 17 | @return An attributed string created by using format as a template into which the remaining argument values are substituted. 18 | */ 19 | + (instancetype)attributedStringWithAttributes:(NSDictionary *)attributes format:(NSString *)format, ... NS_FORMAT_FUNCTION(2,3); 20 | 21 | /** Returns an attributed string created by using a given format string as a template into which the remaining argument values are substituted. 22 | @param format A format string. This value must not be nil. 23 | @param arguments list of arguments to substitute into format. 24 | @return An attributed string created by using format as a template into which the remaining argument values are substituted. 25 | */ 26 | + (instancetype)attributedStringWithFormat:(NSString *)format arguments:(va_list)arguments NS_FORMAT_FUNCTION(1,0); 27 | 28 | /** Returns an attributed string created by using a given format string as a template into which the remaining argument values are substituted. 29 | @param attributes A dictionary of attributes for the format string. May be nil. 30 | @param format A format string. This value must not be nil. 31 | @param arguments list of arguments to substitute into format. 32 | @return An attributed string created by using format as a template into which the remaining argument values are substituted. 33 | */ 34 | + (instancetype)attributedStringWithAttributes:(NSDictionary *)attributes format:(NSString *)format arguments:(va_list)arguments NS_FORMAT_FUNCTION(2,0); 35 | 36 | /** Returns an attributed string created by using a given format string as a template into which the remaining argument values are substituted. 37 | @param format A format string. This value must not be nil. 38 | @param ... A comma-separated list of arguments to substitute into format. 39 | @return An attributed string created by using format as a template into which the remaining argument values are substituted. 40 | */ 41 | - (instancetype)initWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2); 42 | 43 | /** Returns an attributed string created by using a given format string as a template into which the remaining argument values are substituted. 44 | @param attributes A dictionary of attributes for the format string. May be nil. 45 | @param format A format string. This value must not be nil. 46 | @param ... A comma-separated list of arguments to substitute into format. 47 | @return An attributed string created by using format as a template into which the remaining argument values are substituted. 48 | */ 49 | - (instancetype)initWithAttributes:(NSDictionary *)attributes format:(NSString *)format, ... NS_FORMAT_FUNCTION(2,3); 50 | 51 | /** Returns an attributed string created by using a given format string as a template into which the remaining argument values are substituted. 52 | @param format A format string. This value must not be nil. 53 | @param arguments list of arguments to substitute into format. 54 | @return An attributed string created by using format as a template into which the remaining argument values are substituted. 55 | */ 56 | - (instancetype)initWithFormat:(NSString *)format arguments:(va_list)arguments NS_FORMAT_FUNCTION(1,0); 57 | 58 | /** Returns an attributed string created by using a given format string as a template into which the remaining argument values are substituted. 59 | @param attributes A dictionary of attributes for the format string. May be nil. 60 | @param format A format string. This value must not be nil. 61 | @param arguments list of arguments to substitute into format. 62 | @return An attributed string created by using format as a template into which the remaining argument values are substituted. 63 | */ 64 | - (instancetype)initWithAttributes:(NSDictionary *)attributes format:(NSString *)format arguments:(va_list)arguments NS_FORMAT_FUNCTION(2,0); 65 | 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /NSAttributedStringFormat/NSAttributedString+CCLFormat.m: -------------------------------------------------------------------------------- 1 | #import "NSAttributedString+CCLFormat.h" 2 | 3 | #pragma mark - Format String Parsing 4 | 5 | /// Parsing State 6 | typedef NS_ENUM(NSInteger, FormatStringState) { 7 | FormatStringStateUnknown, 8 | FormatStringStartFormat, 9 | FormatStringStatePositional, 10 | FormatStringStateEndPositional, 11 | }; 12 | 13 | /// Structure to contain parsed format positions and ranges 14 | @interface CCLFormatParseResult : NSObject 15 | 16 | /// The range of the string in the format, for example the range of (%@ or %2$@) 17 | @property (nonatomic, assign, readonly) NSRange range; 18 | 19 | /// The index of the replacement, this would be the position of %@ or the position from a positional string (1 for %2$@). 20 | @property (nonatomic, assign, readonly) NSUInteger index; 21 | 22 | @end 23 | 24 | @implementation CCLFormatParseResult 25 | 26 | - (instancetype)initWithRange:(NSRange)range index:(NSUInteger)index { 27 | if (self = [super init]) { 28 | _range = range; 29 | _index = index; 30 | } 31 | 32 | return self; 33 | } 34 | 35 | @end 36 | 37 | /// Parse replacements (%@ and positional %1$@) from a format string into CCLFormatParseResult instances 38 | NSArray *CCLFormatStringParser(NSString *format, NSUInteger *maxPosition) { 39 | NSMutableArray *arguments = [NSMutableArray array]; 40 | NSUInteger position = 0; // Position is incrememented each time an %@ is found 41 | *maxPosition = 0; // The maximum found format replacement 42 | NSUInteger start = NSNotFound; 43 | NSMutableString *positionString = nil; 44 | FormatStringState state = FormatStringStateUnknown; 45 | for (NSUInteger index = 0; index < format.length; ++index) { 46 | unichar character = [format characterAtIndex:index]; 47 | 48 | if (character == '%') { 49 | start = index; 50 | state = FormatStringStartFormat; 51 | } else if (state != FormatStringStateUnknown) { 52 | if (character == '@') { 53 | NSUInteger currentPosition; 54 | if (positionString == nil) { 55 | currentPosition = position; 56 | ++position; 57 | } else { 58 | currentPosition = [positionString integerValue] - 1; 59 | positionString = nil; 60 | } 61 | CCLFormatParseResult *result = [[CCLFormatParseResult alloc] initWithRange:NSMakeRange(start, (index + 1) - start) index:currentPosition]; 62 | [arguments addObject:result]; 63 | *maxPosition = MAX(*maxPosition, currentPosition + 1); 64 | state = FormatStringStateUnknown; 65 | } else if ((state == FormatStringStartFormat || state == FormatStringStatePositional) && isdigit(character)) { 66 | if (positionString == nil) { 67 | positionString = [[NSMutableString alloc] initWithCharacters:&character length:1]; 68 | } else { 69 | [positionString appendString:[[NSMutableString alloc] initWithCharacters:&character length:1]]; 70 | } 71 | state = FormatStringStatePositional; 72 | } else if (state == FormatStringStatePositional && character == '$') { 73 | state = FormatStringStateEndPositional; 74 | } else { 75 | state = FormatStringStateUnknown; 76 | } 77 | } 78 | } 79 | 80 | return arguments; 81 | } 82 | 83 | @implementation NSAttributedString (CCLFormat) 84 | 85 | + (instancetype)attributedStringWithFormat:(NSString *)format, ... { 86 | va_list args; 87 | va_start(args, format); 88 | NSAttributedString *result = [[self alloc] initWithFormat:format arguments:args]; 89 | va_end(args); 90 | 91 | return result; 92 | } 93 | 94 | + (instancetype)attributedStringWithAttributes:(NSDictionary *)attributes format:(NSString *)format, ... NS_FORMAT_FUNCTION(2,3) { 95 | va_list args; 96 | va_start(args, format); 97 | NSAttributedString *result = [[self alloc] initWithAttributes:attributes format:format arguments:args]; 98 | va_end(args); 99 | 100 | return result; 101 | } 102 | 103 | + (instancetype)attributedStringWithFormat:(NSString *)format arguments:(va_list)arguments { 104 | return [[self alloc] initWithFormat:format arguments:arguments]; 105 | } 106 | 107 | + (instancetype)attributedStringWithAttributes:(NSDictionary *)attributes format:(NSString *)format arguments:(va_list)arguments { 108 | return [[self alloc] initWithAttributes:attributes format:format arguments:arguments]; 109 | } 110 | 111 | - (instancetype)initWithFormat:(NSString *)format, ... { 112 | va_list args; 113 | va_start(args, format); 114 | self = [self initWithFormat:format arguments:args]; 115 | va_end(args); 116 | 117 | return self; 118 | } 119 | 120 | - (instancetype)initWithAttributes:(NSDictionary *)attributes format:(NSString *)format, ... { 121 | va_list args; 122 | va_start(args, format); 123 | self = [self initWithAttributes:attributes format:format arguments:args]; 124 | va_end(args); 125 | 126 | return self; 127 | } 128 | 129 | - (instancetype)initWithFormat:(NSString *)format arguments:(va_list)arguments { 130 | return [self initWithAttributes:nil format:format arguments:arguments]; 131 | } 132 | 133 | - (instancetype)initWithAttributes:(NSDictionary *)attributes format:(NSString *)format arguments:(va_list)arguments { 134 | NSMutableAttributedString *attributedString; 135 | if (attributes) { 136 | attributedString = [[NSMutableAttributedString alloc] initWithString:format attributes:attributes]; 137 | } else { 138 | attributedString = [[NSMutableAttributedString alloc] initWithString:format]; 139 | } 140 | 141 | [attributedString beginEditing]; 142 | 143 | NSUInteger maxPosition; 144 | // We want to start at the back 145 | NSArray *parseResults = [CCLFormatStringParser(format, &maxPosition) sortedArrayWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(CCLFormatParseResult *lhs, CCLFormatParseResult *rhs) { 146 | return lhs.range.location < rhs.range.location; 147 | }]; 148 | 149 | NSMutableArray *attributeStrings = [NSMutableArray array]; 150 | for (NSUInteger index = 0; index < maxPosition; ++index) { 151 | id argument = va_arg(arguments, id); 152 | [attributeStrings addObject:argument]; 153 | } 154 | 155 | for (CCLFormatParseResult *parseResult in parseResults) { 156 | id arg = [attributeStrings objectAtIndex:parseResult.index]; 157 | 158 | if ([arg isKindOfClass:[NSAttributedString class]]) { 159 | if (attributes) { 160 | //Copy to main string attributes over the argument copy 161 | NSMutableAttributedString *argCopy = [arg mutableCopy]; 162 | [argCopy setAttributes:attributes range:(NSRange){0, argCopy.length}]; 163 | 164 | //Re-apply the original attributes to keep orignal styling where necessary 165 | //This handles cases where original styling doesn't cover the whole argument 166 | //For example, repeat calls to attributedStringWithFormat: with styled arguments 167 | [arg enumerateAttributesInRange:(NSRange){0, [arg length]} options:0 usingBlock: 168 | ^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { 169 | [argCopy addAttributes:attrs range:range]; 170 | }]; 171 | arg = argCopy; 172 | } 173 | [attributedString replaceCharactersInRange:parseResult.range withAttributedString:arg]; 174 | } else { 175 | [attributedString replaceCharactersInRange:parseResult.range withString:[arg description]]; 176 | } 177 | } 178 | 179 | [attributedString endEditing]; 180 | 181 | return [self initWithAttributedString:attributedString]; 182 | } 183 | 184 | 185 | @end 186 | -------------------------------------------------------------------------------- /NSAttributedStringFormat/NSAttributedStringFormat.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | 3 | //! Project version number for NSAttributedStringFormat. 4 | FOUNDATION_EXPORT double NSAttributedStringFormatVersionNumber; 5 | 6 | //! Project version string for NSAttributedStringFormat. 7 | FOUNDATION_EXPORT const unsigned char NSAttributedStringFormatVersionString[]; 8 | 9 | #import "NSAttributedString+CCLFormat.h" 10 | -------------------------------------------------------------------------------- /NSAttributedStringFormatTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /NSAttributedStringFormatTests/NSAttributedString+CCLFormatTests.m: -------------------------------------------------------------------------------- 1 | @import XCTest; 2 | @import Foundation; 3 | @import NSAttributedStringFormat; 4 | 5 | 6 | @interface NSAttributedStringTests : XCTestCase 7 | 8 | @end 9 | 10 | @implementation NSAttributedStringTests 11 | 12 | - (void)testAttributedFormatStringCanFormatAttributedString { 13 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"Hi" attributes:@{ 14 | NSStrikethroughStyleAttributeName: @(NSUnderlineStyleSingle), 15 | }]; 16 | 17 | NSAttributedString *formattedAttributedString = [NSAttributedString attributedStringWithFormat:@"%@", attributedString]; 18 | 19 | XCTAssertEqualObjects(formattedAttributedString, attributedString); 20 | } 21 | 22 | - (void)testAttributedFormatStringCanFormatString { 23 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithFormat:@"%@", @"Hello World"]; 24 | XCTAssertEqualObjects([attributedString string], @"Hello World"); 25 | } 26 | 27 | - (void)testAttributedFormatStringCanFormatDescription { 28 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithFormat:@"%@", @1337]; 29 | XCTAssertEqualObjects([attributedString string], @"1337"); 30 | } 31 | 32 | - (void)testAttributedFormatString { 33 | // Format Attributed String 34 | NSAttributedString *blue = [[NSAttributedString alloc] initWithString:@"Blue" attributes:@{ 35 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f], 36 | NSForegroundColorAttributeName: [NSColor blueColor], 37 | }]; 38 | 39 | NSAttributedString *green = [[NSAttributedString alloc] initWithString:@"Green" attributes:@{ 40 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f], 41 | NSForegroundColorAttributeName: [NSColor greenColor], 42 | }]; 43 | 44 | NSAttributedString *never = [[NSAttributedString alloc] initWithString:@"never" attributes:@{ 45 | NSStrikethroughStyleAttributeName: @(NSUnderlineStyleSingle), 46 | }]; 47 | 48 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithFormat:@"%@ and %@ must %@ be %@", blue, green, never, @"seen"]; 49 | 50 | // Fixture 51 | NSMutableAttributedString *fixtureAttributedString = [[NSMutableAttributedString alloc] initWithString:@"Blue and Green must never be seen"]; 52 | NSRange blueRange = [[fixtureAttributedString string] rangeOfString:@"Blue"]; 53 | NSRange greenRange = [[fixtureAttributedString string] rangeOfString:@"Green"]; 54 | NSRange neverRange = [[fixtureAttributedString string] rangeOfString:@"never"]; 55 | 56 | [fixtureAttributedString setAttributes:@{ 57 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f], 58 | NSForegroundColorAttributeName: [NSColor blueColor], 59 | } range:blueRange]; 60 | 61 | [fixtureAttributedString setAttributes:@{ 62 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f], 63 | NSForegroundColorAttributeName: [NSColor greenColor], 64 | } range:greenRange]; 65 | 66 | [fixtureAttributedString addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlineStyleSingle) range:neverRange]; 67 | 68 | XCTAssertEqualObjects(attributedString, fixtureAttributedString, ""); 69 | } 70 | 71 | - (void)testPositionalAttributedFormatStringCanFormatAttributedString { 72 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"Hi" attributes:@{ 73 | NSStrikethroughStyleAttributeName: @(NSUnderlineStyleSingle), 74 | }]; 75 | 76 | NSAttributedString *formattedAttributedString = [NSAttributedString attributedStringWithFormat:@"%1$@", attributedString]; 77 | 78 | XCTAssertEqualObjects(formattedAttributedString, attributedString); 79 | } 80 | 81 | - (void)testPositionalAttributedFormatStringCanFormatString { 82 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithFormat:@"%1$@", @"Hello World"]; 83 | XCTAssertEqualObjects([attributedString string], @"Hello World"); 84 | } 85 | 86 | - (void)testPositionalAttributedFormatStringCanFormatDescription { 87 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithFormat:@"%1$@", @"1337"]; 88 | XCTAssertEqualObjects([attributedString string], @"1337"); 89 | } 90 | 91 | - (void)testPositionalAttributedFormatString { 92 | // Format Attributed String 93 | NSAttributedString *blue = [[NSAttributedString alloc] initWithString:@"Blue" attributes:@{ 94 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f], 95 | NSForegroundColorAttributeName: [NSColor blueColor], 96 | }]; 97 | 98 | NSAttributedString *green = [[NSAttributedString alloc] initWithString:@"Green" attributes:@{ 99 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f], 100 | NSForegroundColorAttributeName: [NSColor greenColor], 101 | }]; 102 | 103 | NSAttributedString *never = [[NSAttributedString alloc] initWithString:@"never" attributes:@{ 104 | NSStrikethroughStyleAttributeName: @(NSUnderlineStyleSingle), 105 | }]; 106 | 107 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithFormat:@"%1$@ and %2$@ must %4$@ be %3$@", blue, green, @"seen", never]; 108 | 109 | // Fixture 110 | NSMutableAttributedString *fixtureAttributedString = [[NSMutableAttributedString alloc] initWithString:@"Blue and Green must never be seen"]; 111 | NSRange blueRange = [[fixtureAttributedString string] rangeOfString:@"Blue"]; 112 | NSRange greenRange = [[fixtureAttributedString string] rangeOfString:@"Green"]; 113 | NSRange neverRange = [[fixtureAttributedString string] rangeOfString:@"never"]; 114 | 115 | [fixtureAttributedString setAttributes:@{ 116 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f], 117 | NSForegroundColorAttributeName: [NSColor blueColor], 118 | } range:blueRange]; 119 | 120 | [fixtureAttributedString setAttributes:@{ 121 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f], 122 | NSForegroundColorAttributeName: [NSColor greenColor], 123 | } range:greenRange]; 124 | 125 | [fixtureAttributedString addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlineStyleSingle) range:neverRange]; 126 | 127 | XCTAssertEqualObjects(attributedString, fixtureAttributedString, ""); 128 | } 129 | 130 | - (void)testPositionalAttributedFormatStringCanFormatMoreThanNineArgs { 131 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithFormat: 132 | @"%1$@ %2$@ %3$@ %4$@ %5$@ %6$@ %7$@ %8$@ %9$@ %10$@", 133 | @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10"]; 134 | 135 | XCTAssertEqualObjects([attributedString string], @"1 2 3 4 5 6 7 8 9 10", ""); 136 | } 137 | 138 | - (void)testPositionalFormatStringCanFormatRepeatedArgs { 139 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithFormat: 140 | @"%1$@ %2$@ %1$@", @"1", @"2"]; 141 | XCTAssertEqualObjects([attributedString string], @"1 2 1", ""); 142 | } 143 | 144 | - (void)testFormatStringCanHaveAttributes { 145 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithAttributes:@{ 146 | NSForegroundColorAttributeName: [NSColor blueColor] 147 | } format:@"test %@", @"test"]; 148 | NSAttributedString *formattedAttributedString = [[NSAttributedString alloc] initWithString:@"test test" 149 | attributes:@{NSForegroundColorAttributeName: [NSColor blueColor]}]; 150 | XCTAssertEqualObjects(formattedAttributedString, attributedString); 151 | } 152 | 153 | - (void)testArgumentAttributesOverrideBaseAttributes { 154 | NSAttributedString *attributedArg = [[NSAttributedString alloc] initWithString:@"test" 155 | attributes:@{NSForegroundColorAttributeName: [NSColor greenColor]}]; 156 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithAttributes:@{ 157 | NSForegroundColorAttributeName: [NSColor blueColor] 158 | } format:@"test %@", attributedArg]; 159 | NSMutableAttributedString *formattedAttributedString = [[NSMutableAttributedString alloc] 160 | initWithString:@"test " 161 | attributes:@{NSForegroundColorAttributeName: [NSColor blueColor]}]; 162 | [formattedAttributedString appendAttributedString:attributedArg]; 163 | XCTAssertEqualObjects(formattedAttributedString, attributedString); 164 | } 165 | 166 | - (void)testMissingAttributeInAttributedArgumentUsesBaseAttribute { 167 | NSAttributedString *attributedArg = [[NSAttributedString alloc] initWithString:@"test" 168 | attributes:@{NSForegroundColorAttributeName: [NSColor greenColor]}]; 169 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithAttributes:@{ 170 | NSForegroundColorAttributeName: [NSColor blueColor], 171 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f] 172 | } format:@"test %@", attributedArg]; 173 | NSMutableAttributedString *formattedAttributedString = [[NSMutableAttributedString alloc] 174 | initWithString:@"test " attributes:@{ 175 | NSForegroundColorAttributeName: [NSColor blueColor], 176 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f] 177 | }]; 178 | [formattedAttributedString appendAttributedString:attributedArg]; 179 | [formattedAttributedString addAttributes:@{ 180 | NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f] 181 | } 182 | range:(NSRange) {5,4}]; 183 | XCTAssertEqualObjects(formattedAttributedString, attributedString); 184 | } 185 | 186 | - (void)testNonAttributedArgumentUsesBaseAttributes { 187 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithAttributes:@{ 188 | NSForegroundColorAttributeName: [NSColor blueColor] 189 | } format:@"test %@", @"test"]; 190 | NSMutableAttributedString *formattedAttributedString = [[NSMutableAttributedString alloc] 191 | initWithString:@"test test" attributes:@{ 192 | NSForegroundColorAttributeName: [NSColor blueColor]}]; 193 | XCTAssertEqualObjects(formattedAttributedString, attributedString); 194 | } 195 | 196 | - (void)testAttributedArgumentWithComplexAttributesKeepsAttributes { 197 | NSDictionary *boldBlueAttrs = @{ NSFontAttributeName: [NSFont boldSystemFontOfSize:16.0f], 198 | NSForegroundColorAttributeName: [NSColor blueColor]}; 199 | NSDictionary *redAttrs = @{NSForegroundColorAttributeName: [NSColor redColor]}; 200 | NSDictionary *rootAttributes = @{NSForegroundColorAttributeName: [NSColor greenColor], 201 | NSFontAttributeName: [NSFont systemFontOfSize:14.0f]}; 202 | 203 | NSAttributedString *attributedArg = [NSAttributedString attributedStringWithAttributes:redAttrs 204 | format:@"red %@ red", 205 | [[NSAttributedString alloc] initWithString:@"bold blue" 206 | attributes:boldBlueAttrs]]; 207 | 208 | NSAttributedString *attributedString = [NSAttributedString attributedStringWithAttributes:rootAttributes 209 | format:@"test %@", attributedArg]; 210 | NSMutableAttributedString *formattedAttributedString = [[NSMutableAttributedString alloc] 211 | initWithString:@"test red bold blue red" 212 | attributes:rootAttributes]; 213 | [formattedAttributedString addAttributes:redAttrs range:(NSRange){5, 17}]; 214 | [formattedAttributedString setAttributes:boldBlueAttrs range:(NSRange){9, 9}]; 215 | 216 | XCTAssertEqualObjects(formattedAttributedString, attributedString); 217 | } 218 | 219 | - (void)testSubstituteAttributedStringPerformance { 220 | NSDictionary *attributes = @{ NSStrikethroughStyleAttributeName: @(NSUnderlineStyleSingle) }; 221 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"Hi" attributes:attributes]; 222 | 223 | [self measureBlock:^{ 224 | NSAttributedString *formattedAttributedString = [NSAttributedString attributedStringWithFormat:@"%@", attributedString]; 225 | XCTAssertEqualObjects(formattedAttributedString, attributedString); 226 | }]; 227 | } 228 | 229 | @end 230 | -------------------------------------------------------------------------------- /NSAttributedStringFormatTests/NSAttributedStringFormatTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedStringFormatTests.m 3 | // NSAttributedStringFormatTests 4 | // 5 | // Created by Kyle Fuller on 27/09/2015. 6 | // Copyright © 2015 Cocode. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSAttributedStringFormatTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation NSAttributedStringFormatTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NSAttributedString+CCLFormat 2 | ============================ 3 | 4 | [![Build Status](https://travis-ci.org/cocodelabs/NSAttributedString-CCLFormat.svg?branch=master)](https://travis-ci.org/cocodelabs/NSAttributedString-CCLFormat) 5 | [![Coverage Status](https://img.shields.io/coveralls/cocodelabs/NSAttributedString-CCLFormat.svg)](https://coveralls.io/r/cocodelabs/NSAttributedString-CCLFormat) 6 | 7 | An extension to NSAttributedString for creating attributed strings by using a 8 | given format string as a template into which the remaining argument values are 9 | substituted. This is helpful for using in conjunction with internationalisation. 10 | 11 | ```objective-c 12 | @interface NSAttributedString (CCLFormat) 13 | 14 | + (NSAttributedString *)attributedStringWithFormat:(NSString *)format, ...; 15 | 16 | @end 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```objective-c 22 | NSAttributedString *blue, *green, *never, *result; 23 | 24 | blue = [[NSAttributedString alloc] initWithString:@"Blue" attributes:@{ 25 | NSFontAttributeName: [UIFont boldSystemFontOfSize:16.0f], 26 | NSForegroundColorAttributeName: [UIColor blueColor], 27 | }]; 28 | 29 | green = [[NSAttributedString alloc] initWithString:@"Green" attributes:@{ 30 | NSFontAttributeName: [UIFont boldSystemFontOfSize:16.0f], 31 | NSForegroundColorAttributeName: [UIColor greenColor], 32 | }]; 33 | 34 | never = [[NSAttributedString alloc] initWithString:@"never" attributes:@{ 35 | NSStrikethroughStyleAttributeName: @(NSUnderlineStyleSingle), 36 | }]; 37 | 38 | result = [NSAttributedString attributedStringWithFormat:@"%@ and %@ must %@ be seen", blue, green, never]; 39 | ``` 40 | 41 | ## Installation 42 | 43 | [CocoaPods](http://cocoapods.org) is the recommended way to add 44 | NSAttributedString+CCLFormat to your project. 45 | 46 | Here's an example podfile that installs NSAttributedString+CCLFormat. 47 | 48 | ### Podfile 49 | 50 | ```ruby 51 | pod 'NSAttributedString+CCLFormat' 52 | ``` 53 | 54 | ## License 55 | 56 | NSAttributedString+CCLFormat is released under the BSD license. See [LICENSE](LICENSE). 57 | 58 | -------------------------------------------------------------------------------- /xcconfigs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marius Rackwitz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /xcconfigs/README.md: -------------------------------------------------------------------------------- 1 | # xcconfigs for Universal Frameworks 2 | 3 | > Build iOS, OS X and watchOS frameworks on base of one target. 4 | 5 | If you maintain a framework on multiple platforms, you usually have to duplicate your framework target. Normally you have to maintain all build settings in two or more places, and then you have to duplicate the target membership lists for all source code files and build phases. 6 | 7 | This project intends to aggregate common or universal Xcode configuration settings, specifically for frameworks. Keeping them in hierarchical Xcode configuration files for easy modification and reuse. All build settings for platform-specific values are scoped by conditional variable assignments. This means you can have one framework target and select at build time, which platform it should be build against and the right default build settings are selected automatically. 8 | 9 | ## Usage 10 | 11 | ### Manual Integration 12 | 13 | 1. `$ git submodule add https://github.com/mrackwitz/xcconfigs.git xcconfigs` 14 | * Drop the files into the root group of your framework target. 15 | * Make sure to check `Create groups` instead of `Create folder references` 16 | * Select the project in the file navigator 17 | * Select the project in the left pane of the project editor 18 | * Select the Info tab 19 | * Expand the Configurations pane, if needed 20 | * Expand `Debug`, `Release` and further configurations, if present 21 | * Select `UniversalFramework_Framework` for your framework target 22 | * Select `UniversalFramework_Test` for your test target of your framework target 23 | 24 | ### Carthage 25 | 26 | Carthage supports universal frameworks on master. 27 | -------------------------------------------------------------------------------- /xcconfigs/UniversalFramework_Base.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Put this file alongside to the other both, as it contains what 3 | // both have in common. Don't rename this file. 4 | // 5 | // Copyright (c) 2014-2015 Marius Rackwitz. All rights reserved. 6 | // 7 | 8 | // Make it universal 9 | SUPPORTED_PLATFORMS = macosx iphonesimulator iphoneos watchos watchsimulator 10 | VALID_ARCHS[sdk=macosx*] = i386 x86_64 11 | VALID_ARCHS[sdk=iphoneos*] = arm64 armv7 armv7s 12 | VALID_ARCHS[sdk=iphonesimulator*] = arm64 armv7 armv7s 13 | VALID_ARCHS[sdk=watchos*] = armv7k 14 | VALID_ARCHS[sdk=watchsimulator*] = armv7k 15 | 16 | // Dynamic linking uses different default copy paths 17 | LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' 18 | LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 19 | LD_RUNPATH_SEARCH_PATHS[sdk=iphonesimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 20 | LD_RUNPATH_SEARCH_PATHS[sdk=watchos*] = $(inherited) '@executable_path/Frameworks' 21 | LD_RUNPATH_SEARCH_PATHS[sdk=watchsimulator*] = $(inherited) '@executable_path/Frameworks' 22 | -------------------------------------------------------------------------------- /xcconfigs/UniversalFramework_Framework.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Inherit from this config in your framework target. 3 | // 4 | // Copyright (c) 2014-2015 Marius Rackwitz. All rights reserved. 5 | // 6 | 7 | #include "UniversalFramework_Base.xcconfig" 8 | 9 | // OSX-specific default settings 10 | FRAMEWORK_VERSION[sdk=macosx*] = A 11 | COMBINE_HIDPI_IMAGES[sdk=macosx*] = YES 12 | 13 | // iOS-specific default settings 14 | CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer 15 | TARGETED_DEVICE_FAMILY[sdk=iphonesimulator*] = 1,2 16 | TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 17 | 18 | // Watch-specific default settings 19 | TARGETED_DEVICE_FAMILY[sdk=watchsimulator*] = 4 20 | TARGETED_DEVICE_FAMILY[sdk=watch*] = 4 21 | -------------------------------------------------------------------------------- /xcconfigs/UniversalFramework_Test.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Inherit from this config in the test target for your framework. 3 | // 4 | // Copyright (c) 2014-2015 Marius Rackwitz. All rights reserved. 5 | // 6 | 7 | #include "UniversalFramework_Base.xcconfig" 8 | 9 | FRAMEWORK_SEARCH_PATHS = $(inherited) '$(PLATFORM_DIR)/Developer/Library/Frameworks' 10 | 11 | // Yep. 12 | LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) '@executable_path/../Frameworks' '@loader_path/../Frameworks' 13 | LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 14 | LD_RUNPATH_SEARCH_PATHS[sdk=iphonesimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 15 | LD_RUNPATH_SEARCH_PATHS[sdk=watchos*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 16 | LD_RUNPATH_SEARCH_PATHS[sdk=watchsimulator*] = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 17 | --------------------------------------------------------------------------------