I have a function that takes text containing the # symbol and outputs new text in which any text following the symbol will change colour. It's similar to that of a social media mention. How would I go about making only the newly coloured text tappable in the view?
func textWithHashtags(_ text: String, color: Color) -> Text {
let words = text.split(separator: " ")
var output: Text = Text("")
var firstWord = true // <-- here
for word in words {
let spacer = Text(firstWord ? "" : " ") // <-- here
if word.hasPrefix("#") { // Pick out hash in words
output = output + spacer + Text(String(word))
.foregroundColor(color) // Add custom styling here
} else {
output = output + spacer + Text(String(word))
}
firstWord = false
}
return output
}
To call it in a view I just say:
textWithHashtags("Hello #stackoverflow how is it going?", color: .red)
In this case I need to find a way to make the #stackoverflow tappable only.
Related
I am trying to get NSFontPanel/NSFontManager to work in a SwiftUI Document Template app. I have the following which is a customize version of one I found on GitHub. This lets me pick the size, face, style, etc.
Interestingly, a color picker is included in the FontPanel. The documentation doesn't seem to say this. Is this something new?
Anyway, I would like to either be able to use the color picker to let the user select a color, or if not I would like to hide the color picker - at is not "critical" to this application. I am using this to allow customization of text in a sidebar, so color is nice, but not necessary. Currently the Font settings are working, but the color selection displays, and let you pick on, but it always returns System Color.
Any help would be appreciated.
NOTE: I didn't include the FontPickerDelegate, it just calls this:
public struct FontPicker: View{
let labelString: String
#Binding var font: NSFont
#State var fontPickerDelegate: FontPickerDelegate?
public init(_ label: String, selection: Binding<NSFont>) {
self.labelString = label
self._font = selection
}
let fontManager = NSFontManager.shared
let fontPanel = NSFontPanel.shared
#AppStorage("setSidebarFont") var setSidebarFont = "System"
#AppStorage("setSidebarFontSize") var setSidebarFontSize = 24
#AppStorage("setSidebarFontColor") var setSidebarFontColor = "gray"
public var body: some View {
HStack {
Text(labelString)
Button {
if fontPanel.isVisible {
fontPanel.orderOut(nil)
return
}
self.fontPickerDelegate = FontPickerDelegate(self)
fontManager.target = self.fontPickerDelegate
fontManager.action = #selector(fontPickerDelegate?.changeAttributes)
fontPanel.setPanelFont(self.font, isMultiple: false)
fontPanel.orderBack(nil)
} label: {
Text("Font Selection: \(setSidebarFont)")
.font(.custom(setSidebarFont, size: CGFloat(setSidebarFontSize)))
}
}
}
func fontSelected() {
self.font = fontPanel.convert(self.font)
setSidebarFont = self.font.displayName ?? "System"
setSidebarFontSize = Int(self.font.pointSize)
var newAttributes = fontManager.convertAttributes([String : AnyObject]())
newAttributes["NSForegroundColorAttributeName"] = newAttributes["NSColor"]
newAttributes["NSUnderlineStyleAttributeName"] = newAttributes["NSUnderline"]
newAttributes["NSStrikethroughStyleAttributeName"] = newAttributes["NSStrikethrough"]
newAttributes["NSUnderlineColorAttributeName"] = newAttributes["NSUnderlineColor"]
newAttributes["NSStrikethroughColorAttributeName"] = newAttributes["NSStrikethroughColor"]
print("\(newAttributes["NSForegroundColorAttributeName"]!)")
}
}
I would to like create a custom Text modifier that returns a Text, so that I can use the sum operator: Text(..) + Text(..) + Text(..)
I tried the following mods:
protocol TextModifier {
func body(text: Text) -> Text
}
extension Text {
func modifier<TM: TextModifier>(_ theModifier: TM) -> Text {
return theModifier.body(text: self)
}
}
struct TestModifier: TextModifier {
func body(text: Text) -> Text {
return text.foregroundColor(.orange).font(.largeTitle)
}
}
extension Text {
func mymodifier() -> Text {
modifier(TestModifier())
}
}
// In some view code I can do:
Text("abc").mymodifier() + Text("def").mymodifier()
And the above works fine. The problem that I have though is that in the TestModifier body I need to apply modifiers that return "some View", and then I'm toast as I need that body to return a "Text". In the above I'm only using modifiers that return Text, so all is good, but that's not useful for me.
Is there some way to get back the original Text object after applying modifiers that return "some View"?
As the title states, I am looking for a way to wrap multiline text inside of a shape in SwiftUI (see image below). I tried using .clipToShape(MyShape()), but that just renders any text not within the shape invisible.
I have accomplished this in the past using UIKit and exclusion paths, but would like to find a way to achieve the same effect with SwiftUI.
Any advice would be greatly appreciated.
I found a numeric way to do so, but it is probably not the best one.
It should work on IOS and macOS. I tested it on macOS with swiftUI.
The first thing we do is to find out how many rectangles with the hight of the font size are fitting in the circle with its diameter. Then we figure their width out. The last step is grabbing for each rectangle the amount of characters fitting in and adding them into an array. By converting the hole array back to a String we add after each rectangle an "\n" to get the correct multiline alignment.
func createCircularText(text: String, verticalSpacing: Double, circleDiameter: Double, FontSize: Double) -> String {
var Text = text
var circularText = String()
var CountOfWordLines = Int()
var widthOfWordLine = [Int]()
var widthOfWordLineSorted = [Int]()
var array = [String]()
let heigthOfWordLines = FontSize + verticalSpacing
var Dnum = (((1/heigthOfWordLines) * circleDiameter) - 2.0)
Dnum.round(.up)
CountOfWordLines = Int(Dnum)
for n in 1...(CountOfWordLines / 2) {
let num0 = circleDiameter / 2.0
let num1 = pow(num0, 2.0)
let num2 = (Double(n) * heigthOfWordLines)
let num3 = pow(num2,2.0)
let num4 = num1 - num3
let num5 = sqrt(Double(num4))
let num = Int((num5 / 10) * 3)
widthOfWordLine.append(Int(num))
}
widthOfWordLineSorted.append(contentsOf: widthOfWordLine.sorted { $1 > $0 })
widthOfWordLine.removeFirst()
widthOfWordLineSorted.append(contentsOf: widthOfWordLine)
widthOfWordLine.removeAll()
for n in widthOfWordLineSorted {
array.append(String(Text.prefix(n)))
if Text.isEmpty {} else {
let t = Text.dropFirst(n)
Text = String(t)
}
}
circularText = array.joined(separator: "\n")
return circularText
}
In our view we embed the function like this:
#State var text = "your text"
#State var CircularText = String()
// body:
ZStack {
Circle().frame(width: 200)
Text(CircularText)
.multilineTextAlignment(.center)
}
.onAppear(perform: {
CircularText = createCircularText(text: text, verticalSpacing: 3.0, circleDiameter: 200, FontSize: 12)
})
I just tested it with the font size 12, but it should perform with any other as well quite ok.
By changing the diameter you will notice that the text becomes a bit oval, to fix that please change the verticalSpacing. As smaller the number gets, as taller the circle gets, and the other way around. But feel free to fix that issue.
Also, please make sure that your text is long enough.
Is it possible to dynamically combine a currency symbol to some text before displaying it via TextField on a single text line? Currently my currency symbol is on the line above the TextField text "Enter Amount > "
This code produces the errors: "Cannot assign to property: 'self' is immutable" and "Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols."
I would like to use something like this in a form.
struct ContentView: View {
#State private var moneyS: String = ""
var curr: String = ""
var curSymb: Int = 2
var mxx: String = ""
var body: some View {
Text("Blah Blah Blah")
switch curSymb {
case 1:
curr = "$"
case 2:
curr = "€"
default:
curr = "£"
}
mxx = "Enter Amount > " + curr
TextField(mxx, text: $moneyS)
.keyboardType(.decimalPad)
}
}
Yes, it certainly is possible. By default, it uses VStack-like layout and places all the views vertically. We can use HStack (horizontal stack) to align your text views horizontally.
Here is the updated version:
struct ContentView: View {
...
var curr: String {
switch curSymb {
case 1: return "$"
case 2: return "€"
default: return "£"
}
}
var body: some View {
HStack(alignment: .firstTextBaseline) { // note that we use firstTextBaseline in order to keep the text aligned even if you decided to have different font for every part of the text
Text("Blah Blah Blah")
TextField("Enter Amount > " + curr, text: $moneyS)
.keyboardType(.decimalPad)
}
}
}
Also, note, that I have also moved the curr calculation to a variable so that the body code stays small.
Thanks for the assistance. I like the concise compact code.
Sorry for not being clear about the blah blah blah line. There are other lines of text included in a VStack but just "Enter Amount > " and the currency symbol in the HStack.
.currency(code: "INR")
You can set country code dynamically.
struct ExchangeRateView: View {
#State var currencyFromInput:Double = 0.1
var body: some View {
TextField("Enter Amount", value: $currencyFromInput, format: .currency(code: "INR"))
}
}
I am having a TextField which allows the user to enter time and I have used RegValidator to validate. Currently, I need to fill the particular position with "0" as soon as the user clicks on backspace. Following is the code:
TextField {
id:textField
text:"11:11:11"
width:200
height:80
font.pointSize: 15
color:"white"
inputMask: "99:99:99"
validator: RegExpValidator { regExp: /^([0-1\s]?[0-9\s]|2[0-3\s]):([0-5\s][0-9\s]):([0-5\s][0-9\s])$ / }
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
inputMethodHints: Qt.ImhDigitsOnly
}
when user clicks on backspace
you mean just hitting the backspace key? Then it would be something like this :
TextField {
..
Keys.onBackPressed: text = "00:00:00"
}
EDIT
in order to reset just one of the numbers where the cursor is, you could do something like the following. I did not test it and maybe some of the indices are wrong, but you get the idea
TextField {
..
Keys.onBackPressed: {
var index = cursorPosition
var char = text.charAt(index)
if(char != ":"){
text = text.substr(0, index) + "0"+ text.substr(index);
}
}
}
http://doc.qt.io/qt-5/qml-qtquick-controls-textfield.html#cursorPosition-prop
In practice you can use displayText porperty of TextInput, like this:
TextInput {
id: textInput
inputMask: "00:00:00;0"
onDisplayTextChanged: {
// when user backspacing or deleting some character,
// this property become as visible value: "03:01:35"
// but text property has value: "03:1:35"
console.log(displayText);
}
}