From 1afa643bb13b8a1acf87f481ef9c2e4ad6dc7137 Mon Sep 17 00:00:00 2001 From: Anton Glezman Date: Tue, 26 May 2020 14:50:04 +0300 Subject: [PATCH 1/3] Add export color for iOS without assets --- Package.swift | 4 + README.md | 7 +- Release/figma-export.yaml | 2 + Sources/FigmaExport/Input/Params.swift | 3 +- .../Subcommands/ExportColors.swift | 9 +- .../XcodeExport/Model/XcodeColorsOutput.swift | 4 +- Sources/XcodeExport/XcodeColorExporter.swift | 49 ++++++++-- .../XcodeColorExporterTests.swift | 93 +++++++++++++++++++ 8 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 Tests/XcodeExportTests/XcodeColorExporterTests.swift diff --git a/Package.swift b/Package.swift index 27375264..1e4bfb78 100644 --- a/Package.swift +++ b/Package.swift @@ -53,6 +53,10 @@ let package = Package( .testTarget( name: "figma-exportTests", dependencies: ["FigmaExport"] + ), + .testTarget( + name: "XcodeExportTests", + dependencies: ["XcodeExport"] ) ] ) diff --git a/README.md b/README.md index 0db827f5..7d43c1ad 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ ios: # Parameters for exporting colors colors: + # Should be generate color assets instead of pure swift code + useColorAssets: True # Name of the folder inside Assets.xcassets where to place colors (.colorset directories) assetsFolder: Colors # Absolute path to Color.swift file where to export colors for accessing colors from the code (e.g. UIColor.backgroundPrimary) @@ -165,7 +167,8 @@ android: ### iOS properties * `ios.xcassetsPath` — Folder `Assets.xcassets` where to export colors, icons and images. -* `ios.colors.assetsFolder` — Colors will be exported to this folder inside `Assets.xcassets`. +* `ios.colors.useColorAssets` — Colors will be exported as assets or as swift UIColor initializers only. +* `ios.colors.assetsFolder` — Colors will be exported to this folder inside `Assets.xcassets`. Used only if `useColorAssets == true`. * `ios.colors.colorSwift` — Path to `Color.swift` file. * `ios.colors.nameStyle` — Color name style: camelCase or snake_case * `ios.icons.assetsFolder` — Icons will be exported to this folder inside `Assets.xcassets`. @@ -214,4 +217,4 @@ If you have any issues with the FigmaExport or you want some new features feel f ## Authors -Daniil Subbotin - d.subbotin@redmadrobot.com \ No newline at end of file +Daniil Subbotin - d.subbotin@redmadrobot.com diff --git a/Release/figma-export.yaml b/Release/figma-export.yaml index 5a9221aa..67b896cc 100644 --- a/Release/figma-export.yaml +++ b/Release/figma-export.yaml @@ -26,6 +26,8 @@ ios: # Parameters for exporting colors colors: + # Should be generate color assets instead of pure swift code + useColorAssets: True # Name of the folder inside Assets.xcassets where to place colors (.colorset directories) assetsFolder: Colors # Absolute path to Color.swift file where to export colors for accessing colors from the code (e.g. UIColor.backgroundPrimary) diff --git a/Sources/FigmaExport/Input/Params.swift b/Sources/FigmaExport/Input/Params.swift index 1a5d6465..a4e437fa 100644 --- a/Sources/FigmaExport/Input/Params.swift +++ b/Sources/FigmaExport/Input/Params.swift @@ -36,7 +36,8 @@ struct Params: Decodable { struct iOS: Decodable { struct Colors: Decodable { - let assetsFolder: String + let useColorAssets: Bool + let assetsFolder: String? let colorSwift: URL let nameStyle: NameStyle } diff --git a/Sources/FigmaExport/Subcommands/ExportColors.swift b/Sources/FigmaExport/Subcommands/ExportColors.swift index bb64f302..5675851e 100644 --- a/Sources/FigmaExport/Subcommands/ExportColors.swift +++ b/Sources/FigmaExport/Subcommands/ExportColors.swift @@ -64,7 +64,14 @@ extension FigmaExportCommand { } private func exportXcodeColors(colorPairs: [AssetPair], iosParams: Params.iOS) throws { - let colorsURL = iosParams.xcassetsPath.appendingPathComponent(iosParams.colors.assetsFolder) + var colorsURL: URL? = nil + if iosParams.colors.useColorAssets { + if let folder = iosParams.colors.assetsFolder { + colorsURL = iosParams.xcassetsPath.appendingPathComponent(folder) + } else { + + } + } let output = XcodeColorsOutput( assetsColorsURL: colorsURL, diff --git a/Sources/XcodeExport/Model/XcodeColorsOutput.swift b/Sources/XcodeExport/Model/XcodeColorsOutput.swift index bf058035..f5b37494 100644 --- a/Sources/XcodeExport/Model/XcodeColorsOutput.swift +++ b/Sources/XcodeExport/Model/XcodeColorsOutput.swift @@ -2,10 +2,10 @@ import Foundation public struct XcodeColorsOutput { - public let assetsColorsURL: URL + public let assetsColorsURL: URL? public let colorSwiftURL: URL - public init(assetsColorsURL: URL, colorSwiftURL: URL) { + public init(assetsColorsURL: URL?, colorSwiftURL: URL) { self.assetsColorsURL = assetsColorsURL self.colorSwiftURL = colorSwiftURL } diff --git a/Sources/XcodeExport/XcodeColorExporter.swift b/Sources/XcodeExport/XcodeColorExporter.swift index 56dff05d..6f70ecf8 100644 --- a/Sources/XcodeExport/XcodeColorExporter.swift +++ b/Sources/XcodeExport/XcodeColorExporter.swift @@ -13,7 +13,7 @@ final public class XcodeColorExporter { var files: [FileContents] = [] // Sources/.../Color.swift - let contents = prepareColorDotSwiftContents(colorPairs) + let contents = prepareColorDotSwiftContents(colorPairs, formAsset: output.assetsColorsURL != nil) let contentsData = contents.data(using: .utf8)! let fileURL = URL(string: output.colorSwiftURL.lastPathComponent)! @@ -26,23 +26,25 @@ final public class XcodeColorExporter { ) ) + guard let assetsColorsURL = output.assetsColorsURL else { return files } + // Assets.xcassets/Colors/Contents.json let contentsJson = XcodeEmptyContents() files.append(FileContents( - destination: Destination(directory: output.assetsColorsURL, file: contentsJson.fileURL), + destination: Destination(directory: assetsColorsURL, file: contentsJson.fileURL), data: contentsJson.data )) // Assets.xcassets/Colors/***.colorset/Contents.json colorPairs.forEach { colorPair in let name = colorPair.light.name - let dirURL = output.assetsColorsURL.appendingPathComponent("\(name).colorset") + let dirURL = assetsColorsURL.appendingPathComponent("\(name).colorset") var colors: [XcodeAssetContents.ColorData] = [ XcodeAssetContents.ColorData( appearances: nil, color: XcodeAssetContents.ColorInfo( - components: colorPair.light.toComponents()) + components: colorPair.light.toHexComponents()) ) ] if let darkColor = colorPair.dark { @@ -50,7 +52,7 @@ final public class XcodeColorExporter { XcodeAssetContents.ColorData( appearances: [XcodeAssetContents.DarkAppeareance()], color: XcodeAssetContents.ColorInfo( - components: darkColor.toComponents()) + components: darkColor.toHexComponents()) ) ) } @@ -69,7 +71,7 @@ final public class XcodeColorExporter { return files } - private func prepareColorDotSwiftContents(_ colorPairs: [AssetPair]) -> String { + private func prepareColorDotSwiftContents(_ colorPairs: [AssetPair], formAsset: Bool) -> String { var contents = """ import UIKit @@ -78,7 +80,30 @@ final public class XcodeColorExporter { """ colorPairs.forEach { colorPair in - contents.append(" static var \(colorPair.light.name): UIColor { return UIColor(named: #function)! }\n") + if formAsset { + contents.append(" static var \(colorPair.light.name): UIColor { return UIColor(named: #function)! }\n") + } else { + let lightComponents = colorPair.light.toRgbComponents() + if let darkComponents = colorPair.dark?.toRgbComponents() { + contents.append(""" + static var \(colorPair.light.name): UIColor { + UIColor { traitCollection -> UIColor in + if traitCollection.userInterfaceStyle == .dark { + return UIColor(red: \(darkComponents.red), green: \(darkComponents.green), blue: \(darkComponents.blue), alpha: \(darkComponents.alpha)) + } else { + return UIColor(red: \(lightComponents.red), green: \(lightComponents.green), blue: \(lightComponents.blue), alpha: \(lightComponents.alpha)) + } + } + }\n + """) + } else { + contents.append(""" + static var \(colorPair.light.name): UIColor { + return UIColor(red: \(lightComponents.red), green: \(lightComponents.green), blue: \(lightComponents.blue), alpha: \(lightComponents.alpha)) + }\n + """) + } + } } contents.append("\n}\n") @@ -87,7 +112,7 @@ final public class XcodeColorExporter { } private extension Color { - func toComponents() -> XcodeAssetContents.Components { + func toHexComponents() -> XcodeAssetContents.Components { let red = "0x\(doubleToHex(self.red))" let green = "0x\(doubleToHex(self.green))" let blue = "0x\(doubleToHex(self.blue))" @@ -98,4 +123,12 @@ private extension Color { func doubleToHex(_ double: Double) -> String { String(format: "%02X", arguments: [Int((double * 255).rounded())]) } + + func toRgbComponents() -> XcodeAssetContents.Components { + let red = String(format: "%.3F", arguments: [self.red]) + let green = String(format: "%.3F", arguments: [self.green]) + let blue = String(format: "%.3F", arguments: [self.blue]) + let alpha = String(format: "%.3F", arguments: [self.alpha]) + return XcodeAssetContents.Components(red: red, alpha: alpha, green: green, blue: blue) + } } diff --git a/Tests/XcodeExportTests/XcodeColorExporterTests.swift b/Tests/XcodeExportTests/XcodeColorExporterTests.swift new file mode 100644 index 00000000..fa7e2128 --- /dev/null +++ b/Tests/XcodeExportTests/XcodeColorExporterTests.swift @@ -0,0 +1,93 @@ +import XCTest +import FigmaExportCore +@testable import XcodeExport + +final class XcodeColorExporterTests: XCTestCase { + + // MARK: - Properties + + private let fileManager = FileManager.default + private var colorsFile: URL! + private var colorsAsssetCatalog: URL! + + private let colorPair1 = AssetPair( + light: Color(name: "colorPair1", r: 1, g: 1, b: 1, a: 1), + dark: Color(name: "colorPair1", r: 0, g: 0, b: 0, a: 1)) + + private let colorPair2 = AssetPair( + light: Color(name: "colorPair2", r: 119.0/255.0, g: 3.0/255.0, b: 1.0, a: 0.5), + dark: nil) + + // MARK: - Setup + + override func setUp() { + super.setUp() + colorsFile = fileManager.temporaryDirectory.appendingPathComponent("Colors.swift") + colorsAsssetCatalog = fileManager.temporaryDirectory.appendingPathComponent("Assets.xcassets/Colors") + } + + // MARK: - Tests + + func testExport_without_assets() { + let output = XcodeColorsOutput(assetsColorsURL: nil, colorSwiftURL: colorsFile) + let exporter = XcodeColorExporter(output: output) + + let result = exporter.export(colorPairs: [colorPair1, colorPair2]) + XCTAssertEqual(result.count, 1) + + let content = result[0].data + XCTAssertNotNil(content) + + let generatedCode = String(data: content!, encoding: .utf8) + let referenceCode = """ + import UIKit + + extension UIColor { + static var colorPair1: UIColor { + UIColor { traitCollection -> UIColor in + if traitCollection.userInterfaceStyle == .dark { + return UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000) + } else { + return UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000) + } + } + } + static var colorPair2: UIColor { + return UIColor(red: 0.467, green: 0.012, blue: 1.000, alpha: 0.500) + } + + } + + """ + XCTAssertEqual(generatedCode, referenceCode) + } + + func testExport_with_assets() { + let output = XcodeColorsOutput(assetsColorsURL: colorsAsssetCatalog, colorSwiftURL: colorsFile) + let exporter = XcodeColorExporter(output: output) + let result = exporter.export(colorPairs: [colorPair1, colorPair2]) + + XCTAssertEqual(result.count, 4) + XCTAssertTrue(result[0].destination.url.absoluteString.hasSuffix("Colors.swift")) + XCTAssertTrue(result[1].destination.url.absoluteString.hasSuffix("Assets.xcassets/Colors/Contents.json")) + XCTAssertTrue(result[2].destination.url.absoluteString.hasSuffix("colorPair1.colorset/Contents.json")) + XCTAssertTrue(result[3].destination.url.absoluteString.hasSuffix("colorPair2.colorset/Contents.json")) + + let content = result[0].data + XCTAssertNotNil(content) + + let generatedCode = String(data: content!, encoding: .utf8) + let referenceCode = """ + import UIKit + + extension UIColor { + static var colorPair1: UIColor { return UIColor(named: #function)! } + static var colorPair2: UIColor { return UIColor(named: #function)! } + + } + + """ + XCTAssertEqual(generatedCode, referenceCode) + } + +} From fd67966da4922429fc44ababf8ff94d2df179bf6 Mon Sep 17 00:00:00 2001 From: Anton Glezman Date: Tue, 26 May 2020 15:10:49 +0300 Subject: [PATCH 2/3] Add error --- Sources/FigmaExport/Subcommands/ExportColors.swift | 2 +- Sources/FigmaExport/main.swift | 3 +++ Tests/figma-exportTests/figma_exportTests.swift | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/FigmaExport/Subcommands/ExportColors.swift b/Sources/FigmaExport/Subcommands/ExportColors.swift index 0326a19a..90aafd38 100644 --- a/Sources/FigmaExport/Subcommands/ExportColors.swift +++ b/Sources/FigmaExport/Subcommands/ExportColors.swift @@ -72,7 +72,7 @@ extension FigmaExportCommand { if let folder = iosParams.colors.assetsFolder { colorsURL = iosParams.xcassetsPath.appendingPathComponent(folder) } else { - + throw FigmaExportError.colorsAssetsFolderNotSpecified } } diff --git a/Sources/FigmaExport/main.swift b/Sources/FigmaExport/main.swift index 9bd2c2a1..b9235648 100644 --- a/Sources/FigmaExport/main.swift +++ b/Sources/FigmaExport/main.swift @@ -4,11 +4,14 @@ import Foundation enum FigmaExportError: LocalizedError { case accessTokenNotFound + case colorsAssetsFolderNotSpecified var errorDescription: String? { switch self { case .accessTokenNotFound: return "Environment varibale FIGMA_PERSONAL_TOKEN not specified." + case .colorsAssetsFolderNotSpecified: + return "Option ios.colors.assetsFolder not specified in configuration file." } } } diff --git a/Tests/figma-exportTests/figma_exportTests.swift b/Tests/figma-exportTests/figma_exportTests.swift index 52062e74..7ca4e30b 100644 --- a/Tests/figma-exportTests/figma_exportTests.swift +++ b/Tests/figma-exportTests/figma_exportTests.swift @@ -26,7 +26,7 @@ final class figma_exportTests: XCTestCase { let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8) - XCTAssertEqual(output, "Hello, world!\n") + XCTAssertEqual(output, "") } /// Returns path to the built products directory. From f7a91ded07e5541b2213b05fb1949147eace8cf0 Mon Sep 17 00:00:00 2001 From: Anton Glezman Date: Thu, 28 May 2020 10:20:37 +0300 Subject: [PATCH 3/3] Update readme --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index f5f135f0..6caeb872 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,26 @@ Additionally the `Color.swift` file will be created to use colors from the code. ``` +If you set option `useColorAssets: False` in the configuration file, then will be generated code like this: +```swift +import UIKit + +extension UIColor { + static var primaryText: UIColor { + UIColor { traitCollection -> UIColor in + if traitCollection.userInterfaceStyle == .dark { + return UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000) + } else { + return UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000) + } + } + } + static var backgroundVideo: UIColor { + return UIColor(red: 0.467, green: 0.012, blue: 1.000, alpha: 0.500) + } +} +``` + #### Icons Icons will be exported as PDF files with `Template Image` render mode.