For the first time, I am working with attributed strings so please bear with me!
I have spent a lot of time on SO looking at various attributed string answers but I'm still not convinced I am doing the right thing and would appreciate any advice.
I have created an NSMutableAttributed String (var attributedText) and a simple UIView with a TextView and 2 buttons which should in theory turn any part of highlighted text in the UITextView to bold or italic.
In ViewDidLoad, I have simply added some dummy text and added a couple of attributes to the text such as line spacing and font color. When the view appears all looks as expected.
If I highlight a portion of text and tap the bold button, it returns an attribute and adds it to attributedText and then to the textView (textView.attributedText = attributedText).
The following is my return function:
func boldText() -> Dictionary<NSAttributedString.Key, Any> {
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 15, weight: .bold),
]
return attributes
}
Now, here's where things get confusing. If I manually type some text in the UTextView and highlight a portion of that text and tap the Bold button, the app will crash due to the text being out of range, this makes sense as my attributedText variable is only holding the previous dummy text I added in ViewDidLoad and not the new text I have typed.
So my question is, how do I add the new text I typed to the attributedText variable without messing up all the other formatting that was already part of that text?
If I just add the textView text to the variable:
attributedText = NSMutableAttributedString(string: "\(textView.text!)" )
Then obviously it simply overwrites anything that was held in the attributedText variable previously which in turn removes all formatting.
Am I on the right lines in the fact that every time change happens in the textView I need to somehow update my attributedText variable or should I be using a different approach altogether?
Related
I have a UITextView. It reads its text string and parses the markdown and displays it on screen using NSAttributedString from AttributedString.
The Problem
When the text is short, it's all good. But when the text is long beyond maybe 2 screen height, it starts to jump up and down. It jumps up on newLine, then down on another newLine, then up on the 3rd newLine, then down on 4th, and repeat.
Another problem is that it messes up my draw(in:rect) method in UITextView subclass that draws gray background rectangles for quote and code blocks. The drawing is based on some individual character locations. So I guess the character locations are also messed up when the UITextView tries to calculate and layout its attributedText.
Some Implementation Details
parsing is done in a separate package.
I used parsed detail to construct a new attributedString
I implemented textViewDidChange(_ textView: UITextView) so that it updates the attributedText upon any new characters input or deletion.
Because changing attributedText so often will always bring the typing cursor to the end of the textView, I track the cursor location before applying the attributes, then give it back to textView so that textView can stay where it was.
func textViewDidChange(_ textView: UITextView) {
let cursor = textView.selectedRange
// the problem will be gone if I comment out applyAttributes()
applyAttributes()
textView.selectedRange = cursor
textView.scrollRangeToVisible(cursor)
}
private func applyAttributes() {
// 1. get parsed tokens
// 2. construct attributedString using the tokens
// 3. give the token to textView.attributedText
// 4. draw textView's rect based on displayed attributedText
}
When I remove step 4, the problem persists.
The problem occurs for both software and hardware keyboards
Any tips on how I can improve my code, my habit, and my form are also appreciated.
Thanks in advance.
I have the below code, that runs in viewDidLoad.
let text = "\n \t What is a Call?"
let attributedText = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 30)])
textLabel.attributedText = attributedText
When it runs, nothing gets set in the label. However, if I set it like this
textLabel.text = text
I see the attributed text? Why doesn't the previous line of code work?
I tried adding your code to a completely clean project and found two potential issues. Maybe you have thought of both of them but I couldn't read that from your question so here goes :)
Number of Lines
It is a bit tricky but when you add a \n in your text, that also means that if you haven't explicitly told your UILabel to take that into consideration, it will only show the first line...which is empty in your case :)
So, try adding
textLabel.numberOfLines = 0
to tell your UILabel to use as many lines as is needed for the text.
Size of Your Label
I don't know how you've set up your label, but it could be that the size reserved for the label is too small for all of it to be shown on the screen. You can use the visual debugger to examine the view hierarchy and see if that can give you any clues.
If I set numberOfLines to 0 and added constraints to the textLabel so it had sufficient space, I was able to see "What is a Call?" in glorious 30 point bold system font on my view so...you're close :)
With a Swift UITextField the default behavior when not editing is to ellipse the most recently entered text if the text is too long to fit in the space allowed for the text field. As shown in this image:
When you begin to edit the text field, the text shifts left to make the most recently entered text visible. As shown in this image:
How can I achieve this effect when I am not editing the text field? I would like the most recently viewed text to always be visible no matter whether or not the user is currently editing the text field. If the text is too long for the space provided for the text field, it should not display the oldest text.
This could be easily achieved using UITextView if that suits other requirements:
let textView = UITextView()
textView.textContainer.maximumNumberOfLines = 1
textView.textContainer.lineBreakMode = .byTruncatingHead
I want to change from proportional to tabulated digits in menu items? It's possible?
Menu without tabulated digits
You need to use the monospaced font like this:
label.font = UIFont.monospacedDigitSystemFont(ofSize: 17, weight: UIFontWeightRegular)
Obviously you can specify whatever font size and weight you want (or get them from the current font descriptor).
You can set the attributedTitle property of a NSMenuItem and provide whatever custom alignment or formatting you want through an NSAttributedString
Good examples of how to do string alignment in an attributed string can be seen here:
Attributed Text Center Alignment
Here is an answer that shows how to add tab stops to an attributed string:
How do I add tab stops to an NSAttributedString
You can even create an attributed string from RTF or HTML:
Creating an NSAttributedString from HTML (or RTF)
I am creating an OSX app with NSTextView inside NSScrollView. Using the FormatMenuItem I can modify the text in that NSTextView to Bold, italic etc. But when I get the values using (NSTextViewOutlet.String) I cannot get the modified string, that is the string contain bold and italic characters.
By using below code solve my problem.and i use NSTextField instead of NSTextView.
let str = txt_outlet.attributedStringValue