How to get the selected string from a NSTextView in Swift? - 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)
}

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

Swift String - How to replace a word with an image in a localized string?

in this app we have a localized string (more than 10 languages) and in this string there is a word "%1$s" which should be replaced by an image (a logo).
Having so many languages means that the word to be replaced (%1$s) is not always in the same position in the string.
In order to replace it with an image I tried this code:
// "%1$s" is contained in mystring to know where we should put the logo image
let myString = NSMutableAttributedString(string: "mylocalizedString")
// create our NSTextAttachment
let logoAttachment = NSTextAttachment()
logoAttachment = UIImage(named: "myLogoImage")
// wrap the attachment in its own attributed string so we can append it
let logoImageString = NSAttributedString(attachment: logoAttachment)
// Here I try to replace "%1$s" with the logo image
myString.mutableString.replacingOccurrences(of: "%1$s", with: logoImageString.string)
myLabel.attributedText = myString
myLabel.font = myFont
myLabel.textColor = myColor
But all I get is the localized string with still the "%1$s" word in there without the image of the logo.
Do I miss something?
Thanks a lot.
Solved as suggested by Larme in the comments:
let range = myString.string.range(of: "%1$s")
let nsRange = NSRange(range, in: myString.string)
myString.replaceCharacters(in:range, with:logoImageString)

Fontsize of attributedString doesn't change

I have an attributedString and want to change only it's fontsize. To do that, I use another method that I found on StackOverflow. For most cases, this is working, but somehow it doesn't change the whole attributedString in one case.
Method to change the size:
/**
*A struct with static methods that can be useful for your GUI
*/
struct GuiUtils {
static func setAttributedStringToSize(attributedString: NSAttributedString, size: CGFloat) -> NSMutableAttributedString {
let mus = NSMutableAttributedString(attributedString: attributedString)
mus.enumerateAttribute(.font, in: NSRange(location: 0, length: mus.string.count)) { (value, range, stop) in
if let oldFont = value as? UIFont {
let newFont = oldFont.withSize(size)
mus.addAttribute(.font, value: newFont, range: range)
}
}
return mus
}
}
Working:
label.attributedText = GuiUtils.setAttributedStringToSize(attributedString: attributedString, size: fontSize)
Not working:
mutableAttributedString.replaceCharacters(in: gapRange, with: filledGap)
label.attributedText = GuiUtils.setAttributedStringToSize(attributedString: mutableAttributedString.replaceCharacters, size: fontSize)
Somehow, the replaced text does not change its size.
Excuse me, but do you sure that your filledGap attributed string has font attribute? Because if it doesn't – this part will not be handled by the enumerateAttribute block.
In this case your fix will be just to set any font to the whole filledGap string, to be sure that it's part will be handled by the enumerateAttribute block.

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.

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: