Get NSAttributedString from UITextField within NSRange - swift

Let's assume I have this code:
let string = "This is a <b>bold</b> text"
let data = Data(string.utf8)
let attributedText = try! NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
textField.attributedText = attributedText
Result: This is a bold text
Now my textField is editable, and let's say I have my cursor here:
This is a bold tex|t
You can see my cursor between x and t
I have a variable called cursorPosition where I store the cursor position anytime it changes and in this current case, the value of cursorPosition will be 18
Now my question: Is there a possible way I can get all the NSAttributedString using NSRange. So I want to get all NSAttributedString before my cursor position using 0..<cursorPosition and the result will be:
This is a <b>bold</b> tex
And not
This is a bold tex

You can get an attributed substring of an NSAttributedString using the attributedSubstring method.
let attrStr: NSAttributedString = ... // some attributed string
let range = NSRange(location: 0, length: someLength)
let attrSubStr = attrStr.attributedSubstring(from: range)
But this is not going to give you an HTML string. This is going to give you another NSAttributedString representing the desired range. If you want HTML then you need to take extra steps to convert the attributed string into HTML.

Related

Remove HTML elements from NSAttributedString

let string = "To find out more, click <span>here</span> to go to our help page"
let attributedString = NSAttributedString(string: string)
uitextview.attributedText = attributedString
Now the string constant is dynamically created so I can't manipulate it's content directly. And I'm displaying the attributedString in a UITextView and after the user made some changes, I want to edit the attributedString and remove the a tag from the attributedString
I want to remove the a tag and it's HTML contents except it's text. And I can't do something like this:
attributedString.string
Because there are other HTML elements that might be in the attributedString which might loose it's attributes
What I have done so far:
let range = NSRange(location: 12, length: 14)
let attributedText = uitextview.attributedText!
let subAttrText = attributedText.attributedSubstring(from: range) // "more, click he"
let subText = subAttrText.string
let mutableString = NSMutableAttributedString(attributedString: attributedText)
mutableString.replaceCharacters(in: range, with: subText)
uitextview.attributedText = mutableString
Expectation: To find out re to go to our help page
Result: To find out <span>re</span> to go to our help page
How can I make the link text to be converted to just plain text?
So I found a way to figure it out
let range = NSRange(location: 12, length: 14)
let attributedText = uitextview.attributedText!
let subAttrText = attributedText.attributedSubstring(from: range) // "more, click he"
let subText = subAttrText.string
let mutableString = NSMutableAttributedString(attributedString: attributedText)
mutableString.deleteCharacters(in: range)
mutableString.insert(subText, at: range.location)
uitextview.attributedText = mutableString

Saving attributed string to RTF file no longer saving attributes

