├── .gitignore ├── LICENSE.markdown ├── MGWordCounterDemo.xcodeproj └── project.pbxproj ├── MGWordCounterDemo ├── MGWordCountOperation.h ├── MGWordCountOperation.m ├── MGWordCounter.h ├── MGWordCounter.m ├── MGWordCounterDelegate.h ├── MGWordCounterDemo-Info.plist ├── MGWordCounterDemo-Prefix.pch ├── MLGAppDelegate.h ├── MLGAppDelegate.m ├── MLGDocument.h ├── MLGDocument.m ├── en.lproj │ ├── Credits.rtf │ ├── InfoPlist.strings │ ├── MLGDocument.xib │ └── MainMenu.xib └── main.m ├── MGWordCounterDemoMobile ├── Default-568h@2x.png ├── Default.png ├── Default@2x.png ├── MGWordCounterDemoMobile-Info.plist ├── MGWordCounterDemoMobile-Prefix.pch ├── MLGAppDelegate.h ├── MLGAppDelegate.m ├── MLGViewController.h ├── MLGViewController.m ├── en.lproj │ ├── InfoPlist.strings │ └── MLGViewController.xib └── main.m └── README.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /LICENSE.markdown: -------------------------------------------------------------------------------- 1 | # Matt Gemmell / Instinctive Code Source Code License 2 | 3 | Last updated: 4th February 2013 4 | 5 | * * * 6 | 7 | Thanks for downloading some of our source code! 8 | 9 | This is the license agreement for the source code which this document accompanies (don’t worry: you’re allowed to use it in your own products, commercial or otherwise). 10 | 11 | The full license text is further down this page, and you should only use the source code if you agree to the terms in that text. For convenience, though, we’ve put together a human-readable **non-authoritative** interpretation of the license which will hopefully answer any questions you have. 12 | 13 | 14 | 15 | ## Your rights and obligations 16 | 17 | Basically, the license says that: 18 | 19 | 1. You can use the code in your own products, including commercial and/or closed-source products. 20 | 2. You can modify the code as you wish, and use the modified code in your products. 21 | 3. You can redistribute the original, unmodified code, but you have to include the full license text below. 22 | 4. You can redistribute the modified code as you wish (without the full license text below). 23 | 5. In all cases, you must include a credit mentioning Matt Gemmell as the original author of the source. 24 | 6. Matt Gemmell is not liable for anything you do with the code, no matter what. So be sensible. 25 | 7. You can’t use the name Matt Gemmell, the name Instinctive Code, the Instinctive Code logo or any other related marks to promote your products based on the code. 26 | 8. If you agree to all of that, go ahead and use the source. Otherwise, don’t! 27 | 28 | 29 | 30 | ## Suggested Attribution Format 31 | 32 | The license requires that you give credit to Matt Gemmell, as the original author of any of our source that you use. The placement and format of the credit is up to you, but we prefer the credit to be in the software’s "About" window or similar location. Alternatively, you could put the credit in a list of acknowledgements within the software, in the software’s documentation, or on the web page for the software. The suggested format for the attribution is: 33 | 34 | **Includes [Name of Code] code by [Matt Gemmell](http://mattgemmell.com/)** 35 | 36 | where [Name of Code] would be replaced by the name of the specific source-code package you made use of. Where possible, please link the text "Matt Gemmell" to the following URL, or include the URL as plain text: http://mattgemmell.com/ 37 | 38 | 39 | 40 | ## Full Source Code License Text 41 | 42 | Below you can find the actual text of the license agreement. 43 | 44 | * * * 45 | 46 | **License Agreement for Source Code provided by Matt Gemmell** 47 | 48 | This software is supplied to you by Matt Gemmell in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this software. 49 | 50 | In consideration of your agreement to abide by the following terms, and subject to these terms, Matt Gemmell grants you a personal, non-exclusive license, to use, reproduce, modify and redistribute the software, with or without modifications, in source and/or binary forms; provided that if you redistribute the software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the software, and that in all cases attribution of Matt Gemmell as the original author of the source code shall be included in all such resulting software products or distributions. 51 | 52 | Neither the name, trademarks, service marks or logos of Matt Gemmell or Instinctive Code may be used to endorse or promote products derived from the software without specific prior written permission from Matt Gemmell. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Matt Gemmell herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the software may be incorporated. 53 | 54 | The software is provided by Matt Gemmell on an "AS IS" basis. MATT GEMMELL AND INSTINCTIVE CODE MAKE NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 55 | 56 | IN NO EVENT SHALL MATT GEMMELL OR INSTINCTIVE CODE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF MATT GEMMELL OR INSTINCTIVE CODE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MGWordCounterDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C9DD077D16C012EC000437F6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9DD077C16C012EC000437F6 /* Cocoa.framework */; }; 11 | C9DD078716C012EC000437F6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C9DD078516C012EC000437F6 /* InfoPlist.strings */; }; 12 | C9DD078916C012EC000437F6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD078816C012EC000437F6 /* main.m */; }; 13 | C9DD078D16C012EC000437F6 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = C9DD078B16C012EC000437F6 /* Credits.rtf */; }; 14 | C9DD079016C012EC000437F6 /* MLGDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD078F16C012EC000437F6 /* MLGDocument.m */; }; 15 | C9DD079316C012EC000437F6 /* MLGDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9DD079116C012EC000437F6 /* MLGDocument.xib */; }; 16 | C9DD079616C012EC000437F6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9DD079416C012EC000437F6 /* MainMenu.xib */; }; 17 | C9DD07A216C01339000437F6 /* MGWordCounter.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD079E16C01339000437F6 /* MGWordCounter.m */; }; 18 | C9DD07A316C01339000437F6 /* MGWordCountOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD07A116C01339000437F6 /* MGWordCountOperation.m */; }; 19 | C9DD07AC16C02698000437F6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9DD07AB16C02698000437F6 /* UIKit.framework */; }; 20 | C9DD07AE16C02698000437F6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9DD07AD16C02698000437F6 /* Foundation.framework */; }; 21 | C9DD07B016C02698000437F6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9DD07AF16C02698000437F6 /* CoreGraphics.framework */; }; 22 | C9DD07B616C02698000437F6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C9DD07B416C02698000437F6 /* InfoPlist.strings */; }; 23 | C9DD07B816C02698000437F6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD07B716C02698000437F6 /* main.m */; }; 24 | C9DD07BC16C02698000437F6 /* MLGAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD07BB16C02698000437F6 /* MLGAppDelegate.m */; }; 25 | C9DD07BE16C02698000437F6 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = C9DD07BD16C02698000437F6 /* Default.png */; }; 26 | C9DD07C016C02698000437F6 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C9DD07BF16C02698000437F6 /* Default@2x.png */; }; 27 | C9DD07C216C02698000437F6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C9DD07C116C02698000437F6 /* Default-568h@2x.png */; }; 28 | C9DD07C516C02698000437F6 /* MLGViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD07C416C02698000437F6 /* MLGViewController.m */; }; 29 | C9DD07C816C02698000437F6 /* MLGViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9DD07C616C02698000437F6 /* MLGViewController.xib */; }; 30 | C9DD07CC16C02715000437F6 /* MGWordCounter.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD079E16C01339000437F6 /* MGWordCounter.m */; }; 31 | C9DD07CD16C02715000437F6 /* MGWordCountOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD07A116C01339000437F6 /* MGWordCountOperation.m */; }; 32 | C9DD07D016C045A8000437F6 /* MLGAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DD07CF16C045A8000437F6 /* MLGAppDelegate.m */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | C9DD077916C012EC000437F6 /* MGWordCounterDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MGWordCounterDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | C9DD077C16C012EC000437F6 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 38 | C9DD077F16C012EC000437F6 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 39 | C9DD078016C012EC000437F6 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 40 | C9DD078116C012EC000437F6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 41 | C9DD078416C012EC000437F6 /* MGWordCounterDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MGWordCounterDemo-Info.plist"; sourceTree = ""; }; 42 | C9DD078616C012EC000437F6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 43 | C9DD078816C012EC000437F6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 44 | C9DD078A16C012EC000437F6 /* MGWordCounterDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MGWordCounterDemo-Prefix.pch"; sourceTree = ""; }; 45 | C9DD078C16C012EC000437F6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 46 | C9DD078E16C012EC000437F6 /* MLGDocument.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLGDocument.h; sourceTree = ""; }; 47 | C9DD078F16C012EC000437F6 /* MLGDocument.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLGDocument.m; sourceTree = ""; }; 48 | C9DD079216C012EC000437F6 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MLGDocument.xib; sourceTree = ""; }; 49 | C9DD079516C012EC000437F6 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 50 | C9DD079D16C01339000437F6 /* MGWordCounter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGWordCounter.h; sourceTree = ""; }; 51 | C9DD079E16C01339000437F6 /* MGWordCounter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGWordCounter.m; sourceTree = ""; }; 52 | C9DD079F16C01339000437F6 /* MGWordCounterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGWordCounterDelegate.h; sourceTree = ""; }; 53 | C9DD07A016C01339000437F6 /* MGWordCountOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGWordCountOperation.h; sourceTree = ""; }; 54 | C9DD07A116C01339000437F6 /* MGWordCountOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGWordCountOperation.m; sourceTree = ""; }; 55 | C9DD07A516C018B7000437F6 /* README.markdown */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.markdown; sourceTree = ""; }; 56 | C9DD07AA16C02698000437F6 /* MGWordCounterDemoMobile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MGWordCounterDemoMobile.app; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | C9DD07AB16C02698000437F6 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 58 | C9DD07AD16C02698000437F6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 59 | C9DD07AF16C02698000437F6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; 60 | C9DD07B316C02698000437F6 /* MGWordCounterDemoMobile-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MGWordCounterDemoMobile-Info.plist"; sourceTree = ""; }; 61 | C9DD07B516C02698000437F6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 62 | C9DD07B716C02698000437F6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 63 | C9DD07B916C02698000437F6 /* MGWordCounterDemoMobile-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MGWordCounterDemoMobile-Prefix.pch"; sourceTree = ""; }; 64 | C9DD07BA16C02698000437F6 /* MLGAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLGAppDelegate.h; sourceTree = ""; }; 65 | C9DD07BB16C02698000437F6 /* MLGAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLGAppDelegate.m; sourceTree = ""; }; 66 | C9DD07BD16C02698000437F6 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 67 | C9DD07BF16C02698000437F6 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 68 | C9DD07C116C02698000437F6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 69 | C9DD07C316C02698000437F6 /* MLGViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLGViewController.h; sourceTree = ""; }; 70 | C9DD07C416C02698000437F6 /* MLGViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLGViewController.m; sourceTree = ""; }; 71 | C9DD07C716C02698000437F6 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MLGViewController.xib; sourceTree = ""; }; 72 | C9DD07CE16C045A8000437F6 /* MLGAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLGAppDelegate.h; sourceTree = ""; }; 73 | C9DD07CF16C045A8000437F6 /* MLGAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLGAppDelegate.m; sourceTree = ""; }; 74 | C9DD07D116C057C1000437F6 /* LICENSE.markdown */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.markdown; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | C9DD077616C012EC000437F6 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | C9DD077D16C012EC000437F6 /* Cocoa.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | C9DD07A716C02698000437F6 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | C9DD07AC16C02698000437F6 /* UIKit.framework in Frameworks */, 91 | C9DD07AE16C02698000437F6 /* Foundation.framework in Frameworks */, 92 | C9DD07B016C02698000437F6 /* CoreGraphics.framework in Frameworks */, 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | /* End PBXFrameworksBuildPhase section */ 97 | 98 | /* Begin PBXGroup section */ 99 | C9DD077016C012EC000437F6 = { 100 | isa = PBXGroup; 101 | children = ( 102 | C9DD07A516C018B7000437F6 /* README.markdown */, 103 | C9DD07D116C057C1000437F6 /* LICENSE.markdown */, 104 | C9DD078216C012EC000437F6 /* MGWordCounterDemo */, 105 | C9DD07B116C02698000437F6 /* MGWordCounterDemoMobile */, 106 | C9DD077B16C012EC000437F6 /* Frameworks */, 107 | C9DD077A16C012EC000437F6 /* Products */, 108 | ); 109 | sourceTree = ""; 110 | }; 111 | C9DD077A16C012EC000437F6 /* Products */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | C9DD077916C012EC000437F6 /* MGWordCounterDemo.app */, 115 | C9DD07AA16C02698000437F6 /* MGWordCounterDemoMobile.app */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | C9DD077B16C012EC000437F6 /* Frameworks */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | C9DD077C16C012EC000437F6 /* Cocoa.framework */, 124 | C9DD07AB16C02698000437F6 /* UIKit.framework */, 125 | C9DD07AD16C02698000437F6 /* Foundation.framework */, 126 | C9DD07AF16C02698000437F6 /* CoreGraphics.framework */, 127 | C9DD077E16C012EC000437F6 /* Other Frameworks */, 128 | ); 129 | name = Frameworks; 130 | sourceTree = ""; 131 | }; 132 | C9DD077E16C012EC000437F6 /* Other Frameworks */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | C9DD077F16C012EC000437F6 /* AppKit.framework */, 136 | C9DD078016C012EC000437F6 /* CoreData.framework */, 137 | C9DD078116C012EC000437F6 /* Foundation.framework */, 138 | ); 139 | name = "Other Frameworks"; 140 | sourceTree = ""; 141 | }; 142 | C9DD078216C012EC000437F6 /* MGWordCounterDemo */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | C9DD07A416C01343000437F6 /* MGWordCounter */, 146 | C9DD079C16C01311000437F6 /* Mac demo */, 147 | C9DD078316C012EC000437F6 /* Supporting Files */, 148 | ); 149 | path = MGWordCounterDemo; 150 | sourceTree = ""; 151 | }; 152 | C9DD078316C012EC000437F6 /* Supporting Files */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | C9DD078416C012EC000437F6 /* MGWordCounterDemo-Info.plist */, 156 | C9DD078516C012EC000437F6 /* InfoPlist.strings */, 157 | C9DD078816C012EC000437F6 /* main.m */, 158 | C9DD078A16C012EC000437F6 /* MGWordCounterDemo-Prefix.pch */, 159 | C9DD078B16C012EC000437F6 /* Credits.rtf */, 160 | ); 161 | name = "Supporting Files"; 162 | sourceTree = ""; 163 | }; 164 | C9DD079C16C01311000437F6 /* Mac demo */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | C9DD078E16C012EC000437F6 /* MLGDocument.h */, 168 | C9DD078F16C012EC000437F6 /* MLGDocument.m */, 169 | C9DD079116C012EC000437F6 /* MLGDocument.xib */, 170 | C9DD079416C012EC000437F6 /* MainMenu.xib */, 171 | C9DD07CE16C045A8000437F6 /* MLGAppDelegate.h */, 172 | C9DD07CF16C045A8000437F6 /* MLGAppDelegate.m */, 173 | ); 174 | name = "Mac demo"; 175 | sourceTree = ""; 176 | }; 177 | C9DD07A416C01343000437F6 /* MGWordCounter */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | C9DD079D16C01339000437F6 /* MGWordCounter.h */, 181 | C9DD079E16C01339000437F6 /* MGWordCounter.m */, 182 | C9DD079F16C01339000437F6 /* MGWordCounterDelegate.h */, 183 | C9DD07A016C01339000437F6 /* MGWordCountOperation.h */, 184 | C9DD07A116C01339000437F6 /* MGWordCountOperation.m */, 185 | ); 186 | name = MGWordCounter; 187 | sourceTree = ""; 188 | }; 189 | C9DD07B116C02698000437F6 /* MGWordCounterDemoMobile */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | C9DD07BA16C02698000437F6 /* MLGAppDelegate.h */, 193 | C9DD07BB16C02698000437F6 /* MLGAppDelegate.m */, 194 | C9DD07C316C02698000437F6 /* MLGViewController.h */, 195 | C9DD07C416C02698000437F6 /* MLGViewController.m */, 196 | C9DD07C616C02698000437F6 /* MLGViewController.xib */, 197 | C9DD07B216C02698000437F6 /* Supporting Files */, 198 | ); 199 | path = MGWordCounterDemoMobile; 200 | sourceTree = ""; 201 | }; 202 | C9DD07B216C02698000437F6 /* Supporting Files */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | C9DD07B316C02698000437F6 /* MGWordCounterDemoMobile-Info.plist */, 206 | C9DD07B416C02698000437F6 /* InfoPlist.strings */, 207 | C9DD07B716C02698000437F6 /* main.m */, 208 | C9DD07B916C02698000437F6 /* MGWordCounterDemoMobile-Prefix.pch */, 209 | C9DD07BD16C02698000437F6 /* Default.png */, 210 | C9DD07BF16C02698000437F6 /* Default@2x.png */, 211 | C9DD07C116C02698000437F6 /* Default-568h@2x.png */, 212 | ); 213 | name = "Supporting Files"; 214 | sourceTree = ""; 215 | }; 216 | /* End PBXGroup section */ 217 | 218 | /* Begin PBXNativeTarget section */ 219 | C9DD077816C012EC000437F6 /* MGWordCounterDemo */ = { 220 | isa = PBXNativeTarget; 221 | buildConfigurationList = C9DD079916C012EC000437F6 /* Build configuration list for PBXNativeTarget "MGWordCounterDemo" */; 222 | buildPhases = ( 223 | C9DD077516C012EC000437F6 /* Sources */, 224 | C9DD077616C012EC000437F6 /* Frameworks */, 225 | C9DD077716C012EC000437F6 /* Resources */, 226 | ); 227 | buildRules = ( 228 | ); 229 | dependencies = ( 230 | ); 231 | name = MGWordCounterDemo; 232 | productName = MGWordCounterDemo; 233 | productReference = C9DD077916C012EC000437F6 /* MGWordCounterDemo.app */; 234 | productType = "com.apple.product-type.application"; 235 | }; 236 | C9DD07A916C02698000437F6 /* MGWordCounterDemoMobile */ = { 237 | isa = PBXNativeTarget; 238 | buildConfigurationList = C9DD07C916C02698000437F6 /* Build configuration list for PBXNativeTarget "MGWordCounterDemoMobile" */; 239 | buildPhases = ( 240 | C9DD07A616C02698000437F6 /* Sources */, 241 | C9DD07A716C02698000437F6 /* Frameworks */, 242 | C9DD07A816C02698000437F6 /* Resources */, 243 | ); 244 | buildRules = ( 245 | ); 246 | dependencies = ( 247 | ); 248 | name = MGWordCounterDemoMobile; 249 | productName = MGWordCounterDemoMobile; 250 | productReference = C9DD07AA16C02698000437F6 /* MGWordCounterDemoMobile.app */; 251 | productType = "com.apple.product-type.application"; 252 | }; 253 | /* End PBXNativeTarget section */ 254 | 255 | /* Begin PBXProject section */ 256 | C9DD077116C012EC000437F6 /* Project object */ = { 257 | isa = PBXProject; 258 | attributes = { 259 | CLASSPREFIX = MLG; 260 | LastUpgradeCheck = 0460; 261 | ORGANIZATIONNAME = "Instinctive Code"; 262 | }; 263 | buildConfigurationList = C9DD077416C012EC000437F6 /* Build configuration list for PBXProject "MGWordCounterDemo" */; 264 | compatibilityVersion = "Xcode 3.2"; 265 | developmentRegion = English; 266 | hasScannedForEncodings = 0; 267 | knownRegions = ( 268 | en, 269 | ); 270 | mainGroup = C9DD077016C012EC000437F6; 271 | productRefGroup = C9DD077A16C012EC000437F6 /* Products */; 272 | projectDirPath = ""; 273 | projectRoot = ""; 274 | targets = ( 275 | C9DD077816C012EC000437F6 /* MGWordCounterDemo */, 276 | C9DD07A916C02698000437F6 /* MGWordCounterDemoMobile */, 277 | ); 278 | }; 279 | /* End PBXProject section */ 280 | 281 | /* Begin PBXResourcesBuildPhase section */ 282 | C9DD077716C012EC000437F6 /* Resources */ = { 283 | isa = PBXResourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | C9DD078716C012EC000437F6 /* InfoPlist.strings in Resources */, 287 | C9DD078D16C012EC000437F6 /* Credits.rtf in Resources */, 288 | C9DD079316C012EC000437F6 /* MLGDocument.xib in Resources */, 289 | C9DD079616C012EC000437F6 /* MainMenu.xib in Resources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | C9DD07A816C02698000437F6 /* Resources */ = { 294 | isa = PBXResourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | C9DD07B616C02698000437F6 /* InfoPlist.strings in Resources */, 298 | C9DD07BE16C02698000437F6 /* Default.png in Resources */, 299 | C9DD07C016C02698000437F6 /* Default@2x.png in Resources */, 300 | C9DD07C216C02698000437F6 /* Default-568h@2x.png in Resources */, 301 | C9DD07C816C02698000437F6 /* MLGViewController.xib in Resources */, 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | /* End PBXResourcesBuildPhase section */ 306 | 307 | /* Begin PBXSourcesBuildPhase section */ 308 | C9DD077516C012EC000437F6 /* Sources */ = { 309 | isa = PBXSourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | C9DD078916C012EC000437F6 /* main.m in Sources */, 313 | C9DD079016C012EC000437F6 /* MLGDocument.m in Sources */, 314 | C9DD07A216C01339000437F6 /* MGWordCounter.m in Sources */, 315 | C9DD07A316C01339000437F6 /* MGWordCountOperation.m in Sources */, 316 | C9DD07D016C045A8000437F6 /* MLGAppDelegate.m in Sources */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | C9DD07A616C02698000437F6 /* Sources */ = { 321 | isa = PBXSourcesBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | C9DD07B816C02698000437F6 /* main.m in Sources */, 325 | C9DD07BC16C02698000437F6 /* MLGAppDelegate.m in Sources */, 326 | C9DD07C516C02698000437F6 /* MLGViewController.m in Sources */, 327 | C9DD07CC16C02715000437F6 /* MGWordCounter.m in Sources */, 328 | C9DD07CD16C02715000437F6 /* MGWordCountOperation.m in Sources */, 329 | ); 330 | runOnlyForDeploymentPostprocessing = 0; 331 | }; 332 | /* End PBXSourcesBuildPhase section */ 333 | 334 | /* Begin PBXVariantGroup section */ 335 | C9DD078516C012EC000437F6 /* InfoPlist.strings */ = { 336 | isa = PBXVariantGroup; 337 | children = ( 338 | C9DD078616C012EC000437F6 /* en */, 339 | ); 340 | name = InfoPlist.strings; 341 | sourceTree = ""; 342 | }; 343 | C9DD078B16C012EC000437F6 /* Credits.rtf */ = { 344 | isa = PBXVariantGroup; 345 | children = ( 346 | C9DD078C16C012EC000437F6 /* en */, 347 | ); 348 | name = Credits.rtf; 349 | sourceTree = ""; 350 | }; 351 | C9DD079116C012EC000437F6 /* MLGDocument.xib */ = { 352 | isa = PBXVariantGroup; 353 | children = ( 354 | C9DD079216C012EC000437F6 /* en */, 355 | ); 356 | name = MLGDocument.xib; 357 | sourceTree = ""; 358 | }; 359 | C9DD079416C012EC000437F6 /* MainMenu.xib */ = { 360 | isa = PBXVariantGroup; 361 | children = ( 362 | C9DD079516C012EC000437F6 /* en */, 363 | ); 364 | name = MainMenu.xib; 365 | sourceTree = ""; 366 | }; 367 | C9DD07B416C02698000437F6 /* InfoPlist.strings */ = { 368 | isa = PBXVariantGroup; 369 | children = ( 370 | C9DD07B516C02698000437F6 /* en */, 371 | ); 372 | name = InfoPlist.strings; 373 | sourceTree = ""; 374 | }; 375 | C9DD07C616C02698000437F6 /* MLGViewController.xib */ = { 376 | isa = PBXVariantGroup; 377 | children = ( 378 | C9DD07C716C02698000437F6 /* en */, 379 | ); 380 | name = MLGViewController.xib; 381 | sourceTree = ""; 382 | }; 383 | /* End PBXVariantGroup section */ 384 | 385 | /* Begin XCBuildConfiguration section */ 386 | C9DD079716C012EC000437F6 /* Debug */ = { 387 | isa = XCBuildConfiguration; 388 | buildSettings = { 389 | ALWAYS_SEARCH_USER_PATHS = NO; 390 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 391 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 392 | CLANG_CXX_LIBRARY = "libc++"; 393 | CLANG_ENABLE_OBJC_ARC = YES; 394 | CLANG_WARN_CONSTANT_CONVERSION = YES; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INT_CONVERSION = YES; 398 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 399 | COPY_PHASE_STRIP = NO; 400 | GCC_C_LANGUAGE_STANDARD = gnu99; 401 | GCC_DYNAMIC_NO_PIC = NO; 402 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 403 | GCC_OPTIMIZATION_LEVEL = 0; 404 | GCC_PREPROCESSOR_DEFINITIONS = ( 405 | "DEBUG=1", 406 | "$(inherited)", 407 | ); 408 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 409 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 410 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 411 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 412 | GCC_WARN_UNUSED_VARIABLE = YES; 413 | MACOSX_DEPLOYMENT_TARGET = 10.8; 414 | ONLY_ACTIVE_ARCH = YES; 415 | SDKROOT = macosx; 416 | }; 417 | name = Debug; 418 | }; 419 | C9DD079816C012EC000437F6 /* Release */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | ALWAYS_SEARCH_USER_PATHS = NO; 423 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 424 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 425 | CLANG_CXX_LIBRARY = "libc++"; 426 | CLANG_ENABLE_OBJC_ARC = YES; 427 | CLANG_WARN_CONSTANT_CONVERSION = YES; 428 | CLANG_WARN_EMPTY_BODY = YES; 429 | CLANG_WARN_ENUM_CONVERSION = YES; 430 | CLANG_WARN_INT_CONVERSION = YES; 431 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 432 | COPY_PHASE_STRIP = YES; 433 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 434 | GCC_C_LANGUAGE_STANDARD = gnu99; 435 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 436 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 437 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 438 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 439 | GCC_WARN_UNUSED_VARIABLE = YES; 440 | MACOSX_DEPLOYMENT_TARGET = 10.8; 441 | SDKROOT = macosx; 442 | }; 443 | name = Release; 444 | }; 445 | C9DD079A16C012EC000437F6 /* Debug */ = { 446 | isa = XCBuildConfiguration; 447 | buildSettings = { 448 | COMBINE_HIDPI_IMAGES = YES; 449 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 450 | GCC_PREFIX_HEADER = "MGWordCounterDemo/MGWordCounterDemo-Prefix.pch"; 451 | INFOPLIST_FILE = "MGWordCounterDemo/MGWordCounterDemo-Info.plist"; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | WRAPPER_EXTENSION = app; 454 | }; 455 | name = Debug; 456 | }; 457 | C9DD079B16C012EC000437F6 /* Release */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | COMBINE_HIDPI_IMAGES = YES; 461 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 462 | GCC_PREFIX_HEADER = "MGWordCounterDemo/MGWordCounterDemo-Prefix.pch"; 463 | INFOPLIST_FILE = "MGWordCounterDemo/MGWordCounterDemo-Info.plist"; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | WRAPPER_EXTENSION = app; 466 | }; 467 | name = Release; 468 | }; 469 | C9DD07CA16C02698000437F6 /* Debug */ = { 470 | isa = XCBuildConfiguration; 471 | buildSettings = { 472 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 473 | FRAMEWORK_SEARCH_PATHS = ( 474 | "$(inherited)", 475 | "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Developer/Library/Frameworks\"", 476 | ); 477 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 478 | GCC_PREFIX_HEADER = "MGWordCounterDemoMobile/MGWordCounterDemoMobile-Prefix.pch"; 479 | INFOPLIST_FILE = "MGWordCounterDemoMobile/MGWordCounterDemoMobile-Info.plist"; 480 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | SDKROOT = iphoneos; 483 | WRAPPER_EXTENSION = app; 484 | }; 485 | name = Debug; 486 | }; 487 | C9DD07CB16C02698000437F6 /* Release */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 491 | FRAMEWORK_SEARCH_PATHS = ( 492 | "$(inherited)", 493 | "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Developer/Library/Frameworks\"", 494 | ); 495 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 496 | GCC_PREFIX_HEADER = "MGWordCounterDemoMobile/MGWordCounterDemoMobile-Prefix.pch"; 497 | INFOPLIST_FILE = "MGWordCounterDemoMobile/MGWordCounterDemoMobile-Info.plist"; 498 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 499 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | SDKROOT = iphoneos; 502 | VALIDATE_PRODUCT = YES; 503 | WRAPPER_EXTENSION = app; 504 | }; 505 | name = Release; 506 | }; 507 | /* End XCBuildConfiguration section */ 508 | 509 | /* Begin XCConfigurationList section */ 510 | C9DD077416C012EC000437F6 /* Build configuration list for PBXProject "MGWordCounterDemo" */ = { 511 | isa = XCConfigurationList; 512 | buildConfigurations = ( 513 | C9DD079716C012EC000437F6 /* Debug */, 514 | C9DD079816C012EC000437F6 /* Release */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | defaultConfigurationName = Release; 518 | }; 519 | C9DD079916C012EC000437F6 /* Build configuration list for PBXNativeTarget "MGWordCounterDemo" */ = { 520 | isa = XCConfigurationList; 521 | buildConfigurations = ( 522 | C9DD079A16C012EC000437F6 /* Debug */, 523 | C9DD079B16C012EC000437F6 /* Release */, 524 | ); 525 | defaultConfigurationIsVisible = 0; 526 | }; 527 | C9DD07C916C02698000437F6 /* Build configuration list for PBXNativeTarget "MGWordCounterDemoMobile" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | C9DD07CA16C02698000437F6 /* Debug */, 531 | C9DD07CB16C02698000437F6 /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | }; 535 | /* End XCConfigurationList section */ 536 | }; 537 | rootObject = C9DD077116C012EC000437F6 /* Project object */; 538 | } 539 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MGWordCountOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGWordCountOperation.h 3 | // 4 | // Created by Matt Gemmell on 30/01/2013. 5 | // Copyright (c) 2013 Instinctive Code. License: http://mattgemmell.com/license/ 6 | // 7 | 8 | #import 9 | 10 | typedef enum _MGWordCountOperationType { 11 | MGWordCountFull = 0, // the entire text of the NSTextStorage object 12 | MGWordCountReplacementString = 1, // the replaced and replacement strings during an edit 13 | MGWordCountSelection = 2 // the selected text 14 | } MGWordCountOperationType; 15 | 16 | @interface MGWordCountOperation : NSOperation 17 | 18 | @property (nonatomic, strong) NSString *text; 19 | @property (nonatomic, readonly) NSInteger wordCount; 20 | @property (nonatomic, assign) MGWordCountOperationType countType; 21 | 22 | // For use by non-full word counts, during edits. 23 | @property (nonatomic, assign) NSRange affectedRangeForEdit; 24 | @property (nonatomic, assign) NSRange expandedRangeForEdit; 25 | @property (nonatomic, strong) NSString *replacementString; // string replacing text in affectedRangeForEdit. 26 | @property (nonatomic, strong) NSString *affectedStringForEdit; // padded by (at most) MAX_WORD_LEN on either side of affectedRangeForEdit. 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MGWordCountOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGWordCountOperation.m 3 | // 4 | // Created by Matt Gemmell on 30/01/2013. 5 | // Copyright (c) 2013 Instinctive Code. License: http://mattgemmell.com/license/ 6 | // 7 | 8 | #import "MGWordCountOperation.h" 9 | 10 | @implementation MGWordCountOperation 11 | 12 | 13 | - (void)main 14 | { 15 | if (![self isCancelled]) { 16 | if (self.countType == MGWordCountFull || self.countType == MGWordCountSelection) { 17 | // This is a full word-count. 18 | __block NSUInteger theWordCount = 0; 19 | [self.text enumerateSubstringsInRange:NSMakeRange(0, self.text.length) 20 | options:NSStringEnumerationByWords 21 | usingBlock:^(NSString *character, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { 22 | theWordCount++; 23 | if ([self isCancelled]) { 24 | *stop = YES; 25 | } 26 | }]; 27 | _wordCount = theWordCount; 28 | 29 | } else if (self.countType == MGWordCountReplacementString) { 30 | // This is a two-stage partial word count, dealing with a replaced range of text. 31 | NSInteger deltaWords = 0; 32 | NSRange adjustedExpandedRange = self.expandedRangeForEdit; 33 | NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 34 | NSScanner *scanner = [NSScanner localizedScannerWithString:self.affectedStringForEdit]; 35 | BOOL scannedChars; 36 | 37 | //NSLog(@"Partial count. Starting conditions: affectedRange: %@, expandedRange: %@, affectedString:\r###\r%@\r###\rreplacement string:\r###\r%@\r###\r", NSStringFromRange(self.affectedRangeForEdit), NSStringFromRange(self.expandedRangeForEdit), self.affectedStringForEdit, self.replacementString); 38 | 39 | if (![self isCancelled]) { 40 | // First, we scan affectedStringForEdit to find the first word-boundary (if any) in the padding before the edit. 41 | [scanner setCharactersToBeSkipped:[whitespaceSet invertedSet]]; // skip everything except whitespace 42 | scannedChars = [scanner scanCharactersFromSet:whitespaceSet intoString:NULL]; 43 | //NSLog(@"Expanded string:\r###\r%@\r###\r", self.affectedStringForEdit); 44 | if (scannedChars) { 45 | // We've scanned some whitespace, and scanner's scanLocation is now just past it. 46 | // We'll use the scanLocation as the new start of the affected range. 47 | //NSLog(@"Scanned past some whitespace; now at %ld", (long)scanner.scanLocation); 48 | //NSLog(@"adjustedExpandedRange was: %@", NSStringFromRange(adjustedExpandedRange)); 49 | if (adjustedExpandedRange.location + scanner.scanLocation <= self.affectedRangeForEdit.location) { 50 | adjustedExpandedRange.location += scanner.scanLocation; 51 | adjustedExpandedRange.length -= scanner.scanLocation; 52 | } else { 53 | //NSLog(@"Expanded range seems to be AFTER affected range; moving back. ***"); 54 | } 55 | //NSLog(@"adjustedExpandedRange is now: %@", NSStringFromRange(adjustedExpandedRange)); 56 | } 57 | } 58 | 59 | if (![self isCancelled]) { 60 | // Second, we scan from start of original affected range to find the first word-boundary after the edit. 61 | scanner.scanLocation = self.affectedStringForEdit.length - (NSMaxRange(self.expandedRangeForEdit) - NSMaxRange(self.affectedRangeForEdit)); 62 | NSUInteger initialScanLocation = scanner.scanLocation; 63 | //NSLog(@"Beginning expansion-tail scan at location %ld", scanner.scanLocation); 64 | scannedChars = [scanner scanUpToCharactersFromSet:whitespaceSet intoString:NULL]; 65 | if (scanner.scanLocation == initialScanLocation) { 66 | // Skip initial whitespace. 67 | //NSLog(@"Skipping initial found whitespace at %ld", initialScanLocation); 68 | [scanner scanCharactersFromSet:whitespaceSet intoString:NULL]; 69 | scannedChars = [scanner scanUpToCharactersFromSet:whitespaceSet intoString:NULL]; 70 | } 71 | if (!scannedChars) { // this is correct, despite seeming somewhat illogical. 72 | // We've scanned up to some whitespace, and scanner's scanLocation is now just before it. 73 | // We'll use the scanLocation as the new END of the affected range. 74 | //NSLog(@"Before scan, adjustedExpandedRange is %@", NSStringFromRange(adjustedExpandedRange)); 75 | //NSLog(@"Scanned up to some whitespace; now at %ld in #%@#", (long)scanner.scanLocation, scanner.string); 76 | //NSLog(@"After scan, affected string length: %ld, scanLocation %ld, calc: %ld", self.affectedStringForEdit.length, scanner.scanLocation, (long)MAX(0, (int)(adjustedExpandedRange.length - (self.affectedStringForEdit.length - scanner.scanLocation)))); 77 | if (scanner.scanLocation > initialScanLocation) { 78 | adjustedExpandedRange.length = MAX(0, (int)(adjustedExpandedRange.length - (self.affectedStringForEdit.length - scanner.scanLocation))); 79 | } 80 | //NSLog(@"Final adjustedExpandedRange is %@", NSStringFromRange(adjustedExpandedRange)); 81 | } 82 | } 83 | 84 | if (![self isCancelled]) { 85 | // Third, we count the number of words within this adjusted expanded range of the affected string. 86 | // We must obtain the relevant substring of the affected string first. 87 | // Note that adjustedExpandedRange is relative to the entire storage, not just the affected string, 88 | // so we must first convert it to the equivalent range of the affected string. 89 | NSRange wordBoundaryRange = adjustedExpandedRange; 90 | wordBoundaryRange.location = (adjustedExpandedRange.location - self.expandedRangeForEdit.location); 91 | // Proceed with word-count. 92 | __block NSUInteger theWordCount = 0; 93 | //NSLog(@"Checking words in range %@ of #%@#", NSStringFromRange(wordBoundaryRange), self.affectedStringForEdit); 94 | if (wordBoundaryRange.length > 0) { 95 | [self.affectedStringForEdit enumerateSubstringsInRange:wordBoundaryRange 96 | options:NSStringEnumerationByWords 97 | usingBlock:^(NSString *character, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { 98 | theWordCount++; 99 | if ([self isCancelled]) { 100 | *stop = YES; 101 | } 102 | }]; 103 | } 104 | // We then subtract this number of words from our running delta. 105 | //NSLog(@"Counted %ld words (to subtract) in #%@#", (long)theWordCount, [self.affectedStringForEdit substringWithRange:adjustedExpandedRange]); 106 | deltaWords -= theWordCount; 107 | } 108 | 109 | NSRange replacedAdjustedRange = adjustedExpandedRange; 110 | if (![self isCancelled]) { 111 | // Fourth, we calculate what the adjusted expanded range would correspond to in the post-edit storage. 112 | NSInteger replacementDelta = (self.replacementString.length - self.affectedRangeForEdit.length); 113 | //NSLog(@"Edit causes a length change of %ld (%ld - %ld; replacement #%@#)", replacementDelta, self.replacementString.length, self.affectedRangeForEdit.length, self.replacementString); 114 | //NSLog(@"replacedAdjustedRange was: %@", NSStringFromRange(replacedAdjustedRange)); 115 | replacedAdjustedRange.length = MAX(0, (int)replacedAdjustedRange.length + replacementDelta); 116 | //NSLog(@"replacedAdjustedRange is now: %@", NSStringFromRange(replacedAdjustedRange)); 117 | } 118 | 119 | if (![self isCancelled]) { 120 | // Fifth, we count the number of words in this replaced-expanded range (in the storage, which has been updated). 121 | __block NSUInteger theWordCount = 0; 122 | //NSLog(@"Checking words in replacement range %@ of (all text)", NSStringFromRange(replacedAdjustedRange)); 123 | if (replacedAdjustedRange.length > 0) { 124 | [self.text enumerateSubstringsInRange:replacedAdjustedRange 125 | options:NSStringEnumerationByWords 126 | usingBlock:^(NSString *character, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { 127 | theWordCount++; 128 | if ([self isCancelled]) { 129 | *stop = YES; 130 | } 131 | }]; 132 | } 133 | // We add this number of words to our running delta. 134 | //NSLog(@"Counted %ld words (to add) in #%@#", (long)theWordCount, [self.text substringWithRange:replacedAdjustedRange]); 135 | deltaWords += theWordCount; 136 | } 137 | 138 | // Lastly, we set our running delta as the wordCount of this operation, and allow it to complete. 139 | //NSLog(@">> Net change in words: %ld <<", deltaWords); 140 | _wordCount = deltaWords; 141 | } 142 | } 143 | //NSLog(@"Operation finished %@", self); 144 | } 145 | 146 | 147 | - (NSString *)description 148 | { 149 | return [NSString stringWithFormat:@"%@ (%@)", [super description], [self descriptionOfCountType]]; 150 | } 151 | 152 | 153 | - (NSString *)descriptionOfCountType 154 | { 155 | NSString *desc = @"Full word count"; 156 | if (self.countType == MGWordCountReplacementString) { 157 | desc = @"Partial word count"; 158 | } else if (self.countType == MGWordCountSelection) { 159 | desc = @"Selection word count"; 160 | } 161 | 162 | return desc; 163 | } 164 | 165 | 166 | @end 167 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MGWordCounter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGWordCounter.h 3 | // 4 | // Created by Matt Gemmell on 29/01/2013. 5 | // Copyright (c) 2013 Instinctive Code. License: http://mattgemmell.com/license/ 6 | // 7 | 8 | #import 9 | #import "MGWordCounterDelegate.h" 10 | 11 | #if TARGET_OS_IPHONE 12 | @interface MGWordCounter : NSObject 13 | #else 14 | @interface MGWordCounter : NSObject 15 | #endif 16 | 17 | /* 18 | NOTE: Instances of this class expect to be the delegate of a Text View 19 | (i.e. NSTextViewDelegate on OS X, or UITextViewDelegate on iOS), 20 | but MGWordCounter does NOT attempt to create this relationship itself. 21 | 22 | You should either set your MGWordCounter to be the delegate of the 23 | relevant TextView, or alternatively you should forward the following 24 | (NS/UI)TextViewDelegate method calls from your actual TextView delegate: 25 | 26 | On Mac: 27 | - textView:shouldChangeTextInRange:replacementString: 28 | - textDidChange: 29 | 30 | On iOS: 31 | - textView:shouldChangeTextInRange:replacementText: 32 | - textViewDidChange: 33 | 34 | (As long as the expected information is provided to the above delegate 35 | method, the only other method called on the TextView instance will be 36 | the -string method on OS X, or the -text method on iOS.) 37 | 38 | To support counting of the _selected_ text, you should also ensure that the 39 | MGWordCounter instance receives this (NS/UI)TextViewDelegate method: 40 | 41 | On Mac: 42 | - textView:willChangeSelectionFromCharacterRange:toCharacterRange: 43 | (The last parameter, unchanged, will be the return value.) 44 | 45 | On iOS: 46 | - textViewDidChangeSelection: 47 | */ 48 | 49 | #if TARGET_OS_IPHONE 50 | + (id)wordCounterForTextView:(UITextView *)theTextView; 51 | - (id)initWithTextView:(UITextView *)theTextView; // Takes a text-view whose contents will be monitored. 52 | #else 53 | + (id)wordCounterForTextView:(NSTextView *)theTextView; 54 | - (id)initWithTextView:(NSTextView *)theTextView; // Takes a text-view whose contents will be monitored. 55 | #endif 56 | 57 | - (void)startCounting; // Enables word counting. Will immediately do a full recount. 58 | - (void)stopCounting; // Disables word counting. Pending recounts (and notifications thereof) will be allowed to finish. 59 | // Note: You must explicitly call -startCounting to begin counting. 60 | 61 | - (void)updateCount; // Updates count immediately, via full recount. You shouldn't need to call this; updates happen automatically. 62 | 63 | @property (nonatomic, weak) id delegate; // Delegate for this object. 64 | @property (nonatomic, copy) void (^counterUpdateBlock)(NSInteger, BOOL); // Block object called during update 65 | #if TARGET_OS_IPHONE 66 | @property (nonatomic, strong) UITextView *textView; // This counter's associated text-view. 67 | #else 68 | @property (nonatomic, strong) NSTextView *textView; // This counter's associated text-view. 69 | #endif 70 | 71 | @property (nonatomic, readonly) NSInteger wordCount; // Current word count of full text. 72 | @property (nonatomic, readonly) NSInteger selectionWordCount; // Current word count of selection. 73 | @property (nonatomic, readonly, getter = isUpdating) BOOL updating; // Whether we're in the middle of a recount. 74 | @property (nonatomic, readonly, getter = isEnabled) BOOL enabled; // Whether counting is enabled. 75 | 76 | @end 77 | 78 | // Notifications 79 | // The notification `object' is the sending MGWordCounter. 80 | // Notifications also have a userInfo dictionary with the following keys and values: 81 | #define MGWordCountKey @"MGWordCount" //"MGWordCount" with an NSNumber (integer, zero-based) value. 82 | #define MGCountIsForSelection @"MGCountIsForSelection" // "MGCountIsForSelection" with an NSNumber (BOOL) value. 83 | // Note: if MGCountIsForSelection is YES, then MGWordCount is the selection's word-count. If NO, MGWordCount is for the full text. 84 | extern NSString *MGWordCounterDidUpdateWordCountNotification; // word count was updated 85 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MGWordCounter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGWordCounter.m 3 | // 4 | // Created by Matt Gemmell on 29/01/2013. 5 | // Copyright (c) 2013 Instinctive Code. License: http://mattgemmell.com/license/ 6 | // 7 | 8 | #import "MGWordCounter.h" 9 | #import "MGWordCountOperation.h" 10 | 11 | #define OPERATION_FINISHED_KEY @"isFinished" // For KVO. 12 | #define MAX_WORD_LENGTH 100 // To hint the scanner during live counting. Bigger = more accurate, but slower. 100 is fine for English. 13 | 14 | // Notifications. 15 | NSString *MGWordCounterDidUpdateWordCountNotification = @"MGWordCounterDidUpdateWordCountNotification"; 16 | 17 | 18 | @implementation MGWordCounter 19 | 20 | { 21 | NSOperationQueue *_operationQueue; 22 | NSRange _affectedRangeForEdit; 23 | NSRange _expandedRangeForEdit; 24 | NSString *_replacementStringForEdit; 25 | NSString *_affectedStringForEdit; 26 | NSInteger _deltaWordCount; 27 | BOOL _fullCountPending; 28 | NSRange _lastSelectedRange; 29 | } 30 | 31 | @synthesize delegate = _delegate; 32 | @synthesize textView = _textView; 33 | @synthesize wordCount = _wordCount; 34 | @synthesize updating = _updating; 35 | 36 | #if TARGET_OS_IPHONE 37 | + (id)wordCounterForTextView:(UITextView *)theTextView; 38 | #else 39 | + (id)wordCounterForTextView:(NSTextView *)theTextView; 40 | #endif 41 | { 42 | return [[self alloc] initWithTextView:theTextView]; 43 | } 44 | 45 | #if TARGET_OS_IPHONE 46 | - (id)initWithTextView:(UITextView *)theTextView; 47 | #else 48 | - (id)initWithTextView:(NSTextView *)theTextView; 49 | #endif 50 | { 51 | if (!theTextView) { 52 | return nil; 53 | } 54 | 55 | if (self = [super init]) { 56 | _textView = theTextView; 57 | _fullCountPending = NO; 58 | _updating = NO; 59 | _wordCount = 0; 60 | _delegate = nil; 61 | _fullCountPending = NO; 62 | _enabled = YES; 63 | _lastSelectedRange = NSMakeRange(0, 0); 64 | } 65 | 66 | return self; 67 | } 68 | 69 | 70 | - (void)startCounting 71 | { 72 | _enabled = YES; 73 | 74 | // Do an initial full count. 75 | [self updateCount]; 76 | } 77 | 78 | 79 | - (void)stopCounting 80 | { 81 | _enabled = NO; 82 | 83 | _deltaWordCount = 0; 84 | _operationQueue = nil; 85 | } 86 | 87 | 88 | - (void)updateCount 89 | { 90 | if (_enabled) { 91 | MGWordCountOperation *countOperation = [[MGWordCountOperation alloc] init]; 92 | countOperation.text = [self textViewString]; 93 | countOperation.countType = MGWordCountFull; 94 | _operationQueue = [[NSOperationQueue alloc] init]; 95 | [countOperation addObserver:self forKeyPath:OPERATION_FINISHED_KEY options:0 context:NULL]; 96 | _fullCountPending = YES; 97 | _updating = YES; 98 | [_operationQueue addOperation:countOperation]; 99 | } 100 | } 101 | 102 | 103 | - (NSString *)textViewString 104 | { 105 | // Thanks, Apple, for making crap like this necessary. Thanks A LOT. 106 | #if TARGET_OS_IPHONE 107 | return _textView.text; 108 | #else 109 | return _textView.string; 110 | #endif 111 | } 112 | 113 | 114 | #if TARGET_OS_IPHONE 115 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)affectedCharRange replacementText:(NSString *)replacementString 116 | #else 117 | - (BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString 118 | #endif 119 | { 120 | // This is called when the user has performed an edit, but before the edit is committed to the storage. 121 | if (!_enabled) { 122 | return YES; 123 | } 124 | 125 | //NSLog(@"Text changing in range: %@; replacement string: \"%@\"", NSStringFromRange(affectedCharRange), replacementString); 126 | 127 | // Note parameters for later use. 128 | _affectedRangeForEdit = affectedCharRange; 129 | _replacementStringForEdit = replacementString; 130 | 131 | // Note an expanded string around the affected range, in which we'll count words later. 132 | NSRange expandedRange = affectedCharRange; 133 | NSUInteger delta = 0; 134 | // Expand range's location backwards. 135 | expandedRange.location = MAX(0, (int)affectedCharRange.location - MAX_WORD_LENGTH); 136 | delta = affectedCharRange.location - expandedRange.location; 137 | expandedRange.length += delta; // adjust for the changed starting point. 138 | // Expand range's length forwards. 139 | delta = MIN([self textViewString].length - NSMaxRange(affectedCharRange), MAX_WORD_LENGTH); 140 | expandedRange.length += delta; 141 | _expandedRangeForEdit = expandedRange; 142 | _affectedStringForEdit = [[self textViewString] substringWithRange:_expandedRangeForEdit]; 143 | //NSLog(@"affectedRange: %@, expandedRange: %@", NSStringFromRange(_affectedRangeForEdit), NSStringFromRange(_expandedRangeForEdit)); 144 | 145 | return YES; 146 | } 147 | 148 | 149 | #if TARGET_OS_IPHONE 150 | - (void)textViewDidChange:(UITextView *)aTextView 151 | #else 152 | - (void)textDidChange:(NSNotification *)aNotification 153 | #endif 154 | { 155 | // This is called after an edit has been committed to the storage. 156 | if (!_enabled) { 157 | return; 158 | } 159 | 160 | #if TARGET_OS_IPHONE 161 | if (aTextView == _textView) { 162 | #else 163 | if (aNotification.object == _textView) { 164 | #endif 165 | //NSLog(@"Text changed."); 166 | 167 | // Initiate a partial word-count operation using the information we've gathered about the edit. 168 | MGWordCountOperation *countOperation = [[MGWordCountOperation alloc] init]; 169 | countOperation.text = [self textViewString]; 170 | countOperation.countType = MGWordCountReplacementString; 171 | countOperation.affectedStringForEdit = _affectedStringForEdit; 172 | countOperation.replacementString = [_replacementStringForEdit copy]; 173 | countOperation.affectedRangeForEdit = _affectedRangeForEdit; 174 | countOperation.expandedRangeForEdit = _expandedRangeForEdit; 175 | [countOperation addObserver:self forKeyPath:OPERATION_FINISHED_KEY options:0 context:NULL]; 176 | _updating = YES; 177 | [_operationQueue addOperation:countOperation]; 178 | } 179 | } 180 | 181 | 182 | #if TARGET_OS_IPHONE 183 | - (void)textViewDidChangeSelection:(UITextView *)aTextView 184 | #else 185 | - (NSRange)textView:(NSTextView *)aTextView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange 186 | #endif 187 | { 188 | #if TARGET_OS_IPHONE 189 | NSRange newSelectedCharRange = _textView.selectedRange; 190 | if (_lastSelectedRange.length == 0 && newSelectedCharRange.length == 0) { 191 | // No need to recount. 192 | return; 193 | } 194 | #else 195 | if (oldSelectedCharRange.length == 0 && newSelectedCharRange.length == 0) { 196 | // No need to recount. 197 | return newSelectedCharRange; 198 | } 199 | #endif 200 | _lastSelectedRange = newSelectedCharRange; 201 | 202 | MGWordCountOperation *countOperation = [[MGWordCountOperation alloc] init]; 203 | countOperation.text = [[self textViewString] substringWithRange:newSelectedCharRange]; 204 | countOperation.countType = MGWordCountSelection; 205 | _operationQueue = [[NSOperationQueue alloc] init]; 206 | [countOperation addObserver:self forKeyPath:OPERATION_FINISHED_KEY options:0 context:NULL]; 207 | _updating = YES; 208 | [_operationQueue addOperation:countOperation]; 209 | 210 | #if TARGET_OS_IPHONE 211 | #else 212 | return newSelectedCharRange; 213 | #endif 214 | } 215 | 216 | 217 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 218 | change:(NSDictionary *)change context:(void *)context 219 | { 220 | // We're Key-Value Observing changes to the OPERATION_FINISHED_KEY key-path of each operation. 221 | // When an operation finishes, this method is called. 222 | 223 | MGWordCountOperation *countOperation = (MGWordCountOperation *)object; 224 | if (object && [keyPath isEqualToString:OPERATION_FINISHED_KEY]) { 225 | if (countOperation.isFinished) { 226 | @synchronized(self) { 227 | //NSLog(@"Operation %@ has finished.", countOperation); 228 | if (!(countOperation.isCancelled)) { 229 | if (countOperation.countType == MGWordCountFull || countOperation.countType == MGWordCountSelection) { 230 | // Update our word count, noting any delta which should be applied. 231 | BOOL selectionCount = (countOperation.countType == MGWordCountSelection); 232 | if (!selectionCount) { 233 | _wordCount = countOperation.wordCount; 234 | } else { 235 | _selectionWordCount = countOperation.wordCount; 236 | } 237 | if (_deltaWordCount != 0 && !selectionCount) { 238 | _wordCount += _deltaWordCount; 239 | _deltaWordCount = 0; 240 | } 241 | 242 | // Inform delegate that the full word-count is complete. 243 | NSInteger relevantCount = (selectionCount) ? _selectionWordCount : _wordCount; 244 | if (self.delegate && [self.delegate respondsToSelector:@selector(wordCounter:updatedCount:forSelection:)]) { 245 | dispatch_async(dispatch_get_main_queue(), ^{ 246 | [self.delegate wordCounter:self 247 | updatedCount:relevantCount 248 | forSelection:selectionCount]; 249 | }); 250 | } 251 | 252 | //Update Block 253 | 254 | if (self.counterUpdateBlock) 255 | { 256 | self.counterUpdateBlock(_wordCount,selectionCount); 257 | } 258 | 259 | // Send notification. 260 | NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: 261 | [NSNumber numberWithInteger:relevantCount], MGWordCountKey, 262 | [NSNumber numberWithBool:selectionCount], MGCountIsForSelection, 263 | nil]; 264 | [[NSNotificationCenter defaultCenter] postNotificationName:MGWordCounterDidUpdateWordCountNotification 265 | object:self 266 | userInfo:info]; 267 | 268 | if (!selectionCount) { 269 | _fullCountPending = NO; 270 | } 271 | 272 | } else if (countOperation.countType == MGWordCountReplacementString) { 273 | if (_fullCountPending) { 274 | // A full count is still ongoing; we'll just log our count-delta without informing the delegate. 275 | _deltaWordCount = countOperation.wordCount; 276 | 277 | } else { 278 | // Update our word count using operation's count as a delta, and inform delegate of the change. 279 | _wordCount += countOperation.wordCount; 280 | _deltaWordCount = 0; 281 | 282 | // Inform delegate. 283 | if (self.delegate && [self.delegate respondsToSelector:@selector(wordCounter:updatedCount:forSelection:)]) { 284 | dispatch_async(dispatch_get_main_queue(), ^{ 285 | [self.delegate wordCounter:self updatedCount:_wordCount forSelection:NO]; 286 | }); 287 | } 288 | // Update Block 289 | if (self.counterUpdateBlock) 290 | { 291 | self.counterUpdateBlock(_wordCount,NO); 292 | } 293 | 294 | // Send notification. 295 | NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: 296 | [NSNumber numberWithInteger:_wordCount], MGWordCountKey, 297 | [NSNumber numberWithBool:NO], MGCountIsForSelection, 298 | nil]; 299 | [[NSNotificationCenter defaultCenter] postNotificationName:MGWordCounterDidUpdateWordCountNotification 300 | object:self 301 | userInfo:info]; 302 | } 303 | } 304 | 305 | } else { 306 | // Cancelled. 307 | } 308 | 309 | _replacementStringForEdit = nil; 310 | _affectedStringForEdit = nil; 311 | _updating = NO; 312 | } 313 | } 314 | } else { 315 | [super observeValueForKeyPath:keyPath ofObject:object 316 | change:change context:context]; 317 | } 318 | } 319 | 320 | 321 | @end 322 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MGWordCounterDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGWordCounterDelegate.h 3 | // 4 | // Created by Matt Gemmell on 29/01/2013. 5 | // Copyright (c) 2013 Instinctive Code. License: http://mattgemmell.com/license/ 6 | // 7 | 8 | #if TARGET_OS_IPHONE 9 | #import 10 | #else 11 | #import 12 | #endif 13 | 14 | @class MGWordCounter; 15 | @protocol MGWordCounterDelegate 16 | 17 | @optional 18 | - (void)wordCounter:(MGWordCounter *)wordCounter updatedCount:(NSInteger)count forSelection:(BOOL)selectionOnly; 19 | // If selectionOnly is YES, the count is for the selection. If NO, the count is for the entire text. 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MGWordCounterDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | txt 13 | 14 | CFBundleTypeIconFile 15 | 16 | CFBundleTypeName 17 | Text document 18 | CFBundleTypeOSTypes 19 | 20 | ???? 21 | 22 | CFBundleTypeRole 23 | Editor 24 | NSDocumentClass 25 | MLGDocument 26 | 27 | 28 | CFBundleExecutable 29 | ${EXECUTABLE_NAME} 30 | CFBundleIconFile 31 | 32 | CFBundleIdentifier 33 | com.instinctivecode.${PRODUCT_NAME:rfc1034identifier} 34 | CFBundleInfoDictionaryVersion 35 | 6.0 36 | CFBundleName 37 | ${PRODUCT_NAME} 38 | CFBundlePackageType 39 | APPL 40 | CFBundleShortVersionString 41 | 1.0 42 | CFBundleSignature 43 | ???? 44 | CFBundleVersion 45 | 1 46 | LSApplicationCategoryType 47 | public.app-category.productivity 48 | LSMinimumSystemVersion 49 | ${MACOSX_DEPLOYMENT_TARGET} 50 | NSHumanReadableCopyright 51 | Copyright © 2013 Instinctive Code. All rights reserved. 52 | NSMainNibFile 53 | MainMenu 54 | NSPrincipalClass 55 | NSApplication 56 | 57 | 58 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MGWordCounterDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'MGWordCounterDemo' target in the 'MGWordCounterDemo' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MLGAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // MLGAppDelegate.h 3 | // MGWordCounterDemo 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MLGAppDelegate : NSObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MLGAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // MLGAppDelegate.m 3 | // MGWordCounterDemo 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import "MLGAppDelegate.h" 10 | 11 | @implementation MLGAppDelegate 12 | 13 | 14 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 15 | { 16 | NSDocumentController *sharedDocController = [NSDocumentController sharedDocumentController]; 17 | NSUInteger documentCount = [sharedDocController documents].count; 18 | 19 | // Open an untitled document what if there is no document. (restored, opened). 20 | if (documentCount == 0) { 21 | [sharedDocController openUntitledDocumentAndDisplay:YES error:nil]; 22 | } 23 | } 24 | 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MLGDocument.h: -------------------------------------------------------------------------------- 1 | // 2 | // MLGDocument.h 3 | // MGWordCounterDemo 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MGWordCounter.h" 11 | 12 | @interface MLGDocument : NSDocument 13 | 14 | @property (unsafe_unretained) IBOutlet NSTextView *textview; 15 | @property (unsafe_unretained) IBOutlet NSTextField *wordCountLabel; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGWordCounterDemo/MLGDocument.m: -------------------------------------------------------------------------------- 1 | // 2 | // MLGDocument.m 3 | // MGWordCounterDemo 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import "MLGDocument.h" 10 | 11 | @implementation MLGDocument 12 | 13 | { 14 | MGWordCounter *wordCounter; 15 | } 16 | 17 | 18 | #pragma mark - Setup 19 | 20 | 21 | - (id)init 22 | { 23 | self = [super init]; 24 | if (self) { 25 | // Add your subclass-specific initialization here. 26 | } 27 | return self; 28 | } 29 | 30 | 31 | - (NSString *)windowNibName 32 | { 33 | // Override returning the nib file name of the document 34 | // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead. 35 | return @"MLGDocument"; 36 | } 37 | 38 | 39 | - (void)windowControllerDidLoadNib:(NSWindowController *)aController 40 | { 41 | [super windowControllerDidLoadNib:aController]; 42 | // Add any code here that needs to be executed once the windowController has loaded the document's window. 43 | 44 | // Configure word-counter. 45 | wordCounter = [MGWordCounter wordCounterForTextView:self.textview]; 46 | wordCounter.delegate = self; 47 | wordCounter.counterUpdateBlock = ^(NSInteger count, BOOL selectionOnly){ 48 | //NSLog(@"Word count updated: %ld words (for %@)", (long)count, ((selectionOnly) ? @"selection" : @"full text")); 49 | }; 50 | self.textview.delegate = wordCounter; 51 | [wordCounter startCounting]; // Asynchronous; will return immediately and notify later. 52 | } 53 | 54 | 55 | #pragma mark - MGWordCounterDelegate 56 | 57 | 58 | - (void)wordCounter:(MGWordCounter *)theWordCounter updatedCount:(NSInteger)count forSelection:(BOOL)selectionOnly; 59 | { 60 | //NSLog(@"Word count updated: %ld words (for %@)", (long)count, ((selectionOnly) ? @"selection" : @"full text")); 61 | BOOL hasSelection = (self.textview.selectedRange.length > 0); 62 | NSInteger charCount = (hasSelection) ? self.textview.selectedRange.length : self.textview.string.length; 63 | NSInteger wordCount = (hasSelection) ? theWordCounter.selectionWordCount : theWordCounter.wordCount; 64 | 65 | NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; 66 | [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; 67 | 68 | /* 69 | This bit _looks_ nasty, but basically it just: 70 | 1. Shows a word and character count like "2 words - 13 chars". 71 | 2. Uses the singular or plural form of "word" or "char" where appropriate. 72 | 3. When there's a selection, shows counts only for that selection, surrounded by parentheses. 73 | (In a real app, you should localise "words" and "chars", of course.) 74 | */ 75 | self.wordCountLabel.stringValue = [NSString stringWithFormat:@"%@%@ word%@ - %@ char%@%@", 76 | ((hasSelection) ? @"(" : @""), 77 | [formatter stringFromNumber:[NSNumber numberWithInteger:wordCount]], 78 | ((wordCount != 1) ? @"s" : @""), 79 | [formatter stringFromNumber:[NSNumber numberWithInteger:charCount]], 80 | ((charCount != 1) ? @"s" : @""), 81 | ((hasSelection) ? @")" : @"")]; 82 | } 83 | 84 | 85 | #pragma mark - File-handling (left as an exercise for the reader) 86 | 87 | 88 | + (BOOL)autosavesInPlace 89 | { 90 | return NO; 91 | } 92 | 93 | 94 | - (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError 95 | { 96 | // Insert code here to write your document to data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning nil. 97 | // You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead. 98 | //NSException *exception = [NSException exceptionWithName:@"UnimplementedMethod" reason:[NSString stringWithFormat:@"%@ is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil]; 99 | //@throw exception; 100 | return nil; 101 | } 102 | 103 | 104 | - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError 105 | { 106 | // Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning NO. 107 | // You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead. 108 | // If you override either of these, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded. 109 | //NSException *exception = [NSException exceptionWithName:@"UnimplementedMethod" reason:[NSString stringWithFormat:@"%@ is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil]; 110 | //@throw exception; 111 | return YES; 112 | } 113 | 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /MGWordCounterDemo/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /MGWordCounterDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /MGWordCounterDemo/en.lproj/MLGDocument.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1080 5 | 12C60 6 | 3084 7 | 1187.34 8 | 625.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 3084 12 | 13 | 14 | IBNSLayoutConstraint 15 | NSCustomObject 16 | NSScrollView 17 | NSScroller 18 | NSTextField 19 | NSTextFieldCell 20 | NSTextView 21 | NSView 22 | NSWindowTemplate 23 | 24 | 25 | com.apple.InterfaceBuilder.CocoaPlugin 26 | 27 | 28 | PluginDependencyRecalculationVersion 29 | 30 | 31 | 32 | 33 | MLGDocument 34 | 35 | 36 | FirstResponder 37 | 38 | 39 | 15 40 | 2 41 | {{133, 235}, {507, 413}} 42 | 1886912512 43 | Window 44 | NSWindow 45 | View 46 | 47 | {94, 86} 48 | 49 | 50 | 256 51 | 52 | 53 | 54 | 4352 55 | 56 | 57 | 58 | 2304 59 | 60 | 61 | 62 | 2322 63 | 64 | Apple HTML pasteboard type 65 | Apple PDF pasteboard type 66 | Apple PICT pasteboard type 67 | Apple PNG pasteboard type 68 | Apple URL pasteboard type 69 | CorePasteboardFlavorType 0x6D6F6F76 70 | NSColor pasteboard type 71 | NSFilenamesPboardType 72 | NSStringPboardType 73 | NeXT Encapsulated PostScript v1.2 pasteboard type 74 | NeXT RTFD pasteboard type 75 | NeXT Rich Text Format v1.0 pasteboard type 76 | NeXT TIFF v4.0 pasteboard type 77 | NeXT font pasteboard type 78 | NeXT ruler pasteboard type 79 | WebURLsWithTitlesPboardType 80 | public.url 81 | 82 | {509, 394} 83 | 84 | 85 | 86 | _NS:13 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 38 99 | 100 | 101 | 102 | 509 103 | 1 104 | 105 | 106 | 1140862883 107 | 0 108 | 109 | 110 | 3 111 | MQA 112 | 113 | 114 | 115 | 6 116 | System 117 | selectedTextBackgroundColor 118 | 119 | 3 120 | MC42NjY2NjY2NjY3AA 121 | 122 | 123 | 124 | 6 125 | System 126 | selectedTextColor 127 | 128 | 3 129 | MAA 130 | 131 | 132 | 133 | 134 | 135 | 136 | 1 137 | MCAwIDEAA 138 | 139 | 140 | {8, -8} 141 | 13 142 | 143 | 144 | 145 | 146 | 147 | 1 148 | 149 | 6 150 | {518, 10000000} 151 | 152 | 153 | 154 | {509, 394} 155 | 156 | 157 | 158 | _NS:11 159 | 160 | 161 | 162 | {4, 5} 163 | 164 | 12582912 165 | 166 | 167 | 168 | 169 | 170 | TU0AKgAAEAj///8A////qwAAAP8AAAD/AwMD/BYWFtIFBQVuAAAABv///wD///8E6+vrTGhoaLYtLS3p 171 | BAQE/QAAAP8AAAD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 172 | ////AP///wD///8A////AP///wD///+rAAAA/wAAAP8AAAD/AAAA/wAAAP8EBATSX19fK9HR0ZciIiLv 173 | AAAA/wAAAP8AAAD/AAAA/wAAAP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 174 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////CsPDw0CRkZG1FxcX9AAAAP8bGxvr 175 | ICAg8AAAAP8ICAjlFRUVVQAAAA////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 176 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wrm5uab 177 | FxcX9AAAAP8AAAD/AQEB3wAAAA////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 178 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 179 | ////AP///zaXl5fGAAAA/wAAAP8AAABR////AP///wD///8A////AP///wD///8A////AP///wD///8A 180 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 181 | ////AP///wD///8A////CO7u7q8AAAD/AAAA/wAAAAz///8A////AP///wD///8A////AP///wD///8A 182 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 183 | ////AP///wD///8A////AP///wD///8A////qwAAAP8AAAD/////AP///wD///8A////AP///wD///8A 184 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 185 | ////AP///wD///8A////AP///wD///8A////AP///wD///+rAAAA/wAAAP////8A////AP///wD///8A 186 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 187 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///6sAAAD/AAAA/////wD///8A 188 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 189 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////qwAAAP8AAAD/ 190 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 191 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///+r 192 | AAAA/wAAAP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 193 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 194 | ////AP///6sAAAD/AAAA/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 195 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 196 | ////AP///wD///8A////qwAAAP8AAAD/////AP///wD///8A////AP///wD///8A////AP///wD///8A 197 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 198 | ////AP///wD///8A////AP///wD///+rAAAA/wAAAP////8A////AP///wD///8A////AP///wD///8A 199 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 200 | ////AP///wD///8A////AP///wD///8A////AP///6sAAAD/AAAA/////wD///8A////AP///wD///8A 201 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 202 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////qwAAAP8AAAD/////AP///wD///8A 203 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 204 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////qwAAAP8AAAD/AAAA/wAAAP8AAAD/ 205 | AAAA/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 206 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///+rAAAA/wAAAP8AAAD/ 207 | AAAA/wAAAP8AAAD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 208 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 209 | ////qwAAAP8AAAD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 210 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 211 | ////AP///wD///+rAAAA/wAAAP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 212 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 213 | ////AP///wD///8A////AP///6sAAAD/AAAA/////wD///8A////AP///wD///8A////AP///wD///8A 214 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 215 | ////AP///wD///8A////AP///wD///8A////qwAAAP8AAAD/////AP///wD///8A////AP///wD///8A 216 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 217 | ////AP///wD///8A////AP///wD///8A////AP///wD///+rAAAA/wAAAP////8A////AP///wD///8A 218 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 219 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///6sAAAD/AAAA/////wD///8A 220 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 221 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////qwAAAP8AAAD/ 222 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 223 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///+r 224 | AAAA/wAAAP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 225 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 226 | ////CO7u7q8AAAD/AAAA/wAAAAz///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 227 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 228 | ////AP///wD///80mpqaxQAAAP8AAAD/AAAATv///wD///8A////AP///wD///8A////AP///wD///8A 229 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 230 | ////AP///wD///8A////CuXl5ZYeHh7xAAAA/wAAAP8CAgLXAAAAD////wD///8A////AP///wD///8A 231 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 232 | ////AP///wD///8A////EKurq0mOjo63FxcX9AAAAP8bGxvrICAg8AAAAP8ICAjlHh4eXAAAABj///8A 233 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 234 | ////AP///wD///8A////qwAAAP8AAAD/AAAA/wAAAP8AAAD/BAQE1V9fXyvS0tKZICAg8AAAAP8AAAD/ 235 | AAAA/wAAAP8AAAD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 236 | ////AP///wD///8A////AP///wD///+rAAAA/wAAAP8AAAD/FBQU1AUFBXEAAAAG////AP///wTr6+tO 237 | ZWVluC4uLuoAAAD/AAAA/wAAAP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 238 | ////AP///wD///8A////AP///wD///8AABABAAADAAAAAQAgAAABAQADAAAAAQAgAAABAgADAAAABAAA 239 | EM4BAwADAAAAAQABAAABBgADAAAAAQACAAABEQAEAAAAAQAAAAgBEgADAAAAAQABAAABFQADAAAAAQAE 240 | AAABFgADAAAAAQAgAAABFwAEAAAAAQAAEAABGgAFAAAAAQAAENYBGwAFAAAAAQAAEN4BHAADAAAAAQAB 241 | AAABKAADAAAAAQACAAABUgADAAAAAQACAAABUwADAAAABAAAEOYAAAAAAAgACAAIAAgSAAAAACAAABIA 242 | AAAAIAAAAAEAAQABAAE 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | TU0AKgAABAj///9VQ0ND/wAAAP////8A////AP///6pDQ0P/AAAA/////wD///8A////AP///wD///8A 251 | ////AP///wD///8A////AP///wD///+qAAAA/////6oAAAD/////AP///wD///8A////AP///wD///8A 252 | ////AP///wD///8A////AP///wD///8A////AP///6oAAAD/////AP///wD///8A////AP///wD///8A 253 | ////AP///wD///8A////AP///wD///8A////AP///wD///+qAAAA/////wD///8A////AP///wD///8A 254 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////qgAAAP////8A////AP///wD///8A 255 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///6oAAAD/////AP///wD///8A 256 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///+qAAAA/////wD///8A 257 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////qgAAAP////8A 258 | ////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////qgAAAP8AAAD/ 259 | AAAA/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///+q 260 | AAAA/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 261 | ////qgAAAP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 262 | ////AP///6oAAAD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 263 | ////AP///wD///+qAAAA/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 264 | ////AP///wD///8A////qgAAAP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A 265 | ////AP///wD///8A////qgAAAP////+qAAAA/////wD///8A////AP///wD///8A////AP///wD///8A 266 | ////AP///wD///9VQ0ND/wAAAP////8A////AP///6pDQ0P/AAAA/////wD///8A////AP///wD///8A 267 | ////AP///wD///8AAA4BAAADAAAAAQAQAAABAQADAAAAAQAQAAABAgADAAAABAAABLYBAwADAAAAAQAB 268 | AAABBgADAAAAAQACAAABEQAEAAAAAQAAAAgBEgADAAAAAQABAAABFQADAAAAAQAEAAABFgADAAAAAQAQ 269 | AAABFwAEAAAAAQAABAABHAADAAAAAQABAAABUgADAAAAAQACAAABUwADAAAABAAABL6HcwAHAAAHqAAA 270 | BMYAAAAAAAgACAAIAAgAAQABAAEAAQAAB6hhcHBsAiAAAG1udHJSR0IgWFlaIAfZAAIAGQALABoAC2Fj 271 | c3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAA 272 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFbGNw 273 | cnQAAAbkAAAAOHd0cHQAAAccAAAAFHJYWVoAAAcwAAAAFGdYWVoAAAdEAAAAFGJYWVoAAAdYAAAAFHJU 274 | UkMAAAdsAAAADmNoYWQAAAd8AAAALGJUUkMAAAdsAAAADmdUUkMAAAdsAAAADmRlc2MAAAAAAAAAFEdl 275 | bmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAA 276 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB4AAAAMc2tTSwAA 277 | ACgAAAF4aHJIUgAAACgAAAGgY2FFUwAAACQAAAHIcHRCUgAAACYAAAHsdWtVQQAAACoAAAISZnJGVQAA 278 | ACgAAAI8emhUVwAAABYAAAJkaXRJVAAAACgAAAJ6bmJOTwAAACYAAAKia29LUgAAABYAAALIY3NDWgAA 279 | ACIAAALeaGVJTAAAAB4AAAMAZGVERQAAACwAAAMeaHVIVQAAACgAAANKc3ZTRQAAACYAAAKiemhDTgAA 280 | ABYAAANyamFKUAAAABoAAAOIcm9STwAAACQAAAOiZWxHUgAAACIAAAPGcHRQTwAAACYAAAPobmxOTAAA 281 | ACgAAAQOZXNFUwAAACYAAAPodGhUSAAAACQAAAQ2dHJUUgAAACIAAARaZmlGSQAAACgAAAR8cGxQTAAA 282 | ACwAAASkcnVSVQAAACIAAATQYXJFRwAAACYAAATyZW5VUwAAACYAAAUYZGFESwAAAC4AAAU+AFYBYQBl 283 | AG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBlAHIAaQENAGsAaQAgAFIARwBC 284 | ACAAcAByAG8AZgBpAGwAUABlAHIAZgBpAGwAIABSAEcAQgAgAGcAZQBuAOgAcgBpAGMAUABlAHIAZgBp 285 | AGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8EQAQ+BEQEMAQ5 286 | BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBWAEKQGnUoACAAUgBH 287 | AEIAIIJyX2ljz4/wAFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAGcAZQBuAGUAcgBpAGMAbwBHAGUAbgBl 288 | AHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsx3y8GAAgAFIARwBCACDVBLhc0wzHfABPAGIAZQBj 289 | AG4A/QAgAFIARwBCACAAcAByAG8AZgBpAGwF5AXoBdUF5AXZBdwAIABSAEcAQgAgBdsF3AXcBdkAQQBs 290 | AGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbADBAGwAdABhAGwA4QBuAG8AcwAg 291 | AFIARwBCACAAcAByAG8AZgBpAGxmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBCACAw1zDt 292 | MNUwoTCkMOsAUAByAG8AZgBpAGwAIABSAEcAQgAgAGcAZQBuAGUAcgBpAGMDkwO1A70DuQO6A8wAIAPA 293 | A8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBs 294 | AGcAZQBtAGUAZQBuACAAUgBHAEIALQBwAHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAg 295 | DhcOMQ5IDicORA4bAEcAZQBuAGUAbAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBu 296 | ACAAUgBHAEIALQBwAHIAbwBmAGkAaQBsAGkAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8AZgBp 297 | AGwAIABSAEcAQgQeBDEESQQ4BDkAIAQ/BEAEPgREBDgEOwRMACAAUgBHAEIGRQZEBkEAIAYqBjkGMQZK 298 | BkEAIABSAEcAQgAgBicGRAY5BicGRQBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABl 299 | AEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZXRleHQAAAAAQ29weXJp 300 | Z2h0IDIwMDcgQXBwbGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAAB 301 | Fs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAA 302 | uDZjdXJ2AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAA 303 | wGw 304 | 305 | 306 | 307 | 308 | 309 | 3 310 | MCAwAA 311 | 312 | 313 | 314 | 4 315 | 316 | 317 | 318 | -2147483392 319 | {{493, 0}, {16, 415}} 320 | 321 | 322 | _NS:83 323 | NO 324 | 325 | _doScroller: 326 | 1 327 | 0.85256409645080566 328 | 329 | 330 | 331 | -2147483392 332 | {{-100, -100}, {87, 18}} 333 | 334 | 335 | 336 | _NS:33 337 | NO 338 | 1 339 | 340 | _doScroller: 341 | 1 342 | 0.94565218687057495 343 | 344 | 345 | {{-2, 0}, {509, 394}} 346 | 347 | 348 | 349 | _NS:9 350 | 133648 351 | 352 | 353 | 354 | 0.25 355 | 4 356 | 1 357 | 358 | 359 | 360 | 268 361 | {{257, 396}, {228, 17}} 362 | 363 | 364 | 365 | _NS:1535 366 | YES 367 | 368 | 68157504 369 | 71304192 370 | words 371 | 372 | LucidaGrande 373 | 13 374 | 1044 375 | 376 | _NS:1535 377 | 378 | 379 | 6 380 | System 381 | controlColor 382 | 383 | 384 | 385 | 6 386 | System 387 | controlTextColor 388 | 389 | 390 | 391 | NO 392 | 393 | 394 | {507, 413} 395 | 396 | 397 | 398 | {{0, 0}, {2560, 1418}} 399 | {94, 108} 400 | {10000000000000, 10000000000000} 401 | YES 402 | 403 | 404 | NSApplication 405 | 406 | 407 | 408 | 409 | 410 | 411 | window 412 | 413 | 414 | 415 | 18 416 | 417 | 418 | 419 | textview 420 | 421 | 422 | 423 | 100046 424 | 425 | 426 | 427 | wordCountLabel 428 | 429 | 430 | 431 | 100047 432 | 433 | 434 | 435 | delegate 436 | 437 | 438 | 439 | 17 440 | 441 | 442 | 443 | 444 | 445 | 0 446 | 447 | 448 | 449 | 450 | 451 | -2 452 | 453 | 454 | File's Owner 455 | 456 | 457 | -1 458 | 459 | 460 | First Responder 461 | 462 | 463 | 5 464 | 465 | 466 | 467 | 468 | 469 | Window 470 | 471 | 472 | 6 473 | 474 | 475 | 476 | 477 | 3 478 | 0 479 | 480 | 3 481 | 1 482 | 483 | 0.0 484 | 485 | 1000 486 | 487 | 8 488 | 29 489 | 3 490 | 491 | 492 | 493 | 6 494 | 0 495 | 496 | 6 497 | 1 498 | 499 | 25 500 | 501 | 1000 502 | 503 | 3 504 | 9 505 | 3 506 | 507 | 508 | 509 | 4 510 | 0 511 | 512 | 4 513 | 1 514 | 515 | 0.0 516 | 517 | 1000 518 | 519 | 8 520 | 29 521 | 3 522 | 523 | 524 | 525 | 3 526 | 0 527 | 528 | 3 529 | 1 530 | 531 | 19 532 | 533 | 1000 534 | 535 | 3 536 | 9 537 | 3 538 | 539 | 540 | 541 | 6 542 | 0 543 | 544 | 6 545 | 1 546 | 547 | 0.0 548 | 549 | 1000 550 | 551 | 8 552 | 29 553 | 3 554 | 555 | 556 | 557 | 5 558 | 0 559 | 560 | 5 561 | 1 562 | 563 | -2 564 | 565 | 1000 566 | 567 | 3 568 | 9 569 | 3 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | -3 578 | 579 | 580 | Application 581 | 582 | 583 | 100027 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 100028 594 | 595 | 596 | 597 | 598 | 7 599 | 0 600 | 601 | 0 602 | 1 603 | 604 | 222 605 | 606 | 1000 607 | 608 | 3 609 | 9 610 | 1 611 | 612 | 613 | 614 | 615 | 616 | 617 | 100029 618 | 619 | 620 | 621 | 622 | 100030 623 | 624 | 625 | 626 | 627 | 100031 628 | 629 | 630 | 631 | 632 | 100032 633 | 634 | 635 | 636 | 637 | 100033 638 | 639 | 640 | 641 | 642 | 100040 643 | 644 | 645 | 646 | 647 | 100041 648 | 649 | 650 | 651 | 652 | 100043 653 | 654 | 655 | 656 | 657 | 100044 658 | 659 | 660 | 661 | 662 | 100048 663 | 664 | 665 | 666 | 667 | 100049 668 | 669 | 670 | 671 | 672 | 673 | 674 | com.apple.InterfaceBuilder.CocoaPlugin 675 | com.apple.InterfaceBuilder.CocoaPlugin 676 | com.apple.InterfaceBuilder.CocoaPlugin 677 | 678 | com.apple.InterfaceBuilder.CocoaPlugin 679 | 680 | 681 | 682 | 683 | com.apple.InterfaceBuilder.CocoaPlugin 684 | com.apple.InterfaceBuilder.CocoaPlugin 685 | com.apple.InterfaceBuilder.CocoaPlugin 686 | com.apple.InterfaceBuilder.CocoaPlugin 687 | com.apple.InterfaceBuilder.CocoaPlugin 688 | com.apple.InterfaceBuilder.CocoaPlugin 689 | 690 | com.apple.InterfaceBuilder.CocoaPlugin 691 | com.apple.InterfaceBuilder.CocoaPlugin 692 | com.apple.InterfaceBuilder.CocoaPlugin 693 | com.apple.InterfaceBuilder.CocoaPlugin 694 | com.apple.InterfaceBuilder.CocoaPlugin 695 | com.apple.InterfaceBuilder.CocoaPlugin 696 | 697 | 698 | com.apple.InterfaceBuilder.CocoaPlugin 699 | {{133, 170}, {507, 413}} 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | com.apple.InterfaceBuilder.CocoaPlugin 709 | 710 | 711 | 712 | 713 | 714 | 100049 715 | 716 | 717 | 718 | 719 | MLGDocument 720 | NSDocument 721 | 722 | NSTextView 723 | NSTextField 724 | 725 | 726 | 727 | textview 728 | NSTextView 729 | 730 | 731 | wordCountLabel 732 | NSTextField 733 | 734 | 735 | 736 | IBProjectSource 737 | ./Classes/MLGDocument.h 738 | 739 | 740 | 741 | NSLayoutConstraint 742 | NSObject 743 | 744 | IBProjectSource 745 | ./Classes/NSLayoutConstraint.h 746 | 747 | 748 | 749 | 750 | 0 751 | IBCocoaFramework 752 | YES 753 | 3 754 | YES 755 | 756 | 757 | -------------------------------------------------------------------------------- /MGWordCounterDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MGWordCounterDemo 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **)argv); 14 | } 15 | -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgemmell/MGWordCounter/bf5a27dfbe59d35a2a0b192243d3038a6591b098/MGWordCounterDemoMobile/Default-568h@2x.png -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgemmell/MGWordCounter/bf5a27dfbe59d35a2a0b192243d3038a6591b098/MGWordCounterDemoMobile/Default.png -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgemmell/MGWordCounter/bf5a27dfbe59d35a2a0b192243d3038a6591b098/MGWordCounterDemoMobile/Default@2x.png -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/MGWordCounterDemoMobile-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.instinctivecode.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/MGWordCounterDemoMobile-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'MGWordCounterDemoMobile' target in the 'MGWordCounterDemoMobile' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/MLGAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // MLGAppDelegate.h 3 | // MGWordCounterDemoMobile 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class MLGViewController; 12 | 13 | @interface MLGAppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | 17 | @property (strong, nonatomic) MLGViewController *viewController; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/MLGAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // MLGAppDelegate.m 3 | // MGWordCounterDemoMobile 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import "MLGAppDelegate.h" 10 | 11 | #import "MLGViewController.h" 12 | 13 | @implementation MLGAppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 18 | // Override point for customization after application launch. 19 | self.viewController = [[MLGViewController alloc] initWithNibName:@"MLGViewController" bundle:nil]; 20 | self.window.rootViewController = self.viewController; 21 | [self.window makeKeyAndVisible]; 22 | return YES; 23 | } 24 | 25 | - (void)applicationWillResignActive:(UIApplication *)application 26 | { 27 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 28 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 29 | } 30 | 31 | - (void)applicationDidEnterBackground:(UIApplication *)application 32 | { 33 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 34 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 35 | } 36 | 37 | - (void)applicationWillEnterForeground:(UIApplication *)application 38 | { 39 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 40 | } 41 | 42 | - (void)applicationDidBecomeActive:(UIApplication *)application 43 | { 44 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 45 | } 46 | 47 | - (void)applicationWillTerminate:(UIApplication *)application 48 | { 49 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/MLGViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MLGViewController.h 3 | // MGWordCounterDemoMobile 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MGWordCounter.h" 11 | 12 | @interface MLGViewController : UIViewController 13 | 14 | @property (weak, nonatomic) IBOutlet UITextView *textView; 15 | @property (weak, nonatomic) IBOutlet UILabel *wordCountLabel; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/MLGViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MLGViewController.m 3 | // MGWordCounterDemoMobile 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import "MLGViewController.h" 10 | 11 | @interface MLGViewController () 12 | 13 | { 14 | MGWordCounter *wordCounter; 15 | } 16 | 17 | @end 18 | 19 | @implementation MLGViewController 20 | 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | // Do any additional setup after loading the view, typically from a nib. 26 | 27 | // Configure word-counter. 28 | wordCounter = [MGWordCounter wordCounterForTextView:self.textView]; 29 | wordCounter.counterUpdateBlock = ^(NSInteger count, BOOL selectionOnly){ 30 | //NSLog(@"Word count updated: %ld words (for %@)", (long)count, ((selectionOnly) ? @"selection" : @"full text")); 31 | }; 32 | wordCounter.delegate = self; 33 | self.textView.delegate = wordCounter; 34 | [wordCounter startCounting]; // Asynchronous; will return immediately and notify later. 35 | } 36 | 37 | 38 | - (void)viewDidAppear:(BOOL)animated 39 | { 40 | [self.textView becomeFirstResponder]; 41 | } 42 | 43 | 44 | - (void)didReceiveMemoryWarning 45 | { 46 | [super didReceiveMemoryWarning]; 47 | // Dispose of any resources that can be recreated. 48 | } 49 | 50 | 51 | - (void)wordCounter:(MGWordCounter *)theWordCounter updatedCount:(NSInteger)count forSelection:(BOOL)selectionOnly; 52 | { 53 | //NSLog(@"Word count updated: %ld words (for %@)", (long)count, ((selectionOnly) ? @"selection" : @"full text")); 54 | BOOL hasSelection = (self.textView.selectedRange.length > 0); 55 | NSInteger charCount = (hasSelection) ? self.textView.selectedRange.length : self.textView.text.length; 56 | NSInteger wordCount = (hasSelection) ? theWordCounter.selectionWordCount : theWordCounter.wordCount; 57 | 58 | NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; 59 | [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; 60 | 61 | /* 62 | This bit _looks_ nasty, but basically it just: 63 | 1. Shows a word and character count like "2 words - 13 chars". 64 | 2. Uses the singular or plural form of "word" or "char" where appropriate. 65 | 3. When there's a selection, shows counts only for that selection, surrounded by parentheses. 66 | (In a real app, you should localise "words" and "chars", of course.) 67 | */ 68 | self.wordCountLabel.text = [NSString stringWithFormat:@"%@%@ word%@ - %@ char%@%@", 69 | ((hasSelection) ? @"(" : @""), 70 | [formatter stringFromNumber:[NSNumber numberWithInteger:wordCount]], 71 | ((wordCount != 1) ? @"s" : @""), 72 | [formatter stringFromNumber:[NSNumber numberWithInteger:charCount]], 73 | ((charCount != 1) ? @"s" : @""), 74 | ((hasSelection) ? @")" : @"")]; 75 | } 76 | 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/en.lproj/MLGViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1552 5 | 12C60 6 | 3084 7 | 1187.34 8 | 625.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 2083 12 | 13 | 14 | IBProxyObject 15 | IBUILabel 16 | IBUITextView 17 | IBUIView 18 | 19 | 20 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 21 | 22 | 23 | PluginDependencyRecalculationVersion 24 | 25 | 26 | 27 | 28 | IBFilesOwner 29 | IBCocoaTouchFramework 30 | 31 | 32 | IBFirstResponder 33 | IBCocoaTouchFramework 34 | 35 | 36 | 37 | 274 38 | 39 | 40 | 41 | 274 42 | {{0, 36}, {320, 512}} 43 | 44 | 45 | _NS:9 46 | 47 | 1 48 | MSAxIDEAA 49 | 50 | YES 51 | YES 52 | IBCocoaTouchFramework 53 | NO 54 | Some text goes here. 55 | 56 | 2 57 | IBCocoaTouchFramework 58 | 59 | 60 | 1 61 | 14 62 | 63 | 64 | Helvetica 65 | 14 66 | 16 67 | 68 | 69 | 70 | 71 | 290 72 | {{0, 7}, {320, 21}} 73 | 74 | 75 | 76 | _NS:9 77 | NO 78 | YES 79 | 7 80 | NO 81 | IBCocoaTouchFramework 82 | words 83 | 84 | 1 85 | MCAwIDAAA 86 | darkTextColor 87 | 88 | 89 | 0 90 | 10 91 | 1 92 | 93 | 1 94 | 17 95 | 96 | 97 | Helvetica 98 | 17 99 | 16 100 | 101 | 102 | 103 | {{0, 20}, {320, 548}} 104 | 105 | 106 | 107 | 108 | 3 109 | MC43NQA 110 | 111 | 2 112 | 113 | 114 | NO 115 | 116 | 117 | IBUIScreenMetrics 118 | 119 | YES 120 | 121 | 122 | 123 | 124 | 125 | {320, 568} 126 | {568, 320} 127 | 128 | 129 | IBCocoaTouchFramework 130 | Retina 4 Full Screen 131 | 2 132 | 133 | IBCocoaTouchFramework 134 | 135 | 136 | 137 | 138 | 139 | 140 | view 141 | 142 | 143 | 144 | 7 145 | 146 | 147 | 148 | textView 149 | 150 | 151 | 152 | 20 153 | 154 | 155 | 156 | wordCountLabel 157 | 158 | 159 | 160 | 21 161 | 162 | 163 | 164 | 165 | 166 | 0 167 | 168 | 169 | 170 | 171 | 172 | -1 173 | 174 | 175 | File's Owner 176 | 177 | 178 | -2 179 | 180 | 181 | 182 | 183 | 6 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 8 193 | 194 | 195 | 196 | 197 | 198 | 14 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | MLGViewController 207 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 208 | UIResponder 209 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 210 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 211 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 212 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 213 | 214 | 215 | 216 | 217 | 218 | 21 219 | 220 | 221 | 222 | 223 | MLGViewController 224 | UIViewController 225 | 226 | UITextView 227 | UILabel 228 | 229 | 230 | 231 | textView 232 | UITextView 233 | 234 | 235 | wordCountLabel 236 | UILabel 237 | 238 | 239 | 240 | IBProjectSource 241 | ./Classes/MLGViewController.h 242 | 243 | 244 | 245 | 246 | 0 247 | IBCocoaTouchFramework 248 | YES 249 | 3 250 | 2083 251 | 252 | 253 | -------------------------------------------------------------------------------- /MGWordCounterDemoMobile/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MGWordCounterDemoMobile 4 | // 5 | // Created by Matt Gemmell on 04/02/2013. 6 | // Copyright (c) 2013 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "MLGAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([MLGAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # MGWordCounter 2 | 3 | by **Matt Gemmell** 4 | 5 | - Visit my blog at [http://mattgemmell.com/](http://mattgemmell.com/) 6 | - Follow [@mattgemmell on Twitter](http://twitter.com/mattgemmell) 7 | - Follow [mattgemmell on App.Net](http://alpha.app.net/mattgemmell) 8 | 9 | 10 | ## What is MGWordCounter? 11 | 12 | **MGWordCounter provides live word-counting for NSTextViews on OS X and UITextViews on iOS.** 13 | 14 | - Counting is asynchronous (in the background) 15 | - It tries not to count any more text than is necessary 16 | - It counts both the full text and any _selection_ in the textview 17 | 18 | MGWordCounter uses NSString's own (excellent) understanding of what constitutes a "word", and thus will improve as NSString and the text system are enhanced in future. 19 | 20 | 21 | ## Why did you create this? 22 | 23 | I do a lot of writing, and I find it extremely useful to have a live word-count (and character-count) for the piece I'm working on. I also like to try out new writing apps and text-editors, and I'd be disappointed if they didn't have that feature. 24 | 25 | I thought that the best way to make sure all my favourite writing apps of the future have live word-counting was to implement the feature in a straightforward, drop-in way, then I can just point those developers towards this code. Then they'll have _absolutely no excuse_ for not implementing it. 26 | 27 | 28 | ## Getting the code 29 | 30 | MGWordCounter can be cloned from its git repository on github. You can find the repository here: [http://github.com/mattgemmell/MGWordCounter](http://github.com/mattgemmell/MGWordCounter) 31 | 32 | 33 | ## Requirements and supported OS versions 34 | 35 | - **OS X 10.7 Lion** or later (**with ARC**) for Mac 36 | - **iOS 5.0** or later (**with ARC**) for iPhone, iPad and iPod touch 37 | 38 | 39 | ## License 40 | 41 | MGWordCounter is distributed under an **attribution license**. You're free to use it, with attribution, in any kind of project you like (including commercial and/or closed-source apps). 42 | 43 | You can read the full license here: [http://mattgemmell.com/license/](http://mattgemmell.com/license/) 44 | 45 | See the license page for information on using it _without_ attribution too. 46 | 47 | 48 | ## Saying Thank You 49 | 50 | As with all the other OS X and iOS code I've released over the years, I'm making MGWordCounter available for the benefit of the developer community. 51 | 52 | If you find it useful, a Paypal donation (or something from my Amazon.co.uk Wishlist) would be very much appreciated. Appropriate links (and my other code) can be found here: [http://mattgemmell.com/source](http://mattgemmell.com/source) 53 | 54 | 55 | ## Sample code 56 | 57 | The MGWordCounter project includes simple OS X and iPhone demonstration apps, showing a live word-count display above a text-view. Both apps are separate targets in the same Xcode project. 58 | 59 | 60 | ## How to use MGWordCounter 61 | 62 | Briefly, you create an MGWordCounter object by initialising it with a suitable NSTextView or UITextView, then you tell the MGWordCounter to start counting. 63 | 64 | wordCounter = [MGWordCounter wordCounterForTextView:textview]; 65 | wordCounter.delegate = self; 66 | textview.delegate = wordCounter; 67 | [wordCounter startCounting]; 68 | 69 | MGWordCounter will then post **notifications** (and call a **delegate method**, if you've given it a delegate) whenever the word-count changes. You can also specify a **block** to be executed. 70 | 71 | 72 | ## Does it need to be my Text View's delegate? 73 | 74 | **No**. Normally, the MGWordCounter object _will_ be your text-view's delegate (i.e. its NSTextViewDelegate on OS X, or its UITextViewDelegate on iOS). However, this isn't absolutely required. 75 | 76 | If you already have a text-view delegate object, you can simply forward three delegate methods to the MGWordCounter from your actual text-view delegate, and everything will still work. See the comments in MGWordCounter's header file for more information. 77 | 78 | 79 | ## Can it work with something other than a Text View? 80 | 81 | Potentially. If you can fulfil the (NS/UI)TextViewDelegate contract, and the "text-view" object you pass to the MGWordCounter responds to `-string` on OS X and `-text` on iOS, then yes, you can presumably make it work for other controls or even more exotic objects. This hasn't been tested, however. 82 | 83 | 84 | ## Bugs and feature requests 85 | 86 | There is absolutely **no support** offered with this component. You're on your own! If you want to submit a feature request, please do so via [the issue tracker on github](http://github.com/mattgemmell/MGWordCounter/issues) (and _not_ via Twitter, email, carrier pigeon, smoke signals or anything else). 87 | 88 | MGWordCounter is open source code for developers. If you find a bug, please _investigate it_. There are many `NSLog` statements (commented-out) throughout the code, allowing you to selectively inspect its workings. The algorithm used isn't complex. Find out what's going wrong, and fix it! 89 | 90 | I'll be glad to receive pull-requests for bug-fixes and enhancements on [MGWordCounter's github repository](http://github.com/mattgemmell/MGWordCounter). Please try not to report bugs without a diagnosis and fix. Similarly, please try not to submit fixes without an accompanying explanation of what you're fixing, and how. :) 91 | 92 | 93 | ## Final notes 94 | 95 | To keep up to date with future code releases, amongst many other things, you'd be well advised to: 96 | 97 | - Visit my blog at [http://mattgemmell.com/](http://mattgemmell.com/) 98 | - Follow [@mattgemmell on Twitter](http://twitter.com/mattgemmell) 99 | - Follow [mattgemmell on App.Net](http://alpha.app.net/mattgemmell) 100 | --------------------------------------------------------------------------------