Color ouput with Swift command line tool - swift

I'm writing a command line tool with Swift and I'm having trouble displaying colors in my shell. I'm using the following code:
println("\033[31;32mhey\033[39;39m")
or even
NSFileHandle.fileHandleWithStandardOutput().writeData("\033[31;32mhey\033[39;39m".dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: true)!)
It works when I use a simple echo in php (the text is displayed in green) but is there a reason it doesn't work in a Swift command line tool?
Thanks!

Swift has built in unicode support. This invalidates using of back slash. So that I use color codes with "\u{}" syntax. Here is a println code which works perfectly on terminal.
// \u{001B}[\(attribute code like bold, dim, normal);\(color code)m
// Color codes
// black 30
// red 31
// green 32
// yellow 33
// blue 34
// magenta 35
// cyan 36
// white 37
println("\u{001B}[0;33myellow")
Hope it helps.

Based on #cyt answer, I've written a simple enum with these colors and also overloaded + operator so you can print using that enum.
It's all up on Github, but it's really that simple:
enum ANSIColors: String {
case black = "\u{001B}[0;30m"
case red = "\u{001B}[0;31m"
case green = "\u{001B}[0;32m"
case yellow = "\u{001B}[0;33m"
case blue = "\u{001B}[0;34m"
case magenta = "\u{001B}[0;35m"
case cyan = "\u{001B}[0;36m"
case white = "\u{001B}[0;37m"
case `default` = "\u{001B}[0;0m"
func name() -> String {
switch self {
case .black: return "Black"
case .red: return "Red"
case .green: return "Green"
case .yellow: return "Yellow"
case .blue: return "Blue"
case .magenta: return "Magenta"
case .cyan: return "Cyan"
case .white: return "White"
case .default: return "Default"
}
}
static func all() -> [ANSIColors] {
return [.black, .red, .green, .yellow, .blue, .magenta, .cyan, .white]
}
}
func + (left: ANSIColors, right: String) -> String {
return left.rawValue + right
}
// END
// Demo:
for c in ANSIColors.all() {
print(c + "This is printed in " + c.name())
}

You can use Rainbow if you don't mind using it as a framework.
import Rainbow
print("Red text".red)
print("Yellow background".onYellow)
print("Light green text on white background".lightGreen.onWhite)
https://github.com/onevcat/Rainbow

Combining some of #Diego's answer, you can use Swift's new DefaultStringInterpolation structure to extend this decoration into your string literalsโ€“
enum ASCIIColor: String {
case black = "\u{001B}[0;30m"
case red = "\u{001B}[0;31m"
case green = "\u{001B}[0;32m"
case yellow = "\u{001B}[0;33m"
case blue = "\u{001B}[0;34m"
case magenta = "\u{001B}[0;35m"
case cyan = "\u{001B}[0;36m"
case white = "\u{001B}[0;37m"
case `default` = "\u{001B}[0;0m"
}
extension DefaultStringInterpolation {
mutating func appendInterpolation<T: CustomStringConvertible>(_ value: T, color: ASCIIColor) {
appendInterpolation("\(color.rawValue)\(value)\(ASCIIColor.default.rawValue)")
}
}
// USAGE:
// "\("only this string will be green!", color: .green)"

Expanding upon Diego Freniche's answer we can incorporate the functionality of Rainbow, as referenced in Uncharted Works's Answer, without needing to import the framework itself using a simple String extension:
enum ANSIColor: String {
typealias This = ANSIColor
case black = "\u{001B}[0;30m"
case red = "\u{001B}[0;31m"
case green = "\u{001B}[0;32m"
case yellow = "\u{001B}[0;33m"
case blue = "\u{001B}[0;34m"
case magenta = "\u{001B}[0;35m"
case cyan = "\u{001B}[0;36m"
case white = "\u{001B}[0;37m"
case `default` = "\u{001B}[0;0m"
static var values: [This] {
return [.black, .red, .green, .yellow, .blue, .magenta, .cyan, .white, .default]
}
static var names: [This: String] = {
return [
.black: "black",
.red: "red",
.green: "green",
.yellow: "yellow",
.blue: "blue",
.magenta: "magenta",
.cyan: "cyan",
.white: "white",
.default: "default",
]
}
var name: String {
return This.names[self] ?? "unknown"
}
static func + (lhs: This, rhs: String) -> String {
return lhs.rawValue + rhs
}
static func + (lhs: String, rhs: This) -> String {
return lhs + rhs.rawValue
}
}
extension String {
func colored(_ color: ANSIColor) -> String {
return color + self + ANSIColor.default
}
var black: String {
return colored(.black)
}
var red: String {
return colored(.red)
}
var green: String {
return colored(.green)
}
var yellow: String {
return colored(.yellow)
}
var blue: String {
return colored(.blue)
}
var magenta: String {
return colored(.magenta)
}
var cyan: String {
return colored(.cyan)
}
var white: String {
return colored(.white)
}
}

