SwiftUI: Use of different colours within same Text view - colors

I have a section of text where I am using .replacingOccurrences to display the users' answer within the question sentence:
Text((question.text)
.replacingOccurrences(of: "_____", with:
question.answers[question.userAnswer]
))
.font(Font.custom("ClearSans-Bold", size: 18))
.foregroundColor(.black )
.padding(.bottom, 20)
.multilineTextAlignment(.center)
I want the users' answer question.answers[question.userAnswer] to be a different colour (red/green) to the main body of the text (similar to attached image) however I'm new to SwiftUI so not sure how to add this in.
Image 1

Here's an extension for String that does pretty much what you want:
extension String {
func replacingOccurrences(of: String, with: [Text]) -> Text {
return self.components(separatedBy: of).enumerated().map({(i, s) in
return i < with.count ? Text(s) + with[i] : Text(s)
}).reduce(Text(""), { (r, t) in
return r + t
})
}
}
It uses concatenation of Text elements as George_E suggested. You can use it like this:
struct ContentView: View {
let question: String = "The witch found the concoction extremely _____. _____ makes better ones."
let answers: [String] = ["nauseate", "A friend of her's"]
var body: some View {
question.replacingOccurrences(of: "_____", with: self.answers.map({s in Text(s).foregroundColor(.red)})).foregroundColor(.secondary)
}
}
Result:
You may want to add some extra code for handling cases where the number of answers does not match the occurrences of _____.

Related

SwiftUI: How to combine a Double with a String and do a math calculation?

