How to underline a substring in a Text in SwiftUI? - ios

Is there a way to underline online a specific substring inside Text view in Swift UI?
// Code bellow will underline the ENTIRE string 😢
// Any way to underline on a specific substring?
Text("You must be invited by an....")
.underline()
Here is specifically the UI I am trying to build:

Text(..) views have a + operator that combines texts. Haven't looked how this magic works yet, but it does the job really well.
Text("Regular text... ")
+ Text("underlined text!")
.underline()
There is also as second way of doing this as well using a underlined() function on string:
Text("Regular text... " + "undelined text!".underlined())
Super cool!

What worked for me was using String Interpolation with other Text objects.
e.g.
Text("This sentence is not underlined. \(Text("But this sentence is underlined.").underline())")
Using this strategy I was able to interpolate three different underlined Text objects within the original Text object without the compiler complaining.
Hope this helps!

There are several ways to achieve it. I will mention using AttributedString()
func getAttributedString(string: String) -> AttributedString {
var attributedString = AttributedString(string)
attributedString.font = .body.bold()
attributedString.underlineStyle = .single
return attributedString
}
Here, in the function I have also added bold along with underline, you can add more styles. This can stay in the ViewModel class and call from View like this:
Text("You must be invited by an...." + model. getAttributedString(string: "apply to be on the waitlist"))
Or there is another way, you can create another function in ViewModel that would do what I have shown above like this.
Text(model.getString()) // put this in View
func getString() -> AttributedString {
return "You must be invited by an...." + getAttributedString(string: "apply to be on the waitlist")
}

Related

SwiftUI: does modifier create a new view or return a modified view?

I'm reading SwiftUI materials and it's said that view modifiers for example:
struct ByeView: View {
var body: some View {
Text("Bye bye, world!")
.font(.headline)
}
}
creates a new view with .headline font and returns the view.
So I wonder if it's more like:
func font(_ font: UIFont) -> Text {
Text(text, font: font)
}
rather than:
func font(_ font: UIFont) -> Text {
self.font = font
return self
}
I feel for event modifiers, since they may not have to modify the view, there's no need to "create a new view with modified aspects", but not sure about the modifiers that do adjust the views.
Thanks!
First, note that an implementation such as
func font(_ font: UIFont) -> Text {
self.font = font
return self
}
does not compile. font would need to be mutating for this to work, but it isn't mutating. That said, this would have compiled:
self.someReferenceType.font = font
But this has another problem. This means that font now has a side effect:
var body: some View {
let x = Text("foo")
x.font(.headline)
return x // this should have non-headline font, but if the
// implementation above were used, it would have headline font
}
So I think it is very likely that the actual implementation involves calling the initialiser, rather than return self. This also matches the wording in the documentation's wording that a "new view" is "created".
For modifiers that return some View, you can check the type they return using type(of:). You will most likely see that the type they return is different from self, which means they definitely do not return self! After all, that's one of the reasons why the opaque type some View is used in the signature - to hide the internal types Apple used to implement these modifiers.
// doesn't work in a playground for some reason
// make a .swift file, compile and run
let x = Text("foo").onAppear {}
print(type(of: x))
For me, this prints: ModifiedContent<Text, _AppearanceActionModifier>, not Text, so clearly a new view is created here. (See also ModifiedContent)
TLDR
We can't know for sure as the implementation detail is hidden.
My point of view
I might be wrong but I think it's possible that neither of modifying an instance of a View or assigning to a property that you have described are actually happening, but we can't know for sure since the implementation is private.
Since SwiftUI is a declarative framework, where you describe what you want to get and the OS takes care of that, it could be even that by writing:
SomeView()
.padding()
.background(Color.red)
.cornerRadius(8)
Internally it could become just about any representation such as JSON that the parser could read.
Essentially no instance of any object (apart from the description) representing a View, Color or CornerRadius could exist before its initialization based on the description and current state, thus there would be no instance holding properties (such as font) that could be assigned or altered before the final View is initialized.

Allow ONLY Letters in Keyboard - Jetpack Compose

I'm trying to create a TextField in Jetpack Compose for users to enter their first and last name.
Is there a way to allow ONLY letters and whitespaces ([a-zA-Z\s]) as input? I've tried every single option available, including KeyboardType.Text, KeyboardType.Ascii, KeyboardType.Uri, but they all show numbers!
Am I missing something obvious? Honestly, I'm kind of shocked that Google doesn't offer this option out of the box.
Try this:
val pattern = remember { Regex("[a-zA-z\\s]*") }
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = {
if (it.matches(pattern)) {
text = it
}
}
)
In Android it's not possible to show a keyboard containing only letters (and it's not related to compose). Keyboard apps don't support it either.
There are other kinds of keyboards (like number-only keyboard), but the view of the keyboard is still controlled by the keyboard app.
It's best to filter the given input based on the needed criteria. For example:
onValueChange = {
text = it.letters()
}
private fun String.letters() = filter { it.isLetter() }