Elegant Solution:
struct Colors {
static let reset = "\u{001B}[0;0m"
static let black = "\u{001B}[0;30m"
static let red = "\u{001B}[0;31m"
static let green = "\u{001B}[0;32m"
static let yellow = "\u{001B}[0;33m"
static let blue = "\u{001B}[0;34m"
static let magenta = "\u{001B}[0;35m"
static let cyan = "\u{001B}[0;36m"
static let white = "\u{001B}[0;37m"
}
Demo
print(Colors.yellow + "Please Enter the Output Directory Name:" + Colors.reset)
or
print(Colors.yellow + "Please " + Colors.blue + "Enter " + Colors.magenta + "the Output Directory Name:" + Colors.reset)

Related

How do I replace a placeholder text with an Image in SwiftUI?

I have a string "Hello {world}" which I need to replace with "Hello ๐ŸŒ". The placeholder's position is not fixed at the end. And I may have more than a single placeholder.
I am using SwiftUI and tried to make this work with
Text("Hello {world}".replacingOccurrences(of: "{world}", with: "\(Image(systemName: "globe"))"))
but soon found that this doesn't work and presented with this Hello Image(provider: SwiftUI.ImageProviderBox<SwiftUI.Image.(unknown context at $1ba606db0).NamedImageProvider>)
Since this worked
Text(LocalizedStringKey("Hello \(Image(systemName: "globe"))"))
I assumed I needed to pass a LocalizedStringKey into the Text I tried again with
Text(LocalizedStringKey("Hello {world}".replacingOccurrences(of: "{world}", with: "\(Image(systemName: "globe"))")))
Text(LocalizedStringKey("Hello" + "\(Image(systemName: "globe"))")) //this doesn't work either
but presented with a similar issue SwiftUI.Text.Storage.anyTextStorage(SwiftUI.(unknown context at $1ba668448).LocalizedTextStorage
I looked at the API for LocalizedStringKey and LocalizedStringKey.StringInterpolation but could not find a solution this problem. Is there a way to make replacement of placeholder string work?
After looking at #bewithyou's answer, I got the idea that I need to split this into multiple substrings and recombine the texts individually. This is the best solution I could come up with:
public extension String {
func componentsKeepingSeparator(separatedBy separator: Self) -> Array<String> {
self.components(separatedBy: separator)
.flatMap { [$0, separator] }
.dropLast()
.filter { $0 != "" }
}
}
And on playground, if I were to run this, it works perfectly.
PlaygroundPage.current.setLiveView(
"Hello {world}!"
.componentsKeepingSeparator(separatedBy: "{world}")
.reduce(Text("")) { text, str in
if str == "{world}" { return text + Text("\(Image(systemName: "globe"))") }
return text + Text(str)
}
)
I'm sure there is a more optimal solution, but this will do for now.
EDIT:
Since I needed support for multiple placeholders, I've added some more extensions that does the job more comprehensively.
func componentsKeepingSeparators(separatedBy separators: [Self]) -> [String] {
var finalResult = [self]
separators.forEach { separator in
finalResult = finalResult.flatMap { strElement in
strElement.componentsKeepingSeparator(separatedBy: separator)
}
}
return finalResult
}
and on the playground
PlaygroundPage.current.setLiveView(
"Hello {world}{world}{world}! {wave}"
.componentsKeepingSeparators(separatedBy: ["{world}", "{wave}"])
.reduce(Text("")) { text, str in
if str == "{world}" { return text + Text("\(Image(systemName: "globe"))") }
if str == "{wave}" { return text + Text("\(Image(systemName: "hand.wave"))") }
return text + Text(str)
}
)
This extension has a double loop and might not be very efficient, so again, if someone can think of a better solution, please do post.
I came to this question via answering this one and it piqued my interest. As I say in my answer there, the secret sauce is that LocalizedStringKey, when initialised with an interpolated string literal, is capable of building in references to SwiftUI Image types which can be rendered in Text.
Because you're not using an interpolated string literal, you can either build things up by multiple Texts, as in the other answers here, or do something smart with LocalizedStringKey.StringInterpolation. The advantage of this approach is that you can also use the image-holding text in any other view that uses LocalizedStringKey (which is, well, pretty much any of them that display text).
This extension on LocalizedStringKey will manually build an interpolated string:
extension LocalizedStringKey {
private static let imageMap: [String: String] = [
"world": "globe",
"moon": "moon"
]
init(imageText: String) {
var components = [Any]()
var length = 0
let scanner = Scanner(string: imageText)
scanner.charactersToBeSkipped = nil
while scanner.isAtEnd == false {
let up = scanner.scanUpToString("{")
let start = scanner.scanString("{")
let name = scanner.scanUpToString("}")
let end = scanner.scanString("}")
if let up = up {
components.append(up)
length += up.count
}
if let name = name {
if start != nil, end != nil, let imageName = Self.imageMap[name] {
components.append(Image(systemName: imageName))
length += 1
} else {
components.append(name)
}
}
}
var interp = LocalizedStringKey.StringInterpolation(literalCapacity: length, interpolationCount: components.count)
for component in components {
if let string = component as? String {
interp.appendInterpolation(string)
}
if let image = component as? Image {
interp.appendInterpolation(image)
}
}
self.init(stringInterpolation: interp)
}
}
You may want to cache these values if they are coming from an API, I haven't checked the performance of this code in a rendering loop.
You add an extension on Text, or any other view:
extension Text {
init(imageText: String) {
self.init(LocalizedStringKey(imageText: imageText))
}
}
So you can do this:
Text(imageText: "Hello {world}! or {moon} or {unmapped}")
Which gives you:
For your question the key here is not LocalizedStringKey but the key here is \() methods means string interpolation.
According to Swift document, string interpolation is a way to construct a new String value from a mix of constants, variables, literals, and expressions by including their values inside a string literal. You can use string interpolation in both single-line and multiline string literals.
In here it combines two things which is Text("hello") and Image(systemName: "globe") into a new String. Your code is wrong because of you append the string of value.
Without LocalizedStringKey, Text will appear as same as your Hello ๐ŸŒ!.
Text("Hello \(Image(systemName: "globe"))!")
Or you can use as combination for easier understanding
Text("hello") + Text(Image(systemName: "globe")) + Text("!")
And for you question about mapping value you can make a dictionary for mapping image or name image do that
var dict : [String:String] = ["world" : "globe"]
// Add default name image value if key is nil
Text("Hello \(Image(systemName: dict["world", default:"globe"]))!")
Text("hello") + Text(Image(systemName: dict["world", default: "globe"])) + Text("!")
var dict : [String:Image] = ["world" : Image(systemName: "globe")]
// Add default image value if key is nil
Text("hello\(dict["world", default: Image(systemName: "globe")])!")
Text("hello") + Text(dict["world", default: Image(systemName: "globe")]) + Text("!")
All of them works the same an print out Hello ๐ŸŒ!
Using #Aswath's answer, here's a custom container:
struct CText: View {
var text: String
var placeholders: [String: String]
var imagePlaceholders: [String: Image]
public init(_ text: String) {
self.text = text
self.placeholders = [:]
self.imagePlaceholders = [:]
}
private init(_ text: String, placeholders: [String: String], imagePlaceholders: [String: Image]) {
self.text = text
self.placeholders = placeholders
self.imagePlaceholders = imagePlaceholders
}
private var array: [String] {
let strings = Array(placeholders.keys)
let images = Array(imagePlaceholders.keys)
return strings + images
}
var body: Text {
text
.componentsKeepingSeparators(separatedBy: array)
.reduce(Text("")) { text, str in
if let place = placeholders[str] {
return text + Text(place)
}else if let place = imagePlaceholders[str] {
return text + Text("\(place)")
} else {
return text + Text(str)
}
}
}
func replacing(_ holder: String, with replacement: String) -> CText {
var oldPlaceholders = placeholders
oldPlaceholders[holder] = replacement
return CText(text, placeholders: placeholders, imagePlaceholders: imagePlaceholders)
}
func replacing(_ holder: String, with replacement: Image) -> CText {
var oldPlaceholders = imagePlaceholders
oldPlaceholders[holder] = replacement
return CText(text, placeholders: placeholders, imagePlaceholders: oldPlaceholders)
}
}
Usage:
struct Test: View {
var body: some View {
CText("Hello {world}")
.replacing("{world}", with: Image(systemName: "globe"))
}
}
Edit: If you need to access Text instead of View, add .body at the end:
struct Test: View {
var body: some View {
CText("Hello {world}")
.replacing("{world}", with: Image(systemName: "globe"))
.body
}
}
This is a small improvement to #jrturton's answer tweaked to my needs. Perhaps this might benefit others. However, this is very different to my original answer, and so it made sense to me to add this as a new answer. The deletingPrefix is from hackingwithswift
import PlaygroundSupport
import Foundation
import SwiftUI
extension String {
func deletingPrefix(_ prefix: String) -> String {
guard self.hasPrefix(prefix) else { return self }
return String(self.dropFirst(prefix.count))
}
}
extension LocalizedStringKey {
#available(iOS 15, *)
init(imageText: String, replacementClosure: (String) -> Any) {
var components = [Any]()
var length = 0
let scanner = Scanner(string: imageText)
scanner.charactersToBeSkipped = nil
while scanner.isAtEnd == false {
let up = scanner.scanUpToString("{")
let start = scanner.scanString("{")
let name = scanner.scanUpToString("}")
let end = scanner.scanString("}")
if let up = up {
components.append(up)
length += up.count
}
if let name = name {
if start == nil || end == nil { self.init(stringLiteral: imageText) }
let replacement = replacementClosure(name)
switch replacement {
case let image as Image:
components.append(image)
case let attributedString as AttributedString:
components.append(attributedString)
case let plainString as String:
components.append(plainString)
default:
print("No action.")
}
}
}
var interp = LocalizedStringKey.StringInterpolation(literalCapacity: length, interpolationCount: components.count)
for component in components {
if let string = component as? String {
interp.appendInterpolation(string)
}
if let attrString = component as? AttributedString {
interp.appendInterpolation(attrString)
}
if let image = component as? Image {
interp.appendInterpolation(image)
}
}
self.init(stringInterpolation: interp)
}
}
extension Text {
init(imageText: String) {
self.init(LocalizedStringKey(imageText: imageText, replacementClosure: { string in
switch string {
case "world":
return Image(systemName: "globe")
case "moon":
return Image(systemName: "moon")
case let stylisedString where stylisedString.hasPrefix("style1__"):
return AttributedString(stylisedString.deletingPrefix("style1__"), attributes: AttributeContainer().foregroundColor(.blue))
default: return string
}
}))
}
}
PlaygroundPage.current.setLiveView(Text(imageText: "Hello {world}! or {moon} or {style1__unmapped}")
)

Highlight a specific part of the text in SwiftUI

Hello I'm new to Swift and am using SwiftUI for my project where I download some weather data and I display it in the ContentView().
I would like to highlight some part of the Text if it contains some specific word, but I don't have any idea how to start.
In ContentView(), I have tried to set a function receiving the string downloaded from web and return a string. I believe this is wrong, because SwiftUI does not apply the modifiers at the all for the Text.
For example, in my ContentView() I would like the word thunderstorm to have the .bold() modifier:
struct ContentView: View {
let testo : String = "There is a thunderstorm in the area"
var body: some View {
Text(highlight(str: testo))
}
func highlight(str: String) -> String {
let textToSearch = "thunderstorm"
var result = ""
if str.contains(textToSearch) {
let index = str.startIndex
result = String( str[index])
}
return result
}
}
If that requires just simple word styling then here is possible solution.
Tested with Xcode 11.4 / iOS 13.4
struct ContentView: View {
let testo : String = "There is a thunderstorm in the area. Added some testing long text to demo that wrapping works correctly!"
var body: some View {
hilightedText(str: testo, searched: "thunderstorm")
.multilineTextAlignment(.leading)
}
func hilightedText(str: String, searched: String) -> Text {
guard !str.isEmpty && !searched.isEmpty else { return Text(str) }
var result: Text!
let parts = str.components(separatedBy: searched)
for i in parts.indices {
result = (result == nil ? Text(parts[i]) : result + Text(parts[i]))
if i != parts.count - 1 {
result = result + Text(searched).bold()
}
}
return result ?? Text(str)
}
}
Note: below is previously used function, but as commented by #Lkabo it has limitations on very long strings
func hilightedText(str: String) -> Text {
let textToSearch = "thunderstorm"
var result: Text!
for word in str.split(separator: " ") {
var text = Text(word)
if word == textToSearch {
text = text.bold()
}
result = (result == nil ? text : result + Text(" ") + text)
}
return result ?? Text(str)
}
iOS 13, Swift 5. There is a generic solution described in this medium article. Using it you can highlight any text anywhere with the only catch being it cannot be more then 64 characters in length, since it using bitwise masks.
https://medium.com/#marklucking/an-interesting-challenge-with-swiftui-9ebb26e77376
This is the basic code in the article.
ForEach((0 ..< letter.count), id: \.self) { column in
Text(letter[column])
.foregroundColor(colorCode(gate: Int(self.gate), no: column) ? Color.black: Color.red)
.font(Fonts.futuraCondensedMedium(size: fontSize))
}
And this one to mask the text...
func colorCode(gate:Int, no:Int) -> Bool {
let bgr = String(gate, radix:2).pad(with: "0", toLength: 16)
let bcr = String(no, radix:2).pad(with: "0", toLength: 16)
let binaryColumn = 1 << no - 1
let value = UInt64(gate) & UInt64(binaryColumn)
let vr = String(value, radix:2).pad(with: "0", toLength: 16)
print("bg ",bgr," bc ",bcr,vr)
return value > 0 ? true:false
}
You can concatenate with multiple Text Views.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View{
let testo : String = "There is a thunderstorm in the area"
let stringArray = testo.components(separatedBy: " ")
let stringToTextView = stringArray.reduce(Text(""), {
if $1 == "thunderstorm" {
return $0 + Text($1).bold() + Text(" ")
} else {
return $0 + Text($1) + Text(" ")
}
})
return stringToTextView
}
}
PlaygroundPage.current.setLiveView(ContentView())
If you are targeting iOS15 / macOS12 and above, you can use AttributedString. For example:
private struct HighlightedText: View {
let text: String
let highlighted: String
var body: some View {
Text(attributedString)
}
private var attributedString: AttributedString {
var attributedString = AttributedString(text)
if let range = attributedString.range(of: highlighted)) {
attributedString[range].backgroundColor = .yellow
}
return attributedString
}
}
If you want your match to be case insensitive, you could replace the line
if let range = attributedString.range(of: highlighted)
with
if let range = AttributedString(text.lowercased()).range(of: highlighted.lowercased())
The answer of #Asperi works well. Here is a modified variant with a search by array of single words:
func highlightedText(str: String, searched: [String]) -> Text {
guard !str.isEmpty && !searched.isEmpty else { return Text(str) }
var result: Text!
let parts = str.components(separatedBy: " ")
for part_index in parts.indices {
result = (result == nil ? Text("") : result + Text(" "))
if searched.contains(parts[part_index].trimmingCharacters(in: .punctuationCharacters)) {
result = result + Text(parts[part_index])
.bold()
.foregroundColor(.red)
}
else {
result = result + Text(parts[part_index])
}
}
return result ?? Text(str)
}
Usage example:
let str: String = "There is a thunderstorm in the area. Added some testing long text to demo that wrapping works correctly!"
let searched: [String] = ["thunderstorm", "wrapping"]
highlightedText(str: str, searched: searched)
.padding()
.background(.yellow)
You can also make AttributedString with markdown this way
do {
return try AttributedString(markdown: foreignSentence.replacing(word.foreign, with: "**\(word.foreign)**"))
} catch {
return AttributedString(foreignSentence)
}
and just use Text
Text(foreignSentenceMarkdown)

