From 010225196da2487796c6a385c693c37abab1665e Mon Sep 17 00:00:00 2001 From: Nora Trapp Date: Wed, 2 Mar 2016 18:21:47 -0800 Subject: [PATCH 1/4] Add ability to add child BONChainables to style text between tags. --- Example/BonMot.xcodeproj/project.pbxproj | 22 +- Example/BonMot/Cells/TagStylesCell.h | 13 + Example/BonMot/Cells/TagStylesCell.m | 40 + Example/BonMot/Cells/TagStylesCell.xib | 46 + Example/BonMot/RootViewController.m | 2 + Example/Podfile.lock | 2 +- Example/Pods/Headers/Private/BonMot/BONTag.h | 1 + .../Headers/Private/BonMot/BONTag_Private.h | 1 + Example/Pods/Manifest.lock | 2 +- Example/Pods/Pods.xcodeproj/project.pbxproj | 1202 +++++++++-------- .../xcshareddata/xcschemes/BonMot.xcscheme | 2 +- .../BonMot/BonMot-umbrella.h | 1 + .../BonMot/BonMot.modulemap | 1 + Example/Tests/BONTagComplexStylesTestCase.m | 406 ++++++ Example/Tests/BONTagStylesTestCase.m | 406 ++++++ Pod/BonMot.h | 1 + Pod/Classes/BONChain.h | 12 +- Pod/Classes/BONChain.m | 11 + Pod/Classes/BONTag.h | 69 + Pod/Classes/BONTag.m | 235 ++++ Pod/Classes/BONTag_Private.h | 49 + Pod/Classes/BONText.h | 10 +- Pod/Classes/BONText.m | 32 +- README.md | 40 + readme-images/arbitrary-tag-styling.png | Bin 0 -> 6376 bytes readme-images/tag-styling.png | Bin 0 -> 18461 bytes 26 files changed, 2030 insertions(+), 576 deletions(-) create mode 100644 Example/BonMot/Cells/TagStylesCell.h create mode 100644 Example/BonMot/Cells/TagStylesCell.m create mode 100644 Example/BonMot/Cells/TagStylesCell.xib create mode 120000 Example/Pods/Headers/Private/BonMot/BONTag.h create mode 120000 Example/Pods/Headers/Private/BonMot/BONTag_Private.h create mode 100644 Example/Tests/BONTagComplexStylesTestCase.m create mode 100644 Example/Tests/BONTagStylesTestCase.m create mode 100644 Pod/Classes/BONTag.h create mode 100644 Pod/Classes/BONTag.m create mode 100644 Pod/Classes/BONTag_Private.h create mode 100644 readme-images/arbitrary-tag-styling.png create mode 100644 readme-images/tag-styling.png diff --git a/Example/BonMot.xcodeproj/project.pbxproj b/Example/BonMot.xcodeproj/project.pbxproj index d7f70ee1..496ffc0a 100644 --- a/Example/BonMot.xcodeproj/project.pbxproj +++ b/Example/BonMot.xcodeproj/project.pbxproj @@ -21,8 +21,6 @@ 730361E31C9B5CDD00987809 /* BONHumanReadableStringTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 730361E11C9B5CD300987809 /* BONHumanReadableStringTestCase.m */; }; CD64F15E1CA4DAC800042559 /* BONFigureTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CD64F15D1CA4DAC800042559 /* BONFigureTestCase.m */; }; CD6DEFE11BF6ADF900676E2D /* BONBaseTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6DEFD61BF6ADF900676E2D /* BONBaseTestCase.m */; }; - CD6DEFE31BF6ADF900676E2D /* BONTextableTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6DEFD71BF6ADF900676E2D /* BONTextableTestCase.m */; }; - CD6DEFE51BF6ADF900676E2D /* BONChainEmptyTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6DEFD81BF6ADF900676E2D /* BONChainEmptyTestCase.m */; }; CD6DEFE71BF6ADF900676E2D /* BONDebugStringTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6DEFD91BF6ADF900676E2D /* BONDebugStringTestCase.m */; }; CD6DEFE91BF6ADF900676E2D /* BONDictionaryEqualityTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6DEFDA1BF6ADF900676E2D /* BONDictionaryEqualityTestCase.m */; }; CD6DEFEB1BF6ADF900676E2D /* BONIndentSpacerTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6DEFDB1BF6ADF900676E2D /* BONIndentSpacerTestCase.m */; }; @@ -66,7 +64,11 @@ CD6DF05A1BF6B98500676E2D /* NSDictionary+BONEquality.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6DF04F1BF6B53100676E2D /* NSDictionary+BONEquality.m */; }; CD78701E1CA5F8EC00B2714F /* EBGaramond12-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD6DF03E1BF6AFAA00676E2D /* EBGaramond12-Regular.otf */; }; CDE658581C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE658571C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m */; }; + EC433DB31C88B7D9001B3ABE /* BONTagStylesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB11C88B65B001B3ABE /* BONTagStylesTestCase.m */; }; EC433DB61C88BBDE001B3ABE /* BONUIKitUtilitiesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB41C88BBDB001B3ABE /* BONUIKitUtilitiesTestCase.m */; }; + EC433DB91C88DE5F001B3ABE /* TagStylesCell.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB81C88DE5F001B3ABE /* TagStylesCell.m */; }; + EC433DBB1C88DEF7001B3ABE /* TagStylesCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC433DBA1C88DEF7001B3ABE /* TagStylesCell.xib */; }; + ECD4DB6B1CE3EA2700079675 /* BONTagComplexStylesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = ECD4DB691CE3EA1F00079675 /* BONTagComplexStylesTestCase.m */; }; F6E9CEC93164745FB8BEB8FC /* Pods_BonMot_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2E8BB0552A748FFBA2BD0BE4 /* Pods_BonMot_Example.framework */; }; /* End PBXBuildFile section */ @@ -166,7 +168,12 @@ CDE658571C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONUnderlineAndStrikethroughTestCase.m; sourceTree = ""; }; CFD6D97A946F2FEF294F4BBB /* Pods_BonMot_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BonMot_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EBE7037465B0F6F184875C40 /* Pods-BonMot_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BonMot_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-BonMot_Example/Pods-BonMot_Example.release.xcconfig"; sourceTree = ""; }; + EC433DB11C88B65B001B3ABE /* BONTagStylesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagStylesTestCase.m; sourceTree = ""; }; EC433DB41C88BBDB001B3ABE /* BONUIKitUtilitiesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONUIKitUtilitiesTestCase.m; sourceTree = ""; }; + EC433DB71C88DE5F001B3ABE /* TagStylesCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TagStylesCell.h; sourceTree = ""; }; + EC433DB81C88DE5F001B3ABE /* TagStylesCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TagStylesCell.m; sourceTree = ""; }; + EC433DBA1C88DEF7001B3ABE /* TagStylesCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TagStylesCell.xib; sourceTree = ""; }; + ECD4DB691CE3EA1F00079675 /* BONTagComplexStylesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagComplexStylesTestCase.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -281,6 +288,8 @@ CD6DEFDA1BF6ADF900676E2D /* BONDictionaryEqualityTestCase.m */, 730361E11C9B5CD300987809 /* BONHumanReadableStringTestCase.m */, CD6DEFDB1BF6ADF900676E2D /* BONIndentSpacerTestCase.m */, + EC433DB11C88B65B001B3ABE /* BONTagStylesTestCase.m */, + ECD4DB691CE3EA1F00079675 /* BONTagComplexStylesTestCase.m */, CD6DEFDC1BF6ADF900676E2D /* BONPropertyClobberingTestCase.m */, CD6DEFDD1BF6ADF900676E2D /* BONTextAlignmentConstraintTestCase.m */, CD6DEFDE1BF6ADF900676E2D /* BONTextAlignmentTestCase.m */, @@ -360,6 +369,9 @@ CD6DF01D1BF6AE8200676E2D /* SpecialCharactersCell.h */, CD6DF01E1BF6AE8200676E2D /* SpecialCharactersCell.m */, CD6DF01F1BF6AE8200676E2D /* SpecialCharactersCell.xib */, + EC433DB71C88DE5F001B3ABE /* TagStylesCell.h */, + EC433DB81C88DE5F001B3ABE /* TagStylesCell.m */, + EC433DBA1C88DEF7001B3ABE /* TagStylesCell.xib */, CD6DF0201BF6AE8200676E2D /* TrackingCell.h */, CD6DF0211BF6AE8200676E2D /* TrackingCell.m */, CD6DF0221BF6AE8200676E2D /* TrackingCell.xib */, @@ -467,6 +479,7 @@ CD6DF0251BF6AE8200676E2D /* BaselineCapHeightCell.xib in Resources */, CD6DF04D1BF6B50800676E2D /* Launch Screen.xib in Resources */, CD6DF0311BF6AE8200676E2D /* InlineImagesCell.xib in Resources */, + EC433DBB1C88DEF7001B3ABE /* TagStylesCell.xib in Resources */, CD6DF0351BF6AE8200676E2D /* ProgrammaticBaselineCapHeightCell.xib in Resources */, CD6DF02F1BF6AE8200676E2D /* IndentationCell.xib in Resources */, 6003F598195388D20070C39A /* InfoPlist.strings in Resources */, @@ -609,6 +622,7 @@ CD6DF02C1BF6AE8200676E2D /* FigureStyleCell.m in Sources */, CD6DF04A1BF6B37E00676E2D /* DummyAssetClass.m in Sources */, CD6DF0591BF6B7AF00676E2D /* DashedHairlineView.m in Sources */, + EC433DB91C88DE5F001B3ABE /* TagStylesCell.m in Sources */, CD6DF0381BF6AE8200676E2D /* TrackingCell.m in Sources */, CD6DF0231BF6AE8200676E2D /* AbstractCell.m in Sources */, CD6DEFF61BF6AE4C00676E2D /* AppDelegate.m in Sources */, @@ -628,11 +642,11 @@ CD6DF05A1BF6B98500676E2D /* NSDictionary+BONEquality.m in Sources */, CD6DF04B1BF6B37E00676E2D /* DummyAssetClass.m in Sources */, 730361E31C9B5CDD00987809 /* BONHumanReadableStringTestCase.m in Sources */, - CD6DEFE51BF6ADF900676E2D /* BONChainEmptyTestCase.m in Sources */, + ECD4DB6B1CE3EA2700079675 /* BONTagComplexStylesTestCase.m in Sources */, CD6DEFED1BF6ADF900676E2D /* BONPropertyClobberingTestCase.m in Sources */, CD6DEFF11BF6ADF900676E2D /* BONTextAlignmentTestCase.m in Sources */, CD6DEFE11BF6ADF900676E2D /* BONBaseTestCase.m in Sources */, - CD6DEFE31BF6ADF900676E2D /* BONTextableTestCase.m in Sources */, + EC433DB31C88B7D9001B3ABE /* BONTagStylesTestCase.m in Sources */, CDE658581C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m in Sources */, CD6DEFF31BF6ADF900676E2D /* BONTrackingTestCase.m in Sources */, CD64F15E1CA4DAC800042559 /* BONFigureTestCase.m in Sources */, diff --git a/Example/BonMot/Cells/TagStylesCell.h b/Example/BonMot/Cells/TagStylesCell.h new file mode 100644 index 00000000..3fd450ca --- /dev/null +++ b/Example/BonMot/Cells/TagStylesCell.h @@ -0,0 +1,13 @@ +// +// TagStylesCell.h +// BonMot +// +// Created by Nora Trapp on 3/3/16. +// Copyright © 2016 Zev Eisenberg. All rights reserved. +// + +#import "AbstractCell.h" + +@interface TagStylesCell : AbstractCell + +@end diff --git a/Example/BonMot/Cells/TagStylesCell.m b/Example/BonMot/Cells/TagStylesCell.m new file mode 100644 index 00000000..846db5cd --- /dev/null +++ b/Example/BonMot/Cells/TagStylesCell.m @@ -0,0 +1,40 @@ +// +// TagStylesCell.m +// BonMot +// +// Created by Nora Trapp on 3/3/16. +// Copyright © 2016 Zev Eisenberg. All rights reserved. +// + +#import "TagStylesCell.h" + +@interface TagStylesCell () + +@property (weak, nonatomic) IBOutlet UILabel *label; + +@end + +@implementation TagStylesCell + ++ (NSString *)title +{ + return @"Tag Styles"; +} + +- (void)awakeFromNib +{ + [super awakeFromNib]; + + BONChain *boldChain = BONChain.new.fontNameAndSize(@"Baskerville-Bold", 15); + BONChain *italicChain = BONChain.new.fontNameAndSize(@"Baskerville-Italic", 15); + + BONChain *baseChain = BONChain.new.fontNameAndSize(@"Baskerville", 17) + .tagStyles(@[ BONTagMake(@"bold", boldChain), BONTagMake(@"italic", italicChain) ]) + .string(@"This text is wrapped in a \\ tag.\nThis text is wrapped in an \\ tag."); + + self.label.attributedText = baseChain.attributedString; + + [self.label layoutIfNeeded]; // For auto-sizing cells +} + +@end diff --git a/Example/BonMot/Cells/TagStylesCell.xib b/Example/BonMot/Cells/TagStylesCell.xib new file mode 100644 index 00000000..531b254f --- /dev/null +++ b/Example/BonMot/Cells/TagStylesCell.xib @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/BonMot/RootViewController.m b/Example/BonMot/RootViewController.m index f23161f3..b2b8b902 100644 --- a/Example/BonMot/RootViewController.m +++ b/Example/BonMot/RootViewController.m @@ -20,6 +20,7 @@ #import "SpecialCharactersCell.h" #import "BaselineOffsetCell.h" #import "ConcatenationCell.h" +#import "TagStylesCell.h" @interface RootViewController () @@ -48,6 +49,7 @@ - (void)viewDidLoad [SpecialCharactersCell class], [BaselineOffsetCell class], [ConcatenationCell class], + [TagStylesCell class] ]; for (Class CellClass in self.cellClasses) { diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 13bf0dcc..19b9d0de 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -11,7 +11,7 @@ DEPENDENCIES: EXTERNAL SOURCES: BonMot: - :path: "../" + :path: ../ SPEC CHECKSUMS: BonMot: e8d7b8920dc9363291c308e853ccb5784d5de4bd diff --git a/Example/Pods/Headers/Private/BonMot/BONTag.h b/Example/Pods/Headers/Private/BonMot/BONTag.h new file mode 120000 index 00000000..b2872707 --- /dev/null +++ b/Example/Pods/Headers/Private/BonMot/BONTag.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/BONTag.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/BonMot/BONTag_Private.h b/Example/Pods/Headers/Private/BonMot/BONTag_Private.h new file mode 120000 index 00000000..92a7f9cf --- /dev/null +++ b/Example/Pods/Headers/Private/BonMot/BONTag_Private.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/BONTag_Private.h \ No newline at end of file diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock index 13bf0dcc..19b9d0de 100644 --- a/Example/Pods/Manifest.lock +++ b/Example/Pods/Manifest.lock @@ -11,7 +11,7 @@ DEPENDENCIES: EXTERNAL SOURCES: BonMot: - :path: "../" + :path: ../ SPEC CHECKSUMS: BonMot: e8d7b8920dc9363291c308e853ccb5784d5de4bd diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index f3077bea..91c0a080 100644 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -10,54 +10,33 @@ 46 objects - 00D954F3747AA02DCA0A3809CFCD2B17 + 0080E3345719F60099A110B1C33C05F2 fileRef - FAB64E9029A05F73B2B4A250B105E030 - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - - 01E1BE6EC18457D2F88E270800F860C3 - - fileRef - 085FB431F3FE96089534EAAC6FE8BDF8 - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - - 0219B207E1DFB298AE0638868013EE2A - - fileRef - C3B7BDF5CE5CB8F095FE12DA55D9A9BB + 17E86E730C75BDA8893FD85A83CD3723 isa PBXBuildFile - 085FB431F3FE96089534EAAC6FE8BDF8 + 055C51A91D330461B7E2BE7BCD601A5B includeInIndex 1 isa PBXFileReference lastKnownFileType - sourcecode.c.h + sourcecode.c.objc path - BONSpecial.h + BONTag.m sourceTree <group> + 06821309EE78916F536F3C35B4FAA4B2 + + fileRef + D4B83982FE17B01A0A18B9382CE242A7 + isa + PBXBuildFile + 0918C87910956FED935D88D6DB334CFB includeInIndex @@ -71,12 +50,32 @@ sourceTree <group> - 0E4C967D9164B39B23F7D8574CADABE0 + 0D51A7EEC01DE4DFD00FAFC8910C3101 fileRef - D744BF15A46AA8823523F480524DA6D1 + 7C6D0196353D8E5AC65F6AC29B7C8777 isa PBXBuildFile + settings + + ATTRIBUTES + + Public + + + + 104DDEC1C49A667B0F3B8AD43A9B20D2 + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + BONCompatibility.h + sourceTree + <group> 10BC01F663CA7849D1A21B7F337370F0 @@ -125,7 +124,7 @@ children - BE8601DA8860F7392B95146FC34AA1C7 + 684EC576FF4124037A49D8AB0E73113E isa PBXGroup @@ -134,29 +133,30 @@ sourceTree <group> - 13394710B1F81A23CB801EC414777F81 + 1511D8A08DEB71B22A57C315F7151F13 includeInIndex 1 isa PBXFileReference - lastKnownFileType - sourcecode.c.h path - BonMot.h + BonMot.modulemap sourceTree <group> - 1511D8A08DEB71B22A57C315F7151F13 + 160CA269F2AAB7710B6A6740751DA459 - includeInIndex - 1 + fileRef + 47FFC4B90C298DBEA36FB326D190DBAB isa - PBXFileReference - path - BonMot.modulemap - sourceTree - <group> + PBXBuildFile + settings + + ATTRIBUTES + + Public + + 179F4E16F04D6C2B2FCF3166BA15B72A @@ -220,41 +220,21 @@ name Release - 18340F12838DEE43507667CC74401EB1 - - children - - 6E7E5AF37923BFA48D6994B9BFC6535F - C3B7BDF5CE5CB8F095FE12DA55D9A9BB - - isa - PBXGroup - name - iOS - sourceTree - <group> - - 1BB8C8B0D6FFC76CF6E70FF4AAFD9A79 + 17C1148DA9BA0313110FC2436C3C936F - includeInIndex - 1 + fileRef + A8EE48AA815968BBE1E66AB3493C1EAA isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - BONChain.m - sourceTree - <group> + PBXBuildFile - 20A63EF13894B3AA22B933560AA53F18 + 17E3669F8B32E52BE6C792E64AED7DA7 fileRef - 71C80B4B47FA325B1D150662D2EF15C2 + 6E7E5AF37923BFA48D6994B9BFC6535F isa PBXBuildFile - 212D5BC776EC758921C6BA007E2B182E + 17E86E730C75BDA8893FD85A83CD3723 includeInIndex 1 @@ -263,38 +243,38 @@ lastKnownFileType sourcecode.c.objc path - BONText.m + NSAttributedString+BonMotUtilities.m sourceTree <group> - 223A0AE1B4C1AB6C22803C3F6F4E7C36 + 18340F12838DEE43507667CC74401EB1 - fileRef - 946B30FF1BFF268E9AA4A27BAC2E5ECD + children + + 6E7E5AF37923BFA48D6994B9BFC6535F + C3B7BDF5CE5CB8F095FE12DA55D9A9BB + isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - + PBXGroup + name + iOS + sourceTree + <group> - 26DAE09174C6886D8EFBA0B3C9BF3BDB + 19F16EEB16822AFB8041C4343BE170DA includeInIndex 1 isa PBXFileReference lastKnownFileType - sourcecode.c.objc + sourcecode.c.h path - Pods-BonMot_Tests-dummy.m + BonMot.h sourceTree <group> - 28D4FDE16DE8250F25DC730765DC221B + 1F7D0CFA4095C319AEDC668D27E01781 includeInIndex 1 @@ -303,11 +283,11 @@ lastKnownFileType sourcecode.c.h path - BONCompatibility.h + NSAttributedString+BonMotUtilities.h sourceTree <group> - 29A9CD70F27600DBF695A88C9FF0AF50 + 1FBDCB556EABB04AC7BF1AE02CCFD08D includeInIndex 1 @@ -316,11 +296,25 @@ lastKnownFileType sourcecode.c.objc path - NSAttributedString+BonMotUtilities.m + BONSpecial.m sourceTree <group> - 2AA3029BF1F6DE94522F0133B389D5F1 + 216E0E24328F1BE6ACC478A93004F78B + + fileRef + BCCC75202C930C2B7DD65DF3E5EEDF8D + isa + PBXBuildFile + settings + + ATTRIBUTES + + Public + + + + 26DAE09174C6886D8EFBA0B3C9BF3BDB includeInIndex 1 @@ -329,14 +323,14 @@ lastKnownFileType sourcecode.c.objc path - BONSpecial.m + Pods-BonMot_Tests-dummy.m sourceTree <group> - 2C763DD88B7C65E2E698491B831F3F3D + 2BF8A970211510DABEA917F79FBE0240 fileRef - 13394710B1F81A23CB801EC414777F81 + 9CC10C208517AA62B5773BA59C750FCB isa PBXBuildFile settings @@ -385,17 +379,10 @@ sourceTree <group> - 304E716C2718C5366409757C167F395B - - fileRef - 65E326689AAFF02F1507650324813E89 - isa - PBXBuildFile - - 32467CAF5979C65E3DB3C11F76C85B53 + 323FBE9C4D2D6E9803B27A7332776538 fileRef - 7656BC2502D0F175B42AA5251C923450 + 1F7D0CFA4095C319AEDC668D27E01781 isa PBXBuildFile settings @@ -432,10 +419,23 @@ sourceTree <group> - 382422134FD5BE7524B02520A21D6690 + 38A2136A23298CA3E69A4E692A4317E9 + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + BONText.m + sourceTree + <group> + + 38F8823CD9270173773704F2E964D6BB fileRef - 29A9CD70F27600DBF695A88C9FF0AF50 + 61712505D3A454DA4C6DB55A419A8F77 isa PBXBuildFile @@ -463,28 +463,6 @@ sourceTree <group> - 420D64DDCD3ADF1EC24AF6BB68D18AFC - - buildActionMask - 2147483647 - files - - FC8B3E0621B411FD79396927F3D0BC13 - 478830357B13B6F648D4E1ED203CD27A - 9ECD0A9EB5DCC43C18F291D874BE6207 - 73F001AAAAAB975ABFFA01EAF7E8FCBC - A357495232D4BF470F4CE2BC59C9FC09 - 382422134FD5BE7524B02520A21D6690 - AE93C94C5CFC1F805B75EE4FD8FFD8E3 - 0E4C967D9164B39B23F7D8574CADABE0 - 304E716C2718C5366409757C167F395B - 20A63EF13894B3AA22B933560AA53F18 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - 433CD3331B6C3787F473C941B61FC68F children @@ -518,12 +496,18 @@ isa PBXBuildFile - 478830357B13B6F648D4E1ED203CD27A + 47FFC4B90C298DBEA36FB326D190DBAB - fileRef - D4B83982FE17B01A0A18B9382CE242A7 + includeInIndex + 1 isa - PBXBuildFile + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + BONChain.h + sourceTree + <group> 4C4450E89E7003A9B4F4B4AA685F16EA @@ -562,6 +546,35 @@ sourceTree <group> + 4F5FFDF0F9B2BE42D4027C887021A16F + + buildActionMask + 2147483647 + files + + 160CA269F2AAB7710B6A6740751DA459 + 88A895FA00365308CE594B482FF5FEC4 + 69F31D72D1D25BFE2ACE445415C13988 + 8EDCFA33B79E29125AFB799A710C0DB5 + 6A11AA127CAD88D0423AFDA9D76F657D + 6AFBB9136CA124A7592C60C3219064D2 + D09F361B9D1EE649D7E855E3A4727820 + E3C1E20D4E3714E84B74E812591B9970 + 96413C2DBE2E96EBC51E56737CFCBA6A + AD22D5D55F424595890CEBFBC370239C + 216E0E24328F1BE6ACC478A93004F78B + C9426127C7D9364DA549FFA1CBAAE21E + 323FBE9C4D2D6E9803B27A7332776538 + 0D51A7EEC01DE4DFD00FAFC8910C3101 + F70AB5068C980C99E1D3CB1D0EEC3356 + 2BF8A970211510DABEA917F79FBE0240 + FB5E82DA859C42DFF27B99BDC321C21F + + isa + PBXHeadersBuildPhase + runOnlyForDeploymentPostprocessing + 0 + 51D4DC924673736C887833A213B8733A includeInIndex @@ -588,19 +601,6 @@ runOnlyForDeploymentPostprocessing 0 - 53BEB5EF5FD30BA79950A4BD482E3F3B - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - UIImage+BonMotUtilities.h - sourceTree - <group> - 55572D20A461EDABEAD1B8C1D03101A2 explicitFileType @@ -678,118 +678,190 @@ name Debug - 621B433020566394142071265EABE9A0 + 60209B4FEFC22D3FDD07669CBB7A5D4A - includeInIndex - 1 + fileRef + 055C51A91D330461B7E2BE7BCD601A5B isa - PBXFileReference - lastKnownFileType - text.script.sh - path - Pods-BonMot_Example-resources.sh - sourceTree - <group> + PBXBuildFile - 6300608DB9CA45054481077A506554A4 + 61712505D3A454DA4C6DB55A419A8F77 includeInIndex 1 isa PBXFileReference lastKnownFileType - sourcecode.c.h + sourcecode.c.objc path - UITextView+BonMotUtilities.h + BONChain.m sourceTree <group> - 65E326689AAFF02F1507650324813E89 + 621B433020566394142071265EABE9A0 includeInIndex 1 isa PBXFileReference lastKnownFileType - sourcecode.c.objc + text.script.sh path - UITextField+BonMotUtilities.m + Pods-BonMot_Example-resources.sh sourceTree <group> - 690A840FE201A4B932BB6539F2BA5173 + 6300608DB9CA45054481077A506554A4 includeInIndex 1 isa PBXFileReference + lastKnownFileType + sourcecode.c.h path - Pods-BonMot_Tests.modulemap + UITextView+BonMotUtilities.h sourceTree <group> - 6C3A2FADB025B64FEEAE2DFC85035890 + 65901492A3F64EDD919D5D15AE892F84 - explicitFileType - wrapper.framework - includeInIndex - 0 - isa - PBXFileReference - name - Pods_BonMot_Tests.framework - path - Pods_BonMot_Tests.framework - sourceTree - BUILT_PRODUCTS_DIR + baseConfigurationReference + 907B9450CCC329D3E8FFEB42C73C99EE + buildSettings + + CODE_SIGN_IDENTITY[sdk=iphoneos*] + iPhone Developer + CURRENT_PROJECT_VERSION + 1 + DEFINES_MODULE + YES + DYLIB_COMPATIBILITY_VERSION + 1 + DYLIB_CURRENT_VERSION + 1 + DYLIB_INSTALL_NAME_BASE + @rpath + ENABLE_STRICT_OBJC_MSGSEND + YES + GCC_PREFIX_HEADER + Target Support Files/BonMot/BonMot-prefix.pch + INFOPLIST_FILE + Target Support Files/BonMot/Info.plist + INSTALL_PATH + $(LOCAL_LIBRARY_DIR)/Frameworks + IPHONEOS_DEPLOYMENT_TARGET + 8.0 + LD_RUNPATH_SEARCH_PATHS + + $(inherited) + @executable_path/Frameworks + @loader_path/Frameworks + + MODULEMAP_FILE + Target Support Files/BonMot/BonMot.modulemap + MTL_ENABLE_DEBUG_INFO + NO + PRODUCT_NAME + BonMot + SDKROOT + iphoneos + SKIP_INSTALL + YES + TARGETED_DEVICE_FAMILY + 1,2 + VERSIONING_SYSTEM + apple-generic + VERSION_INFO_PREFIX + + + isa + XCBuildConfiguration + name + Release - 6E7E5AF37923BFA48D6994B9BFC6535F + 65E326689AAFF02F1507650324813E89 + includeInIndex + 1 isa PBXFileReference lastKnownFileType - wrapper.framework + sourcecode.c.objc + path + UITextField+BonMotUtilities.m + sourceTree + <group> + + 684EC576FF4124037A49D8AB0E73113E + + children + + 19F16EEB16822AFB8041C4343BE170DA + FD46195AAC067287FDF6898C915D60C0 + + isa + PBXGroup name - Foundation.framework + Pod path - Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/Foundation.framework + Pod sourceTree - DEVELOPER_DIR + <group> - 71C80B4B47FA325B1D150662D2EF15C2 + 6868B12CE64A02DC58A825D43FCD6AED includeInIndex 1 isa PBXFileReference lastKnownFileType - sourcecode.c.objc + sourcecode.c.h path - UITextView+BonMotUtilities.m + BONTag_Private.h sourceTree <group> - 726940D4C81A5A90179226804F99A3A0 + 690A840FE201A4B932BB6539F2BA5173 includeInIndex 1 isa PBXFileReference - lastKnownFileType - sourcecode.c.h path - BONChain.h + Pods-BonMot_Tests.modulemap sourceTree <group> - 73F001AAAAAB975ABFFA01EAF7E8FCBC + 69F31D72D1D25BFE2ACE445415C13988 fileRef - 212D5BC776EC758921C6BA007E2B182E + CA7D9F5FC702DA9A09A5F3128839B84A isa PBXBuildFile + settings + + ATTRIBUTES + + Public + + - 7656BC2502D0F175B42AA5251C923450 + 6A11AA127CAD88D0423AFDA9D76F657D + + fileRef + 19F16EEB16822AFB8041C4343BE170DA + isa + PBXBuildFile + settings + + ATTRIBUTES + + Public + + + + 6A767B48037B33454A2A30879F170FDE includeInIndex 1 @@ -798,39 +870,14 @@ lastKnownFileType sourcecode.c.h path - NSAttributedString+BonMotUtilities.h + BONSpecial.h sourceTree <group> - 7960D5870921BF8F1FE6568BBEB009B5 - - buildConfigurationList - 9DA064760DB15CDFD92BBF5A6F53276A - buildPhases - - 420D64DDCD3ADF1EC24AF6BB68D18AFC - 806F7E65027C5EA011501EEDA6C86696 - C7AC1A115C88CDA55C8F507E58A1DDBF - - buildRules - - dependencies - - isa - PBXNativeTarget - name - BonMot - productName - BonMot - productReference - 987B388AEB3482E871DC28F0AE48017A - productType - com.apple.product-type.framework - - 79C19A2B453E4B0567FF6F03C5B1B0EA + 6AFBB9136CA124A7592C60C3219064D2 fileRef - 53BEB5EF5FD30BA79950A4BD482E3F3B + 6A767B48037B33454A2A30879F170FDE isa PBXBuildFile settings @@ -841,6 +888,47 @@ + 6C3A2FADB025B64FEEAE2DFC85035890 + + explicitFileType + wrapper.framework + includeInIndex + 0 + isa + PBXFileReference + name + Pods_BonMot_Tests.framework + path + Pods_BonMot_Tests.framework + sourceTree + BUILT_PRODUCTS_DIR + + 6E7E5AF37923BFA48D6994B9BFC6535F + + isa + PBXFileReference + lastKnownFileType + wrapper.framework + name + Foundation.framework + path + Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/Foundation.framework + sourceTree + DEVELOPER_DIR + + 71C80B4B47FA325B1D150662D2EF15C2 + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + UITextView+BonMotUtilities.m + sourceTree + <group> + 7AA4AFFEF440529E9D52D5EAC848B442 includeInIndex @@ -854,6 +942,19 @@ sourceTree <group> + 7C6D0196353D8E5AC65F6AC29B7C8777 + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + UIImage+BonMotUtilities.h + sourceTree + <group> + 7DB346D0F39D3F0E887471402A8071AB children @@ -869,95 +970,33 @@ sourceTree <group> - 806F7E65027C5EA011501EEDA6C86696 + 7FE1C74423FCE3D210EDB21FE1B4AC15 buildActionMask 2147483647 files - 9256FE898C951D38DA2ACB8BE4AC0C49 - 0219B207E1DFB298AE0638868013EE2A + 17E3669F8B32E52BE6C792E64AED7DA7 + 9D19B2AC9B6CBC83D7017C1DBD745057 isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 - 82CA6517CFCBD1CC5D4D001E9D7A6427 + 80B3B962FB551BA93445CCBECA715AC8 fileRef - 6E7E5AF37923BFA48D6994B9BFC6535F + 38A2136A23298CA3E69A4E692A4317E9 isa PBXBuildFile - 840456DBAF5B6C919D75BBE641E11DDD - - baseConfigurationReference - 907B9450CCC329D3E8FFEB42C73C99EE - buildSettings - - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - CURRENT_PROJECT_VERSION - 1 - DEFINES_MODULE - YES - DYLIB_COMPATIBILITY_VERSION - 1 - DYLIB_CURRENT_VERSION - 1 - DYLIB_INSTALL_NAME_BASE - @rpath - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_PREFIX_HEADER - Target Support Files/BonMot/BonMot-prefix.pch - INFOPLIST_FILE - Target Support Files/BonMot/Info.plist - INSTALL_PATH - $(LOCAL_LIBRARY_DIR)/Frameworks - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - LD_RUNPATH_SEARCH_PATHS - - $(inherited) - @executable_path/Frameworks - @loader_path/Frameworks - - MODULEMAP_FILE - Target Support Files/BonMot/BonMot.modulemap - MTL_ENABLE_DEBUG_INFO - YES - PRODUCT_NAME - BonMot - SDKROOT - iphoneos - SKIP_INSTALL - YES - TARGETED_DEVICE_FAMILY - 1,2 - VERSIONING_SYSTEM - apple-generic - VERSION_INFO_PREFIX - - - isa - XCBuildConfiguration - name - Debug - - 854FD81E8C8A6D67C6177C34537726A8 + 82CA6517CFCBD1CC5D4D001E9D7A6427 - includeInIndex - 1 + fileRef + 6E7E5AF37923BFA48D6994B9BFC6535F isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - UIImage+BonMotUtilities.m - sourceTree - <group> + PBXBuildFile 881C9B3E63E37A68AED32A58F7C155D3 @@ -979,6 +1018,20 @@ sourceTree <group> + 88A895FA00365308CE594B482FF5FEC4 + + fileRef + 104DDEC1C49A667B0F3B8AD43A9B20D2 + isa + PBXBuildFile + settings + + ATTRIBUTES + + Public + + + 88E07F9197F21D50F764DBA2D8F1C787 containerPortal @@ -988,7 +1041,7 @@ proxyType 1 remoteGlobalIDString - 7960D5870921BF8F1FE6568BBEB009B5 + 936C2599775DA43BC28A7798C3663474 remoteInfo BonMot @@ -1005,20 +1058,6 @@ sourceTree <group> - 8B63557B3C823AE3067A0A62BC06400A - - fileRef - 6300608DB9CA45054481077A506554A4 - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - 8CC583263BA5A300A8153C1A29EAB54E includeInIndex @@ -1067,6 +1106,20 @@ sourceTree <group> + 8EDCFA33B79E29125AFB799A710C0DB5 + + fileRef + 10BC01F663CA7849D1A21B7F337370F0 + isa + PBXBuildFile + settings + + ATTRIBUTES + + Public + + + 907B9450CCC329D3E8FFEB42C73C99EE includeInIndex @@ -1080,10 +1133,42 @@ sourceTree <group> - 91095713BC69DBA99E10B37AADC85750 + 936C2599775DA43BC28A7798C3663474 + + buildConfigurationList + AC0079E3C3B20A676835CB2D0B1573A9 + buildPhases + + CB0AE9160D3D0094A5DAE320035944A6 + 7FE1C74423FCE3D210EDB21FE1B4AC15 + 4F5FFDF0F9B2BE42D4027C887021A16F + + buildRules + + dependencies + + isa + PBXNativeTarget + name + BonMot + productName + BonMot + productReference + 987B388AEB3482E871DC28F0AE48017A + productType + com.apple.product-type.framework + + 95FE86EF52CFAE2CC062808BF065C3D7 fileRef - 8CC583263BA5A300A8153C1A29EAB54E + B40764AE97F30C2BAAD83E38C5B52A6D + isa + PBXBuildFile + + 96413C2DBE2E96EBC51E56737CFCBA6A + + fileRef + FC5E1EE731C93D8B60AB0AEA8CEF4500 isa PBXBuildFile settings @@ -1094,26 +1179,13 @@ - 9256FE898C951D38DA2ACB8BE4AC0C49 + 96647DB5078B8E474BF86755E8A3557A fileRef - 6E7E5AF37923BFA48D6994B9BFC6535F + D744BF15A46AA8823523F480524DA6D1 isa PBXBuildFile - 946B30FF1BFF268E9AA4A27BAC2E5ECD - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - BONTextable.h - sourceTree - <group> - 987B388AEB3482E871DC28F0AE48017A explicitFileType @@ -1175,26 +1247,68 @@ isa PBXBuildFile - 9DA064760DB15CDFD92BBF5A6F53276A + 9D19B2AC9B6CBC83D7017C1DBD745057 - buildConfigurations - - 840456DBAF5B6C919D75BBE641E11DDD - B95F26D83A85C76C3949416F60C7E1E1 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release + fileRef + C3B7BDF5CE5CB8F095FE12DA55D9A9BB isa - XCConfigurationList + PBXBuildFile - 9ECD0A9EB5DCC43C18F291D874BE6207 + 9DA7495C431B48DDE89B63D01FC1BC30 - fileRef - 2AA3029BF1F6DE94522F0133B389D5F1 + baseConfigurationReference + 907B9450CCC329D3E8FFEB42C73C99EE + buildSettings + + CODE_SIGN_IDENTITY[sdk=iphoneos*] + iPhone Developer + CURRENT_PROJECT_VERSION + 1 + DEFINES_MODULE + YES + DYLIB_COMPATIBILITY_VERSION + 1 + DYLIB_CURRENT_VERSION + 1 + DYLIB_INSTALL_NAME_BASE + @rpath + ENABLE_STRICT_OBJC_MSGSEND + YES + GCC_PREFIX_HEADER + Target Support Files/BonMot/BonMot-prefix.pch + INFOPLIST_FILE + Target Support Files/BonMot/Info.plist + INSTALL_PATH + $(LOCAL_LIBRARY_DIR)/Frameworks + IPHONEOS_DEPLOYMENT_TARGET + 8.0 + LD_RUNPATH_SEARCH_PATHS + + $(inherited) + @executable_path/Frameworks + @loader_path/Frameworks + + MODULEMAP_FILE + Target Support Files/BonMot/BonMot.modulemap + MTL_ENABLE_DEBUG_INFO + YES + PRODUCT_NAME + BonMot + SDKROOT + iphoneos + SKIP_INSTALL + YES + TARGETED_DEVICE_FAMILY + 1,2 + VERSIONING_SYSTEM + apple-generic + VERSION_INFO_PREFIX + + isa - PBXBuildFile + XCBuildConfiguration + name + Debug A03AFD7A68BE8224BF54B742392FB216 @@ -1218,14 +1332,14 @@ proxyType 1 remoteGlobalIDString - 7960D5870921BF8F1FE6568BBEB009B5 + 936C2599775DA43BC28A7798C3663474 remoteInfo BonMot - A357495232D4BF470F4CE2BC59C9FC09 + A42782D2876C5E4DF94A9F5A68D9AC59 fileRef - B40764AE97F30C2BAAD83E38C5B52A6D + 1FBDCB556EABB04AC7BF1AE02CCFD08D isa PBXBuildFile @@ -1236,7 +1350,7 @@ name BonMot target - 7960D5870921BF8F1FE6568BBEB009B5 + 936C2599775DA43BC28A7798C3663474 targetProxy 88E07F9197F21D50F764DBA2D8F1C787 @@ -1326,6 +1440,19 @@ name Debug + A8EE48AA815968BBE1E66AB3493C1EAA + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + UIImage+BonMotUtilities.m + sourceTree + <group> + AA8AFAE9DD36173AD2A960E6AA0A0B21 buildConfigurations @@ -1340,27 +1467,34 @@ isa XCConfigurationList - AE604A23841D67F0735CD4518EB98573 + AC0079E3C3B20A676835CB2D0B1573A9 + + buildConfigurations + + 9DA7495C431B48DDE89B63D01FC1BC30 + 65901492A3F64EDD919D5D15AE892F84 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + AD22D5D55F424595890CEBFBC370239C fileRef - 9CC10C208517AA62B5773BA59C750FCB + B0F2F8A5622AF131C09AB271FA889955 isa PBXBuildFile settings ATTRIBUTES - Public + Private - AE93C94C5CFC1F805B75EE4FD8FFD8E3 - - fileRef - 854FD81E8C8A6D67C6177C34537726A8 - isa - PBXBuildFile - B0AFE2D343DE0EA96E89CE31EF4A1EC4 isa @@ -1368,10 +1502,23 @@ name BonMot target - 7960D5870921BF8F1FE6568BBEB009B5 + 936C2599775DA43BC28A7798C3663474 targetProxy A106EC63441BF09009E975CB16456475 + B0F2F8A5622AF131C09AB271FA889955 + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + BONText_Private.h + sourceTree + <group> + B40764AE97F30C2BAAD83E38C5B52A6D includeInIndex @@ -1400,62 +1547,6 @@ sourceTree <group> - B95F26D83A85C76C3949416F60C7E1E1 - - baseConfigurationReference - 907B9450CCC329D3E8FFEB42C73C99EE - buildSettings - - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - CURRENT_PROJECT_VERSION - 1 - DEFINES_MODULE - YES - DYLIB_COMPATIBILITY_VERSION - 1 - DYLIB_CURRENT_VERSION - 1 - DYLIB_INSTALL_NAME_BASE - @rpath - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_PREFIX_HEADER - Target Support Files/BonMot/BonMot-prefix.pch - INFOPLIST_FILE - Target Support Files/BonMot/Info.plist - INSTALL_PATH - $(LOCAL_LIBRARY_DIR)/Frameworks - IPHONEOS_DEPLOYMENT_TARGET - 8.0 - LD_RUNPATH_SEARCH_PATHS - - $(inherited) - @executable_path/Frameworks - @loader_path/Frameworks - - MODULEMAP_FILE - Target Support Files/BonMot/BonMot.modulemap - MTL_ENABLE_DEBUG_INFO - NO - PRODUCT_NAME - BonMot - SDKROOT - iphoneos - SKIP_INSTALL - YES - TARGETED_DEVICE_FAMILY - 1,2 - VERSIONING_SYSTEM - apple-generic - VERSION_INFO_PREFIX - - - isa - XCBuildConfiguration - name - Release - BA6428E9F66FD5A23C0A2E06ED26CD2F includeInIndex @@ -1488,19 +1579,29 @@ sourceTree <group> - BE8601DA8860F7392B95146FC34AA1C7 + BB09BD8F14BEF9FC69FEB912DCCED55C - children - - 13394710B1F81A23CB801EC414777F81 - ED3DCC7B86AC9F7CF038A2160156040E - + includeInIndex + 1 isa - PBXGroup - name - Pod + PBXFileReference + lastKnownFileType + sourcecode.c.h path - Pod + BONTag.h + sourceTree + <group> + + BCCC75202C930C2B7DD65DF3E5EEDF8D + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + BONTextable.h sourceTree <group> @@ -1570,32 +1671,19 @@ sourceTree <group> - C7AC1A115C88CDA55C8F507E58A1DDBF + C9426127C7D9364DA549FFA1CBAAE21E - buildActionMask - 2147483647 - files - - F522BE1C9B2D42743D52239BE44E1CC5 - CE30C228369D6FD5AEC0AB99341AA4CF - EF3639A79EE8F73A22918377F0B42D82 - D39213E93EC1038DC1113AA269E845AF - 2C763DD88B7C65E2E698491B831F3F3D - 01E1BE6EC18457D2F88E270800F860C3 - 00D954F3747AA02DCA0A3809CFCD2B17 - D57F5373D6DC1FBDE880D03BC460552E - 223A0AE1B4C1AB6C22803C3F6F4E7C36 - 91095713BC69DBA99E10B37AADC85750 - 32467CAF5979C65E3DB3C11F76C85B53 - 79C19A2B453E4B0567FF6F03C5B1B0EA - D1BB6107B193D42B208AE41C084B7438 - AE604A23841D67F0735CD4518EB98573 - 8B63557B3C823AE3067A0A62BC06400A - + fileRef + 8CC583263BA5A300A8153C1A29EAB54E isa - PBXHeadersBuildPhase - runOnlyForDeploymentPostprocessing - 0 + PBXBuildFile + settings + + ATTRIBUTES + + Public + + CA7D9F5FC702DA9A09A5F3128839B84A @@ -1610,6 +1698,29 @@ sourceTree <group> + CB0AE9160D3D0094A5DAE320035944A6 + + buildActionMask + 2147483647 + files + + 38F8823CD9270173773704F2E964D6BB + 06821309EE78916F536F3C35B4FAA4B2 + A42782D2876C5E4DF94A9F5A68D9AC59 + 60209B4FEFC22D3FDD07669CBB7A5D4A + 80B3B962FB551BA93445CCBECA715AC8 + 95FE86EF52CFAE2CC062808BF065C3D7 + 0080E3345719F60099A110B1C33C05F2 + 17C1148DA9BA0313110FC2436C3C936F + 96647DB5078B8E474BF86755E8A3557A + CEB8563653F8C3F6F2EC101EBA790485 + CCE0D044A452AF00A504EC9739EEA80A + + isa + PBXSourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + CB2FD0FB92AFB48BF4840DB66DE560C8 includeInIndex @@ -1623,6 +1734,13 @@ sourceTree <group> + CCE0D044A452AF00A504EC9739EEA80A + + fileRef + 71C80B4B47FA325B1D150662D2EF15C2 + isa + PBXBuildFile + CDA4F5B325BBC8347176E2207B0430BF baseConfigurationReference @@ -1685,10 +1803,17 @@ name Release - CE30C228369D6FD5AEC0AB99341AA4CF + CEB8563653F8C3F6F2EC101EBA790485 + + fileRef + 65E326689AAFF02F1507650324813E89 + isa + PBXBuildFile + + D09F361B9D1EE649D7E855E3A4727820 fileRef - 28D4FDE16DE8250F25DC730765DC221B + BB09BD8F14BEF9FC69FEB912DCCED55C isa PBXBuildFile settings @@ -1725,34 +1850,6 @@ runOnlyForDeploymentPostprocessing 0 - D1BB6107B193D42B208AE41C084B7438 - - fileRef - 10BE4F0AA4813318BC4FB1C492F89E90 - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - - D39213E93EC1038DC1113AA269E845AF - - fileRef - 10BC01F663CA7849D1A21B7F337370F0 - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - D41D8CD98F00B204E9800998ECF8427E attributes @@ -1788,7 +1885,7 @@ targets - 7960D5870921BF8F1FE6568BBEB009B5 + 936C2599775DA43BC28A7798C3663474 E84C4E74F02AFF5309A6A3817A638C1F C6A0921570F1E8C9ECC7B63F726B4556 @@ -1806,20 +1903,6 @@ sourceTree <group> - D57F5373D6DC1FBDE880D03BC460552E - - fileRef - E1927D7CD5C58406A4D41F8C171BF93B - isa - PBXBuildFile - settings - - ATTRIBUTES - - Private - - - D628178EF8FFEF0FD32001265F6CC679 buildConfigurations @@ -1949,19 +2032,6 @@ name Debug - E1927D7CD5C58406A4D41F8C171BF93B - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - BONText_Private.h - sourceTree - <group> - E25641E248FCA33FB78676B255411C8B includeInIndex @@ -1982,6 +2052,20 @@ isa PBXBuildFile + E3C1E20D4E3714E84B74E812591B9970 + + fileRef + 6868B12CE64A02DC58A825D43FCD6AED + isa + PBXBuildFile + settings + + ATTRIBUTES + + Private + + + E40A36D2E548722345E1738CFBAB4824 includeInIndex @@ -2048,33 +2132,6 @@ sourceTree <group> - ED3DCC7B86AC9F7CF038A2160156040E - - children - - 726940D4C81A5A90179226804F99A3A0 - 1BB8C8B0D6FFC76CF6E70FF4AAFD9A79 - 28D4FDE16DE8250F25DC730765DC221B - 085FB431F3FE96089534EAAC6FE8BDF8 - 2AA3029BF1F6DE94522F0133B389D5F1 - FAB64E9029A05F73B2B4A250B105E030 - 212D5BC776EC758921C6BA007E2B182E - E1927D7CD5C58406A4D41F8C171BF93B - 946B30FF1BFF268E9AA4A27BAC2E5ECD - 7656BC2502D0F175B42AA5251C923450 - 29A9CD70F27600DBF695A88C9FF0AF50 - 53BEB5EF5FD30BA79950A4BD482E3F3B - 854FD81E8C8A6D67C6177C34537726A8 - - isa - PBXGroup - name - Classes - path - Classes - sourceTree - <group> - ED562394440514D7886F9C32296C2260 children @@ -2089,24 +2146,10 @@ sourceTree <group> - EF3639A79EE8F73A22918377F0B42D82 - - fileRef - CA7D9F5FC702DA9A09A5F3128839B84A - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - - F522BE1C9B2D42743D52239BE44E1CC5 + F70AB5068C980C99E1D3CB1D0EEC3356 fileRef - 726940D4C81A5A90179226804F99A3A0 + 10BE4F0AA4813318BC4FB1C492F89E90 isa PBXBuildFile settings @@ -2131,19 +2174,6 @@ - FAB64E9029A05F73B2B4A250B105E030 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - BONText.h - sourceTree - <group> - FB45FFD90572718D82AB9092B750F0CA buildSettings @@ -2212,12 +2242,62 @@ name Release - FC8B3E0621B411FD79396927F3D0BC13 + FB5E82DA859C42DFF27B99BDC321C21F fileRef - 1BB8C8B0D6FFC76CF6E70FF4AAFD9A79 + 6300608DB9CA45054481077A506554A4 isa PBXBuildFile + settings + + ATTRIBUTES + + Public + + + + FC5E1EE731C93D8B60AB0AEA8CEF4500 + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + BONText.h + sourceTree + <group> + + FD46195AAC067287FDF6898C915D60C0 + + children + + 47FFC4B90C298DBEA36FB326D190DBAB + 61712505D3A454DA4C6DB55A419A8F77 + 104DDEC1C49A667B0F3B8AD43A9B20D2 + 6A767B48037B33454A2A30879F170FDE + 1FBDCB556EABB04AC7BF1AE02CCFD08D + BB09BD8F14BEF9FC69FEB912DCCED55C + 055C51A91D330461B7E2BE7BCD601A5B + 6868B12CE64A02DC58A825D43FCD6AED + FC5E1EE731C93D8B60AB0AEA8CEF4500 + 38A2136A23298CA3E69A4E692A4317E9 + B0F2F8A5622AF131C09AB271FA889955 + BCCC75202C930C2B7DD65DF3E5EEDF8D + 1F7D0CFA4095C319AEDC668D27E01781 + 17E86E730C75BDA8893FD85A83CD3723 + 7C6D0196353D8E5AC65F6AC29B7C8777 + A8EE48AA815968BBE1E66AB3493C1EAA + + isa + PBXGroup + name + Classes + path + Classes + sourceTree + <group> FEC4B2323A49EDD7A9FE32C85F2DE364 diff --git a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/BonMot.xcscheme b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/BonMot.xcscheme index a784c97d..49c32ed6 100644 --- a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/BonMot.xcscheme +++ b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/BonMot.xcscheme @@ -14,7 +14,7 @@ buildForArchiving = "YES"> diff --git a/Example/Pods/Target Support Files/BonMot/BonMot-umbrella.h b/Example/Pods/Target Support Files/BonMot/BonMot-umbrella.h index 638c08bc..46810c1c 100644 --- a/Example/Pods/Target Support Files/BonMot/BonMot-umbrella.h +++ b/Example/Pods/Target Support Files/BonMot/BonMot-umbrella.h @@ -4,6 +4,7 @@ #import "BONChain.h" #import "BONCompatibility.h" #import "BONSpecial.h" +#import "BONTag.h" #import "BONText.h" #import "BONTextable.h" #import "NSAttributedString+BonMotUtilities.h" diff --git a/Example/Pods/Target Support Files/BonMot/BonMot.modulemap b/Example/Pods/Target Support Files/BonMot/BonMot.modulemap index 43546d1f..f0217cf1 100644 --- a/Example/Pods/Target Support Files/BonMot/BonMot.modulemap +++ b/Example/Pods/Target Support Files/BonMot/BonMot.modulemap @@ -4,5 +4,6 @@ framework module BonMot { export * module * { export * } + private header "BONTag_Private.h" private header "BONText_Private.h" } diff --git a/Example/Tests/BONTagComplexStylesTestCase.m b/Example/Tests/BONTagComplexStylesTestCase.m new file mode 100644 index 00000000..b635c273 --- /dev/null +++ b/Example/Tests/BONTagComplexStylesTestCase.m @@ -0,0 +1,406 @@ +// +// BONTagComplexStylingTestCase.m +// BonMot +// +// Created by Nora Trapp on 3/3/16. +// +// + +#import "BONBaseTestCase.h" + +@import BonMot; + +@interface BONTagComplexStylingTestCase : BONBaseTestCase + +@end + +@implementation BONTagComplexStylingTestCase + +- (void)testSingleTagSingleStyle +{ + BONChain *chain = BONChain.new.string(@"Hello, [i]world(i)!") + .tagStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 7) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleTagsSingleStyle +{ + BONChain *chain = BONChain.new.string(@"[i]Hello(i), [i]world(i)!") + .tagStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(5, 2) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testSingleTagMultipleStyles +{ + BONChain *chain = BONChain.new.string(@"Hello, >bb<*!") + .tagStyles(@[ + BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 7) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleTagsMultipleStyles +{ + BONChain *chain = BONChain.new.string(@">bb<*, [i]world(i)!") + .tagStyles(@[ + BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(5, 2) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testInterleavedTags +{ + BONChain *chain = BONChain.new.string(@">bb<*, world(i)!") + .tagStyles(@[ + BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello[i], world(i)!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 8) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(8, 11) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testNestedTags +{ + BONChain *chain = BONChain.new.string(@">b<[i]Hello(i)>b<*, world!") + .tagStyles(@[ + BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"[i]Hello(i), world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 11) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(11, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMixedOrdering +{ + BONChain *chain = BONChain.new.string(@">bb<*[i]lo(i), >bb<*[i]ld!(i)") + .tagStyles(@[ + BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 3) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(3, 2) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(5, 2) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 3) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(10, 3) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testEscapedStartTag +{ + BONChain *chain = BONChain.new.string(@"qwerty(b)(b)Hello[/b], world!") + .tagStyles(@[ + BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"(b)Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 3) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(3, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(8, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleEscapedStartTag +{ + BONChain *chain = BONChain.new.string(@"qwerty(b)(b)Hello[/b]qwerty(b), world!") + .tagStyles(@[ + BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"(b)Hello(b), world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 3) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(3, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(8, 11) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleEscapedEndTag +{ + BONChain *chain = BONChain.new.string(@"qwerty[/b](b)Hello[/b]qwerty[/b], world!") + .tagStyles(@[ + BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"[/b]Hello[/b], world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 4) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(4, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(9, 12) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testNestedEscapedEndTag +{ + BONChain *chain = BONChain.new.string(@"(b)Helloqwerty[/b][/b], world!") + .tagStyles(@[ + BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello[/b], world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 9) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(9, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testInterleavedEscapedEndTag +{ + BONChain *chain = BONChain.new.string(@"qwerty[/b]qwerty[/i]qwerty[/b](i)Helloqwerty[/i][/i], world!") + .tagStyles(@[ + BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"[/b][/i][/b]Hello[/i], world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 12) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 9) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(21, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +@end diff --git a/Example/Tests/BONTagStylesTestCase.m b/Example/Tests/BONTagStylesTestCase.m new file mode 100644 index 00000000..58b8203b --- /dev/null +++ b/Example/Tests/BONTagStylesTestCase.m @@ -0,0 +1,406 @@ +// +// BONTagStylesTestCase.m +// BonMot +// +// Created by Nora Trapp on 3/3/16. +// +// + +#import "BONBaseTestCase.h" + +@import BonMot; + +@interface BONTagStylesTestCase : BONBaseTestCase + +@end + +@implementation BONTagStylesTestCase + +- (void)testSingleTagSingleStyle +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 7) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleTagsSingleStyle +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(5, 2) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testSingleTagMultipleStyles +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 7) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleTagsMultipleStyles +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(5, 2) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testInterleavedTags +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 8) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(8, 12) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testNestedTags +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 12) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMixedOrdering +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 3) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(3, 2) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(5, 2) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 3) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(10, 3) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testEscapedStartTag +{ + BONChain *chain = BONChain.new.string(@"\\Hello, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 3) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(3, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(8, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleEscapedStartTag +{ + BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 3) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(3, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(8, 11) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleEscapedEndTag +{ + BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 4) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(4, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(9, 12) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testNestedEscapedEndTag +{ + BONChain *chain = BONChain.new.string(@"Hello\\, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 9) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(9, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testInterleavedEscapedEndTag +{ + BONChain *chain = BONChain.new.string(@"\\\\\\Hello\\, world!") + .tagStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 12) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 9) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(21, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +@end diff --git a/Pod/BonMot.h b/Pod/BonMot.h index 3cd1219c..208e10b3 100644 --- a/Pod/BonMot.h +++ b/Pod/BonMot.h @@ -10,3 +10,4 @@ #import "BONChain.h" #import "BONSpecial.h" #import "UIImage+BonMotUtilities.h" +#import "BONTag.h" diff --git a/Pod/Classes/BONChain.h b/Pod/Classes/BONChain.h index f2d0829c..48659d30 100644 --- a/Pod/Classes/BONChain.h +++ b/Pod/Classes/BONChain.h @@ -13,7 +13,7 @@ BON_ASSUME_NONNULL_BEGIN -@class BONChain; +@class BONChain, BONTag; typedef BONChain *BONCNonnull (^BONChainFontNameAndSize)(NSString *BONCNonnull fontName, CGFloat fontSize); typedef BONChain *BONCNonnull (^BONChainFont)(UIFont *BONCNullable font); @@ -44,6 +44,8 @@ typedef BONChain *BONCNonnull (^BONChainUnderlineColor)(UIColor *BONCNullable co typedef BONChain *BONCNonnull (^BONChainStrikethroughStyle)(NSUnderlineStyle style); typedef BONChain *BONCNonnull (^BONChainStrikethroughColor)(UIColor *BONCNullable color); +typedef BONChain *BONCNonnull (^BONTagStyles)(BONGeneric(NSArray, BONTag *) * BONCNullable styles); + @interface BONChain : NSObject @property (copy, nonatomic, readonly) NSAttributedString *attributedString; @@ -94,6 +96,14 @@ typedef BONChain *BONCNonnull (^BONChainStrikethroughColor)(UIColor *BONCNullabl @property (copy, nonatomic, readonly) BONChainStrikethroughStyle strikethroughStyle; @property (copy, nonatomic, readonly) BONChainStrikethroughColor strikethroughColor; +/** + * Assign @p BONTextables to use in styling substrings surrounded in given tags. + * For example, ["b": boldTextable] would apply the @p boldChain + * to any substring surrounded by and remove the tags from the resulting + * attributed string. Nested tagging is not supported. + */ +@property (copy, nonatomic, readonly) BONTagStyles tagStyles; + // concatenation - (void)appendLink:(id)link; - (void)appendLink:(id)link separator:(BONNullable NSString *)separator; diff --git a/Pod/Classes/BONChain.m b/Pod/Classes/BONChain.m index 6dba4990..d1122c8b 100644 --- a/Pod/Classes/BONChain.m +++ b/Pod/Classes/BONChain.m @@ -353,6 +353,17 @@ - (BONChainStrikethroughColor)strikethroughColor return [strikethroughColorBlock copy]; } +- (BONTagStyles)tagStyles +{ + BONTagStyles tagStylesBlock = ^(NSArray *tagStyles) { + __typeof(self) newChain = self.copyWithoutNextText; + newChain.text.tagStyles = tagStyles; + return newChain; + }; + + return [tagStylesBlock copy]; +} + - (void)appendLink:(id)link { [self appendLink:link separator:nil]; diff --git a/Pod/Classes/BONTag.h b/Pod/Classes/BONTag.h new file mode 100644 index 00000000..d7c7fc60 --- /dev/null +++ b/Pod/Classes/BONTag.h @@ -0,0 +1,69 @@ +// +// BONTag.h +// Pods +// +// Created by Nora Trapp on 5/11/16. +// +// + +#import +#import "BONCompatibility.h" + +@protocol BONTextable; + +@interface BONTag : NSObject + +@property (copy, nonatomic, readonly, BONNonnull) NSString *startTag; +@property (copy, nonatomic, readonly, BONNonnull) NSString *endTag; +@property (copy, nonatomic, readonly, BONNonnull) NSString *escapeString; +@property (copy, nonatomic, readonly, BONNonnull) id textable; + +- (BONNonnull instancetype)initWithStartTag:(BONNonnull NSString *)startTag endTag:(BONNonnull NSString *)endTag escapeString:(BONNonnull NSString *)escapeString textable:(BONNonnull id)textable; +- (BONNonnull instancetype)initWithTag:(BONNonnull NSString *)tag textable:(BONNonnull id)textable; + +@end + +/** + * Create a tag with a custom start/end/escape combination. + * + * @param startTag The start tag. + * @param endTag The end tag. + * @param escapeString The escape string. + * @param textable The style to apply. + * + * @return A @p BONTag instance representing the tag. + */ +CG_INLINE BONTag *BONCNonnull BONTagComplexMake(NSString *BONCNonnull startTag, NSString *BONCNonnull endTag, NSString *BONCNonnull escapeString, id BONCNonnull textable) +{ + return [[BONTag alloc] initWithStartTag:startTag endTag:endTag escapeString:escapeString textable:textable]; +} + +/** + * Create a tag using the default start and end formats and using \ as an escape character. + * + * @param tag The tag string. + * @param textable The style to apply. + * + * @return A @p BONTag instance representing the tag. + */ +CG_INLINE BONTag *BONCNonnull BONTagMake(NSString *BONCNonnull tag, id BONCNonnull textable) +{ + return [[BONTag alloc] initWithTag:tag textable:textable]; +} + +/** + * Create an array of tag objects from a dictionary of ["tag": textable] + * + * @param dictionary A dictionary of textables mapped to tags. + * + * @return An array of @p BONTag objects representing the tags in the dictionary. + */ +CG_INLINE BONGeneric(NSArray, BONTag *) * BONCNonnull BONTagsFromDictionary(BONGeneric(NSDictionary, NSString *, id) * BONCNonnull dictionary) +{ + NSMutableArray *tags = [NSMutableArray array]; + for (NSString *key in dictionary) { + id textable = dictionary[key]; + [tags addObject:BONTagMake(key, textable)]; + } + return tags; +} diff --git a/Pod/Classes/BONTag.m b/Pod/Classes/BONTag.m new file mode 100644 index 00000000..50ed19ff --- /dev/null +++ b/Pod/Classes/BONTag.m @@ -0,0 +1,235 @@ +// +// BONTag.m +// Pods +// +// Created by Nora Trapp on 5/11/16. +// +// + +#import "BONTag_Private.h" +#import "BONMot.h" + +static const NSString *kBONTagDefaultStartPrefix = @"<"; +static const NSString *kBONTagDefaultStartSuffix = @">"; +static const NSString *kBONTagDefaultEndPrefix = @""; +static const NSString *kBONTagDefaultEscapeString = @"\\"; + +@interface BONTag () + +@property (copy, nonatomic) NSString *startTag; +@property (copy, nonatomic) NSString *endTag; +@property (copy, nonatomic) NSString *escapeString; +@property (copy, nonatomic) id textable; + +@end + +@implementation BONTag + +- (instancetype)initWithStartTag:(NSString *)startTag endTag:(NSString *)endTag escapeString:(NSString *)escapeString textable:(id)textable +{ + self = [super init]; + if (self) { + self.startTag = startTag; + self.endTag = endTag; + self.escapeString = escapeString; + self.textable = textable; + self.ranges = [NSMutableArray array]; + } + + return self; +} + +- (instancetype)initWithTag:(NSString *)tag textable:(id)textable +{ + NSString *startTag = [NSString stringWithFormat:@"%@%@%@", kBONTagDefaultStartPrefix, tag, kBONTagDefaultStartSuffix]; + NSString *endTag = [NSString stringWithFormat:@"%@%@%@", kBONTagDefaultEndPrefix, tag, kBONTagDefaultEndSuffix]; + + return [self initWithStartTag:startTag endTag:endTag escapeString:(NSString *)kBONTagDefaultEscapeString textable:textable]; +} + +- (id)copyWithZone:(NSZone *)zone +{ + __typeof(self) tag = [[self.class alloc] init]; + + tag.startTag = self.startTag; + tag.endTag = self.endTag; + tag.escapeString = self.escapeString; + tag.textable = self.textable; + tag.ranges = self.ranges; + + return tag; +} + +- (NSString *)description +{ + NSString *description = [NSString stringWithFormat:@"<%@: %p, startTag: %@, endTag: %@, textable: <%@: %p>>", NSStringFromClass(self.class), self, self.startTag, self.endTag, NSStringFromClass(self.textable.class), self.textable]; + + return description; +} + +#pragma mark - Tag Matching + ++ (NSArray *)escapedRangesInString:(NSString **)string withTags:(NSArray *)tags stripEscapeCharacters:(BOOL)stripEscapeCharacters +{ + NSMutableArray *escapedRanges = [NSMutableArray array]; + + NSString *theString = *string; + + NSRange searchRange = NSMakeRange(0, theString.length); + + // Iterate over the string finding each escape in order until there are no more escape strings + while (YES) { + BONTag *nextTag; + NSRange nextEscapeRange; + NSRange nextEscapedTagRange; + + // Find the next escape string + for (BONTag *tag in tags) { + NSRange escapeRange = [theString rangeOfString:tag.escapeString options:0 range:searchRange]; + + if (escapeRange.location != NSNotFound) { + if (!nextTag || (escapeRange.location < nextEscapeRange.location)) { + // Check if this character is escaping a start or end tag + for (NSString *tagString in @[ tag.startTag, tag.endTag ]) { + // Check if there is room for this tag to exist after the escape character + if ((NSInteger)(NSMaxRange(escapeRange) + tagString.length) < theString.length) { + // Check if the following characters are actually the tag + NSRange potentialEscapedTagRange = NSMakeRange(NSMaxRange(escapeRange), tagString.length); + if ([[theString substringWithRange:potentialEscapedTagRange] isEqualToString:tagString]) { + nextTag = tag; + nextEscapeRange = escapeRange; + nextEscapedTagRange = potentialEscapedTagRange; + } + } + } + } + } + } + + if (!nextTag) { + break; + } + + NSRange range = NSMakeRange(nextEscapeRange.location, nextEscapedTagRange.length + nextEscapeRange.length); + + if (stripEscapeCharacters) { + theString = [theString stringByReplacingOccurrencesOfString:nextTag.escapeString withString:@"" options:0 range:nextEscapeRange]; + range = NSMakeRange(nextEscapeRange.location, nextEscapedTagRange.length); + } + + [escapedRanges addObject:[NSValue valueWithRange:range]]; + + searchRange = NSMakeRange(NSMaxRange(range), [theString length] - NSMaxRange(range)); + } + + *string = theString; + + return escapedRanges; +} + ++ (NSRange)findNextString:(NSString *)string inString:(NSString *)stringToSearch ignoringRanges:(NSArray *)escapedRanges range:(NSRange)range +{ + NSRange searchRange = range; + + while (YES) { + NSRange stringRange = [stringToSearch rangeOfString:string options:0 range:searchRange]; + + // Ignore this match + if ([escapedRanges containsObject:[NSValue valueWithRange:stringRange]]) { + searchRange = NSMakeRange(NSMaxRange(stringRange), [stringToSearch length] - NSMaxRange(stringRange)); + continue; + } + + return stringRange; + } +} + ++ (NSArray *)rangesInString:(NSString **)string betweenTags:(NSArray *)tags stripTags:(BOOL)stripTags +{ + NSArray *escapedRanges = [BONTag escapedRangesInString:string withTags:tags stripEscapeCharacters:stripTags]; + + NSArray *tagsWithRanges = [[NSArray alloc] initWithArray:tags copyItems:YES]; + + NSString *theString = *string; + + NSRange searchRange = NSMakeRange(0, theString.length); + + // Iterate over the string finding each tag in order until there are no more tags + while (YES) { + BONTag *nextTag; + NSRange nextStartTagRange; + NSRange nextEndTagRange; + + // Find the next start tag + for (BONTag *tag in tagsWithRanges) { + NSRange startTagRange = [BONTag findNextString:tag.startTag inString:theString ignoringRanges:escapedRanges range:searchRange]; + NSRange endTagRange = [BONTag findNextString:tag.endTag inString:theString ignoringRanges:escapedRanges range:searchRange]; + if (startTagRange.location != NSNotFound && endTagRange.location != NSNotFound) { + if (!nextTag || (startTagRange.location < nextStartTagRange.location)) { + nextTag = tag; + nextStartTagRange = startTagRange; + nextEndTagRange = endTagRange; + } + } + } + + if (!nextTag) { + break; + } + + NSRange range = NSMakeRange(NSMaxRange(nextStartTagRange), nextEndTagRange.location - NSMaxRange(nextStartTagRange)); + + if (stripTags) { + range.location -= nextTag.startTag.length; + + theString = [theString stringByReplacingOccurrencesOfString:nextTag.startTag withString:@"" options:0 range:nextStartTagRange]; + nextStartTagRange.length = 0; + + nextEndTagRange.location -= nextTag.startTag.length; + theString = [theString stringByReplacingOccurrencesOfString:nextTag.endTag withString:@"" options:0 range:nextEndTagRange]; + nextEndTagRange.length = 0; + } + + [nextTag.ranges addObject:[NSValue valueWithRange:range]]; + + searchRange = NSMakeRange(NSMaxRange(nextEndTagRange), [theString length] - NSMaxRange(nextEndTagRange)); + } + + *string = theString; + + return tagsWithRanges; +} + +#pragma mark - Equality + +- (BOOL)isEqualToTag:(BONTag *)tag +{ + BOOL startTagEqual = [tag.startTag isEqualToString:self.startTag]; + BOOL endTagEqual = [tag.endTag isEqualToString:self.endTag]; + BOOL chainEqual = [tag.textable isEqual:self.textable]; + BOOL escapeEqual = [tag.escapeString isEqualToString:self.escapeString]; + BOOL rangesEqual = [tag.ranges isEqualToArray:self.ranges]; + + return startTagEqual && endTagEqual && chainEqual && escapeEqual && rangesEqual; +} + +- (BOOL)isEqual:(id)object +{ + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[BONTag class]]) { + return NO; + } + + return [self isEqualToTag:(BONTag *)object]; +} + +- (NSUInteger)hash +{ + return self.startTag.hash ^ self.endTag.hash ^ self.textable.hash ^ self.escapeString.hash ^ self.ranges.hash; +} + +@end diff --git a/Pod/Classes/BONTag_Private.h b/Pod/Classes/BONTag_Private.h new file mode 100644 index 00000000..82928121 --- /dev/null +++ b/Pod/Classes/BONTag_Private.h @@ -0,0 +1,49 @@ +// +// BONTag_Private.h +// Pods +// +// Created by Nora Trapp on 5/11/16. +// +// + +#import "BONTag.h" + +@interface BONTag () + +@property (strong, nonatomic, BONNonnull) BONGeneric(NSMutableArray, NSValue *) * ranges; + +/** + * Finds all escaped tags within a given string. + * + * @param string The string to search. + * @param tags The tags to search for. + * @param stripEscapeCharacters If YES, the escape characters will be removed from the string and the resulting ranges. + * + * @return An array of ranges representing the escaped tags. + */ ++ (BONNonnull BONGeneric(NSArray, NSValue *) *)escapedRangesInString:(NSString *BONCNonnull *BONCNonnull)string withTags:(BONNonnull BONGeneric(NSArray, BONTag *) *)tags stripEscapeCharacters:(BOOL)stripEscapeCharacters; + +/** + * Search through a string to find the next matching string that is not in an escaped range. + * + * @param string The string to look for. + * @param stringToSearch The string to search within. + * @param escapedRanges The ranges to ignore matches within. + * @param range The range of @p string to search within. + * + * @return The range of the matched string. If the string is not found, location will be NSNotFound. + */ ++ (NSRange)findNextString:(BONNonnull NSString *)string inString:(BONNonnull NSString *)stringToSearch ignoringRanges:(BONNonnull BONGeneric(NSArray, NSValue *) *)escapedRanges range:(NSRange)range; + +/** + * Find all tagged strings within a given string. + * + * @param string The string to search. + * @param tags The tags to search for. + * @param stripTags If YES, the start and end tags will be stripped from the string and the resulting ranges. + * + * @return An array of tags with the matching ranges defined. + */ ++ (BONNonnull BONGeneric(NSArray, BONTag *) *)rangesInString:(NSString *BONCNonnull *BONCNonnull)string betweenTags:(BONNonnull BONGeneric(NSArray, BONTag *) *)tags stripTags:(BOOL)stripTags; + +@end diff --git a/Pod/Classes/BONText.h b/Pod/Classes/BONText.h index 0f305e6f..74e7b291 100644 --- a/Pod/Classes/BONText.h +++ b/Pod/Classes/BONText.h @@ -23,7 +23,7 @@ typedef NS_ENUM(NSUInteger, BONFigureSpacing) { BONFigureSpacingProportional, }; -@class BONText; +@class BONText, BONTag; @interface BONText : NSObject @@ -84,6 +84,14 @@ typedef NS_ENUM(NSUInteger, BONFigureSpacing) { @property (nonatomic) NSUnderlineStyle strikethroughStyle; @property (strong, nonatomic, BONNullable) UIColor *strikethroughColor; +/** + * Assign @p BONTextables to use in styling substrings surrounded in given tags. + * For example, ["b": boldChainable] would apply the @p boldChain + * to any substring surrounded by and remove the tags from the resulting + * attributed string. Nested tagging is not supported. + */ +@property (strong, nonatomic, BONNullable) BONGeneric(NSArray, BONTag *) * tagStyles; + // Getting Values Out @property (copy, nonatomic, readonly, BONNonnull) NSAttributedString *attributedString; diff --git a/Pod/Classes/BONText.m b/Pod/Classes/BONText.m index 152d4a36..1ad158b7 100644 --- a/Pod/Classes/BONText.m +++ b/Pod/Classes/BONText.m @@ -9,6 +9,7 @@ #import "BONText.h" #import "BONText_Private.h" #import "BONSpecial.h" +#import "BONTag_Private.h" @import CoreText.SFNTLayoutTypes; @@ -72,6 +73,8 @@ - (NSAttributedString *)attributedStringLastConcatenant:(BOOL)lastConcatenant { NSMutableAttributedString *mutableAttributedString = nil; + NSString *string = self.string; + if (self.image) { NSAssert(!self.string, @"If self.image is non-nil, self.string must be nil"); NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; @@ -89,11 +92,26 @@ - (NSAttributedString *)attributedStringLastConcatenant:(BOOL)lastConcatenant [mutableAttributedString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\t" attributes:self.attributes]]; } } - else if (self.string) { - mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:self.string + else if (string) { + // If there is tag styling applied, strip the tags from the string and identify the ranges to apply the tag based chains to. + NSArray *rangesPerTag = nil; + + if (self.tagStyles) { + rangesPerTag = [BONTag rangesInString:&string betweenTags:self.tagStyles stripTags:YES]; + } + + mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:string attributes:self.attributes]; - if (lastConcatenant && self.string.length > 0) { - NSRange lastCharacterRange = NSMakeRange(self.string.length - 1, 1); + + for (BONTag *tag in rangesPerTag) { + NSDictionary *attributes = tag.textable.text.attributes; + for (NSValue *value in tag.ranges) { + [mutableAttributedString setAttributes:attributes range:value.rangeValue]; + } + } + + if (lastConcatenant && string.length > 0) { + NSRange lastCharacterRange = NSMakeRange(string.length - 1, 1); [mutableAttributedString removeAttribute:NSKernAttributeName range:lastCharacterRange]; } else { @@ -114,8 +132,8 @@ - (NSAttributedString *)attributedStringLastConcatenant:(BOOL)lastConcatenant if (self.image) { indentation += self.image.size.width; } - else if (self.string) { - NSAttributedString *measurementString = [[NSAttributedString alloc] initWithString:self.string attributes:self.attributes]; + else if (string) { + NSAttributedString *measurementString = [[NSAttributedString alloc] initWithString:string attributes:self.attributes]; CGRect boundingRect = [measurementString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil]; @@ -409,6 +427,8 @@ - (id)copyWithZone:(NSZone *)zone text.strikethroughStyle = self.strikethroughStyle; text.strikethroughColor = self.strikethroughColor; + text.tagStyles = self.tagStyles; + return text; } diff --git a/README.md b/README.md index 7c2fe0c8..a469a756 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ BonMot uses attributed strings to give you control over the following typographi - Figure case (uppercase vs. lowercase numbers) - Figure spacing (monospace vs. proportional numbers) - Inline Images with optional multi-line paragraph alignment +- Tag parsing (excluding nested tags) Think something is missing? Please [file an issue](https://github.com/Raizlabs/BonMot/issues) (or add a +1 if one already exists). @@ -267,6 +268,45 @@ Prints this: {image24x36}{noBreakSpace}Monday{enDash}Friday ``` +## Tag Styles + +BonMot can style text between arbirtrary tags using a `` format and `\` as an escape character. + +```objc +BONChain *boldChain = BONChain.new.fontNameAndSize(@"Baskerville-Bold", 15.0f); +BONChain *italicChain = BONChain.new.fontNameAndSize(@"Baskerville-Italic", 15.0f); + +BONChain *chain = BONChain.new.fontNameAndSize(@"Baskerville", 17.0f) + .tagStyles(@[BONTagMake(@"bold", boldChain), BONTagMake(@"italic", italicChain)]) + .string(@"This text is wrapped in a \\ tag.\nThis text is wrapped in an \\ tag."); + +NSAttributedString *string = chain.attributedString; +``` + +Outputs: + + + +BonMot can also style text between any arbitrary start and end strings using any escape string. + +```objc +BONChain *boldChain = BONChain.new.fontNameAndSize(@"Baskerville-Bold", 15.0f); +BONChain *italicChain = BONChain.new.fontNameAndSize(@"Baskerville-Italic", 15.0f); + +BONChain *chain = BONChain.new.fontNameAndSize(@"Baskerville", 17.0f) +.tagStyles(@[BONTagComplexMake(@"~start~", @"!end", @"escape", boldChain)]) +.string(@"~start~This text is wrapped in a escape~start~ tag.!end"); + +NSAttributedString *string = chain.attributedString; +``` + +Outputs: + + + +**Note:** Tag styles do not support nested or interleaved tags. The first tag matched will be applied, any additional tags between the start end end will be ignored. + + ## Contributing Issues and pull requests are welcome! Please format all code using [`clang-format`](http://clang.llvm.org/docs/ClangFormat.html) and the included `.clang-format` configuration file. diff --git a/readme-images/arbitrary-tag-styling.png b/readme-images/arbitrary-tag-styling.png new file mode 100644 index 0000000000000000000000000000000000000000..3de039d8597e1d25f9a22e4453521a0893159d72 GIT binary patch literal 6376 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 W&OIHao8~V6dr(u-RxFdZ4*Eaa*~`=b literal 0 HcmV?d00001 diff --git a/readme-images/tag-styling.png b/readme-images/tag-styling.png new file mode 100644 index 0000000000000000000000000000000000000000..64900b6e5413c429ac222577923a31de4b0d3ae2 GIT binary patch literal 18461 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 Date: Mon, 16 May 2016 09:57:50 -0700 Subject: [PATCH 2/4] Change tagStyles property on BONChain to take a dictionary, add tagComplexStyles to handle advanced cases. --- Example/BonMot.xcodeproj/project.pbxproj | 6 +- Example/Tests/BONTagComplexStylesTestCase.m | 24 +- .../Tests/BONTagDictionaryStylesTestCase.m | 406 ++++++++++++++++++ Example/Tests/BONTagStylesTestCase.m | 24 +- Pod/Classes/BONChain.h | 8 +- Pod/Classes/BONChain.m | 14 +- Pod/Classes/BONText.h | 5 +- 7 files changed, 456 insertions(+), 31 deletions(-) create mode 100644 Example/Tests/BONTagDictionaryStylesTestCase.m diff --git a/Example/BonMot.xcodeproj/project.pbxproj b/Example/BonMot.xcodeproj/project.pbxproj index 496ffc0a..d760300f 100644 --- a/Example/BonMot.xcodeproj/project.pbxproj +++ b/Example/BonMot.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ CD6DF05A1BF6B98500676E2D /* NSDictionary+BONEquality.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6DF04F1BF6B53100676E2D /* NSDictionary+BONEquality.m */; }; CD78701E1CA5F8EC00B2714F /* EBGaramond12-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD6DF03E1BF6AFAA00676E2D /* EBGaramond12-Regular.otf */; }; CDE658581C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE658571C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m */; }; + EC2F199E1CEA324900D9F1FE /* BONTagDictionaryStylesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC2F199C1CEA324300D9F1FE /* BONTagDictionaryStylesTestCase.m */; }; EC433DB31C88B7D9001B3ABE /* BONTagStylesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB11C88B65B001B3ABE /* BONTagStylesTestCase.m */; }; EC433DB61C88BBDE001B3ABE /* BONUIKitUtilitiesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB41C88BBDB001B3ABE /* BONUIKitUtilitiesTestCase.m */; }; EC433DB91C88DE5F001B3ABE /* TagStylesCell.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB81C88DE5F001B3ABE /* TagStylesCell.m */; }; @@ -168,6 +169,7 @@ CDE658571C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONUnderlineAndStrikethroughTestCase.m; sourceTree = ""; }; CFD6D97A946F2FEF294F4BBB /* Pods_BonMot_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BonMot_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EBE7037465B0F6F184875C40 /* Pods-BonMot_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BonMot_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-BonMot_Example/Pods-BonMot_Example.release.xcconfig"; sourceTree = ""; }; + EC2F199C1CEA324300D9F1FE /* BONTagDictionaryStylesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagDictionaryStylesTestCase.m; sourceTree = ""; }; EC433DB11C88B65B001B3ABE /* BONTagStylesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagStylesTestCase.m; sourceTree = ""; }; EC433DB41C88BBDB001B3ABE /* BONUIKitUtilitiesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONUIKitUtilitiesTestCase.m; sourceTree = ""; }; EC433DB71C88DE5F001B3ABE /* TagStylesCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TagStylesCell.h; sourceTree = ""; }; @@ -289,6 +291,7 @@ 730361E11C9B5CD300987809 /* BONHumanReadableStringTestCase.m */, CD6DEFDB1BF6ADF900676E2D /* BONIndentSpacerTestCase.m */, EC433DB11C88B65B001B3ABE /* BONTagStylesTestCase.m */, + EC2F199C1CEA324300D9F1FE /* BONTagDictionaryStylesTestCase.m */, ECD4DB691CE3EA1F00079675 /* BONTagComplexStylesTestCase.m */, CD6DEFDC1BF6ADF900676E2D /* BONPropertyClobberingTestCase.m */, CD6DEFDD1BF6ADF900676E2D /* BONTextAlignmentConstraintTestCase.m */, @@ -440,7 +443,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = ""; - LastSwiftUpdateCheck = 0710; + LastSwiftUpdateCheck = 0730; LastUpgradeCheck = 0710; ORGANIZATIONNAME = "Zev Eisenberg"; }; @@ -638,6 +641,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EC2F199E1CEA324900D9F1FE /* BONTagDictionaryStylesTestCase.m in Sources */, EC433DB61C88BBDE001B3ABE /* BONUIKitUtilitiesTestCase.m in Sources */, CD6DF05A1BF6B98500676E2D /* NSDictionary+BONEquality.m in Sources */, CD6DF04B1BF6B37E00676E2D /* DummyAssetClass.m in Sources */, diff --git a/Example/Tests/BONTagComplexStylesTestCase.m b/Example/Tests/BONTagComplexStylesTestCase.m index b635c273..1cc85468 100644 --- a/Example/Tests/BONTagComplexStylesTestCase.m +++ b/Example/Tests/BONTagComplexStylesTestCase.m @@ -19,7 +19,7 @@ @implementation BONTagComplexStylingTestCase - (void)testSingleTagSingleStyle { BONChain *chain = BONChain.new.string(@"Hello, [i]world(i)!") - .tagStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + .tagComplexStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); NSAttributedString *attributedString = chain.attributedString; @@ -48,7 +48,7 @@ - (void)testSingleTagSingleStyle - (void)testMultipleTagsSingleStyle { BONChain *chain = BONChain.new.string(@"[i]Hello(i), [i]world(i)!") - .tagStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + .tagComplexStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); NSAttributedString *attributedString = chain.attributedString; @@ -82,7 +82,7 @@ - (void)testMultipleTagsSingleStyle - (void)testSingleTagMultipleStyles { BONChain *chain = BONChain.new.string(@"Hello, >bb<*!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -114,7 +114,7 @@ - (void)testSingleTagMultipleStyles - (void)testMultipleTagsMultipleStyles { BONChain *chain = BONChain.new.string(@">bb<*, [i]world(i)!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -151,7 +151,7 @@ - (void)testMultipleTagsMultipleStyles - (void)testInterleavedTags { BONChain *chain = BONChain.new.string(@">bb<*, world(i)!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -179,7 +179,7 @@ - (void)testInterleavedTags - (void)testNestedTags { BONChain *chain = BONChain.new.string(@">b<[i]Hello(i)>b<*, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -207,7 +207,7 @@ - (void)testNestedTags - (void)testMixedOrdering { BONChain *chain = BONChain.new.string(@">bb<*[i]lo(i), >bb<*[i]ld!(i)") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"[i]", @"(i)", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@">b<", @">b<*", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -250,7 +250,7 @@ - (void)testMixedOrdering - (void)testEscapedStartTag { BONChain *chain = BONChain.new.string(@"qwerty(b)(b)Hello[/b], world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -282,7 +282,7 @@ - (void)testEscapedStartTag - (void)testMultipleEscapedStartTag { BONChain *chain = BONChain.new.string(@"qwerty(b)(b)Hello[/b]qwerty(b), world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -314,7 +314,7 @@ - (void)testMultipleEscapedStartTag - (void)testMultipleEscapedEndTag { BONChain *chain = BONChain.new.string(@"qwerty[/b](b)Hello[/b]qwerty[/b], world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -346,7 +346,7 @@ - (void)testMultipleEscapedEndTag - (void)testNestedEscapedEndTag { BONChain *chain = BONChain.new.string(@"(b)Helloqwerty[/b][/b], world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -374,7 +374,7 @@ - (void)testNestedEscapedEndTag - (void)testInterleavedEscapedEndTag { BONChain *chain = BONChain.new.string(@"qwerty[/b]qwerty[/i]qwerty[/b](i)Helloqwerty[/i][/i], world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); diff --git a/Example/Tests/BONTagDictionaryStylesTestCase.m b/Example/Tests/BONTagDictionaryStylesTestCase.m new file mode 100644 index 00000000..49ad0758 --- /dev/null +++ b/Example/Tests/BONTagDictionaryStylesTestCase.m @@ -0,0 +1,406 @@ +// +// BONTagDictionaryStylesTestCase.m +// BonMot +// +// Created by Nora Trapp on 5/16/16. +// Copyright © 2016 Zev Eisenberg. All rights reserved. +// + +#import "BONBaseTestCase.h" + +@import BonMot; + +@interface BONTagDictionaryStylesTestCase : BONBaseTestCase + +@end + +@implementation BONTagDictionaryStylesTestCase + +- (void)testSingleTagSingleStyle +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@{ @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]) }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 7) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleTagsSingleStyle +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@{ @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]) }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(5, 2) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testSingleTagMultipleStyles +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 7) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleTagsMultipleStyles +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(5, 2) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 5) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 1) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testInterleavedTags +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 8) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(8, 12) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testNestedTags +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 12) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMixedOrdering +{ + BONChain *chain = BONChain.new.string(@"Hello, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 3) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(3, 2) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(5, 2) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(7, 3) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(10, 3) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testEscapedStartTag +{ + BONChain *chain = BONChain.new.string(@"\\Hello, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 3) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(3, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(8, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleEscapedStartTag +{ + BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 3) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(3, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(8, 11) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testMultipleEscapedEndTag +{ + BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 4) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(4, 5) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(9, 12) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testNestedEscapedEndTag +{ + BONChain *chain = BONChain.new.string(@"Hello\\, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 9) : @{ + NSFontAttributeName : [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(9, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +- (void)testInterleavedEscapedEndTag +{ + BONChain *chain = BONChain.new.string(@"\\\\\\Hello\\, world!") + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); + + NSAttributedString *attributedString = chain.attributedString; + + XCTAssertEqualObjects(attributedString.string, @"Hello, world!"); + + NSParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle alloc] init]; + + NSDictionary *controlAttributes = @{ + BONValueFromRange(0, 12) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(12, 9) : @{ + NSFontAttributeName : [UIFont italicSystemFontOfSize:16], + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + + BONValueFromRange(21, 8) : @{ + NSParagraphStyleAttributeName : defaultParagraphStyle, + }, + }; + + BONAssertAttributedStringHasAttributes(attributedString, controlAttributes); +} + +@end diff --git a/Example/Tests/BONTagStylesTestCase.m b/Example/Tests/BONTagStylesTestCase.m index 58b8203b..4d6670b0 100644 --- a/Example/Tests/BONTagStylesTestCase.m +++ b/Example/Tests/BONTagStylesTestCase.m @@ -19,7 +19,7 @@ @implementation BONTagStylesTestCase - (void)testSingleTagSingleStyle { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); NSAttributedString *attributedString = chain.attributedString; @@ -48,7 +48,7 @@ - (void)testSingleTagSingleStyle - (void)testMultipleTagsSingleStyle { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); NSAttributedString *attributedString = chain.attributedString; @@ -82,7 +82,7 @@ - (void)testMultipleTagsSingleStyle - (void)testSingleTagMultipleStyles { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -114,7 +114,7 @@ - (void)testSingleTagMultipleStyles - (void)testMultipleTagsMultipleStyles { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -151,7 +151,7 @@ - (void)testMultipleTagsMultipleStyles - (void)testInterleavedTags { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -179,7 +179,7 @@ - (void)testInterleavedTags - (void)testNestedTags { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -207,7 +207,7 @@ - (void)testNestedTags - (void)testMixedOrdering { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -250,7 +250,7 @@ - (void)testMixedOrdering - (void)testEscapedStartTag { BONChain *chain = BONChain.new.string(@"\\Hello, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -282,7 +282,7 @@ - (void)testEscapedStartTag - (void)testMultipleEscapedStartTag { BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -314,7 +314,7 @@ - (void)testMultipleEscapedStartTag - (void)testMultipleEscapedEndTag { BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -346,7 +346,7 @@ - (void)testMultipleEscapedEndTag - (void)testNestedEscapedEndTag { BONChain *chain = BONChain.new.string(@"Hello\\, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); @@ -374,7 +374,7 @@ - (void)testNestedEscapedEndTag - (void)testInterleavedEscapedEndTag { BONChain *chain = BONChain.new.string(@"\\\\\\Hello\\, world!") - .tagStyles(@[ + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); diff --git a/Pod/Classes/BONChain.h b/Pod/Classes/BONChain.h index 48659d30..6cec58c7 100644 --- a/Pod/Classes/BONChain.h +++ b/Pod/Classes/BONChain.h @@ -44,7 +44,8 @@ typedef BONChain *BONCNonnull (^BONChainUnderlineColor)(UIColor *BONCNullable co typedef BONChain *BONCNonnull (^BONChainStrikethroughStyle)(NSUnderlineStyle style); typedef BONChain *BONCNonnull (^BONChainStrikethroughColor)(UIColor *BONCNullable color); -typedef BONChain *BONCNonnull (^BONTagStyles)(BONGeneric(NSArray, BONTag *) * BONCNullable styles); +typedef BONChain *BONCNonnull (^BONTagStyles)(BONGeneric(NSDictionary, NSString *, id) * BONCNullable styles); +typedef BONChain *BONCNonnull (^BONTagComplexStyles)(BONGeneric(NSArray, BONTag *) * BONCNullable styles); @interface BONChain : NSObject @@ -104,6 +105,11 @@ typedef BONChain *BONCNonnull (^BONTagStyles)(BONGeneric(NSArray, BONTag *) * BO */ @property (copy, nonatomic, readonly) BONTagStyles tagStyles; +/** + * Assign an array of @p BONTags to use in styling substrings. + */ +@property (copy, nonatomic, readonly) BONTagComplexStyles tagComplexStyles; + // concatenation - (void)appendLink:(id)link; - (void)appendLink:(id)link separator:(BONNullable NSString *)separator; diff --git a/Pod/Classes/BONChain.m b/Pod/Classes/BONChain.m index d1122c8b..33f3b16b 100644 --- a/Pod/Classes/BONChain.m +++ b/Pod/Classes/BONChain.m @@ -10,6 +10,7 @@ #import "BONText.h" #import "BONText_Private.h" +#import "BONTag.h" @interface BONChain () @@ -355,7 +356,18 @@ - (BONChainStrikethroughColor)strikethroughColor - (BONTagStyles)tagStyles { - BONTagStyles tagStylesBlock = ^(NSArray *tagStyles) { + BONTagStyles tagStylesBlock = ^(NSDictionary *tagStyles) { + __typeof(self) newChain = self.copyWithoutNextText; + newChain.text.tagStyles = BONTagsFromDictionary(tagStyles); + return newChain; + }; + + return [tagStylesBlock copy]; +} + +- (BONTagComplexStyles)tagComplexStyles +{ + BONTagComplexStyles tagStylesBlock = ^(NSArray *tagStyles) { __typeof(self) newChain = self.copyWithoutNextText; newChain.text.tagStyles = tagStyles; return newChain; diff --git a/Pod/Classes/BONText.h b/Pod/Classes/BONText.h index 74e7b291..65e9d4a8 100644 --- a/Pod/Classes/BONText.h +++ b/Pod/Classes/BONText.h @@ -85,10 +85,7 @@ typedef NS_ENUM(NSUInteger, BONFigureSpacing) { @property (strong, nonatomic, BONNullable) UIColor *strikethroughColor; /** - * Assign @p BONTextables to use in styling substrings surrounded in given tags. - * For example, ["b": boldChainable] would apply the @p boldChain - * to any substring surrounded by and remove the tags from the resulting - * attributed string. Nested tagging is not supported. + * Assign an array of @p BONTags to use in styling substrings. */ @property (strong, nonatomic, BONNullable) BONGeneric(NSArray, BONTag *) * tagStyles; From f2350f2d22691c1e79a37d3b544ea82bd0f99c5d Mon Sep 17 00:00:00 2001 From: Nora Trapp Date: Tue, 17 May 2016 14:12:50 -0700 Subject: [PATCH 3/4] Cleanup based on Zev's PR review. --- Example/BonMot.xcodeproj/project.pbxproj | 28 +++--- .../xcschemes/BonMot-Example.xcscheme | 2 +- Example/BonMot/Cells/TagStylesCell.m | 5 +- ...TestCase.m => BONTagComplexMakeTestCase.m} | 12 +-- ...yStylesTestCase.m => BONTagMakeTestCase.m} | 92 +++++++++--------- Example/Tests/BONTagStylesTestCase.m | 88 ++++++++--------- Example/Tests/BONTagTestCase.m | 83 ++++++++++++++++ Pod/Classes/BONChain.h | 7 +- Pod/Classes/BONChain.m | 4 +- Pod/Classes/BONTag.h | 18 ++-- Pod/Classes/BONTag.m | 84 ++++++++-------- Pod/Classes/BONTag_Private.h | 20 ++-- Pod/Classes/BONText.h | 5 +- Pod/Classes/BONText.m | 8 +- Pod/UIKit/BonMot+UIKit.h | 2 +- Pod/UIKit/Classes/UILabel+BonMotUtilities.h | 2 +- Pod/UIKit/Classes/UILabel+BonMotUtilities.m | 2 +- .../Classes/UITextField+BonMotUtilities.h | 2 +- .../Classes/UITextField+BonMotUtilities.m | 2 +- .../Classes/UITextView+BonMotUtilities.h | 2 +- .../Classes/UITextView+BonMotUtilities.m | 2 +- README.md | 8 +- readme-images/tag-styling.png | Bin 18461 -> 17781 bytes 23 files changed, 287 insertions(+), 191 deletions(-) rename Example/Tests/{BONTagComplexStylesTestCase.m => BONTagComplexMakeTestCase.m} (97%) rename Example/Tests/{BONTagDictionaryStylesTestCase.m => BONTagMakeTestCase.m} (77%) create mode 100644 Example/Tests/BONTagTestCase.m diff --git a/Example/BonMot.xcodeproj/project.pbxproj b/Example/BonMot.xcodeproj/project.pbxproj index d760300f..33cd9420 100644 --- a/Example/BonMot.xcodeproj/project.pbxproj +++ b/Example/BonMot.xcodeproj/project.pbxproj @@ -64,12 +64,13 @@ CD6DF05A1BF6B98500676E2D /* NSDictionary+BONEquality.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6DF04F1BF6B53100676E2D /* NSDictionary+BONEquality.m */; }; CD78701E1CA5F8EC00B2714F /* EBGaramond12-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD6DF03E1BF6AFAA00676E2D /* EBGaramond12-Regular.otf */; }; CDE658581C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE658571C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m */; }; - EC2F199E1CEA324900D9F1FE /* BONTagDictionaryStylesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC2F199C1CEA324300D9F1FE /* BONTagDictionaryStylesTestCase.m */; }; - EC433DB31C88B7D9001B3ABE /* BONTagStylesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB11C88B65B001B3ABE /* BONTagStylesTestCase.m */; }; + EC2F199E1CEA324900D9F1FE /* BONTagStylesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC2F199C1CEA324300D9F1FE /* BONTagStylesTestCase.m */; }; + EC2F19A11CEBBAF900D9F1FE /* BONTagTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC2F199F1CEBBA9E00D9F1FE /* BONTagTestCase.m */; }; + EC433DB31C88B7D9001B3ABE /* BONTagMakeTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB11C88B65B001B3ABE /* BONTagMakeTestCase.m */; }; EC433DB61C88BBDE001B3ABE /* BONUIKitUtilitiesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB41C88BBDB001B3ABE /* BONUIKitUtilitiesTestCase.m */; }; EC433DB91C88DE5F001B3ABE /* TagStylesCell.m in Sources */ = {isa = PBXBuildFile; fileRef = EC433DB81C88DE5F001B3ABE /* TagStylesCell.m */; }; EC433DBB1C88DEF7001B3ABE /* TagStylesCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC433DBA1C88DEF7001B3ABE /* TagStylesCell.xib */; }; - ECD4DB6B1CE3EA2700079675 /* BONTagComplexStylesTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = ECD4DB691CE3EA1F00079675 /* BONTagComplexStylesTestCase.m */; }; + ECD4DB6B1CE3EA2700079675 /* BONTagComplexMakeTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = ECD4DB691CE3EA1F00079675 /* BONTagComplexMakeTestCase.m */; }; F6E9CEC93164745FB8BEB8FC /* Pods_BonMot_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2E8BB0552A748FFBA2BD0BE4 /* Pods_BonMot_Example.framework */; }; /* End PBXBuildFile section */ @@ -169,13 +170,14 @@ CDE658571C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONUnderlineAndStrikethroughTestCase.m; sourceTree = ""; }; CFD6D97A946F2FEF294F4BBB /* Pods_BonMot_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BonMot_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EBE7037465B0F6F184875C40 /* Pods-BonMot_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BonMot_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-BonMot_Example/Pods-BonMot_Example.release.xcconfig"; sourceTree = ""; }; - EC2F199C1CEA324300D9F1FE /* BONTagDictionaryStylesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagDictionaryStylesTestCase.m; sourceTree = ""; }; - EC433DB11C88B65B001B3ABE /* BONTagStylesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagStylesTestCase.m; sourceTree = ""; }; + EC2F199C1CEA324300D9F1FE /* BONTagStylesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagStylesTestCase.m; sourceTree = ""; }; + EC2F199F1CEBBA9E00D9F1FE /* BONTagTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagTestCase.m; sourceTree = ""; }; + EC433DB11C88B65B001B3ABE /* BONTagMakeTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagMakeTestCase.m; sourceTree = ""; }; EC433DB41C88BBDB001B3ABE /* BONUIKitUtilitiesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONUIKitUtilitiesTestCase.m; sourceTree = ""; }; EC433DB71C88DE5F001B3ABE /* TagStylesCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TagStylesCell.h; sourceTree = ""; }; EC433DB81C88DE5F001B3ABE /* TagStylesCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TagStylesCell.m; sourceTree = ""; }; EC433DBA1C88DEF7001B3ABE /* TagStylesCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TagStylesCell.xib; sourceTree = ""; }; - ECD4DB691CE3EA1F00079675 /* BONTagComplexStylesTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagComplexStylesTestCase.m; sourceTree = ""; }; + ECD4DB691CE3EA1F00079675 /* BONTagComplexMakeTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BONTagComplexMakeTestCase.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -290,9 +292,10 @@ CD6DEFDA1BF6ADF900676E2D /* BONDictionaryEqualityTestCase.m */, 730361E11C9B5CD300987809 /* BONHumanReadableStringTestCase.m */, CD6DEFDB1BF6ADF900676E2D /* BONIndentSpacerTestCase.m */, - EC433DB11C88B65B001B3ABE /* BONTagStylesTestCase.m */, - EC2F199C1CEA324300D9F1FE /* BONTagDictionaryStylesTestCase.m */, - ECD4DB691CE3EA1F00079675 /* BONTagComplexStylesTestCase.m */, + EC2F199C1CEA324300D9F1FE /* BONTagStylesTestCase.m */, + EC433DB11C88B65B001B3ABE /* BONTagMakeTestCase.m */, + ECD4DB691CE3EA1F00079675 /* BONTagComplexMakeTestCase.m */, + EC2F199F1CEBBA9E00D9F1FE /* BONTagTestCase.m */, CD6DEFDC1BF6ADF900676E2D /* BONPropertyClobberingTestCase.m */, CD6DEFDD1BF6ADF900676E2D /* BONTextAlignmentConstraintTestCase.m */, CD6DEFDE1BF6ADF900676E2D /* BONTextAlignmentTestCase.m */, @@ -641,16 +644,17 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - EC2F199E1CEA324900D9F1FE /* BONTagDictionaryStylesTestCase.m in Sources */, + EC2F199E1CEA324900D9F1FE /* BONTagStylesTestCase.m in Sources */, EC433DB61C88BBDE001B3ABE /* BONUIKitUtilitiesTestCase.m in Sources */, + EC2F19A11CEBBAF900D9F1FE /* BONTagTestCase.m in Sources */, CD6DF05A1BF6B98500676E2D /* NSDictionary+BONEquality.m in Sources */, CD6DF04B1BF6B37E00676E2D /* DummyAssetClass.m in Sources */, 730361E31C9B5CDD00987809 /* BONHumanReadableStringTestCase.m in Sources */, - ECD4DB6B1CE3EA2700079675 /* BONTagComplexStylesTestCase.m in Sources */, + ECD4DB6B1CE3EA2700079675 /* BONTagComplexMakeTestCase.m in Sources */, CD6DEFED1BF6ADF900676E2D /* BONPropertyClobberingTestCase.m in Sources */, CD6DEFF11BF6ADF900676E2D /* BONTextAlignmentTestCase.m in Sources */, CD6DEFE11BF6ADF900676E2D /* BONBaseTestCase.m in Sources */, - EC433DB31C88B7D9001B3ABE /* BONTagStylesTestCase.m in Sources */, + EC433DB31C88B7D9001B3ABE /* BONTagMakeTestCase.m in Sources */, CDE658581C24ADD8009C7D09 /* BONUnderlineAndStrikethroughTestCase.m in Sources */, CD6DEFF31BF6ADF900676E2D /* BONTrackingTestCase.m in Sources */, CD64F15E1CA4DAC800042559 /* BONFigureTestCase.m in Sources */, diff --git a/Example/BonMot.xcodeproj/xcshareddata/xcschemes/BonMot-Example.xcscheme b/Example/BonMot.xcodeproj/xcshareddata/xcschemes/BonMot-Example.xcscheme index 5fe6e4d7..301b5c4c 100644 --- a/Example/BonMot.xcodeproj/xcshareddata/xcschemes/BonMot-Example.xcscheme +++ b/Example/BonMot.xcodeproj/xcshareddata/xcschemes/BonMot-Example.xcscheme @@ -28,7 +28,7 @@ buildForAnalyzing = "YES"> diff --git a/Example/BonMot/Cells/TagStylesCell.m b/Example/BonMot/Cells/TagStylesCell.m index 846db5cd..71e28e6f 100644 --- a/Example/BonMot/Cells/TagStylesCell.m +++ b/Example/BonMot/Cells/TagStylesCell.m @@ -29,8 +29,9 @@ - (void)awakeFromNib BONChain *italicChain = BONChain.new.fontNameAndSize(@"Baskerville-Italic", 15); BONChain *baseChain = BONChain.new.fontNameAndSize(@"Baskerville", 17) - .tagStyles(@[ BONTagMake(@"bold", boldChain), BONTagMake(@"italic", italicChain) ]) - .string(@"This text is wrapped in a \\ tag.\nThis text is wrapped in an \\ tag."); + .tagStyles(@{ @"bold" : boldChain, + @"italic" : italicChain }) + .string(@"This text contains a \\ tag.\nThis text contains an \\ tag."); self.label.attributedText = baseChain.attributedString; diff --git a/Example/Tests/BONTagComplexStylesTestCase.m b/Example/Tests/BONTagComplexMakeTestCase.m similarity index 97% rename from Example/Tests/BONTagComplexStylesTestCase.m rename to Example/Tests/BONTagComplexMakeTestCase.m index 1cc85468..6240d000 100644 --- a/Example/Tests/BONTagComplexStylesTestCase.m +++ b/Example/Tests/BONTagComplexMakeTestCase.m @@ -1,20 +1,20 @@ // -// BONTagComplexStylingTestCase.m +// BONTagComplexMakeTestCase.m // BonMot // // Created by Nora Trapp on 3/3/16. -// +// Copyright © 2016 Zev Eisenberg. All rights reserved. // #import "BONBaseTestCase.h" @import BonMot; -@interface BONTagComplexStylingTestCase : BONBaseTestCase +@interface BONTagComplexMakeTestCase : BONBaseTestCase @end -@implementation BONTagComplexStylingTestCase +@implementation BONTagComplexMakeTestCase - (void)testSingleTagSingleStyle { @@ -373,9 +373,9 @@ - (void)testNestedEscapedEndTag - (void)testInterleavedEscapedEndTag { - BONChain *chain = BONChain.new.string(@"qwerty[/b]qwerty[/i]qwerty[/b](i)Helloqwerty[/i][/i], world!") + BONChain *chain = BONChain.new.string(@"qwerty[/b]asdfgh[/i]qwerty[/b](i)Helloasdfgh[/i][/i], world!") .tagComplexStyles(@[ - BONTagComplexMake(@"(i)", @"[/i]", @"qwerty", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagComplexMake(@"(i)", @"[/i]", @"asdfgh", BONChain.new.font([UIFont italicSystemFontOfSize:16])), BONTagComplexMake(@"(b)", @"[/b]", @"qwerty", BONChain.new.font([UIFont boldSystemFontOfSize:16])), ]); diff --git a/Example/Tests/BONTagDictionaryStylesTestCase.m b/Example/Tests/BONTagMakeTestCase.m similarity index 77% rename from Example/Tests/BONTagDictionaryStylesTestCase.m rename to Example/Tests/BONTagMakeTestCase.m index 49ad0758..61d219da 100644 --- a/Example/Tests/BONTagDictionaryStylesTestCase.m +++ b/Example/Tests/BONTagMakeTestCase.m @@ -1,8 +1,8 @@ // -// BONTagDictionaryStylesTestCase.m +// BONTagMakeTestCase.m // BonMot // -// Created by Nora Trapp on 5/16/16. +// Created by Nora Trapp on 3/3/16. // Copyright © 2016 Zev Eisenberg. All rights reserved. // @@ -10,16 +10,16 @@ @import BonMot; -@interface BONTagDictionaryStylesTestCase : BONBaseTestCase +@interface BONTagMakeTestCase : BONBaseTestCase @end -@implementation BONTagDictionaryStylesTestCase +@implementation BONTagMakeTestCase - (void)testSingleTagSingleStyle { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@{ @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]) }); + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); NSAttributedString *attributedString = chain.attributedString; @@ -48,7 +48,7 @@ - (void)testSingleTagSingleStyle - (void)testMultipleTagsSingleStyle { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@{ @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]) }); + .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); NSAttributedString *attributedString = chain.attributedString; @@ -82,10 +82,10 @@ - (void)testMultipleTagsSingleStyle - (void)testSingleTagMultipleStyles { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; @@ -114,10 +114,10 @@ - (void)testSingleTagMultipleStyles - (void)testMultipleTagsMultipleStyles { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; @@ -151,10 +151,10 @@ - (void)testMultipleTagsMultipleStyles - (void)testInterleavedTags { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; @@ -179,10 +179,10 @@ - (void)testInterleavedTags - (void)testNestedTags { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; @@ -207,10 +207,10 @@ - (void)testNestedTags - (void)testMixedOrdering { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; @@ -250,10 +250,10 @@ - (void)testMixedOrdering - (void)testEscapedStartTag { BONChain *chain = BONChain.new.string(@"\\Hello, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; @@ -282,10 +282,10 @@ - (void)testEscapedStartTag - (void)testMultipleEscapedStartTag { BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; @@ -314,10 +314,10 @@ - (void)testMultipleEscapedStartTag - (void)testMultipleEscapedEndTag { BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; @@ -346,10 +346,10 @@ - (void)testMultipleEscapedEndTag - (void)testNestedEscapedEndTag { BONChain *chain = BONChain.new.string(@"Hello\\, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; @@ -374,10 +374,10 @@ - (void)testNestedEscapedEndTag - (void)testInterleavedEscapedEndTag { BONChain *chain = BONChain.new.string(@"\\\\\\Hello\\, world!") - .tagStyles(@{ - @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), - @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), - }); + .tagComplexStyles(@[ + BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), + BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), + ]); NSAttributedString *attributedString = chain.attributedString; diff --git a/Example/Tests/BONTagStylesTestCase.m b/Example/Tests/BONTagStylesTestCase.m index 4d6670b0..41e0a2ea 100644 --- a/Example/Tests/BONTagStylesTestCase.m +++ b/Example/Tests/BONTagStylesTestCase.m @@ -2,8 +2,8 @@ // BONTagStylesTestCase.m // BonMot // -// Created by Nora Trapp on 3/3/16. -// +// Created by Nora Trapp on 5/16/16. +// Copyright © 2016 Zev Eisenberg. All rights reserved. // #import "BONBaseTestCase.h" @@ -19,7 +19,7 @@ @implementation BONTagStylesTestCase - (void)testSingleTagSingleStyle { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + .tagStyles(@{ @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]) }); NSAttributedString *attributedString = chain.attributedString; @@ -48,7 +48,7 @@ - (void)testSingleTagSingleStyle - (void)testMultipleTagsSingleStyle { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagComplexStyles(@[ BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])) ]); + .tagStyles(@{ @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]) }); NSAttributedString *attributedString = chain.attributedString; @@ -82,10 +82,10 @@ - (void)testMultipleTagsSingleStyle - (void)testSingleTagMultipleStyles { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; @@ -114,10 +114,10 @@ - (void)testSingleTagMultipleStyles - (void)testMultipleTagsMultipleStyles { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; @@ -151,10 +151,10 @@ - (void)testMultipleTagsMultipleStyles - (void)testInterleavedTags { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; @@ -179,10 +179,10 @@ - (void)testInterleavedTags - (void)testNestedTags { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; @@ -207,10 +207,10 @@ - (void)testNestedTags - (void)testMixedOrdering { BONChain *chain = BONChain.new.string(@"Hello, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; @@ -250,10 +250,10 @@ - (void)testMixedOrdering - (void)testEscapedStartTag { BONChain *chain = BONChain.new.string(@"\\Hello, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; @@ -282,10 +282,10 @@ - (void)testEscapedStartTag - (void)testMultipleEscapedStartTag { BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; @@ -314,10 +314,10 @@ - (void)testMultipleEscapedStartTag - (void)testMultipleEscapedEndTag { BONChain *chain = BONChain.new.string(@"\\Hello\\, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; @@ -346,10 +346,10 @@ - (void)testMultipleEscapedEndTag - (void)testNestedEscapedEndTag { BONChain *chain = BONChain.new.string(@"Hello\\, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; @@ -374,10 +374,10 @@ - (void)testNestedEscapedEndTag - (void)testInterleavedEscapedEndTag { BONChain *chain = BONChain.new.string(@"\\\\\\Hello\\, world!") - .tagComplexStyles(@[ - BONTagMake(@"i", BONChain.new.font([UIFont italicSystemFontOfSize:16])), - BONTagMake(@"b", BONChain.new.font([UIFont boldSystemFontOfSize:16])), - ]); + .tagStyles(@{ + @"i" : BONChain.new.font([UIFont italicSystemFontOfSize:16]), + @"b" : BONChain.new.font([UIFont boldSystemFontOfSize:16]), + }); NSAttributedString *attributedString = chain.attributedString; diff --git a/Example/Tests/BONTagTestCase.m b/Example/Tests/BONTagTestCase.m new file mode 100644 index 00000000..694f5357 --- /dev/null +++ b/Example/Tests/BONTagTestCase.m @@ -0,0 +1,83 @@ +// +// BONTagTestCase.m +// BonMot +// +// Created by Nora Trapp on 5/17/16. +// Copyright © 2016 Zev Eisenberg. All rights reserved. +// + +#import "BONBaseTestCase.h" +@import BonMot; + +@interface BONTag () + +@property (strong, nonatomic, BONNonnull) BONGeneric(NSMutableArray, NSValue *) * ranges; + ++ (BONNonnull BONGeneric(NSArray, NSValue *) *)escapedRangesInString:(NSString *BONCNonnull *BONCNonnull)string withTags:(BONNonnull BONGeneric(NSArray, BONTag *) *)tags; ++ (NSRange)firstOccurrenceOfString:(BONNonnull NSString *)string inString:(BONNonnull NSString *)stringToSearch ignoringRanges:(BONNonnull BONGeneric(NSArray, NSValue *) *)escapedRanges inRange:(NSRange)range; ++ (BONNonnull BONGeneric(NSArray, BONTag *) *)rangesInString:(NSString *BONCNonnull *BONCNonnull)string betweenTags:(BONNonnull BONGeneric(NSArray, BONTag *) *)tags; +@end + +@import BonMot; + +@interface BONTagTestCase : BONBaseTestCase + +@end + +@implementation BONTagTestCase + +- (void)testEscapedRangesInString +{ + NSString *string = @"This is a \\ string that \\ has \\escaped characters."; + NSArray *ranges = [BONTag escapedRangesInString:&string withTags:@[ BONTagMake(@"b", BONChain.new) ]]; + + NSString *stringWithoutEscapes = @"This is a string that has \\escaped characters."; + XCTAssertEqualObjects(string, stringWithoutEscapes); + + NSArray *escapedRanges = @[ [NSValue valueWithRange:NSMakeRange(10, 3)], [NSValue valueWithRange:NSMakeRange(26, 4)] ]; + XCTAssertEqualObjects(ranges, escapedRanges); +} + +- (void)testMixedEscapeStrings +{ + NSString *string = @"This is a \\ string that \\ has \\escaped ~escaped characters."; + NSArray *ranges = [BONTag escapedRangesInString:&string withTags:@[ BONTagMake(@"b", BONChain.new), BONTagComplexMake(@"escaped", @"escaped", @"~", BONChain.new) ]]; + + NSString *stringWithoutEscapes = @"This is a string that has \\escaped escaped characters."; + XCTAssertEqualObjects(string, stringWithoutEscapes); + + NSArray *escapedRanges = @[ [NSValue valueWithRange:NSMakeRange(10, 3)], [NSValue valueWithRange:NSMakeRange(26, 4)], [NSValue valueWithRange:NSMakeRange(44, 7)] ]; + XCTAssertEqualObjects(ranges, escapedRanges); +} + +- (void)testFirstOccurenceOfString +{ + NSString *string = @"This is a \\ string that \\ has \\escaped characters."; + NSRange range = [BONTag firstOccurrenceOfString:@"" inString:string ignoringRanges:@[] inRange:NSMakeRange(0, string.length)]; + + XCTAssertTrue(NSEqualRanges(range, NSMakeRange(11, 3))); +} + +- (void)testFirstOccurenceOfStringWithIgnoredRange +{ + NSString *string = @"This is a \\ string that \\ has \\escaped characters."; + NSArray *ignoredRanges = @[ [NSValue valueWithRange:NSMakeRange(8, 3)] ]; + NSRange range = [BONTag firstOccurrenceOfString:@"" inString:string ignoringRanges:ignoredRanges inRange:NSMakeRange(0, string.length)]; + + XCTAssertTrue(NSEqualRanges(range, NSMakeRange(15, 3))); +} + +- (void)testRangesBetweenTags +{ + NSString *string = @"This is a tagged string."; + NSArray *tags = [BONTag rangesInString:&string betweenTags:@[ BONTagMake(@"b", BONChain.new) ]]; + + NSString *stringWithoutTags = @"This is a tagged string."; + XCTAssertEqualObjects(string, stringWithoutTags); + + NSArray *tagRanges = @[ [NSValue valueWithRange:NSMakeRange(10, 6)] ]; + BONTag *resultTag = tags.firstObject; + XCTAssertEqualObjects(resultTag.ranges, tagRanges); +} + +@end diff --git a/Pod/Classes/BONChain.h b/Pod/Classes/BONChain.h index 6cec58c7..0a69a353 100644 --- a/Pod/Classes/BONChain.h +++ b/Pod/Classes/BONChain.h @@ -13,7 +13,8 @@ BON_ASSUME_NONNULL_BEGIN -@class BONChain, BONTag; +@class BONChain; +@class BONTag; typedef BONChain *BONCNonnull (^BONChainFontNameAndSize)(NSString *BONCNonnull fontName, CGFloat fontSize); typedef BONChain *BONCNonnull (^BONChainFont)(UIFont *BONCNullable font); @@ -98,8 +99,8 @@ typedef BONChain *BONCNonnull (^BONTagComplexStyles)(BONGeneric(NSArray, BONTag @property (copy, nonatomic, readonly) BONChainStrikethroughColor strikethroughColor; /** - * Assign @p BONTextables to use in styling substrings surrounded in given tags. - * For example, ["b": boldTextable] would apply the @p boldChain + * Assign @p BONTextables to use in styling substrings surrounded by given tags. + * For example, ["b": boldChain] would apply the @p boldChain * to any substring surrounded by and remove the tags from the resulting * attributed string. Nested tagging is not supported. */ diff --git a/Pod/Classes/BONChain.m b/Pod/Classes/BONChain.m index 33f3b16b..c49d0b44 100644 --- a/Pod/Classes/BONChain.m +++ b/Pod/Classes/BONChain.m @@ -356,7 +356,7 @@ - (BONChainStrikethroughColor)strikethroughColor - (BONTagStyles)tagStyles { - BONTagStyles tagStylesBlock = ^(NSDictionary *tagStyles) { + BONTagStyles tagStylesBlock = ^(BONGeneric(NSDictionary, NSString *, id)*tagStyles) { __typeof(self) newChain = self.copyWithoutNextText; newChain.text.tagStyles = BONTagsFromDictionary(tagStyles); return newChain; @@ -367,7 +367,7 @@ - (BONTagStyles)tagStyles - (BONTagComplexStyles)tagComplexStyles { - BONTagComplexStyles tagStylesBlock = ^(NSArray *tagStyles) { + BONTagComplexStyles tagStylesBlock = ^(BONGeneric(NSArray, BONTag *)*tagStyles) { __typeof(self) newChain = self.copyWithoutNextText; newChain.text.tagStyles = tagStyles; return newChain; diff --git a/Pod/Classes/BONTag.h b/Pod/Classes/BONTag.h index d7c7fc60..a1ad8f18 100644 --- a/Pod/Classes/BONTag.h +++ b/Pod/Classes/BONTag.h @@ -1,9 +1,9 @@ // // BONTag.h -// Pods +// BonMot // // Created by Nora Trapp on 5/11/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // #import @@ -33,20 +33,24 @@ * * @return A @p BONTag instance representing the tag. */ -CG_INLINE BONTag *BONCNonnull BONTagComplexMake(NSString *BONCNonnull startTag, NSString *BONCNonnull endTag, NSString *BONCNonnull escapeString, id BONCNonnull textable) +NS_INLINE BONTag *BONCNonnull BONTagComplexMake(NSString *BONCNonnull startTag, NSString *BONCNonnull endTag, NSString *BONCNonnull escapeString, id BONCNonnull textable) { return [[BONTag alloc] initWithStartTag:startTag endTag:endTag escapeString:escapeString textable:textable]; } /** - * Create a tag using the default start and end formats and using \ as an escape character. + * Create a tag using the default start and end formats and using \ as an escape character. Example: + * @code + * Normal String + * String with escaped \ + * @endcode * * @param tag The tag string. * @param textable The style to apply. * * @return A @p BONTag instance representing the tag. */ -CG_INLINE BONTag *BONCNonnull BONTagMake(NSString *BONCNonnull tag, id BONCNonnull textable) +NS_INLINE BONTag *BONCNonnull BONTagMake(NSString *BONCNonnull tag, id BONCNonnull textable) { return [[BONTag alloc] initWithTag:tag textable:textable]; } @@ -54,11 +58,11 @@ CG_INLINE BONTag *BONCNonnull BONTagMake(NSString *BONCNonnull tag, id) * BONCNonnull dictionary) +NS_INLINE BONGeneric(NSArray, BONTag *) * BONCNonnull BONTagsFromDictionary(BONGeneric(NSDictionary, NSString *, id) * BONCNonnull dictionary) { NSMutableArray *tags = [NSMutableArray array]; for (NSString *key in dictionary) { diff --git a/Pod/Classes/BONTag.m b/Pod/Classes/BONTag.m index 50ed19ff..fd9b1a2d 100644 --- a/Pod/Classes/BONTag.m +++ b/Pod/Classes/BONTag.m @@ -1,19 +1,19 @@ // // BONTag.m -// Pods +// BonMot // // Created by Nora Trapp on 5/11/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // #import "BONTag_Private.h" #import "BONMot.h" -static const NSString *kBONTagDefaultStartPrefix = @"<"; -static const NSString *kBONTagDefaultStartSuffix = @">"; -static const NSString *kBONTagDefaultEndPrefix = @""; -static const NSString *kBONTagDefaultEscapeString = @"\\"; +static NSString *const kBONTagDefaultStartPrefix = @"<"; +static NSString *const kBONTagDefaultStartSuffix = @">"; +static NSString *const kBONTagDefaultEndPrefix = @""; +static NSString *const kBONTagDefaultEscapeString = @"\\"; @interface BONTag () @@ -70,15 +70,18 @@ - (NSString *)description #pragma mark - Tag Matching -+ (NSArray *)escapedRangesInString:(NSString **)string withTags:(NSArray *)tags stripEscapeCharacters:(BOOL)stripEscapeCharacters ++ (BONGeneric(NSArray, NSValue *) *)escapedRangesInString:(NSString **)string withTags:(BONGeneric(NSArray, BONTag *) *)tags { + NSParameterAssert(string); + NSParameterAssert(tags); + NSMutableArray *escapedRanges = [NSMutableArray array]; NSString *theString = *string; NSRange searchRange = NSMakeRange(0, theString.length); - // Iterate over the string finding each escape in order until there are no more escape strings + // Iterate over the string, finding each escape in order, until there are no more escape strings while (YES) { BONTag *nextTag; NSRange nextEscapeRange; @@ -111,16 +114,13 @@ + (NSArray *)escapedRangesInString:(NSString **)string withTags:(NSArray *)tags break; } - NSRange range = NSMakeRange(nextEscapeRange.location, nextEscapedTagRange.length + nextEscapeRange.length); - - if (stripEscapeCharacters) { - theString = [theString stringByReplacingOccurrencesOfString:nextTag.escapeString withString:@"" options:0 range:nextEscapeRange]; - range = NSMakeRange(nextEscapeRange.location, nextEscapedTagRange.length); - } + // Strip escape characters + theString = [theString stringByReplacingOccurrencesOfString:nextTag.escapeString withString:@"" options:0 range:nextEscapeRange]; + NSRange range = NSMakeRange(nextEscapeRange.location, nextEscapedTagRange.length); [escapedRanges addObject:[NSValue valueWithRange:range]]; - searchRange = NSMakeRange(NSMaxRange(range), [theString length] - NSMaxRange(range)); + searchRange = NSMakeRange(NSMaxRange(range), theString.length - NSMaxRange(range)); } *string = theString; @@ -128,8 +128,12 @@ + (NSArray *)escapedRangesInString:(NSString **)string withTags:(NSArray *)tags return escapedRanges; } -+ (NSRange)findNextString:(NSString *)string inString:(NSString *)stringToSearch ignoringRanges:(NSArray *)escapedRanges range:(NSRange)range ++ (NSRange)firstOccurrenceOfString:(NSString *)string inString:(NSString *)stringToSearch ignoringRanges:(BONGeneric(NSArray, NSValue *) *)escapedRanges inRange:(NSRange)range { + NSParameterAssert(string); + NSParameterAssert(stringToSearch); + NSParameterAssert(escapedRanges); + NSRange searchRange = range; while (YES) { @@ -137,7 +141,7 @@ + (NSRange)findNextString:(NSString *)string inString:(NSString *)stringToSearch // Ignore this match if ([escapedRanges containsObject:[NSValue valueWithRange:stringRange]]) { - searchRange = NSMakeRange(NSMaxRange(stringRange), [stringToSearch length] - NSMaxRange(stringRange)); + searchRange = NSMakeRange(NSMaxRange(stringRange), stringToSearch.length - NSMaxRange(stringRange)); continue; } @@ -145,17 +149,20 @@ + (NSRange)findNextString:(NSString *)string inString:(NSString *)stringToSearch } } -+ (NSArray *)rangesInString:(NSString **)string betweenTags:(NSArray *)tags stripTags:(BOOL)stripTags ++ (BONGeneric(NSArray, BONTag *) *)rangesInString:(NSString **)string betweenTags:(BONGeneric(NSArray, BONTag *) *)tags { - NSArray *escapedRanges = [BONTag escapedRangesInString:string withTags:tags stripEscapeCharacters:stripTags]; + NSParameterAssert(string); + NSParameterAssert(tags); + + BONGeneric(NSArray, NSValue *)*escapedRanges = [BONTag escapedRangesInString:string withTags:tags]; - NSArray *tagsWithRanges = [[NSArray alloc] initWithArray:tags copyItems:YES]; + BONGeneric(NSArray, BONTag *)*tagsWithRanges = [[NSArray alloc] initWithArray:tags copyItems:YES]; NSString *theString = *string; NSRange searchRange = NSMakeRange(0, theString.length); - // Iterate over the string finding each tag in order until there are no more tags + // Iterate over the string, finding each tag in order, until there are no more tags while (YES) { BONTag *nextTag; NSRange nextStartTagRange; @@ -163,8 +170,8 @@ + (NSArray *)rangesInString:(NSString **)string betweenTags:(NSArray *)tags stri // Find the next start tag for (BONTag *tag in tagsWithRanges) { - NSRange startTagRange = [BONTag findNextString:tag.startTag inString:theString ignoringRanges:escapedRanges range:searchRange]; - NSRange endTagRange = [BONTag findNextString:tag.endTag inString:theString ignoringRanges:escapedRanges range:searchRange]; + NSRange startTagRange = [BONTag firstOccurrenceOfString:tag.startTag inString:theString ignoringRanges:escapedRanges inRange:searchRange]; + NSRange endTagRange = [BONTag firstOccurrenceOfString:tag.endTag inString:theString ignoringRanges:escapedRanges inRange:searchRange]; if (startTagRange.location != NSNotFound && endTagRange.location != NSNotFound) { if (!nextTag || (startTagRange.location < nextStartTagRange.location)) { nextTag = tag; @@ -180,20 +187,19 @@ + (NSArray *)rangesInString:(NSString **)string betweenTags:(NSArray *)tags stri NSRange range = NSMakeRange(NSMaxRange(nextStartTagRange), nextEndTagRange.location - NSMaxRange(nextStartTagRange)); - if (stripTags) { - range.location -= nextTag.startTag.length; + // Strip valid tags + range.location -= nextTag.startTag.length; - theString = [theString stringByReplacingOccurrencesOfString:nextTag.startTag withString:@"" options:0 range:nextStartTagRange]; - nextStartTagRange.length = 0; + theString = [theString stringByReplacingOccurrencesOfString:nextTag.startTag withString:@"" options:0 range:nextStartTagRange]; + nextStartTagRange.length = 0; - nextEndTagRange.location -= nextTag.startTag.length; - theString = [theString stringByReplacingOccurrencesOfString:nextTag.endTag withString:@"" options:0 range:nextEndTagRange]; - nextEndTagRange.length = 0; - } + nextEndTagRange.location -= nextTag.startTag.length; + theString = [theString stringByReplacingOccurrencesOfString:nextTag.endTag withString:@"" options:0 range:nextEndTagRange]; + nextEndTagRange.length = 0; [nextTag.ranges addObject:[NSValue valueWithRange:range]]; - searchRange = NSMakeRange(NSMaxRange(nextEndTagRange), [theString length] - NSMaxRange(nextEndTagRange)); + searchRange = NSMakeRange(NSMaxRange(nextEndTagRange), theString.length - NSMaxRange(nextEndTagRange)); } *string = theString; @@ -205,13 +211,11 @@ + (NSArray *)rangesInString:(NSString **)string betweenTags:(NSArray *)tags stri - (BOOL)isEqualToTag:(BONTag *)tag { - BOOL startTagEqual = [tag.startTag isEqualToString:self.startTag]; - BOOL endTagEqual = [tag.endTag isEqualToString:self.endTag]; - BOOL chainEqual = [tag.textable isEqual:self.textable]; - BOOL escapeEqual = [tag.escapeString isEqualToString:self.escapeString]; - BOOL rangesEqual = [tag.ranges isEqualToArray:self.ranges]; - - return startTagEqual && endTagEqual && chainEqual && escapeEqual && rangesEqual; + return [tag.startTag isEqualToString:self.startTag] && + [tag.endTag isEqualToString:self.endTag] && + [tag.textable isEqual:self.textable] && + [tag.escapeString isEqualToString:self.escapeString] && + [tag.ranges isEqualToArray:self.ranges]; } - (BOOL)isEqual:(id)object diff --git a/Pod/Classes/BONTag_Private.h b/Pod/Classes/BONTag_Private.h index 82928121..861f8d04 100644 --- a/Pod/Classes/BONTag_Private.h +++ b/Pod/Classes/BONTag_Private.h @@ -1,9 +1,9 @@ // // BONTag_Private.h -// Pods +// BonMot // // Created by Nora Trapp on 5/11/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // #import "BONTag.h" @@ -15,13 +15,12 @@ /** * Finds all escaped tags within a given string. * - * @param string The string to search. + * @param string The @p string to search. The @p string will be mutated to strip escape characters. * @param tags The tags to search for. - * @param stripEscapeCharacters If YES, the escape characters will be removed from the string and the resulting ranges. * * @return An array of ranges representing the escaped tags. */ -+ (BONNonnull BONGeneric(NSArray, NSValue *) *)escapedRangesInString:(NSString *BONCNonnull *BONCNonnull)string withTags:(BONNonnull BONGeneric(NSArray, BONTag *) *)tags stripEscapeCharacters:(BOOL)stripEscapeCharacters; ++ (BONNonnull BONGeneric(NSArray, NSValue *) *)escapedRangesInString:(NSString *BONCNonnull *BONCNonnull)string withTags:(BONNonnull BONGeneric(NSArray, BONTag *) *)tags; /** * Search through a string to find the next matching string that is not in an escaped range. @@ -29,21 +28,20 @@ * @param string The string to look for. * @param stringToSearch The string to search within. * @param escapedRanges The ranges to ignore matches within. - * @param range The range of @p string to search within. + * @param range The range of @p string to search. * - * @return The range of the matched string. If the string is not found, location will be NSNotFound. + * @return The range of the matched string. If the string is not found, location will be @p NSNotFound. */ -+ (NSRange)findNextString:(BONNonnull NSString *)string inString:(BONNonnull NSString *)stringToSearch ignoringRanges:(BONNonnull BONGeneric(NSArray, NSValue *) *)escapedRanges range:(NSRange)range; ++ (NSRange)firstOccurrenceOfString:(BONNonnull NSString *)string inString:(BONNonnull NSString *)stringToSearch ignoringRanges:(BONNonnull BONGeneric(NSArray, NSValue *) *)escapedRanges inRange:(NSRange)range; /** * Find all tagged strings within a given string. * - * @param string The string to search. + * @param string The @p string to search. The @p string will be mutated to strip valid tags. * @param tags The tags to search for. - * @param stripTags If YES, the start and end tags will be stripped from the string and the resulting ranges. * * @return An array of tags with the matching ranges defined. */ -+ (BONNonnull BONGeneric(NSArray, BONTag *) *)rangesInString:(NSString *BONCNonnull *BONCNonnull)string betweenTags:(BONNonnull BONGeneric(NSArray, BONTag *) *)tags stripTags:(BOOL)stripTags; ++ (BONNonnull BONGeneric(NSArray, BONTag *) *)rangesInString:(NSString *BONCNonnull *BONCNonnull)string betweenTags:(BONNonnull BONGeneric(NSArray, BONTag *) *)tags; @end diff --git a/Pod/Classes/BONText.h b/Pod/Classes/BONText.h index 65e9d4a8..ae3f6081 100644 --- a/Pod/Classes/BONText.h +++ b/Pod/Classes/BONText.h @@ -23,7 +23,8 @@ typedef NS_ENUM(NSUInteger, BONFigureSpacing) { BONFigureSpacingProportional, }; -@class BONText, BONTag; +@class BONText; +@class BONTag; @interface BONText : NSObject @@ -85,7 +86,7 @@ typedef NS_ENUM(NSUInteger, BONFigureSpacing) { @property (strong, nonatomic, BONNullable) UIColor *strikethroughColor; /** - * Assign an array of @p BONTags to use in styling substrings. + * An array of @p BONTag objects to use in styling substrings. */ @property (strong, nonatomic, BONNullable) BONGeneric(NSArray, BONTag *) * tagStyles; diff --git a/Pod/Classes/BONText.m b/Pod/Classes/BONText.m index 1ad158b7..1479af79 100644 --- a/Pod/Classes/BONText.m +++ b/Pod/Classes/BONText.m @@ -93,18 +93,18 @@ - (NSAttributedString *)attributedStringLastConcatenant:(BOOL)lastConcatenant } } else if (string) { - // If there is tag styling applied, strip the tags from the string and identify the ranges to apply the tag based chains to. - NSArray *rangesPerTag = nil; + // If there is tag styling applied, strip the tags from the string and identify the ranges to apply the tag-based chains to. + BONGeneric(NSArray, BONTag *)*rangesPerTag = nil; if (self.tagStyles) { - rangesPerTag = [BONTag rangesInString:&string betweenTags:self.tagStyles stripTags:YES]; + rangesPerTag = [BONTag rangesInString:&string betweenTags:self.tagStyles]; } mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:string attributes:self.attributes]; for (BONTag *tag in rangesPerTag) { - NSDictionary *attributes = tag.textable.text.attributes; + BONStringDict *attributes = tag.textable.text.attributes; for (NSValue *value in tag.ranges) { [mutableAttributedString setAttributes:attributes range:value.rangeValue]; } diff --git a/Pod/UIKit/BonMot+UIKit.h b/Pod/UIKit/BonMot+UIKit.h index e10f4771..656a4b5e 100644 --- a/Pod/UIKit/BonMot+UIKit.h +++ b/Pod/UIKit/BonMot+UIKit.h @@ -3,7 +3,7 @@ // BonMot // // Created by Nora Trapp on 3/10/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // #import "BONTextAlignmentConstraint.h" diff --git a/Pod/UIKit/Classes/UILabel+BonMotUtilities.h b/Pod/UIKit/Classes/UILabel+BonMotUtilities.h index 3b5aef65..ab3ad7ff 100644 --- a/Pod/UIKit/Classes/UILabel+BonMotUtilities.h +++ b/Pod/UIKit/Classes/UILabel+BonMotUtilities.h @@ -3,7 +3,7 @@ // BonMot // // Created by Nora Trapp on 3/2/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // @import UIKit; diff --git a/Pod/UIKit/Classes/UILabel+BonMotUtilities.m b/Pod/UIKit/Classes/UILabel+BonMotUtilities.m index 77766652..3969ffb7 100644 --- a/Pod/UIKit/Classes/UILabel+BonMotUtilities.m +++ b/Pod/UIKit/Classes/UILabel+BonMotUtilities.m @@ -3,7 +3,7 @@ // BonMot // // Created by Nora Trapp on 3/2/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // #import "BonMot.h" diff --git a/Pod/UIKit/Classes/UITextField+BonMotUtilities.h b/Pod/UIKit/Classes/UITextField+BonMotUtilities.h index b9bcb896..51fadfc4 100644 --- a/Pod/UIKit/Classes/UITextField+BonMotUtilities.h +++ b/Pod/UIKit/Classes/UITextField+BonMotUtilities.h @@ -3,7 +3,7 @@ // BonMot // // Created by Nora Trapp on 3/2/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // @import UIKit; diff --git a/Pod/UIKit/Classes/UITextField+BonMotUtilities.m b/Pod/UIKit/Classes/UITextField+BonMotUtilities.m index 94e88e68..b2a23143 100644 --- a/Pod/UIKit/Classes/UITextField+BonMotUtilities.m +++ b/Pod/UIKit/Classes/UITextField+BonMotUtilities.m @@ -3,7 +3,7 @@ // BonMot // // Created by Nora Trapp on 3/2/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // #import "BonMot.h" diff --git a/Pod/UIKit/Classes/UITextView+BonMotUtilities.h b/Pod/UIKit/Classes/UITextView+BonMotUtilities.h index ddcce300..9578df3e 100644 --- a/Pod/UIKit/Classes/UITextView+BonMotUtilities.h +++ b/Pod/UIKit/Classes/UITextView+BonMotUtilities.h @@ -3,7 +3,7 @@ // BonMot // // Created by Nora Trapp on 3/2/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // @import UIKit; diff --git a/Pod/UIKit/Classes/UITextView+BonMotUtilities.m b/Pod/UIKit/Classes/UITextView+BonMotUtilities.m index 17531209..128f31ad 100644 --- a/Pod/UIKit/Classes/UITextView+BonMotUtilities.m +++ b/Pod/UIKit/Classes/UITextView+BonMotUtilities.m @@ -3,7 +3,7 @@ // BonMot // // Created by Nora Trapp on 3/2/16. -// +// Copyright © 2015 Zev Eisenberg. All rights reserved. // #import "BonMot.h" diff --git a/README.md b/README.md index a469a756..ebc03697 100644 --- a/README.md +++ b/README.md @@ -270,15 +270,15 @@ Prints this: ## Tag Styles -BonMot can style text between arbirtrary tags using a `` format and `\` as an escape character. +BonMot can style text between arbirtrary tags using a `` format and `\` as an escape character. This allows you to apply styles to substrings of localized strings, whose position, order, and even existence may change from language to language. ```objc BONChain *boldChain = BONChain.new.fontNameAndSize(@"Baskerville-Bold", 15.0f); BONChain *italicChain = BONChain.new.fontNameAndSize(@"Baskerville-Italic", 15.0f); BONChain *chain = BONChain.new.fontNameAndSize(@"Baskerville", 17.0f) - .tagStyles(@[BONTagMake(@"bold", boldChain), BONTagMake(@"italic", italicChain)]) - .string(@"This text is wrapped in a \\ tag.\nThis text is wrapped in an \\ tag."); + .tagStyles( @{ @"bold": boldChain, @"italic": italicChain } ) + .string(@"This text contains a \\ tag.\nThis text contains an \\ tag."); NSAttributedString *string = chain.attributedString; ``` @@ -294,7 +294,7 @@ BONChain *boldChain = BONChain.new.fontNameAndSize(@"Baskerville-Bold", 15.0f); BONChain *italicChain = BONChain.new.fontNameAndSize(@"Baskerville-Italic", 15.0f); BONChain *chain = BONChain.new.fontNameAndSize(@"Baskerville", 17.0f) -.tagStyles(@[BONTagComplexMake(@"~start~", @"!end", @"escape", boldChain)]) +.tagComplexStyles(@[BONTagComplexMake(@"~start~", @"!end", @"escape", boldChain)]) .string(@"~start~This text is wrapped in a escape~start~ tag.!end"); NSAttributedString *string = chain.attributedString; diff --git a/readme-images/tag-styling.png b/readme-images/tag-styling.png index 64900b6e5413c429ac222577923a31de4b0d3ae2..56eb475ccf3361121f7a5c536b007cede9d4e927 100644 GIT binary patch literal 17781 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 Date: Thu, 19 May 2016 16:46:50 -0700 Subject: [PATCH 4/4] Mark BONTagMake functions unavailable in swift. --- Pod/Classes/BONTag.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Pod/Classes/BONTag.h b/Pod/Classes/BONTag.h index a1ad8f18..5257365f 100644 --- a/Pod/Classes/BONTag.h +++ b/Pod/Classes/BONTag.h @@ -33,7 +33,7 @@ * * @return A @p BONTag instance representing the tag. */ -NS_INLINE BONTag *BONCNonnull BONTagComplexMake(NSString *BONCNonnull startTag, NSString *BONCNonnull endTag, NSString *BONCNonnull escapeString, id BONCNonnull textable) +NS_INLINE BONTag *BONCNonnull BONTagComplexMake(NSString *BONCNonnull startTag, NSString *BONCNonnull endTag, NSString *BONCNonnull escapeString, id BONCNonnull textable) NS_SWIFT_UNAVAILABLE("Use BONTag(startTag:endTag:escapeString:textable:)") { return [[BONTag alloc] initWithStartTag:startTag endTag:endTag escapeString:escapeString textable:textable]; } @@ -50,7 +50,7 @@ NS_INLINE BONTag *BONCNonnull BONTagComplexMake(NSString *BONCNonnull startTag, * * @return A @p BONTag instance representing the tag. */ -NS_INLINE BONTag *BONCNonnull BONTagMake(NSString *BONCNonnull tag, id BONCNonnull textable) +NS_INLINE BONTag *BONCNonnull BONTagMake(NSString *BONCNonnull tag, id BONCNonnull textable) NS_SWIFT_UNAVAILABLE("Use BONTag(tag:textable:)") { return [[BONTag alloc] initWithTag:tag textable:textable]; }