I am still totally new to Swift / SwiftUI and coding in general.
I asked this question before but it was closed because it was apparently a duplicate, but (!) this does not account for SwiftUI and I cannot get this thing to work in SwiftUI, because even if I use the suggested NSExpression, I cannot use the result, because it is not a View and I have no idea on how to convert it to a View (see the update at bottom of the question).
I am trying to make a simple converter with a set of predefined conversion from a DataModel. So far I can populate the ContentView and move into a DetailView where I have created a slider which automatically updates a TextView whenever the slider is moved and the whole screen is populated through the DataModel (I hope I explain this the right way).
Now my problem is that every single conversion uses a different calculation obviously. For instance "/ 1000" or "* 50". I have stored these parts as Strings in the DataModel. Now what I'd like to do is to use these Strings in my calculation, but I have no idea how I can include them. Obviously I cannot convert these to Doubles to make a calculation. Here is the code and DataModel:
DataModel:
struct Converter: Identifiable {
var id = UUID()
var title: String
var range = 0.0...0.0
var calc: String
var sliderValue: Double
var color: [Color]
var description: String
}
As you can see "calc" is of type String. I am also using "range" which I defined with 0.0...0.0 because I didn't know how I could initialize it otherwise (I always got an error because of a closed range (?) so this is what I cam up with to silence the error.
Here is an example of some data:
let converterData: [Converter] = [
Converter(
title: "Conversion1",
range: 0.0...200,
calc: "/ 1000",
sliderValue: 0.0,
color: [Color.blue, Color.red],
description: "This is conversion 1"
),
...
...
As you can see I have changed the range to "0.0...200" because the slider shouldn't go beyond 200. I have entered the part of the calculation "/ 1000" into calc. This is the thing I'd like to add to the calculation in the DetailView (see below). I am not using the rest of the data right now, because I am still stuck with this calculation.
Code:
#State var sliderValue: Double = 0.0
var conversion: Converter
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("value1".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
Text("\(sliderValue, specifier: "%.2f")")
.font(.system(size: 60, weight: .bold))
Text("to value2".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
self.calculatedView(for: sliderValue)
}
Slider(value: $sliderValue, in: conversion.range, step: 0.5)
.padding(8)
.overlay(
Capsule()
.stroke(Color.purple, style: StrokeStyle(lineWidth: 5))
)
}
.padding()
}
As you can see I use "conversion.range" to specify the exact range for the slider. Here is the calculation:
private func calculatedView(for value: Double) -> some View {
Text("\(value / 1000, specifier: "%.2f")")
.font(.system(size: 60, weight: .bold))
}
}
Update:
So what I did was to try to use NSExpression in calculatedView:
private func calculatedView(for value: Double) -> some View {
let calculation = String(value) + conversion.calc
let expn = NSExpression(format:calculation)
let result = expn.expressionValue(with: nil, context: nil)
Text(result)
.font(.system(size: 60, weight: .bold))
}
I am not even sure if this is the way to use NSExpression. I am trying to create a string from the value of the slider and add conversion.calc. It should look something like this: 125.5 / 1000 (just as an example). I am not sure if this is right in the first place.
Now I try to use NSExpression and store the result in the variable result and this variable should be shown as a TextView. I get two error messages:
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
And for Text(result):
2. No exact matches in call to initializer
Question
Right now I just add the value and hard-code the "/ 1000" in. What I'd like to change is instead use converter.calc (which is "/ 1000). But obviously when I just add this the whole calculation is broken, but I have no idea how the re-format the whole thing so that it works. Before I would have just created a switch statement (or something else) with a new calculation for each conversion, which would blow up the whole code.
I really hope someone can point me into the right direction, because I am so happy that I have finally understood how to use a simple DataModel.
Thanks for reading and for any tip. With best regards!
Rather have the calc property in Converter hold a closure. This way you could pass in any sort of conversion function when initializing a Converter (by passing in a conversion closure). Perform all calculations in Double (as numbers) and then convert to String for displaying.
Here is a link to Closures at Swift.org
It's going to be a huge mess trying to perform maths with Strings and Numbers.
EDIT Simple example of using Closure
All else being the same except for the calc variable
struct Converter {
var calc: ((Double) -> Double)
}
Usage
let closure: ((Double) -> Double) = { num in
return num / 1000
}
let converter = Converter(calc: closure)
print("Conversion result: \(converter.calc(500))")
You could pass any sort of calculation method into the converter, or you could hard the method in the struct itself and just call calc which will auto grab any variables within the struct.
But for your issue instead of the CalculatedView function:
VStack(alignment: .leading) {
Text("value1".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
Text("\(sliderValue, specifier: "%.2f")")
.font(.system(size: 60, weight: .bold))
Text("to value2".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
Text("\(conversion.calc(sliderValue))")
.font(.system(size: 60, weight: .bold))
}

Grails convert String to Map with comma in string values

I want convert string to Map in grails. I already have a function of string to map conversion. Heres the code,
static def StringToMap(String reportValues){
Map result=[:]
result=reportValues.replace('[','').replace(']','').replace(' ','').split(',').inject([:]){map,token ->
List tokenizeStr=token.split(':');
tokenizeStr.size()>1?tokenizeStr?.with {map[it[0]?.toString()?.trim()]=it[1]?.toString()?.trim()}:tokenizeStr?.with {map[it[0]?.toString()?.trim()]=''}
map
}
return result
}
But, I have String with comma in the values, so the above function doesn't work for me. Heres my String
[program_type:, subsidiary_code:, groupName:, termination_date:, effective_date:, subsidiary_name:ABC, INC]
my function returns ABC only. not ABC, INC. I googled about it but couldnt find any concrete help.
Generally speaking, if I have to convert a Stringified Map to a Map object I try to make use of Eval.me. Your example String though isn't quite right to do so, if you had the following it would "just work":
// Note I have added '' around the values.
​String a = "[program_type:'', subsidiary_code:'', groupName:'', termination_date:'', effective_date:'', subsidiary_name:'ABC']"
Map b = Eval.me(a)​
// returns b = [program_type:, subsidiary_code:, groupName:, termination_date:, effective_date:, subsidiary_name:ABC]
If you have control of the String then if you can create it following this kind of pattern, it would be the easiest solution I suspect.
In case it is not possible to change the input parameter, this might be a not so clean and not so short option. It relies on the colon instead of comma values.
​String reportValues = "[program_type:, subsidiary_code:, groupName:, termination_date:, effective_date:, subsidiary_name:ABC, INC]"
reportValues = reportValues[1..-2]
def m = reportValues.split(":")
def map = [:]
def length = m.size()
m.eachWithIndex { v, i ->
if(i != 0) {
List l = m[i].split(",")
if (i == length-1) {
map.put(m[i-1].split(",")[-1], l.join(","))
} else {
map.put(m[i-1].split(",")[-1], l[0..-2].join(","))
}
}
}
map.each {key, value -> println "key: " + key + " value: " + value}
BTW: Only use eval on trusted input, AFAIK it executes everything.
You could try messing around with this bit of code:
String tempString = "[program_type:11, 'aa':'bb', subsidiary_code:, groupName:, termination_date:, effective_date:, subsidiary_name:ABC, INC]"
List StringasList = tempString.tokenize('[],')
def finalMap=[:]
StringasList?.each { e->
def f = e?.split(':')
finalMap."${f[0]}"= f.size()>1 ? f[1] : null
}
println """-- tempString: ${tempString.getClass()} StringasList: ${StringasList.getClass()}
finalMap: ${finalMap.getClass()} \n Results\n finalMap ${finalMap}
"""
Above produces:
-- tempString: class java.lang.String StringasList: class java.util.ArrayList
finalMap: class java.util.LinkedHashMap
Results
finalMap [program_type:11, 'aa':'bb', subsidiary_code:null, groupName:null, termination_date:null, effective_date:null, subsidiary_name:ABC, INC:null]
It tokenizes the String then converts ArrayList by iterating through the list and passing each one again split against : into a map. It also has to check to ensure the size is greater than 1 otherwise it will break on f[1]

swift/parse: incrementing strings

The Swift part of the question:
So what I mean by incrementing strings is that say we start off with var string = "title" I want to be able to increment numbers to the end of that like "title1", "title2", "title3...". Should I use a for loop to do this? If so, how? Or another method?
for var i = 1; i < 6; i = i + 1 {
//increment the strings here
}
The Parse part of the question:
I want to have my objectForKey use the many different titles and numbers we will produce above so that the objectForKey will be "title1", "title2", "title3"... I would make multiple columns on Parse with names " title1, title2, title3 and the cells in the tableview would correspond to that data. So cell1 would use title1's data, cell2 will use title2's data and so on. Will it work like this?
var output1 = object.objectForKey(i) as! String
A loop in Swift is like for i in 1...5, and then you can use string interpolation to get the correct string like this:
for i in 1...5 {
let title = "title\(i)"
print(title)
}
Also read Dan's answer.
There are a few ways of looping in Swift, but you should keep in mind that and as of Swift 3,
this will no longer be one of them:
for var i = 0; i <6; i++ {
let string = "title\(i+1)"
}
source : Swift Evolution
Swift's preferred way of general looping is, as GvS stated:
for i in 1...5 {
let title = "title\(i)"
}
However, you are also welcome to use Swifts higher order functions to loop:
(1...5).forEach { i in
let title = "title \(i)"
}
or
(1...5).forEach { let title = "title \($0)" }

How to include emoticons in Swift string?

Here is a pretty good article that references iOS emoticons and their code. For example \ue008 for the small camera.
I tried this in my code :
var myText: String = "\ue008"
This is not accepted by Xcode. How to include it ?
If I understand what you are trying to achieve, then:
Press "ctrl + cmd + space" while in XCode. A sample usage of 'hearts' emoticon
cell.textLabel?.text = "❤️" + " \(liker) liked \(userBeingliked)'s photo"
That's from swift documentation:
let dollarSign = "\u{24}" // $, Unicode scalar U+0024
let blackHeart = "\u{2665}" // ♥, Unicode scalar U+2665
let sparklingHeart = "\u{1F496}" // 💖, Unicode scalar U+1F496
You don't need the unicode constants at all. Just use the character viewer and type the character directly. 😝
let sparklingHeart = "💖"
1 Decoding the Unicode:
extension String {
var decodeEmoji: String{
let data = self.data(using: String.Encoding.utf8);
let decodedStr = NSString(data: data!, encoding: String.Encoding.nonLossyASCII.rawValue)
if let str = decodedStr{
return str as String
}
return self
}
}
Usage
let decodedString = yourString.decodeEmoji
2 Encoding the Unicode:
extension String {
var encodeEmoji: String{
if let encodeStr = NSString(cString: self.cString(using: .nonLossyASCII)!, encoding: String.Encoding.utf8.rawValue){
return encodeStr as String
}
return self
}
}
Usage
let encodedString = yourString.encodeEmoji
You could insert the emoji directly using ⌘ ^ Space.
Or, based on Greg's answer:
var myText: String = "\u{e008}"
As Greg posted above, you can directly input the emoji into Swift using the OSx character viewer. The character viewer is disabled by default. Here is how to enable it:
Go to System Preferences > Language and Region > Keyboard Preferences > Keyboard then check Show Keyboard, Emoji, & Symbol Viewers in menu bar. Once checked you can open the character viewer from the top right menu bar next to your Wifi and Date/Time icons.
from your Hex "0x1F52D" to actual Emoji
let c = 0x1F602
next step would possibly getting an Uint32 from your Hex
let intEmoji = UnicodeScalar(c!).value
from this you can do something like
titleLabel.text = String(UnicodeScalar(intEmoji)!)
here you have a "😂"
it work with range of hexadecimal too
let emojiRanges = [
0x1F600...0x1F636,
0x1F645...0x1F64F,
0x1F910...0x1F91F,
0x1F30D...0x1F52D
]
for range in emojiRanges {
for i in range {
let c = UnicodeScalar(i)!.value
data.append(c)
}
}
to get multiple UInt32 from your Hex range for exemple
Chris Slowik's and Greg's answers are close.
The easiest answer is just to "rephrase" your String from this:
var myText: String = "\ue008"
To this:
var myText: String = "\u{008}"
The Unicodes found on the link you've attached are not wrong, as someone else claimed. You just need to rephrase it inside the String.
The important piece of code in your example above is the "008" part.
I've created a simple function to convert these kinds Unicode to their corresponding Emojis:
func convertHexToEmoji(_ u:Int) -> String {
return "\(UnicodeScalar(u)!)" }
To use:
let myText = convertHexToEmoji(008)
print(myText)
This took me a bit of time to figure out in MacOS 11, so I thought I would share.
If you prefer to input the unicode characters rather than pasting literal emojis, you can find out the unicode for the system emojis like this:
Focus/click into a text field (e.g. the search bar in your web browser).
Press ctrl+cmd+space or go to Edit->Emoji & Symbols in the menu bar.
Scroll up in the character viewer until you see the window expand icon in the upper right:
In the expanded Character Viewer window, press the upper left button and select Customize List....
Scroll down to Code Tables minimized list, expand the list, toggle on Unicode, and press Done (system changed this window to dark mode for whatever reason).
Now, click the different emojis and you should see the unicode underneath the image.
Then you inject it the unicode like this:
var myText: String = "\u{e008}"

Using loops to go through string and check char as a dict key

so I want to kind of build a "Decrypter", I have a dictionary with the keys being the symbol, and the value the respective value for the symbol, then I have this string that the code is suppose to look into, the translate will be saved in a other string, in this case called output. This is the way I did the loop part, but is not working:
var outputText = " "
for character in textForScan{
for key in gematriaToLetters{
if (gematriaToLetters.keys == textForScan[character]){
outputText.insert(gematriaToLetters.values, atIndex: outputText.endIndex)
}
}
}
You could also consider using map:
let outputText = "".join(map(textForScan) { gematriaToLetters[String($0)] ?? String($0) })
If you don't specify a specific letter in the dictionary it returns the current letter without "converting".
I think you are looking for something like this:
for aCharacter in textForScan {
let newChar = gematrialToLetters["\(aCharacter)"]
outputText += newChar
}
print(outputText)

Resources