Iterable Array of Enums

I am trying to initialize a deck of cards. I have card attributes in my card struct. My approach is to try and create an array of "enum states", then iterate through those to initialize each card. I am having trouble doing so.
Game Class
import Foundation
struct Set{
var cards = [Card]()
init(){
let properties : [Any] =
[cardShape.self, cardColor.self, cardNumber.self, cardShading.self]
for prop in properties{
// Not really sure how to iterate through this array...
// Ideally it would be something like this.
// Iterate through array, for property in array,
// card.add(property)
}
}
}
Card Class
import UIKit
import Foundation
struct Card{
var attributes : properties = properties()
mutating func addProperty(value : Property){
if value is cardShape{
attributes.shape = value as! cardShape
} else if value is cardColor{
attributes.color = value as! cardColor
} else if value is cardNumber{
attributes.number = value as! cardNumber
}else if value is cardShading{
attributes.shading = value as! cardShading
}else{
print("error")
}
}
}
protocol Property{
static var allValues : [Property] {get}
}
struct properties{
var shape : cardShape = cardShape.none
var color : cardColor = cardColor.none
var number : cardNumber = cardNumber.none
var shading : cardShading = cardShading.none
}
enum cardShape : String,Property{
case Square = "โ– "
case Triangle = "โ–ฒ"
case Circle = "โ—"
case none
static var allValues : [Property]{ return [cardShape.Square,cardShape.Triangle,cardShape.Circle]}
}
enum cardColor:Property {
case Red
case Purple
case Green
case none
static var allValues : [Property] {return [cardColor.Red,cardColor.Purple,cardColor.Green]}
}
enum cardNumber : Int,Property{
case One = 1
case Two = 2
case Three = 3
case none
static var allValues : [Property] {return [cardNumber.One,cardNumber.Two,cardNumber.Three]}
}
enum cardShading: Property {
case Solid
case Striped
case Outlined
case none
static var allValues : [Property] {return [cardShading.Solid,cardShading.Striped,cardShading.Outlined]}
}
So to summarize, my main issue is trying to create an array of enums, then cycling through the enum states to initialize a card with specific attribute states.
You will want to make sure you cover all combinations of attributes and make sure each card has one of each of the four types of attributes. I would suggest using nested loops:
for shape in cardShape.allValues {
for color in cardColor.allValues {
for number in cardNumber.allValues {
for shading in cardShading.allValues {
var card = Card()
card.addProperty(shape)
card.addProperty(color)
card.addProperty(number)
card.addProperty(shading)
cards.append(card)
}
}
}
}
I believe your Card struct is a bit too complex. If you change your representation, it will be easier to create the cards.
Have your card represent the different attributes as their own property:
struct Card {
let shape: CardShape
let color: CardColor
let number: CardNumber
let shading: CardShading
}
Then use nested loops to create your cards:
for shape in CardShape.allValues {
for color in CardColor.allValues {
for number in CardNumber.allValues {
for shading in CardShading.allValues {
cards.append(Card(shape: shape, color: color, number: number, shading: shading))
}
}
}
}
Notes:
Your enums should start with uppercase characters, and your enum values should start with lowercase characters.
Using separate properties for each attribute will make it much easier to check for matching attributes between cards.
You get an initializer by default that initializes all properties. By initializing them with nested loops, you will be able to create all possible cards.
Change your allValues properties to return arrays of the specific attribute type (for example [CardShape]).
Alternate Answer:
Instead of using nested arrays, you could use MartinR's combinations function to create the list of combinations of the properties. Adding an init to Card that takes [Property], you can create the cards in two lines of code:
struct Card {
var shape = CardShape.none
var color = CardColor.none
var number = CardNumber.none
var shading = CardShading.none
init(properties: [Property]) {
for property in properties {
switch property {
case let shape as CardShape:
self.shape = shape
case let color as CardColor:
self.color = color
case let number as CardNumber:
self.number = number
case let shading as CardShading:
self.shading = shading
default:
break
}
}
}
}
// https://stackoverflow.com/a/45136672/1630618
func combinations<T>(options: [[T]]) -> AnySequence<[T]> {
guard let lastOption = options.last else {
return AnySequence(CollectionOfOne([]))
}
let headCombinations = combinations(options: Array(options.dropLast()))
return AnySequence(headCombinations.lazy.flatMap { head in
lastOption.lazy.map { head + [$0] }
})
}
struct SetGame {
let cards: [Card]
init(){
let properties: [Property.Type] = [CardShape.self, CardColor.self, CardNumber.self, CardShading.self]
cards = combinations(options: properties.map { $0.allValues }).map(Card.init)
}
}
How this works:
properties.map { $0.allValues } calls allValues on each item of the properties array creating an [[Property]] with [[.square, .triangle, .circle], [.red, .purple, .green], [.one, .two, .three], [.solid, .striped, .outlined]]
This is passed to combinations which creates a sequence with all 81 combinations of these properties: [[.square, .red, .one, .solid], ..., [.circle, .green, .three, .outlined]].
map is run on this sequence to call Card.init with each combination which results in an [Card] with 81 cards.