Instantly format a UITextView using AttributedString

I'm developing a macOS rich-text editor that applies pre-defined style for each line of the text view.
To format the lines, I'm using NSAttributedString, then, I'm inserting that string into my UITextView. To make things easier, I'm using a tool called SwiftRichString.
My code looks like below. It's straight-forward and works fine.
import Cocoa
import SwiftRichString
import AppKit
class ViewController: NSViewController {
#IBOutlet var textView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
// Format the string
let style = Style {
$0.font = NSFont(name: "Arial", size: 20)
$0.color = NSColor.black
$0.alignment = .center
}
let attributedText = "Hello World!".set(style: style)
// Add formatted string to the text view
textView.textStorage?.append(attributedText)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Current situation:
User is typing a formatted line. Then when user hits Return and types something, format of the new line returns back to the default style of the UITextView.
What I want:
User is typing a certain formatted line, then he hits Return. The next line should be formatted to another pre-defined style on-the-go.
Example:
User is typing the Title line. Current style is (Arial, bold, 20pt).
He hits Return.
Next line should be styled as Normal Text using a pre-defined style (Arial, 12pt).
Important Note:
In my above code, I was able to format the line easily because it's hard-coded. My real issue is, how can I instantly format the next line, because the next line will be entered by the user. The style should be applied to the next line before user begins writing it.
Okay, I just figured out how to use typingAttributtes to solve this question (thanks to #Larme for the hint).
// Define next attributes
let attributes: [NSAttributedString.Key: Any] = [
.foregroundColor: NSColor.red,
.font: NSFont(name: "Arial", size: 12)!,
]
// Assign attributes to the text view typing attributes
textView.typingAttributes = attributes
Very easy!
Pardon off topic. If you're making a text editor, you may consider using a table view, where each text line is a cell - this is extra work for you as a programmer but it'll boost the performance significantly. That's how Xcode editor is built.
Maybe you might use optional func textViewDidChange(_ textView: UITextView)
https://developer.apple.com/documentation/uikit/uitextviewdelegate/1618599-textviewdidchange
and after the change, just parse the text, find new lines, split them and apply all styling you want

How to stop typing characters in textfield when text is the same size as textfield?

I want to find a way where when the characters completely fill up a textfield, to let the user type but it doesn't add any new characters to the textfield (like Snapchat). I've only see this done in Objective-C and I wasn't able to translate it into Swift. Can someone please help? Thanks!
You could do like this:
#IBAction func textField_EditingChanged(sender: AnyObject) {
//Set your font and add attributes if needed.
let stringSize: CGSize = textField!.text!.sizeWithAttributes([NSFontAttributeName: textField.font!])
if (stringSize.width > textField.bounds.width - 20){
textField.deleteBackward()
}
}
I added - 20 since I wanted to be sure that no small chars is added like "i", "1" etc.

iOS Swift - Add target(selector) to string

Is there a way to add a target to a specified word from an UITextView? For example.
I want to add a target to a hash-tagged word from a UITextView.
I'm using a function to get an array with the hash-tagged words from the textview, but I don't know how to add them a target, or a tap-gesture.
You can use an attributed string to set the text in the text view. That attributed string uses link attributes (NSLinkAttributeName with a URL value of your choice) in the range of your target hash-tagged words. You need to search the text for your hash-tagged words and add the appropriate link attributes. You probably want to create the link URLs to have a custom scheme and include information about the hash-tagged word. When one of the links is tapped you get a delegate callback from the text view.
I had the same question recently and I found a solution that fit my needs. It's not perfect but maybe it will help:
I decided to go for a whole text button with specific style around my target element. In a user experience way, user may be inclined to touch the styled word.
#IBOutlet weak var stringBtn: UIButton!
override func viewWillAppear(animated: Bool) {
let string = "string with #tag" as NSString
var attributedString = NSMutableAttributedString(string: string as String)
let firstAttributes = [NSUnderlineStyleAttributeName: 1, NSForegroundColorAttributeName: UIColor.whiteColor()]
attributedString.addAttributes(firstAttributes, range: string.rangeOfString("#tag"))
stringBtn.setAttributedTitle(attributedString, forState: .Normal)
//Add target to your button
}

Resources