Instantly format a UITextView using AttributedString - ios

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

Related

How to underline a substring in a Text in SwiftUI?

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")
}

Structuring UIViewControllers with images inside of text

I have to create an application that has 5 UIViewControllers, each with big text and images inside the text. What I have done so far was to add a UITextView in the UIViewController and load with an rtf file the whole text (every text is big).
Now I have to add images inside the text in some places. What do you propose that could be the best way to construct it? I tried to add the image inside the the rtf file but it is not working properly. Since the text is too big I did not want to add the text manually by typing it. Also, I have a top bar menu that slides the view to each content, that is why I had to have only one UITextView. I am looking for a best solution.
What about adding your texts with images on UIWebView with loading these texts by wrapping them into html?
You can also add javascript callbacks which you will be able to handle in swift or obj-c by adding JavascriptCore.framework to your build phases:
Add button in your code:
<button text="Close" onclick="javascript:callSwiftCode()">Call swift code</button>
And in your UIWebViewDelegate class:
func webViewDidFinishLoad(webView: UIWebView) {
let context: JSContext = webView.valueForKeyPath("documentView.webView.mainFrame.javaScriptContext") as! JSContext
let codeClosure: #convention(block) ()->() = { ()->() in
print ("This is callback from javascript you can add your code in this closure")
}
let casted: AnyObject = unsafeBitCast(codeClosure, AnyObject.self) as AnyObject
context.setObject(casted, forKeyedSubscript: "callSwiftCode")
}
You can achieve this using NSAttributedString and NSTextAttachment. Attributed strings are strings with formatting attached(bold, italics, colors, etc), but you can also attach the images inside attributed strings, and they just get drawn right along with the text. Below example might help you understand:
//Create a mutable attributed string so that we could append everything to it.
let bigText = NSMutableAttributedString(string: "Your text starts here")
//Create a NSTextAttachment
let image1Attachment = NSTextAttachment()
image1Attachment.image = UIImage(named: "image1.png")
// wrap the attachment in its own attributed string so we can append it
let image1String = NSAttributedString(attachment: image1Attachment)
// add the NSTextAttachment wrapper to our full string, then add some more text.
bigText.appendAttributedString(image1String)
bigText.appendAttributedString(NSAttributedString(string: "End of text"))
// Then set this bigText to your label's attributedText property.
yourLabel.attributedText = bigText

How to change UITextView Hyperlink selection background color?

I am using a UITextView to make hashtags selectable. It detects taps perfectly. My issue is with the selection color. It looks black and I wanted the text to fade a bit on selection instead.
Here's what I've got now:
I've tried changing the tintColor, the NSForegroundColorAttributeName, and the NSBackgroundColorAttributeName but it doesn't work.
There is no documented property for the selected or highlighted color of detected links, but you should be able to achieve the same effect by overriding the delegate method textView:shouldInteractWithURL:inRange: and changing the color yourself.
From the UITextViewDelegate protocol reference:
The text view calls this method if the user taps or long-presses the
URL link. Implementation of this method is optional. By default, the
text view opens the application responsible for handling the URL type
and passes it the URL. You can use this method to trigger an
alternative action, such as displaying the web content at the URL in a
web view within the current application.
The last parameter is an NSRange object called characterRange, which represents the character range containing the tapped URL (or hashtag). Using that range, you should be able to add attributes such as NSForegroundColorAttributeName so as to change only the color of the particular hashtag that was tapped.
You'll probably want to revert any changes on touchesEnded and touchesCancelled.
Alternatively, you could make your own subclass and manually handle the above.
Although there is no public API to do this, I was curious and decided to dig through the private header for UITextField. I found that there is a class method on UITextField _sharedHighlightView which returns an instance of the private _UIHighlightView class. This is the class in charge of the highlighting. Swizzling _sharedHighlightView and changing its color will allow you to change the color of any data-detected links:
WARNING: This is a hack that uses method swizzling and private APIs/properties.
class MyTextView: UITextView {
var newHighlightView: AnyObject?
func changeHighlight() {
self.newHighlightView = UITextView.performSelector(Selector("_sharedHighlightView")).takeUnretainedValue()
self.newHighlightView?.setValue(UIColor.redColor().colorWithAlphaComponent(0.6), forKey: "_color")
let originalHighlightView = class_getClassMethod(MyTextView.self, Selector("_sharedHighlightView"))
let newHighlightView = class_getClassMethod(MyTextView.self, #selector(MyTextView.swizzleHighlightView))
method_exchangeImplementations(originalHighlightView, newHighlightView)
}
func swizzleHighlightView() -> AnyObject {
return self.newHighlightView!
}
}
In your View Controller:
let textView = MyTextView(frame: CGRect(x: 0, y: 40.0, width: 200.0, height: 200.0))
textView.dataDetectorTypes = .All
textView.editable = false
textView.text = "Some text. http://www.google.com/"
textView.changeHighlight()
self.view.addSubview(textView)
Result:
This could probably be cleaned up a little further by not force-unwrapping any optionals.
Work around for this can be done, you can use UIButton instead of textview. By using button you can have the same effect.

UiTextView change font size not working

i just want to modify the size of character of the UITextView. until now is possible to attach the string, but i Try to change the dimension: is not possible.
As i searched into the Forum i found that some people got it selecting Editable and deselecting it. Other people got it by selecting Selectable from the View properties. Then i tried this way... no way to change. Only Plain text.
import UIKit
#objc(TextControllerSwift) class TextControllerSwift: UIViewController {
var selectedMovie: String?
var textPlaying: String?
#IBOutlet weak var textMuseum: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
realPlay()
}
func playText(textSelect: String) {
textPlaying = textSelect
}
//Giving the time to Segue method to wakeup.
func realPlay(){
var textRoom: String?
//reading the file .txt
let path = NSBundle.mainBundle().pathForResource(textPlaying, ofType:"txt")
if (path != nil){
do {
textRoom= try String(contentsOfFile: path!, encoding: NSUTF8StringEncoding)
//here i'm getting the string.
}
catch {/* ignore it */}
//i tried it these options...
textMuseum.editable=true
textMuseum.selectable=true
textMuseum!.text=""
//Got the text, now i put into UiView
textMuseum.font = UIFont(name: "Arial-Unicode", size: 50)
textMuseum.text=textMuseum.text.stringByAppendingString(String(textRoom!))
}}}
hmmm where am i getting wrong?
because i changed the textMuseum font. Should i free some Costraint on the UITextView Object put in the StoryBoard? also with the editable and selectable removed the result is the same. why?
thank you for every help.
EDIT:
Added Git Repository - No Working Video as i deleted this part. To see the problem just click on uisegmented "testo" and select play or the table.
https://github.com/sanxius/TryText.git
After reviewing your source code:
Make UITextView selectable: textMuseum.selectable = true
If you want to use custom font then do not forget it's file name to add it to Info.plist Fonts provided by application array.
Use existing font name. There is no font with name Arial-Unicode. Arial-Unicode.ttf is file name not font name.
You can find your font name by listing all loaded fonts with:
for familyName in UIFont.familyNames() {
for fontName in UIFont.fontNamesForFamilyName(familyName) {
print(fontName)
}
}
Also iOS has built-in Arial font that can be loaded by UIFont(name: "ArialMT", size: 50). So you do not need to add your Arial-Unicode.ttf.

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