Return value from struct when used

I am quite new to Swift and I am learning about structs. However, I have a problem which might sound obvious, but I'm not sure how to do it. I am doing all this in the playground.
I have a struct called Colour, where I create an RGB colour type. I want to access its variables (e.g. by doing yellow.red which will read and write the variable to find the value of red in the colour yellow).
struct Colour {
var red: Int
var blue: Int
var green: Int
var rgb: [Int] // <- I don't want to have this variable, I want to make the struct Colour be this array
init(red: Int, green: Int, blue: Int) {
self.red = red
self.green = green
self.blue = blue
rgb = [red, green, blue]
}
}
Call:
let red = Colour(red: 255, green: 0, blue: 0) // Colour
red.red // 255
red.green // 0
red.blue // 0
red // Colour
red.rgb // [255, 0, 0]
When I access red, I want it to automatically return the value of red.rgb, without the variable.
So how can I call red and return an Array with the value [255, 0, 0]?
Notes:
A get or set cannot be implemented
I cannot use Colour as a variable, as I need initialisation
return keyword cannot be used, as structs don't work in this way
Edit
Sorry for not making this clear enough. I was looking to return a [Int], but it is clearly not worth what I was originally trying.
The solutions with the protocols definitely work if you want to return a string, which is why I have accepted an answer.
Edit 2
We now have a working answer using type alias!
Alternatively, you can define a typealias Colour. This might be a little more efficient, but perhaps not as type safe.
See code:
typealias Colour = [Int]
extension Array where Element == Int {
init(red: Int, green: Int, blue: Int) {
self = [red, green, blue]
}
var red: Int { return self.count > 0 ? self[0] : 0 }
var green: Int { return self.count > 1 ? self[1] : 0 }
var blue: Int { return self.count > 2 ? self[2] : 0 }
}
Example Usage:
let red = Colour(red: 255, green: 0, blue: 0) // Colour
red.red // 255
red.green // 0
red.blue // 0
red // [255, 0, 0]
red is a Colour. There's nothing you can do to change that. Nothing you do can make red act directly as a [Int] containing the red, green, and blue components.
There are different things you can do, depending on what your actual needs are. For example, if you just want it to act like an [Int] for the sake of printing like [255, 0, 0], then you can override var description: String and conform to CustomStringConvertible.
The sort of feature you're looking for is like C++'s user-defined conversion operators. These can be really cool, but can also easily lead to very unclear code. Swift opts for explicitly expressing such conversions, for the sake of clarity.
Swift has a Protocol called CustomDebugStringConvertible that should accomplish what you're looking for. After declaring that your struct adopts that Protocol, just implement the debugDescription variable in your code.
See update code:
struct Colour: CustomDebugStringConvertible {
var red: Int
var green: Int
var blue: Int
var debugDescription: String {
return "\(array)"
}
var array: [Int] {
return [red, green, blue]
}
init(red: Int, green: Int, blue: Int) {
self.red = red
self.green = green
self.blue = blue
}
}
Example usage:
let red = Colour(red: 255, green: 0, blue: 0) // Colour
red.red // 255
red.green // 0
red.blue // 0
red // "[255, 0, 0]"
red.array // [255, 0, 0]
I would suggest adding a function such as toArray()
That could look something like this:
struct Colour {
var red: Int
var blue: Int
var green: Int
init(red: Int, green: Int, blue: Int) {
self.red = red
self.green = green
self.blue = blue
}
func toArray() -> [Int] {
return [self.red, self.green, self.blue]
}
}
Then, in your code, you can just use let rgbArray = MyColour.toArray()
Also, I would advise against storing the same underlying data twice, such as with your rgb variable. If you change the Colour.red variable, that would mean your Colour.rgb value is not updated with that new value automatically and you suddenly get back values that you did not expect. With the function approach, you won't have that problem.
If you want to make the Colour type a custom collection, enabling you to use it like an Array but make it type-safe at the same time, you can create a custom struct and make it conform to Collection. You can create an RGB enum that can be used for indexing the custom collection and a failable initializer that ensures you can only create a Colour instance with valid RGB values.
struct Colour: Collection, RandomAccessCollection {
// Custom enum used for indexing Colour
enum RGB: Int, Hashable, Comparable, Strideable {
case red, green ,blue
// Find the last existing rawValue, assumes that the first case has rawValue 0 and that rawValues are incremented by 1
static let maxRawValue: RGB.RawValue = {
var maxRawVal = 0
while RGB(rawValue: maxRawVal) != nil {
maxRawVal += 1
}
return maxRawVal
}()
static let rawValues: [RGB.RawValue] = {
var rawValues = [RGB.RawValue]()
var currentRawValue = 0
while RGB(rawValue: currentRawValue) != nil {
rawValues.append(currentRawValue)
currentRawValue += 1
}
return rawValues
}()
static func <(lhs: RGB, rhs: RGB) -> Bool {
return lhs.rawValue < rhs.rawValue
}
typealias Stride = Int
func distance(to other: RGB) -> RGB.Stride {
return self.rawValue - other.rawValue
}
func advanced(by n: RGB.Stride) -> RGB {
return RGB(rawValue: (self.rawValue+n)%RGB.maxRawValue)!
}
}
typealias Element = Int
typealias Index = RGB
//Private backing Array
private var components:[Element] = Array<Element>.init(repeating: Element.init(), count: RGB.rawValues.count)
var startIndex: Colour.Index {
return RGB(rawValue: components.startIndex)!
}
var endIndex: Colour.Index {
return RGB(rawValue: components.endIndex)!
}
subscript (position: Index) -> Element {
get {
return components[position.rawValue]
}
set {
components[position.rawValue] = newValue
}
}
func index(after i: Index) -> Index {
return Index(rawValue: components.index(after: i.rawValue))!
}
private init(){}
//Failable initializer that makes sure only a 3 element Array can be used as an input and that each element is in the valid RGB color code range
init?<C:Collection>(_ collection:C) where C.Element == Element, C.Index == Index.RawValue {
guard collection.indices.map({$0}) == RGB.rawValues else {return nil}
for (index, element) in collection.enumerated() {
guard element <= 255 && element >= 0 else {return nil}
self.components[index] = element
}
}
}
Then you can use your custom collection safely:
let red = Colour([255,0,0]) // Colour
let invalidColour = Colour([255]) // nil
let invalidColour2 = Colour([255,2,3,4]) // nil
let invalidColour3 = Colour([255,-1,256]) // nil
let randomColour = Colour([1,5,231]) // Colour
// you can use the RGB cases to access the respective values in the collection
randomColour?[.red] // 1