After a holiday related nightmare with version control, I can't work out why the below is no longer working.
I'm trying to save attributed text to an .rtf file which is then read later.
This was working but now isn't for some reason I can't understand after a couple of hours of going through line by line.
I'm trying to save the contents of a textview to a .rtf file with its attributes like this :
func saveExistingScene() {
let textView = textViewOutlet
let fileURL = getFileURL() //shared function to get the URL of an existing RTF file: there’s a different one for “setFileURL” when saving a new scene
textView?.attributedText = NSAttributedString(string: (textView?.text)!, attributes: nil)
if let attributedText = textView?.attributedText {
let documentAttributes: [NSAttributedString.DocumentAttributeKey: Any] = [.documentType: NSAttributedString.DocumentType.rtf]
do {
let rtfData = try attributedText.data(from: NSRange(location: 0, length: attributedText.length), documentAttributes: documentAttributes)
let rtfString = String(data: rtfData, encoding: .utf8) ?? ""
try rtfString.write(to: fileURL, atomically: true, encoding: .utf8)
print("Saved an update")
} catch {
print("failed to save an update with error: \(error)")
}
}
The text can be changed by the user to bold and italics when they press CMD+B or CMD+I as per here (haven't put them all as don't think there is a need)
let makeBold: [String : Any] = [
NSAttributedStringKey.font.rawValue: UIFont(name: "AvenirNext-Bold", size: 15.0)!, NSAttributedStringKey.foregroundColor.rawValue: UIColor.white
]
which is enabled via key commands. This works fine and is reflected in the UITextView properly.
The generic colour of the text in the textview is set as white in viewDidLoad by
textViewOutlet.UIColour = white
The file is saving fine and creating an .rtf file but the attributes are all being stripped away and leaves it in helvetica, in black with no bold or italics.
This is so frustrating because it was working at some point in the few days before going on holiday! Help appreciated!
UPDATE : I've added a print statement below the 'if let' and it is printing out in RTF format but is defaulting to Helvetica size 12 rather than the attributes that are actually in the textview (white, avenir, mix of bold/italic/normal etc). Does that help anyone!?

How to make the text as a hyperlink in NSTextView programmatically? Swift 4, Xcode 9.4

How to make the text as a hyperlink in NSTextView programmatically?
Like this:
Just click here to register
or like this:
Just http://example.com to register
I found this solution but it works only for iOS, not for macOS
Try this:
let attributedString = NSMutableAttributedString(string: "Just click here to register")
let range = NSRange(location: 5, length: 10)
let url = URL(string: "https://www.apple.com")!
attributedString.setAttributes([.link: url], range: range)
textView.textStorage?.setAttributedString(attributedString)
// Define how links should look like within the text view
textView.linkTextAttributes = [
.foregroundColor: NSColor.blue,
.underlineStyle: NSUnderlineStyle.styleSingle.rawValue
]
If you needed set Font Size for links use this ->
let input = "Your string with urls"
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
let attributes = [NSAttributedString.Key.font: NSFont.systemFont(ofSize: 15)]
let attributedString = NSMutableAttributedString(string: input, attributes: attributes)
if matches.count > 0{
for match in matches {
guard let range = Range(match.range, in: input) else { continue }
let url = input[range]
attributedString.setAttributes([.link: url, .font: NSFont.systemFont(ofSize: 15)], range: match.range)
}
}
youTextView.textStorage?.setAttributedString(attributedString)
Swift 5 / macOS 12 solution to make the link clickable and have the same font any NSTextField's you display in the same window:
Define some NSTextView IBOutlet (mine is called tutorialLink):
#IBOutlet weak var tutorialLink : NSTextView!
Then here's the styling code in one function:
fileprivate func initTutorialLink() {
let attributedString = NSMutableAttributedString(string: "Here's a tutorial on how to set up a Digital Ocean S3 account.")
// Set font for entire string
let fontAttributes = [NSAttributedString.Key.font: NSFont.systemFont(ofSize: 13)]
let range2 = NSRange(location: 0, length: 61)
attributedString.setAttributes(fontAttributes, range: range2)
// Set url and same font for just the link range
let range = NSRange(location: 21, length: 40)
let url = URL(string: tutorialUrl)!
attributedString.setAttributes([.link: url, NSAttributedString.Key.font:NSFont.systemFont(ofSize: 13)], range: range)
// Store the attributed string
tutorialLink.textStorage?.setAttributedString(attributedString)
// Mark how link text should be colored and underlined
tutorialLink.linkTextAttributes = [
.foregroundColor: NSColor.systemBlue,
.underlineStyle: NSUnderlineStyle.single.rawValue
]
// Give non-linked text same color as other labels
tutorialLink.textColor = .labelColor
}
In IB, you obviously need to connect your NSTextView (called Scrollable Text View in the Objects Library) to the IBOutlet. Less obviously, you need to click the box to make the NSTextView Selectable. I don't know why exactly, but if it's not Selectable then your link will not react when clicked.

How to get the selected string from a NSTextView in Swift?

How to do to get the selected string from a NSTextView in Swift?
// create a range of selected text
let range = mainTextField.selectedRange()
// this works but I need a plain string not an attributed string
let str = mainTextField.textStorage?.attributedSubstring(from: range)
Maybe I have to add an intermediate step where I get the full string and then apply the range on it?
What about
let str = mainTextField.text.substring(with: range)
Edit:
This should work now:
let range = mainTextField.selectedRange() // Returns NSRange :-/ (instead of Range)
let str = mainTextField.string as NSString? // So we cast String? to NSString?
let substr = str?.substring(with: range) // To be able to use the range in substring(with:)
The code snippet in the Swift 5. That's not very hard but repeated
let string = textView.attributedString().string
let selectedRange = textView.selectedRange()
let startIndex = string.index(string.startIndex, offsetBy: selectedRange.lowerBound)
let endIndex = string.index(string.startIndex, offsetBy: selectedRange.upperBound)
let substring = textView.attributedString().string[startIndex..<endIndex]
let selectedString = String(substring)
In case anyone is having issues with the selectedRanges() not returning the proper range for an NSTextView, make sure that you have made your NSTextView selectable. I don't know if it's not selectable by default but I had to enable it;
textView.isSelectable = true
I was trying this in Swift 5 using #user25917 code sample above and I couldn't get the ranges no matter what. I was getting a visual confirmation the text was selected through highlighting which threw me off. This was driving me mad for a few hours.
This may be helpfull for you :
let textView = NSTextView(frame: NSMakeRect(0, 0, 100, 100))
let attributes = [NSForegroundColorAttributeName: NSColor.redColor(),
NSBackgroundColorAttributeName: NSColor.blackColor()]
let attrStr = NSMutableAttributedString(string: "my string", attributes: attributes)
let area = NSMakeRange(0, attrStr.length)
if let font = NSFont(name: "Helvetica Neue Light", size: 16) {
attrStr.addAttribute(NSFontAttributeName, value: font, range: area)
textView.textStorage?.appendAttributedString(attrStr)
}

Swift UITextView with different formatting

Sorry for a basic question, but I'm not sure where to start. Is the following possible in Swift?
In a UITextView (Not a label, as in the possible duplicate), different bits of text having different formatting: for instance, a line of large text in the same UITextView as a line of small text. Here is a mockup of what I mean:
Is this possible in one UITextView?
You should have a look at NSAttributedString. Here's an example of how you could use it:
let largeTextString = "Here is some large, bold text"
let smallTextString = "Here is some smaller text"
let textString = "\n\(largeTextString)\n\n\(smallTextString)"
let attrText = NSMutableAttributedString(string: textString)
let largeFont = UIFont(name: "Arial-BoldMT", size: 50.0)!
let smallFont = UIFont(name: "Arial", size: 30.0)!
// Convert textString to NSString because attrText.addAttribute takes an NSRange.
let largeTextRange = (textString as NSString).range(of: largeTextString)
let smallTextRange = (textString as NSString).range(of: smallTextString)
attrText.addAttribute(NSFontAttributeName, value: largeFont, range: largeTextRange)
attrText.addAttribute(NSFontAttributeName, value: smallFont, range: smallTextRange)
textView.attributedText = attrText
The result: