├── .gitignore ├── R.swift.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── R.swift.xcscheme ├── R.swift ├── Info.plist ├── MainPlugin.swift ├── NSObject_Extension.swift ├── PluginHelper.swift ├── RContentGenerator.swift ├── R_template.txt ├── String_Extension.swift └── SwizzledMethods.swift ├── README.md └── screenshots ├── pic_assets.png └── sample.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | ## Playgrounds 30 | timeline.xctimeline 31 | playground.xcworkspace 32 | 33 | # Swift Package Manager 34 | # 35 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 36 | # Packages/ 37 | .build/ 38 | 39 | # CocoaPods 40 | # 41 | # We recommend against adding the Pods directory to your .gitignore. However 42 | # you should judge for yourself, the pros and cons are mentioned at: 43 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 44 | # 45 | # Pods/ 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | # Carthage/Checkouts 51 | 52 | Carthage/Build 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 57 | # screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 60 | 61 | fastlane/report.xml 62 | fastlane/screenshots 63 | -------------------------------------------------------------------------------- /R.swift.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 53282FAF1C67C5F100081171 /* R.swift.xcscheme in Resources */ = {isa = PBXBuildFile; fileRef = 53282FAE1C67C5F100081171 /* R.swift.xcscheme */; }; 11 | 53282FB11C67C5F100081171 /* MainPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53282FB01C67C5F100081171 /* MainPlugin.swift */; }; 12 | 53282FB31C67C5F100081171 /* NSObject_Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53282FB21C67C5F100081171 /* NSObject_Extension.swift */; }; 13 | 535393C71C6B8770003585F1 /* PluginHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535393C61C6B8770003585F1 /* PluginHelper.swift */; }; 14 | 535393C91C6B87EC003585F1 /* String_Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535393C81C6B87EC003585F1 /* String_Extension.swift */; }; 15 | 535393CF1C6BE194003585F1 /* R_template.txt in Resources */ = {isa = PBXBuildFile; fileRef = 535393CE1C6BE194003585F1 /* R_template.txt */; }; 16 | 535393D11C6CCA87003585F1 /* RContentGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535393D01C6CCA87003585F1 /* RContentGenerator.swift */; }; 17 | 537E26221C82324600273CD1 /* SwizzledMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537E26211C82324600273CD1 /* SwizzledMethods.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 53282FAA1C67C5F100081171 /* R.swift.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = R.swift.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 53282FAE1C67C5F100081171 /* R.swift.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = "R.swift.xcscheme"; path = "R.swift.xcodeproj/xcshareddata/xcschemes/R.swift.xcscheme"; sourceTree = SOURCE_ROOT; }; 23 | 53282FB01C67C5F100081171 /* MainPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainPlugin.swift; sourceTree = ""; }; 24 | 53282FB21C67C5F100081171 /* NSObject_Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObject_Extension.swift; sourceTree = ""; }; 25 | 53282FB41C67C5F100081171 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 535393C61C6B8770003585F1 /* PluginHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PluginHelper.swift; sourceTree = ""; }; 27 | 535393C81C6B87EC003585F1 /* String_Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = String_Extension.swift; sourceTree = ""; }; 28 | 535393CE1C6BE194003585F1 /* R_template.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = R_template.txt; sourceTree = ""; }; 29 | 535393D01C6CCA87003585F1 /* RContentGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RContentGenerator.swift; sourceTree = ""; }; 30 | 537E26211C82324600273CD1 /* SwizzledMethods.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwizzledMethods.swift; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXGroup section */ 34 | 53282FA21C67C5F100081171 = { 35 | isa = PBXGroup; 36 | children = ( 37 | 53282FAC1C67C5F100081171 /* R.swift */, 38 | 53282FAB1C67C5F100081171 /* Products */, 39 | ); 40 | sourceTree = ""; 41 | }; 42 | 53282FAB1C67C5F100081171 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 53282FAA1C67C5F100081171 /* R.swift.xcplugin */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 53282FAC1C67C5F100081171 /* R.swift */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 53282FB21C67C5F100081171 /* NSObject_Extension.swift */, 54 | 535393C81C6B87EC003585F1 /* String_Extension.swift */, 55 | 53282FB01C67C5F100081171 /* MainPlugin.swift */, 56 | 537E26211C82324600273CD1 /* SwizzledMethods.swift */, 57 | 535393C61C6B8770003585F1 /* PluginHelper.swift */, 58 | 535393D01C6CCA87003585F1 /* RContentGenerator.swift */, 59 | 53282FB41C67C5F100081171 /* Info.plist */, 60 | 535393CE1C6BE194003585F1 /* R_template.txt */, 61 | 53282FAD1C67C5F100081171 /* Supporting Files */, 62 | ); 63 | path = R.swift; 64 | sourceTree = ""; 65 | }; 66 | 53282FAD1C67C5F100081171 /* Supporting Files */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 53282FAE1C67C5F100081171 /* R.swift.xcscheme */, 70 | ); 71 | name = "Supporting Files"; 72 | sourceTree = ""; 73 | }; 74 | /* End PBXGroup section */ 75 | 76 | /* Begin PBXNativeTarget section */ 77 | 53282FA91C67C5F100081171 /* R.swift */ = { 78 | isa = PBXNativeTarget; 79 | buildConfigurationList = 53282FB71C67C5F100081171 /* Build configuration list for PBXNativeTarget "R.swift" */; 80 | buildPhases = ( 81 | 53282FA71C67C5F100081171 /* Sources */, 82 | 53282FA81C67C5F100081171 /* Resources */, 83 | ); 84 | buildRules = ( 85 | ); 86 | dependencies = ( 87 | ); 88 | name = R.swift; 89 | productName = "R.swift"; 90 | productReference = 53282FAA1C67C5F100081171 /* R.swift.xcplugin */; 91 | productType = "com.apple.product-type.bundle"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | 53282FA31C67C5F100081171 /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | LastSwiftUpdateCheck = 0720; 100 | LastUpgradeCheck = 0720; 101 | ORGANIZATIONNAME = AzureChen; 102 | TargetAttributes = { 103 | 53282FA91C67C5F100081171 = { 104 | CreatedOnToolsVersion = 7.2; 105 | }; 106 | }; 107 | }; 108 | buildConfigurationList = 53282FA61C67C5F100081171 /* Build configuration list for PBXProject "R.swift" */; 109 | compatibilityVersion = "Xcode 3.2"; 110 | developmentRegion = English; 111 | hasScannedForEncodings = 0; 112 | knownRegions = ( 113 | en, 114 | ); 115 | mainGroup = 53282FA21C67C5F100081171; 116 | productRefGroup = 53282FAB1C67C5F100081171 /* Products */; 117 | projectDirPath = ""; 118 | projectRoot = ""; 119 | targets = ( 120 | 53282FA91C67C5F100081171 /* R.swift */, 121 | ); 122 | }; 123 | /* End PBXProject section */ 124 | 125 | /* Begin PBXResourcesBuildPhase section */ 126 | 53282FA81C67C5F100081171 /* Resources */ = { 127 | isa = PBXResourcesBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 53282FAF1C67C5F100081171 /* R.swift.xcscheme in Resources */, 131 | 535393CF1C6BE194003585F1 /* R_template.txt in Resources */, 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | /* End PBXResourcesBuildPhase section */ 136 | 137 | /* Begin PBXSourcesBuildPhase section */ 138 | 53282FA71C67C5F100081171 /* Sources */ = { 139 | isa = PBXSourcesBuildPhase; 140 | buildActionMask = 2147483647; 141 | files = ( 142 | 535393C91C6B87EC003585F1 /* String_Extension.swift in Sources */, 143 | 537E26221C82324600273CD1 /* SwizzledMethods.swift in Sources */, 144 | 53282FB11C67C5F100081171 /* MainPlugin.swift in Sources */, 145 | 53282FB31C67C5F100081171 /* NSObject_Extension.swift in Sources */, 146 | 535393D11C6CCA87003585F1 /* RContentGenerator.swift in Sources */, 147 | 535393C71C6B8770003585F1 /* PluginHelper.swift in Sources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXSourcesBuildPhase section */ 152 | 153 | /* Begin XCBuildConfiguration section */ 154 | 53282FB51C67C5F100081171 /* Debug */ = { 155 | isa = XCBuildConfiguration; 156 | buildSettings = { 157 | ALWAYS_SEARCH_USER_PATHS = NO; 158 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 159 | CLANG_CXX_LIBRARY = "libc++"; 160 | CLANG_ENABLE_MODULES = YES; 161 | CLANG_ENABLE_OBJC_ARC = YES; 162 | CLANG_WARN_BOOL_CONVERSION = YES; 163 | CLANG_WARN_CONSTANT_CONVERSION = YES; 164 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 165 | CLANG_WARN_EMPTY_BODY = YES; 166 | CLANG_WARN_ENUM_CONVERSION = YES; 167 | CLANG_WARN_INT_CONVERSION = YES; 168 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 169 | CLANG_WARN_UNREACHABLE_CODE = YES; 170 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 171 | COPY_PHASE_STRIP = NO; 172 | DEBUG_INFORMATION_FORMAT = dwarf; 173 | ENABLE_STRICT_OBJC_MSGSEND = YES; 174 | ENABLE_TESTABILITY = YES; 175 | GCC_C_LANGUAGE_STANDARD = gnu99; 176 | GCC_DYNAMIC_NO_PIC = NO; 177 | GCC_NO_COMMON_BLOCKS = YES; 178 | GCC_OPTIMIZATION_LEVEL = 0; 179 | GCC_PREPROCESSOR_DEFINITIONS = ( 180 | "DEBUG=1", 181 | "$(inherited)", 182 | ); 183 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 184 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 185 | GCC_WARN_UNDECLARED_SELECTOR = YES; 186 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 187 | GCC_WARN_UNUSED_FUNCTION = YES; 188 | GCC_WARN_UNUSED_VARIABLE = YES; 189 | MTL_ENABLE_DEBUG_INFO = YES; 190 | ONLY_ACTIVE_ARCH = YES; 191 | }; 192 | name = Debug; 193 | }; 194 | 53282FB61C67C5F100081171 /* Release */ = { 195 | isa = XCBuildConfiguration; 196 | buildSettings = { 197 | ALWAYS_SEARCH_USER_PATHS = NO; 198 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 199 | CLANG_CXX_LIBRARY = "libc++"; 200 | CLANG_ENABLE_MODULES = YES; 201 | CLANG_ENABLE_OBJC_ARC = YES; 202 | CLANG_WARN_BOOL_CONVERSION = YES; 203 | CLANG_WARN_CONSTANT_CONVERSION = YES; 204 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 205 | CLANG_WARN_EMPTY_BODY = YES; 206 | CLANG_WARN_ENUM_CONVERSION = YES; 207 | CLANG_WARN_INT_CONVERSION = YES; 208 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 209 | CLANG_WARN_UNREACHABLE_CODE = YES; 210 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 211 | COPY_PHASE_STRIP = NO; 212 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 213 | ENABLE_NS_ASSERTIONS = NO; 214 | ENABLE_STRICT_OBJC_MSGSEND = YES; 215 | GCC_C_LANGUAGE_STANDARD = gnu99; 216 | GCC_NO_COMMON_BLOCKS = YES; 217 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 218 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 219 | GCC_WARN_UNDECLARED_SELECTOR = YES; 220 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 221 | GCC_WARN_UNUSED_FUNCTION = YES; 222 | GCC_WARN_UNUSED_VARIABLE = YES; 223 | MTL_ENABLE_DEBUG_INFO = NO; 224 | }; 225 | name = Release; 226 | }; 227 | 53282FB81C67C5F100081171 /* Debug */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | COMBINE_HIDPI_IMAGES = YES; 231 | DEPLOYMENT_LOCATION = YES; 232 | DSTROOT = "$(HOME)"; 233 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 234 | INFOPLIST_FILE = R.swift/Info.plist; 235 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 236 | LD_RUNPATH_SEARCH_PATHS = "$(DT_TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 237 | MACOSX_DEPLOYMENT_TARGET = 10.10; 238 | PRODUCT_BUNDLE_IDENTIFIER = com.azurechen.Rswift; 239 | PRODUCT_NAME = R.swift; 240 | WRAPPER_EXTENSION = xcplugin; 241 | }; 242 | name = Debug; 243 | }; 244 | 53282FB91C67C5F100081171 /* Release */ = { 245 | isa = XCBuildConfiguration; 246 | buildSettings = { 247 | COMBINE_HIDPI_IMAGES = YES; 248 | DEPLOYMENT_LOCATION = YES; 249 | DSTROOT = "$(HOME)"; 250 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 251 | INFOPLIST_FILE = R.swift/Info.plist; 252 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 253 | LD_RUNPATH_SEARCH_PATHS = "$(DT_TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 254 | MACOSX_DEPLOYMENT_TARGET = 10.10; 255 | PRODUCT_BUNDLE_IDENTIFIER = com.azurechen.Rswift; 256 | PRODUCT_NAME = R.swift; 257 | WRAPPER_EXTENSION = xcplugin; 258 | }; 259 | name = Release; 260 | }; 261 | /* End XCBuildConfiguration section */ 262 | 263 | /* Begin XCConfigurationList section */ 264 | 53282FA61C67C5F100081171 /* Build configuration list for PBXProject "R.swift" */ = { 265 | isa = XCConfigurationList; 266 | buildConfigurations = ( 267 | 53282FB51C67C5F100081171 /* Debug */, 268 | 53282FB61C67C5F100081171 /* Release */, 269 | ); 270 | defaultConfigurationIsVisible = 0; 271 | defaultConfigurationName = Release; 272 | }; 273 | 53282FB71C67C5F100081171 /* Build configuration list for PBXNativeTarget "R.swift" */ = { 274 | isa = XCConfigurationList; 275 | buildConfigurations = ( 276 | 53282FB81C67C5F100081171 /* Debug */, 277 | 53282FB91C67C5F100081171 /* Release */, 278 | ); 279 | defaultConfigurationIsVisible = 0; 280 | defaultConfigurationName = Release; 281 | }; 282 | /* End XCConfigurationList section */ 283 | }; 284 | rootObject = 53282FA31C67C5F100081171 /* Project object */; 285 | } 286 | -------------------------------------------------------------------------------- /R.swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /R.swift.xcodeproj/xcshareddata/xcschemes/R.swift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 56 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /R.swift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | DVTPlugInCompatibilityUUIDs 26 | 27 | FEC992CC-CA4A-4CFD-8881-77300FCB848A 28 | C4A681B0-4A26-480E-93EC-1218098B9AA0 29 | A2E4D43F-41F4-4FB9-BB94-7177011C9AED 30 | AD68E85B-441B-4301-B564-A45E4919A6AD 31 | 63FC1C47-140D-42B0-BB4D-A10B2D225574 32 | 37B30044-3B14-46BA-ABAA-F01000C27B63 33 | 640F884E-CE55-4B40-87C0-8869546CAB7A 34 | 992275C1-432A-4CF7-B659-D84ED6D42D3F 35 | A16FF353-8441-459E-A50C-B071F53F51B7 36 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 37 | E969541F-E6F9-4D25-8158-72DC3545A6C6 38 | 8DC44374-2B35-4C57-A6FE-2AD66A36AAD9 39 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 40 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 41 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 42 | CC0D0F4F-05B3-431A-8F33-F84AFCB2C651 43 | 7265231C-39B4-402C-89E1-16167C4CC990 44 | 9AFF134A-08DC-4096-8CEE-62A4BB123046 45 | F41BD31E-2683-44B8-AE7F-5F09E919790E 46 | E71C2CFE-BFD8-4044-8F06-00AE685A406C 47 | ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C 48 | 49 | LSMinimumSystemVersion 50 | $(MACOSX_DEPLOYMENT_TARGET) 51 | NSPrincipalClass 52 | MainPlugin 53 | XC4Compatible 54 | 55 | XCPluginHasUI 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /R.swift/MainPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainPlugin.swift 3 | // 4 | // Created by AzureChen on 2/8/16. 5 | // Copyright © 2016 AzureChen. All rights reserved. 6 | // 7 | 8 | import AppKit 9 | 10 | var sharedPlugin: MainPlugin? 11 | 12 | class MainPlugin: NSObject, NSMenuDelegate { 13 | 14 | var bundle: NSBundle 15 | lazy var center = NSNotificationCenter.defaultCenter() 16 | 17 | let pluginMenu = NSMenu() 18 | 19 | static var currentDocumentName: String? 20 | static var states: [String: Bool] = [:] // [workspace: enable] 21 | 22 | static let REGISTERED_RESOURCE_FILE_PATTERNS = [ 23 | // 1. PBXBuildFile section 24 | "\\n*?\\t*?.{24}? /\\* R.swift in Sources \\*/ = \\{isa = PBXBuildFile; fileRef = .*? /\\* R.swift \\*/; \\};", 25 | // 2. PBXFileReference section 26 | "\\n*?\\t*?.{24}? /\\* R.swift \\*/ = \\{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = .*?R.swift; sourceTree = \"\"; \\};", 27 | // 3. PBXGroup section 28 | "\\n*?\\t*?.{24}? /\\* R.swift \\*/,", 29 | // 4. PBXSourcesBuildPhase section 30 | "\\n*?\\t*?.{24}? /\\* R.swift in Sources \\*/,", 31 | ] 32 | 33 | init(bundle: NSBundle) { 34 | self.bundle = bundle 35 | 36 | super.init() 37 | center.addObserver(self, selector: Selector("createMenu"), name: NSApplicationDidFinishLaunchingNotification, object: nil) 38 | 39 | self.swizzleClass(NSWindow.self, 40 | replace: Selector("becomeKeyWindow"), 41 | with: Selector("hook_becomeKeyWindow")) 42 | self.swizzleClass(NSTabView.self, 43 | replace: Selector("selectTabViewItem:"), 44 | with: Selector("hook_selectTabViewItem:")) 45 | self.swizzleClass(NSTabViewItem.self, 46 | replace: Selector("setLabel:"), 47 | with: Selector("hook_setLabel:")) 48 | } 49 | 50 | deinit { 51 | removeObserver() 52 | } 53 | 54 | func removeObserver() { 55 | center.removeObserver(self) 56 | } 57 | 58 | func createMenu() { 59 | removeObserver() 60 | 61 | let item = NSApp.mainMenu!.itemWithTitle("Edit") 62 | if (item != nil) { 63 | item!.submenu!.addItem(NSMenuItem.separatorItem()) 64 | 65 | // first level item 66 | let pluginItem = NSMenuItem(title: "R.swift", action: nil, keyEquivalent: "") 67 | item!.submenu!.addItem(pluginItem) 68 | 69 | pluginMenu.delegate = self 70 | item!.submenu!.setSubmenu(pluginMenu, forItem: pluginItem) 71 | } 72 | } 73 | 74 | func menuWillOpen(menu: NSMenu) { 75 | if (menu == pluginMenu) { 76 | var enabled = false 77 | 78 | // get state 79 | if let project = PluginHelper.project() { 80 | let key = "\(project.path)/\(project.name)" 81 | if let state = MainPlugin.states[key] { 82 | enabled = state 83 | } 84 | } 85 | resetMenuItems(enabled: enabled) 86 | } 87 | } 88 | 89 | func resetMenuItems(enabled enabled: Bool) { 90 | pluginMenu.removeAllItems() 91 | 92 | // 1. Enable 93 | let enableItem = NSMenuItem(title: "Enable Auto Sync", action: "enableAction:", keyEquivalent: "") 94 | enableItem.state = enabled ? NSOnState : NSOffState 95 | enableItem.target = self 96 | pluginMenu.addItem(enableItem) 97 | 98 | // 2. separator 99 | pluginMenu.addItem(NSMenuItem.separatorItem()) 100 | 101 | // 3. sync 102 | let syncItem = NSMenuItem(title: "Sync", action: "syncAction", keyEquivalent: "") 103 | syncItem.target = self 104 | pluginMenu.addItem(syncItem) 105 | 106 | // 4. clean 107 | let cleanItem = NSMenuItem(title: "Clean", action: "cleanAction", keyEquivalent: "") 108 | cleanItem.target = self 109 | pluginMenu.addItem(cleanItem) 110 | } 111 | 112 | func enableAction(sender: NSMenuItem) { 113 | if let project = PluginHelper.project() { 114 | let key = "\(project.path)/\(project.name)" 115 | 116 | if (MainPlugin.states[key] != nil) { 117 | if (sender.state == NSOnState) { 118 | MainPlugin.states[key] = false 119 | } else { 120 | MainPlugin.states[key] = true 121 | MainPlugin.sync() 122 | } 123 | } 124 | } 125 | } 126 | 127 | func syncAction() { 128 | MainPlugin.sync() 129 | } 130 | 131 | func cleanAction() { 132 | MainPlugin.clean() 133 | 134 | // Set auto sync disable to avoid sync again after cleaning R 135 | if let project = PluginHelper.project() { 136 | let key = "\(project.path)/\(project.name)" 137 | 138 | if (MainPlugin.states[key] != nil) { 139 | MainPlugin.states[key] = false 140 | } 141 | } 142 | } 143 | 144 | static func autoSyncIfNeeded(document documentName: String?) { 145 | let prevDocumentName = currentDocumentName 146 | currentDocumentName = documentName 147 | //print("\(prevDocumentName) \(currentDocumentName)") 148 | 149 | if (currentDocumentName == nil) || isRFile(prevDocumentName) || 150 | (!isRFile(currentDocumentName) && 151 | prevDocumentName != currentDocumentName && 152 | isTargetFile(prevDocumentName) && 153 | !isTargetFile(currentDocumentName)) { 154 | 155 | if let project = PluginHelper.project() { 156 | let key = "\(project.path)/\(project.name)" 157 | // Auto sync is enabled 158 | if (states[key] == true) { 159 | sync() 160 | } 161 | } 162 | } 163 | } 164 | 165 | private static func isRFile(name: String?) -> Bool { 166 | if (name != nil) { 167 | return name!.match(PluginHelper.TARGET_NAME_PATTERN_R) 168 | } 169 | return false 170 | } 171 | 172 | private static func isTargetFile(name: String?) -> Bool { 173 | if (name != nil) { 174 | return name!.match(PluginHelper.TARGET_NAME_PATTERN_COLOR) || name!.match(PluginHelper.TARGET_NAME_PATTERN_IMAGE) || name!.match(PluginHelper.TARGET_NAME_PATTERN_LOCALIZABLE) 175 | } 176 | return false 177 | } 178 | 179 | static func sync() { 180 | print("R.swift Sync") 181 | if let project = PluginHelper.project() { 182 | // 1. register the R.swift file in project.pbxproj 183 | registerResourceFileIfNeeded(inProject: project) 184 | // 2. create and write the R.swift file 185 | createResourceFile(inProject: project) 186 | } else { 187 | print("Cannot find the root path of the current project.") 188 | } 189 | } 190 | 191 | static func clean() { 192 | print("R.swift Clean") 193 | if let project = PluginHelper.project() { 194 | // 1. remove the R.swift file 195 | removeResourceFile(inProject: project) 196 | // 2. clean registered the R.swift file in project.pbxproj 197 | unregisterResourceFile(inProject: project) 198 | } else { 199 | print("Cannot find the root path of the current project.") 200 | } 201 | } 202 | 203 | private static func createResourceFile(inProject project: (path: String, name: String)) { 204 | let rPath = PluginHelper.resourceFilePath(inProject: project) 205 | 206 | // if the R.swift file exist, remove it 207 | if (NSFileManager.defaultManager().fileExistsAtPath(rPath)) { 208 | removeResourceFile(inProject: project) 209 | } 210 | 211 | // create the R.swift file 212 | if (!NSFileManager.defaultManager().fileExistsAtPath(rPath)) { 213 | NSFileManager.defaultManager().createFileAtPath(rPath, contents: nil, attributes: nil) 214 | } 215 | 216 | // generate contents of the R.swift 217 | let generator = RContentGenerator(project: project) 218 | generator.generate()?.writeToFile(rPath) 219 | 220 | // lock R.swift 221 | PluginHelper.runShellCommand("chmod 444 \(rPath.stringEscapeSpaces())") 222 | } 223 | 224 | private static func removeResourceFile(inProject project: (path: String, name: String)) { 225 | // remove R files 226 | PluginHelper.runShellCommand("find \(project.path.stringEscapeSpaces())/\(project.name.stringEscapeSpaces()) -name \(PluginHelper.TARGET_NAME_PATTERN_R) -type f -delete") 227 | } 228 | 229 | private static func registerResourceFileIfNeeded(inProject project: (path: String, name: String)) { 230 | let projectFilePath = PluginHelper.projectFilePath(inProject: project) 231 | 232 | // check status first 233 | let status = checkResourceFile(inProject: project) 234 | if (status == 1) { // the R.swift file is registered 235 | return 236 | } else if (status != 0) { // some parts of info have been registered, but not completed 237 | unregisterResourceFile(inProject: project) 238 | } 239 | 240 | // read the content of project.pbxproj and register R file in project.pbxproj 241 | if var projectContent = String.readFile(projectFilePath) { 242 | // create UUIDs 243 | let UUID1 = PluginHelper.UUID(withLength: 24) 244 | let UUID2 = PluginHelper.UUID(withLength: 24) 245 | 246 | // 1. PBXBuildFile section 247 | if let range = projectContent.rangeOfString("/\\* Begin PBXBuildFile section \\*/", options: .RegularExpressionSearch) { 248 | projectContent.insert("\n\t\t\(UUID1) /* R.swift in Sources */ = {isa = PBXBuildFile; fileRef = \(UUID2) /* R.swift */; };", atIndex: range.endIndex) 249 | } 250 | // 2. PBXFileReference section 251 | if let range = projectContent.rangeOfString("/\\* Begin PBXFileReference section \\*/", options: .RegularExpressionSearch) { 252 | projectContent.insert("\n\t\t\(UUID2) /* R.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = R.swift; sourceTree = \"\"; };", atIndex: range.endIndex) 253 | } 254 | // 3. PBXGroup section (Supporting Files) 255 | let matches = projectContent.matches("/\\* \(project.name) \\*/ = \\{\\n*?\\t*?isa = PBXGroup;\\n*?\\t*?children = \\(([\\s\\S]*?)\\n*?\\t*?\\);") 256 | if (!matches.isEmpty) { 257 | if let range = projectContent.rangeFromNSRange(matches[0].rangeAtIndex(1)) { 258 | projectContent.insert("\n\t\t\t\t\(UUID2) /* R.swift */,", atIndex: range.endIndex) 259 | } 260 | } 261 | // 4. PBXSourcesBuildPhase section 262 | if let range = projectContent.rangeOfString("/\\* Begin PBXSourcesBuildPhase section \\*/\\n*?\\t*?[\\s\\S]*?files = \\(", options: .RegularExpressionSearch) { 263 | projectContent.insert("\n\t\t\t\t\(UUID1) /* R.swift in Sources */,", atIndex: range.endIndex) 264 | } 265 | 266 | // save file 267 | projectContent.writeToFile(projectFilePath) 268 | } else { 269 | print("Cannot read the project.pbxproj file.") 270 | } 271 | } 272 | 273 | private static func unregisterResourceFile(inProject project: (path: String, name: String)) { 274 | let projectFilePath = PluginHelper.projectFilePath(inProject: project) 275 | 276 | // remove from project.pbxproj 277 | if var projectContent = String.readFile(projectFilePath) { 278 | do { 279 | for pattern in REGISTERED_RESOURCE_FILE_PATTERNS { 280 | let regex = try NSRegularExpression(pattern: pattern, options: .CaseInsensitive) 281 | projectContent = regex.stringByReplacingMatchesInString(projectContent, options: [], range: NSMakeRange(0, projectContent.characters.count), withTemplate: "") 282 | } 283 | 284 | // save file 285 | projectContent.writeToFile(projectFilePath) 286 | } catch { 287 | } 288 | } else { 289 | print("Cannot read the project.pbxproj file.") 290 | } 291 | } 292 | 293 | private static func checkResourceFile(inProject project: (path: String, name: String)) -> Int { 294 | let projectFilePath = PluginHelper.projectFilePath(inProject: project) 295 | 296 | // check if R.swift exists in project.pbxproj 297 | if let projectContent = String.readFile(projectFilePath) { 298 | var count1 = 0 299 | var count2 = 0 300 | for pattern in REGISTERED_RESOURCE_FILE_PATTERNS { 301 | count1 += projectContent.matches(pattern).count 302 | count2++ 303 | } 304 | 305 | if (count1 == 0 && count2 == 0) { // registered file not found 306 | return 0 307 | } else if (count1 == 4 && count2 == 4) { // registered file found 308 | return 1 309 | } else { // error 310 | return -1 311 | } 312 | } else { 313 | print("Cannot read the project.pbxproj file.") 314 | return -1 315 | } 316 | } 317 | } 318 | 319 | -------------------------------------------------------------------------------- /R.swift/NSObject_Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject_Extension.swift 3 | // 4 | // Created by AzureChen on 2/8/16. 5 | // Copyright © 2016 AzureChen. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | extension NSObject { 11 | 12 | class func pluginDidLoad(bundle: NSBundle) { 13 | let appName = NSBundle.mainBundle().infoDictionary?["CFBundleName"] as? NSString 14 | if appName == "Xcode" { 15 | if sharedPlugin == nil { 16 | sharedPlugin = MainPlugin(bundle: bundle) 17 | } 18 | } 19 | } 20 | 21 | func swizzleClass(aClass: AnyClass, replace originalSelector: Selector, with swizzledSelector: Selector) { 22 | let originalMethod = class_getInstanceMethod(aClass, originalSelector) 23 | let swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector) 24 | // print("swizzle \(aClass) \(originalMethod), \(swizzledMethod)") 25 | 26 | method_exchangeImplementations(originalMethod, swizzledMethod) 27 | } 28 | } -------------------------------------------------------------------------------- /R.swift/PluginHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PluginHelper.swift 3 | // R.swift 4 | // 5 | // Created by Azure Chen on 2/8/16. 6 | // Copyright © 2016 AzureChen. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | class PluginHelper { 12 | 13 | typealias Project = (path: String, name: String) 14 | 15 | static let TARGET_NAME_PATTERN_R = "R.swift" 16 | static let TARGET_NAME_PATTERN_LOCALIZABLE = "Localizable.strings" 17 | static let TARGET_NAME_PATTERN_COLOR = "Color.strings" 18 | static let TARGET_NAME_PATTERN_IMAGE = ".*?.xcassets" 19 | 20 | static func project() -> Project? { 21 | if let path = workspacePath(), let name = projectName(atPath: path) { 22 | return (path, name) 23 | } else { 24 | return nil 25 | } 26 | } 27 | 28 | private static func workspacePath() -> String? { 29 | if let anyClass = NSClassFromString("IDEWorkspaceWindowController") as? NSObject.Type, 30 | let windowControllers = anyClass.valueForKey("workspaceWindowControllers") as? [NSObject], 31 | let window = NSApp.keyWindow ?? NSApp.windows.first { 32 | 33 | for controller in windowControllers { 34 | if controller.valueForKey("window")?.isEqual(window) == true, 35 | let workspacePath = controller.valueForKey("_workspace")?.valueForKeyPath("representingFilePath._pathString") as? NSString { 36 | return workspacePath.stringByDeletingLastPathComponent as String 37 | } 38 | } 39 | } 40 | return nil 41 | } 42 | 43 | private static func projectName(atPath projectPath: String) -> String? { 44 | let projectFilePath = runShellCommand("ls \(projectPath.stringEscapeSpaces()) | grep .xcodeproj") 45 | return projectFilePath?.stringByReplacingOccurrencesOfString(".xcodeproj", withString: "") 46 | } 47 | 48 | static func projectFilePath(inProject project: Project) -> String { 49 | return "\(project.path)/\(project.name).xcodeproj/project.pbxproj" 50 | } 51 | 52 | static func resourceFilePath(inProject project: Project) -> String { 53 | return "\(project.path)/\(project.name)/\(TARGET_NAME_PATTERN_R)" 54 | } 55 | 56 | static func baseLocalizableFilePath(inProject project: Project) -> String { 57 | return "\(project.path)/\(project.name)/Base.lproj/\(TARGET_NAME_PATTERN_LOCALIZABLE)" 58 | } 59 | 60 | static func colorFilePaths(inProject project: Project) -> [String] { 61 | return findFilePaths(TARGET_NAME_PATTERN_COLOR, inProject: project) 62 | } 63 | 64 | static func imageDirPaths(inProject project: Project) -> [String] { 65 | return findFilePaths(TARGET_NAME_PATTERN_IMAGE, inProject: project) 66 | } 67 | 68 | private static func findFilePaths(pattern: String, inProject project: Project) -> [String] { 69 | let projectFilePath = PluginHelper.projectFilePath(inProject: project) 70 | 71 | var paths: [String] = [] 72 | if let projectContent = String.readFile(projectFilePath) { 73 | do { 74 | let regex = try NSRegularExpression(pattern: "/\\* \(pattern) \\*/.*?path = (.*?);", options: .CaseInsensitive) 75 | let matches = regex.matchesInString(projectContent, options: [], range: NSMakeRange(0, projectContent.characters.count)) 76 | 77 | for match in matches { 78 | let relativePath = (projectContent as NSString).substringWithRange(match.rangeAtIndex(1)) as String 79 | paths.append("\(project.path)/\(project.name)/\(relativePath)") 80 | } 81 | } catch { 82 | } 83 | } 84 | 85 | return paths 86 | } 87 | 88 | static func runShellCommand(command: String) -> String? { 89 | let pipe = NSPipe() 90 | let task = NSTask() 91 | 92 | task.launchPath = "/bin/sh" 93 | task.arguments = ["-c", String(format: "%@", command)] 94 | task.standardOutput = pipe 95 | task.launch() 96 | 97 | let file = pipe.fileHandleForReading 98 | if let result = NSString(data: file.readDataToEndOfFile(), encoding: NSUTF8StringEncoding)?.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet()) where result != "" { 99 | return result as String 100 | } 101 | return nil 102 | } 103 | 104 | static func UUID(withLength len: Int) -> String { 105 | var uuid = "" 106 | for (var i = 0; i < len; i++){ 107 | let rand = arc4random_uniform(16) 108 | uuid += String(format:"%X", Int(rand)) 109 | } 110 | 111 | return uuid 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /R.swift/RContentGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResourceGenerator.swift 3 | // R.swift 4 | // 5 | // Created by Azure Chen on 2/11/16. 6 | // Copyright © 2016 AzureChen. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | class RContentGenerator { 12 | 13 | let PATTERN_STRINGS = "\"(.*?)\"[\\n\\s]*?=[\\n\\s]*?\"(.*?)\"[\\n\\s]*?;" 14 | 15 | var project: (path: String, name: String) 16 | var content: String? 17 | 18 | init(project: (path: String, name: String)) { 19 | self.project = project 20 | } 21 | 22 | func generate() -> String? { 23 | // 1. read template from the R_template.swift 24 | initFromTemplate() 25 | 26 | if (content != nil) { 27 | // 2. generate colors 28 | generateColors() 29 | // 3. generate images 30 | generateImages() 31 | // 4. generate localizable strings 32 | generateStrings() 33 | } 34 | 35 | return content 36 | } 37 | 38 | private func initFromTemplate() { 39 | let url = NSBundle(forClass: self.dynamicType).URLForResource("R_template", withExtension: "txt") 40 | content = try? String(contentsOfURL: url!, encoding: NSUTF8StringEncoding) 41 | // content? += "\n\n// \(NSDate())" // for debug 42 | } 43 | 44 | private func generateColors() { 45 | let colorFilePaths = PluginHelper.colorFilePaths(inProject: project) 46 | 47 | // generate enum members 48 | var generatedContent = "" 49 | for colorFilePath in colorFilePaths { 50 | // read the Color.strings file 51 | if let originalContent = String.readFile(colorFilePath) { 52 | let matches = originalContent.matches(PATTERN_STRINGS) 53 | if (!matches.isEmpty) { 54 | for match in matches { 55 | let key = (originalContent as NSString).substringWithRange(match.rangeAtIndex(1)) as String 56 | let value = (originalContent as NSString).substringWithRange(match.rangeAtIndex(2)) as String 57 | generatedContent += " case \(key) = \"\(value)\"\n" 58 | } 59 | // replace members of enum color 60 | replaceEnumMembers("color: String", members: generatedContent) 61 | } 62 | } 63 | } 64 | } 65 | 66 | private func generateImages() { 67 | let imageDirPaths = PluginHelper.imageDirPaths(inProject: project) 68 | 69 | // generate enum members 70 | var generatedContent = "" 71 | for imageDirPath in imageDirPaths { 72 | // read images in the Images.xcassets dir 73 | if let ls = PluginHelper.runShellCommand("ls \(imageDirPath.stringEscapeSpaces()) | grep imageset") { 74 | let imagePaths = ls.componentsSeparatedByString("\n") 75 | 76 | if (!imagePaths.isEmpty) { 77 | for imagePath in imagePaths { 78 | let key = imagePath.stringByReplacingOccurrencesOfString(".imageset", withString: "") 79 | generatedContent += " case \(key)\n" 80 | } 81 | // replace members of enum string 82 | replaceEnumMembers("image", members: generatedContent) 83 | } 84 | } 85 | } 86 | } 87 | 88 | private func generateStrings() { 89 | let baseStringFilePath = PluginHelper.baseLocalizableFilePath(inProject: project) 90 | // read the Localizable.strings file 91 | if let originalContent = String.readFile(baseStringFilePath) { 92 | let matches = originalContent.matches(PATTERN_STRINGS) 93 | if (!matches.isEmpty) { 94 | // generate enum members 95 | var generatedContent = "" 96 | for match in matches { 97 | let key = (originalContent as NSString).substringWithRange(match.rangeAtIndex(1)) as String 98 | generatedContent += " case \(key)\n" 99 | } 100 | // replace members of enum string 101 | replaceEnumMembers("string", members: generatedContent) 102 | } 103 | } 104 | } 105 | 106 | private func replaceEnumMembers(identifier: String, members: String) { 107 | let range = content!.matches("enum \(identifier) \\{\\n([\\s\\S]*?)\\s{4}\\}")[0].rangeAtIndex(1) 108 | content = (content! as NSString).stringByReplacingCharactersInRange(range, withString: members) 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /R.swift/R_template.txt: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE. DO NOT MODIFY. 3 | // 4 | // This file was automatically generated by R.swift Xcode Plugin. 5 | // Please do not modify it by hand. 6 | // 7 | 8 | import UIKit 9 | 10 | class R { 11 | 12 | enum color: String { 13 | case __ = "" 14 | } 15 | 16 | enum image { 17 | } 18 | 19 | enum string { 20 | } 21 | 22 | } 23 | 24 | extension NSObject { 25 | 26 | func color(r: R.color) -> UIColor { 27 | let argb = r.rawValue 28 | 29 | var alpha: CGFloat = 1.0 30 | var red: CGFloat = 0.0 31 | var green: CGFloat = 0.0 32 | var blue: CGFloat = 0.0 33 | if argb.hasPrefix("#") { 34 | let index = argb.startIndex.advancedBy(1) 35 | let hex = argb.substringFromIndex(index) 36 | let scanner = NSScanner(string: hex) 37 | var hexValue: CUnsignedLongLong = 0 38 | if scanner.scanHexLongLong(&hexValue) { 39 | switch (hex.characters.count) { 40 | case 3: 41 | red = CGFloat((hexValue & 0xF00) >> 8) / 15.0 42 | green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0 43 | blue = CGFloat( hexValue & 0x00F) / 15.0 44 | case 4: 45 | alpha = CGFloat((hexValue & 0xF000) >> 12) / 15.0 46 | red = CGFloat((hexValue & 0x0F00) >> 8) / 15.0 47 | green = CGFloat((hexValue & 0x00F0) >> 4) / 15.0 48 | blue = CGFloat( hexValue & 0x000F) / 15.0 49 | case 6: 50 | red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0 51 | green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0 52 | blue = CGFloat( hexValue & 0x0000FF) / 255.0 53 | case 8: 54 | alpha = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0 55 | red = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0 56 | green = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0 57 | blue = CGFloat( hexValue & 0x000000FF) / 255.0 58 | default: 59 | print("Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8", terminator: "") 60 | } 61 | } else { 62 | print("Scan hex error") 63 | } 64 | } else { 65 | print("Invalid RGB string, missing '#' as prefix", terminator: "") 66 | } 67 | return UIColor(red: red, green: green, blue: blue, alpha: alpha) 68 | } 69 | 70 | func image(r: R.image) -> UIImage? { 71 | return UIImage(named: String(r)) 72 | } 73 | 74 | func string(r: R.string) -> String { 75 | return NSLocalizedString(String(r), tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "") 76 | } 77 | 78 | func getColor(r: R.color) -> UIColor { 79 | return color(r) 80 | } 81 | 82 | func getImage(r: R.image) -> UIImage? { 83 | return image(r) 84 | } 85 | 86 | func getString(r: R.string) -> String { 87 | return string(r) 88 | } 89 | } -------------------------------------------------------------------------------- /R.swift/String_Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String_Extension.swift 3 | // R.swift 4 | // 5 | // Created by Azure Chen on 2/9/16. 6 | // Copyright © 2016 AzureChen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | mutating func insert(string: String, atIndex index: Index) { 13 | self = self.substringToIndex(index) + string + self.substringFromIndex(index) 14 | } 15 | 16 | func rangeFromNSRange(nsRange: NSRange) -> Range? { 17 | if let range = nsRange.toRange() { 18 | let startIndex = self.startIndex.advancedBy(range.startIndex) 19 | let endIndex = startIndex.advancedBy(range.endIndex - range.startIndex) 20 | return Range(start: startIndex, end: endIndex) 21 | } 22 | return nil 23 | } 24 | 25 | static func readFile(path: String) -> String? { 26 | do { 27 | let content = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding) 28 | return content as String 29 | } catch { 30 | return nil 31 | } 32 | } 33 | 34 | func writeToFile(path: String) { 35 | do { 36 | try self.writeToFile(path, atomically: true, encoding: NSUTF8StringEncoding) 37 | } catch { 38 | } 39 | } 40 | 41 | func match(pattern: String) -> Bool { 42 | let range = self.rangeOfString(pattern, options: .RegularExpressionSearch) 43 | return range?.startIndex == self.startIndex && range?.endIndex == self.endIndex 44 | } 45 | 46 | func matches(pattern: String) -> [NSTextCheckingResult] { 47 | do { 48 | let regex = try NSRegularExpression(pattern: pattern, options: .CaseInsensitive) 49 | return regex.matchesInString(self, options: [], range: NSMakeRange(0, self.characters.count)) 50 | } catch { 51 | return [] 52 | } 53 | } 54 | 55 | func stringEscapeSpaces() -> String { 56 | return self.stringByReplacingOccurrencesOfString(" ", withString: "\\ ") 57 | } 58 | } -------------------------------------------------------------------------------- /R.swift/SwizzledMethods.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwizzledMethods.swift 3 | // R.swift 4 | // 5 | // Created by Azure Chen on 2/28/16. 6 | // Copyright © 2016 AzureChen. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | extension NSWindow { 12 | // change window 13 | func hook_becomeKeyWindow() { 14 | self.hook_becomeKeyWindow() 15 | 16 | dispatch_async(dispatch_get_main_queue()) { () -> Void in 17 | // create a state of current workspace at first time 18 | if let project = PluginHelper.project() { 19 | let key = "\(project.path)/\(project.name)" 20 | 21 | if (MainPlugin.states[key] == nil) { 22 | let rPath = PluginHelper.resourceFilePath(inProject: project) 23 | MainPlugin.states[key] = NSFileManager.defaultManager().fileExistsAtPath(rPath) 24 | } 25 | } 26 | 27 | // sync 28 | MainPlugin.autoSyncIfNeeded(document: nil) 29 | } 30 | } 31 | } 32 | 33 | extension NSTabView { 34 | // change tab 35 | func hook_selectTabViewItem(tabViewItem: NSTabViewItem?) { 36 | self.hook_selectTabViewItem(tabViewItem) 37 | 38 | // sync 39 | MainPlugin.autoSyncIfNeeded(document: tabViewItem?.label) 40 | } 41 | } 42 | 43 | extension NSTabViewItem { 44 | // change file 45 | func hook_setLabel(label: String) { 46 | self.hook_setLabel(label) 47 | 48 | // sync 49 | MainPlugin.autoSyncIfNeeded(document: self.label) 50 | } 51 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | R.swift Plugin for Xcode 2 | ====================== 3 | 4 | This Xcode Plugin can generate a resource file like the `R.java` of `Android` automatically in `Swift`. 5 | 6 | It make you access resources like `colors`, `images` or `localizable strings` easier and more accurately with syntax highlighting. 7 | 8 | Image.xcassets 9 | 10 | Usage 11 | ----- 12 | 13 | ###Strings 14 | 15 | Get strings by calling `string(R.string.{string_name})` 16 | 17 | ```swift 18 | let str: String = string(R.string.hello_world) 19 | ``` 20 | 21 | Or use the abbreviated form 22 | 23 | ```swift 24 | let str: String = string(.hello_world) 25 | ``` 26 | 27 | If you have some localizable string files, the `string(R.string.{string_name})` will show the text in your language. 28 | 29 | * `Base.lproj/Localizable.strings` 30 | 31 | ``` 32 | // titles 33 | "title1" = "Title 1"; 34 | "title2" = "Title 2"; 35 | 36 | // hello world 37 | "hello_world" = "Hello, world!"; 38 | ``` 39 | 40 | * `es.lproj/Localizable.strings` 41 | 42 | ``` 43 | // titles 44 | "title1" = "título 1"; 45 | "title2" = "título 2"; 46 | 47 | // hello world 48 | "hello_world" = "Hola mundo!"; 49 | ``` 50 | 51 | ###Images 52 | 53 | Assume there is any `*.xcassets` folder in your project 54 | 55 | * `Images.xcassets` 56 | 57 | Image.xcassets 58 | 59 | Get images by calling `image(R.string.{image_name})` 60 | 61 | ```swift 62 | imageView.image = image(R.image.icon_play) 63 | ``` 64 | 65 | or 66 | 67 | ```swift 68 | imageView.image = image(.icon_play) 69 | ``` 70 | 71 | ###Colors 72 | 73 | Create a `Color.strings` file first and define some colors you like. The hex code of colors should follow `#RRGGBB`, `#RGB`, `#AARRGGBB` or `#ARGB` rules. 74 | 75 | * `Color.strings` 76 | 77 | ``` 78 | // colors 79 | "dark_red" = "#8B0000"; 80 | "popcorn_yellow" = "#FAA"; 81 | "lake_michigan" = "#DE50A6C2"; 82 | "cat_eye" = "#EBE5"; 83 | ``` 84 | 85 | And get colors by calling `color(R.color.{color_name})` 86 | 87 | ```swift 88 | view.backgroundColor = color(R.color.popcorn_yellow) 89 | ``` 90 | 91 | or 92 | 93 | ```swift 94 | view.backgroundColor = color(.popcorn_yellow) 95 | ``` 96 | 97 | ... 98 | 99 | ####Java-like Syntax 100 | 101 | `R.swift` supports the `Java-like syntax` if you used to code `Java`. But still suggest using a `Swift syntax`. 102 | 103 | ```swift 104 | getString(R.string.hello_world) 105 | getString(.hello_world) 106 | 107 | getImage(R.image.icon_play) 108 | getImage(.icon_play) 109 | 110 | getColor(R.color.popcorn_yellow) 111 | getColor(.popcorn_yellow) 112 | ``` 113 | 114 | How to Install 115 | -------------- 116 | 117 | Install it via Alcatraz and search `R.swift` 118 | 119 | or 120 | 121 | 1. `Clone` this repository and `build` it. 122 | 2. `Restart` Xcode. 123 | 124 | or 125 | 126 | 1. Download and unzip a release to `~/Library/Application Support/Developer/Shared/Xcode/Plug-ins`. 127 | 2. `Restart` Xcode. 128 | 129 | After restarting Xcode, open any project and 130 | 131 | 1. The first use 132 | * Click `Edit` -> `R.swift` -> `Enable Auto Sync` 133 | 2. If you want to manually sync 134 | * Click `Edit` -> `R.swift` -> `Sync` 135 | 3. If you don't use `R.swift` in this project anymore 136 | * Click `Edit` -> `R.swift` -> `Clean` 137 | 138 | -------------------------------------------------------------------------------- /screenshots/pic_assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/R.swift-plugin/ad721f7e574e6db2271738232d56f0dec60bf0fb/screenshots/pic_assets.png -------------------------------------------------------------------------------- /screenshots/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/R.swift-plugin/ad721f7e574e6db2271738232d56f0dec60bf0fb/screenshots/sample.png --------------------------------------------------------------------------------