Swift: Operate on a Given Property

I have a struct which represents a bitmap image in RGBA form. That struct has properties for each of the color channels, red, blue and green.
I'm trying to build an image filter which adjusts a particular color level. This version works fine, but clearly the pixel.COLOR property is hard coded.
func boostColor(inputColor: String) -> UIImage {
let avgColor = "avg" + inputColor
// let color = inputColor.lowercaseString // plan to use this to set the property
for y in 0..<self.inputRGBA.height {
for x in 0..<self.inputRGBA.width {
let index = y * self.inputRGBA.width + x
var pixel = self.inputRGBA.pixels[index]
// see how far this pixel's chosen color varies from the average
let colorDiff = Int(pixel.red) - Int(self.pixelColorAverages[avgColor]!)
// if less than average red,
if(colorDiff>0){
pixel.red = UInt8( max(0,min(255,Int(self.pixelColorAverages[avgColor]!) + colorDiff*100 ) ) )
// write the adjusted pixel back to the image object
self.inputRGBA.pixels[index] = pixel
}
}
}
// write the filtered RGBA image to a UIImage
return self.inputRGBA.toUIImage()!
}
This loop's function takes in a string value of either "red", "blue" or "green". What I would like to do is replace instances of
pixel.red
with
pixel.[ONE OF THE INPUT COLORS]
Thanks!
(This is an academic playground project. I realize I'm probably re-inventing the wheel by building a color filter. It isn't the color filter itself that I'm interested in, but how to solve this property problem in Swift.)
Here is the struct:
public struct Pixel {
public var value: UInt32
public var red: UInt8 {
get {
return UInt8(value & 0xFF)
}
set {
value = UInt32(newValue) | (value & 0xFFFFFF00)
}
}
public var green: UInt8 {
get {
return UInt8((value >> 8) & 0xFF)
}
set {
value = (UInt32(newValue) << 8) | (value & 0xFFFF00FF)
}
}
public var blue: UInt8 {
get {
return UInt8((value >> 16) & 0xFF)
}
set {
value = (UInt32(newValue) << 16) | (value & 0xFF00FFFF)
}
}
public var alpha: UInt8 {
get {
return UInt8((value >> 24) & 0xFF)
}
set {
value = (UInt32(newValue) << 24) | (value & 0x00FFFFFF)
}
}
}
Implementing Key Value Coding is possible with reflecting but it is very un-Swift. In your case it is also just a bad idea since there are much more powerful and useful alternatives.
I would go for an OptionSetType since there are only a fixed number of possibilities (RGBA). An enum would be fine too but an OptionSetType has the extra benefit of being a "set". So you can choose to alter RGB and not A in a very convenient way.
interesting read on OptionSetType
This is an enum to store the rawValues for the OptionSetType. Can be used on it's own.
public enum RGBAColor : Int, CustomStringConvertible {
case Red = 1, Green = 2, Blue = 4, Alpha = 8
public var description : String { // if you still want to have a String value for each color
var shift = 0
while (rawValue >> shift != 1) { shift++ }
return ["Red","Green","Blue","Alpha"][shift]
}
}
This is the OptionSetType. I create snippets of these kind of structures and copy / paste / modify them when needed. No need to reinvent the wheel when it will always be the same pattern.
public struct RGBAColors : OptionSetType, CustomStringConvertible {
public let rawValue : Int
public init(rawValue:Int) { self.rawValue = rawValue}
private init(_ color:RGBAColor) { self.rawValue = color.rawValue }
static let Red = RGBAColors(RGBAColor.Red)
static let Green = RGBAColors(RGBAColor.Green)
static let Blue = RGBAColors(RGBAColor.Blue)
static let Alpha = RGBAColors(RGBAColor.Alpha)
public var description : String {
var result = ""
var shift = 0
while let currentcolor = RGBAColor(rawValue: 1 << shift++){
if self.contains(RGBAColors(currentcolor)){
result += (result.characters.count == 0) ? "\(currentcolor)" : ",\(currentcolor)"
}
}
return "[\(result)]"
}
}
Now you can add a function to your Pixel struct that will return the color value based on an enum case. You can also choose to pass the OptionSetType directly to this function.
public struct Pixel {
...
public func getValueForColor(color:RGBAColor) -> UInt8 {
switch color {
case .Red : return self.red
case .Green : return self.green
case .Blue : return self.blue
case .Alpha : return self.alpha
}
}
}
This is just a control flow function. You can now do different things based on which color that needs to be altered. (or apply the same to all)
This is also the only reason to choose for an OptionSetType, if you don't need this, just go for an enum. Since the OptionSetType builds on the enum you can always add it later.
func someFuncWithOptions(colors:RGBAColors) {
if colors.contains(.Red) {
someFunc(.Red)
}
if colors.contains(.Blue) {
someFunc(.Blue)
}
if colors.contains(.Green) {
someFunc(.Green)
}
if colors.contains(.Alpha) {
someFunc(.Alpha)
}
}
This will be your actual function
func someFunc(color:RGBAColor) {
// do something with pixel.getValueForColor(color)
print(color)
}
This will be exposed to other parts of your code. So the actual usage of all this will be really simpel. Just input .SomeColor.
The best thing is that the compiler will warn you if you change all this to CMYK for example. Key Value Coding will never generate warnings, it will just crash.
Because it is a "set" and not per definition one color, you pass a collection of options. [.option1,.option2,...] or you pass nothing []
someFuncWithOptions([.Red]) // red
someFuncWithOptions([.Green,.Red]) // red green
You can also add convenient collections to the OptionSetType. This one will be a set of all RGB colors but without the Alpha channel.
static let RGB : RGBAColors = [.Red,.Green,.Blue]
someFuncWithOptions([.RGB]) // red blue green
One approach would be to add a KVC-like syntax to Swift using Swift's built-in reflection capabilities. You can learn how to do that here, although it's serious overkill for this problem:
http://blog.shiftybit.net/2015/07/kvc-in-swift/
A more narrow and suitable solution would just be to write a function for your struct that takes a String argument, switches over it, and returns the desired value. You'd also want to write another function for your struct that takes a String and a value, and sets the value on your struct.
Examples:
struct Pixel
{
// ... rest of struct definition ...
func getColor(color:String) -> UInt8
{
switch color
{
case "red":
return self.red
case "green":
return self.green
case "blue":
return self.blue
case "alpha":
return self.alpha
}
}
func setColor(color:String, value:UInt8)
{
switch color
{
case "red":
self.red = value
case "green":
self.green = value
case "blue":
self.blue = value
case "alpha":
self.alpha = value
}
}
}
If you wanted, you could take it a step further and use an enum instead of a String for improved clarity and type safety.
If you want to pass the color name as string, why don't you define a subscript for your structure? It's awesome and very flexible.
Classes, structures, and enumerations can define subscripts, which are shortcuts for accessing the member elements of a collection, list, or sequence. You use subscripts to set and retrieve values by index without needing separate methods for setting and retrieval.
struct Pixel {
// ...
subscript(color: String) -> UInt8? {
get {
switch color {
case "red":
return red
case "green":
return green
case "blue":
return blue
case "alpha":
return alpha
default:
return nil
}
}
set {
if newValue != nil {
switch color {
case "red":
red = newValue!
case "green":
green = newValue!
case "blue":
blue = newValue!
case "alpha":
alpha = newValue!
default:
break
}
}
}
}
}
var pixel = Pixel(value: 0)
pixel["red"] = 128
Note that it will fail silently in case a wrong color is set like in pixel["yellow"] = 155. To avoid situations like that you could implement a enum and use it as key.
enum Color {
case Red
case Green
case Blue
case Alpha
}
extension Pixel {
subscript(color: Color) -> UInt8 {
get {
switch color {
case .Red:
return red
case .Green:
return green
case .Blue:
return blue
case .Alpha:
return alpha
}
}
set {
switch color {
case .Red:
red = newValue
case .Green:
green = newValue
case .Blue:
blue = newValue
case .Alpha:
alpha = newValue
}
}
}
}
var pixel = Pixel(value: 0)
pixel[.Red] = 128