diff --git a/.gitignore b/.gitignore index 47042c22..1e6f41e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store xcuserdata +xcshareddata \ No newline at end of file diff --git a/License b/License new file mode 100644 index 00000000..9fd42567 --- /dev/null +++ b/License @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Mathijs Kadijk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 00000000..8bd1ff04 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# TypedImages +_Tool to help you use strong typed images in Swift_ + +## Why use this? + +Normally you access your images from the `Images.xcassets` folder with `UIImage(names: "Something")` this sucks because the compiler won't warn you about unexisting images, which means you will get errors at runtime! + +With TypedImages we make sure you can do `Images.Something` to get your image, the `Images` class will be automatically update on build. So it's never outdated and you will get compiler errors if you rename or delete an image. + +## Usage + +After installing TypedImages into your project you can use the name of the `xcassets` folder followed by the name of the image as you named it in the assets folder. If the file is outdated just press build and TypedImages will correct the missing/changed images for you. + +## Installation + +1. [Download](https://github.com/mac-cain13/TypedImages/releases) a TypedImage release, unzip it and put it into your source root directory +2. In XCode: Click on your project in the file list, choose your target under `TARGETS`, click the `Build Phases` tab and add a `New Run Script Phase` by clicking the little plus icon in the top left +3. Drag the new `Run Script` phase *above* the `Compile Sources` phase, expand it and paste the following script: `"$SRCROOT/TypedImages" "$SRCROOT"` +4. Build your project, in Finder you will now see `*.generated.swift` files along side the `*.xcassets` folders, drag the `*.generated.swift` file into your project + +_Optional:_ Add the `*.generated.swift` to your `.gitignore` file to prevent unnecessary conflicts. + +## Contribute + +Please post any issues, ideas and compliments in the GitHub issue tracker and feel free to submit pull request with fixes and improvements. Keep in mind; a good pull request is small, well explained and should benifit most of the users. + +## License + +TypedImage is released under MIT License and created by Mathijs Kadijk. diff --git a/TypedImages.xcodeproj/project.pbxproj b/TypedImages.xcodeproj/project.pbxproj new file mode 100644 index 00000000..4a1d1052 --- /dev/null +++ b/TypedImages.xcodeproj/project.pbxproj @@ -0,0 +1,241 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + D5EF81E81A3A174A00A38927 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EF81E71A3A174A00A38927 /* main.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + D5EF81E21A3A174A00A38927 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + D5EF81E41A3A174A00A38927 /* TypedImages */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TypedImages; sourceTree = BUILT_PRODUCTS_DIR; }; + D5EF81E71A3A174A00A38927 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D5EF81E11A3A174A00A38927 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D5EF81DB1A3A174A00A38927 = { + isa = PBXGroup; + children = ( + D5EF81E61A3A174A00A38927 /* TypedImages */, + D5EF81E51A3A174A00A38927 /* Products */, + ); + sourceTree = ""; + }; + D5EF81E51A3A174A00A38927 /* Products */ = { + isa = PBXGroup; + children = ( + D5EF81E41A3A174A00A38927 /* TypedImages */, + ); + name = Products; + sourceTree = ""; + }; + D5EF81E61A3A174A00A38927 /* TypedImages */ = { + isa = PBXGroup; + children = ( + D5EF81E71A3A174A00A38927 /* main.swift */, + ); + path = TypedImages; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D5EF81E31A3A174A00A38927 /* TypedImages */ = { + isa = PBXNativeTarget; + buildConfigurationList = D5EF81EB1A3A174A00A38927 /* Build configuration list for PBXNativeTarget "TypedImages" */; + buildPhases = ( + D5EF81E01A3A174A00A38927 /* Sources */, + D5EF81E11A3A174A00A38927 /* Frameworks */, + D5EF81E21A3A174A00A38927 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TypedImages; + productName = TypedImages; + productReference = D5EF81E41A3A174A00A38927 /* TypedImages */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D5EF81DC1A3A174A00A38927 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = "Mathijs Kadijk"; + TargetAttributes = { + D5EF81E31A3A174A00A38927 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = D5EF81DF1A3A174A00A38927 /* Build configuration list for PBXProject "TypedImages" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = D5EF81DB1A3A174A00A38927; + productRefGroup = D5EF81E51A3A174A00A38927 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D5EF81E31A3A174A00A38927 /* TypedImages */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + D5EF81E01A3A174A00A38927 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D5EF81E81A3A174A00A38927 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + D5EF81E91A3A174A00A38927 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + D5EF81EA1A3A174A00A38927 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + D5EF81EC1A3A174A00A38927 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + D5EF81ED1A3A174A00A38927 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D5EF81DF1A3A174A00A38927 /* Build configuration list for PBXProject "TypedImages" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D5EF81E91A3A174A00A38927 /* Debug */, + D5EF81EA1A3A174A00A38927 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D5EF81EB1A3A174A00A38927 /* Build configuration list for PBXNativeTarget "TypedImages" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D5EF81EC1A3A174A00A38927 /* Debug */, + D5EF81ED1A3A174A00A38927 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D5EF81DC1A3A174A00A38927 /* Project object */; +} diff --git a/TypedImages.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/TypedImages.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..3487d540 --- /dev/null +++ b/TypedImages.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/TypedImages/main.swift b/TypedImages/main.swift new file mode 100644 index 00000000..dc2c0c82 --- /dev/null +++ b/TypedImages/main.swift @@ -0,0 +1,112 @@ +// +// main.swift +// TypedImages +// +// Created by Mathijs Kadijk on 11-12-14. +// From: http://github.com/mac-cain13/TypedImages +// License: MIT License +// + +import Foundation + +// MARK: Helper functions + +func projectDirectoryURL(fileManager: NSFileManager, processInfo: NSProcessInfo) -> NSURL! { + let processInfoArguments = processInfo.arguments + if processInfoArguments.count >= 2 { + return NSURL(fileURLWithPath: processInfoArguments[1] as String) + } + + return NSURL(fileURLWithPath: fileManager.currentDirectoryPath) +} + +func findAssetsFoldersRecursively(fileManager: NSFileManager, urlToScan: NSURL) -> [NSURL] { + let errorHandler: (NSURL!, NSError!) -> Bool = { (url, error) in + println("WARN: \(url) could not be enumerated due error: \(error)") + return true + } + + var assetFolders = [NSURL]() + + if let enumerator = fileManager.enumeratorAtURL(urlToScan, includingPropertiesForKeys: [NSURLIsDirectoryKey], options: NSDirectoryEnumerationOptions.SkipsHiddenFiles|NSDirectoryEnumerationOptions.SkipsPackageDescendants, errorHandler: errorHandler) { + + while let enumeratorItem: AnyObject = enumerator.nextObject() { + if let url = enumeratorItem as? NSURL { + var isDirectoryValue: AnyObject? + url.getResourceValue(&isDirectoryValue, forKey: NSURLIsDirectoryKey, error: nil) + + if let isDirectory = isDirectoryValue as? Bool { + if isDirectory && url.absoluteString!.pathExtension == "xcassets" { + assetFolders.append(url) + enumerator.skipDescendants() + } + } + } + } + } + + return assetFolders +} + +struct AssetFolderInfo { + let codeFileURL: NSURL + let name: String + let imageInfo: [(varName: String, imageName: String)] + + init(url: NSURL, fileManager: NSFileManager) { + name = sanatizeForSwiftName(url.lastPathComponent!.stringByDeletingPathExtension.capitalizedString) + self.codeFileURL = url.URLByDeletingLastPathComponent!.URLByAppendingPathComponent(name + ".generated", isDirectory: false).URLByAppendingPathExtension("swift") + + let contents = fileManager.contentsOfDirectoryAtURL(url, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.SkipsHiddenFiles, error: nil) as [NSURL] + imageInfo = distinct(contents.map { $0.lastPathComponent!.stringByDeletingPathExtension }) + .map { (varName: sanatizeForSwiftName($0), imageName: $0) } + + if imageInfo.count != contents.count { + println("WARN: Asset folder \(url) has \(contents.count - imageInfo.count) duplicate images.") + } + } +} + +func sanatizeForSwiftName(name: String) -> String { + var components = name.componentsSeparatedByString("-") + let firstComponent = components.removeAtIndex(0) + return components.reduce(firstComponent) { $0 + $1.capitalizedString } +} + +func generateTypedAssetCode(assetFolderInfo: AssetFolderInfo) -> String { + return "//\n" + + "// \(assetFolderInfo.name).generated.swift\n" + + "// GENERATED FILE, DO NOT EDIT.\n" + + "//\n" + + "// Generated by TypedImages. See: http://github.com/mac-cain13/TypedImages\n" + + "//\n" + + "\n" + + "class \(assetFolderInfo.name) {\n" + + assetFolderInfo.imageInfo.reduce("") { $0 + " class var \($1.varName): UIImage! { return UIImage(named: \"\($1.imageName)\") }\n" } + + "}\n" +} + +func writeCode(code: String, # toURL: NSURL) { + println("Writing \(toURL)...") + code.writeToURL(toURL, atomically: true, encoding: NSUTF8StringEncoding, error: nil) +} + +func distinct(source: [T]) -> [T] { + var unique = [T]() + for item in source { + if !contains(unique, item) { + unique.append(item) + } + } + return unique +} + +// MARK: Procedure + +let defaultFileManager = NSFileManager.defaultManager() +let directoryURL = projectDirectoryURL(defaultFileManager, NSProcessInfo.processInfo()) + +findAssetsFoldersRecursively(defaultFileManager, directoryURL) + .map { AssetFolderInfo(url: $0, fileManager: defaultFileManager) } + .map { (url: $0.codeFileURL, code: generateTypedAssetCode($0)) } + .map { writeCode($0.code, toURL: $0.url) }