How to use Attributed String in SwiftUI
iOS 15 and Swift 5.5
Text
now supports markdown and also you can create custom attributes:
You can even get defined attributes remotely like:
iOS 13 and 14
You can combine multiple Text
objects together with a simple +
operator and that will handle some of the attributions:
Each one can have multiple and specific modifiers
A fully supported fallback!
Since it doesn't support directly on Text
(till iOS 15), you can bring the UILabel
there and modify it in anyway you like:
Implementation:
struct UIKLabel: UIViewRepresentable {
typealias TheUIView = UILabel
fileprivate var configuration = { (view: TheUIView) in }
func makeUIView(context: UIViewRepresentableContext<Self>) -> TheUIView { TheUIView() }
func updateUIView(_ uiView: TheUIView, context: UIViewRepresentableContext<Self>) {
configuration(uiView)
}
}
Usage:
var body: some View {
UIKLabel {
$0.attributedText = NSAttributedString(string: "HelloWorld")
}
}
How to use an NSAttributedString with a ScrollView in SwiftUI?
The reason is that SwiftUI ScrollView
requires defined content size, but used UITextView
is itself a UIScrollView
and detects content based on available space in parent view. Thus it happens cycle of undefined sizes.
Here is a simplified demo of possible approach how to solve this. The idea is to calculate content size of UITextView
and pass it to SwiftUI...
struct TextWithAttributedString: UIViewRepresentable {
@Binding var height: CGFloat
var attributedString: NSAttributedString
func makeUIView(context: Context) -> UITextView {
let textView = UITextView(frame: .zero)
textView.isEditable = false
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
textView.attributedText = self.attributedString
// calculate height based on main screen, but this might be
// improved for more generic cases
DispatchQueue.main.async { // << fixed
height = textView.sizeThatFits(UIScreen.main.bounds.size).height
}
}
}
struct NSAttributedStringView: View {
@State private var textHeight: CGFloat = .zero
var body: some View {
ScrollView {
TextWithAttributedString(height: $textHeight, attributedString: NSAttributedString(string: exampleText))
.frame(height: textHeight) // << specify height explicitly !
}
}
}
backup
Text alignment won't work using AttributedString SwiftUI 3
First Thank you Omer Tekbiyik
you give me the idea how I can make it work.
The issue with TextAlignment
is it depend on trailing and leading
not left and right , that mean if your device on LTR Language
the leading will be left and trailing will be right
but on RTL it will be the opposite leading will be right and trailing will left.
So what I dd, first I check the text language itself if it's RTL or LTR by depended on NaturalLanguage
, then I check the device layout using layoutDirection
it's like using UIApplication.shared.userInterfaceLayoutDirection
but on SwiftUI code.
Notice , this line will work if the text content one line of text.
.frame(maxWidth: .infinity, alignment: naturalAlignment)
but this line will work with text that continue multiple lines of text.
.multilineTextAlignment(naturalTextAlignment)
import SwiftUI
import NaturalLanguage
struct NaturalText: View {
@Environment(\.layoutDirection) private var layoutDirection
var text : String
var body: some View {
Text(text)
.frame(maxWidth: .infinity, alignment: naturalAlignment)
.multilineTextAlignment(naturalTextAlignment)
}
private var naturalAlignment: Alignment {
guard let dominantLanguage = dominantLanguage else {
// If we can't identify the strings language, use the system language's natural alignment
return .leading
}
switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
case .leftToRight:
if layoutDirection == .rightToLeft {
return .trailing
} else {
return .leading
}
case .rightToLeft:
if layoutDirection == .leftToRight {
return .trailing
} else {
return .leading
}
case .natural:
return .leading
@unknown default:
return .leading
}
}
private var naturalTextAlignment: TextAlignment {
guard let dominantLanguage = dominantLanguage else {
// If we can't identify the strings language, use the system language's natural alignment
return .leading
}
switch NSParagraphStyle.defaultWritingDirection(forLanguage: dominantLanguage) {
case .leftToRight:
if layoutDirection == .rightToLeft {
return .trailing
} else {
return .leading
}
case .rightToLeft:
if layoutDirection == .leftToRight {
return .trailing
} else {
return .leading
}
case .natural:
return .leading
@unknown default:
return .leading
}
}
private var dominantLanguage: String? {
let firstChar = "\(text.first ?? " ")"
return NLLanguageRecognizer.dominantLanguage(for: firstChar)?.rawValue
}
}
How to use it
struct ContentView: View {
var body: some View {
VStack {
NaturalTextView(text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.")
.padding()
NaturalTextView(text: "هذا النص هو مثال لنص يمكن أن يستبدل في نفس المساحة، لقد تم توليد هذا النص من مولد النص العربى، حيث يمكنك أن تولد مثل هذا النص أو العديد من النصوص الأخرى إضافة إلى زيادة عدد الحروف التى يولدها التطبيق.إذا كنت تحتاج إلى عدد أكبر من الفقرات يتيح لك مولد النص العربى زيادة عدد الفقرات كما تريد، النص لن يبدو مقسما ولا يحوي أخطاء لغوية، مولد النص العربى مفيد لمصممي المواقع على وجه الخصوص، حيث يحتاج العميل فى كثير من الأحيان أن يطلع على صورة حقيقية لتصميم الموقع.ومن هنا وجب على المصمم أن يضع نصوصا مؤقتة على التصميم ليظهر للعميل الشكل كاملاً،دور مولد النص العربى أن يوفر على المصمم عناء البحث عن نص بديل لا علاقة له بالموضوع الذى يتحدث عنه التصميم فيظهر بشكل لا يليق. هذا النص يمكن أن يتم تركيبه على أي تصميم دون مشكلة فلن يبدو وكأنه نص منسوخ، غير منظم، غير منسق، أو حتى غير مفهوم. لأنه مازال نصاً بديلاً ومؤقتاً.")
.padding()
Spacer()
}
}
}
The result
you can use
.environment(\.layoutDirection, .rightToLeft)
to force the layout to be RTL you will notice it won't change because it depend on the text language itself
Save a UITextView attributed string to file in SwiftUI
There are several mistakes in the code.
- You have set the wrong delegate. So your delegate method not worked.
Give self delegate, not-self. like
func setupTextView() {
// Setup Text View delegate
textView.delegate = delegate
- Use
NSMutableAttributedString
in the delegate.
func textViewDidBeginEditing(_ textView: UITextView) {
self.parent.document = NSMutableAttributedString(attributedString: textView.attributedText)
self.parent.onEditingChanged()
}
func textViewDidChange(_ textView: UITextView) {
self.parent.document = NSMutableAttributedString(attributedString: textView.attributedText)
}
func textViewDidEndEditing(_ textView: UITextView) {
self.parent.document = NSMutableAttributedString(attributedString: textView.attributedText)
self.parent.onCommit()
}
- Remove static text from
override func draw(_ rect: CGRect)
this line overrides the existing text.
override func draw(_ rect: CGRect) {
super.draw(rect)
setupTextView()
// Set tap gesture
let tap = UITapGestureRecognizer(target: self, action: #selector(didTapTextView(_:)))
tap.delegate = self
textView.addGestureRecognizer(tap)
// create paragraph style
self.paragraphStyle.headIndent = 108
// create attributes
self.attributes = [
.foregroundColor: UIColor.red,
.font: UIFont(name: "Courier", size: 12)!,
.paragraphStyle: paragraphStyle,
]
}
Note: Remove other code from draw rect and use init or func awakeFromNib()
SwiftUI attributed string from HTML crashes the app
Use This:-
struct HTMLLabel: UIViewRepresentable {
let html: String
func makeUIView(context: UIViewRepresentableContext<Self>) -> UILabel {
let label = UILabel()
DispatchQueue.main.async {
if let attributedText = self.html.convertHtml() {
label.attributedText = attributedText
}
}
return label
}
func updateUIView(_ uiView: UILabel, context: UIViewRepresentableContext<Self>) {}
}
NSAttributedString.DocumentType.html is Only Work with Main Thread That's why you are getting crash
How do I apply my custom attribute in an AttributedString in SwiftUI
Custom Implementations
Custom attributes only serve as data storage, they are not displayed or processed by SwiftUI or UIKit. Additional custom behavior would have to be implemented manually.
Workaround
For behavior as described in the question, stick to extensions.
var str = AttributedString("It's a ******")
let secret = str.range(of: "******")!
str[secret].swap = "secret"
That would display "It's a ******", while still holding your "secret" data in the attributes.
To improve on that, you could create an extension:
extension AttributedString {
func swapping(_ value: String, with new: Character) -> AttributedString {
let mutableAttributedString = NSMutableAttributedString(self)
let range = mutableAttributedString.mutableString.range(of: value)
let newString = String(repeating: new, count: range.length)
// Use replaceCharacters(in:with:) to keep all original attributes
mutableAttributedString.replaceCharacters(in: range, with: newString)
// Set the "secret" data
var str = AttributedString(mutableAttributedString)
str[str.range(of: newString)!].swap = value
return str
}
}
var str = AttributedString("It's a secret")
.swapping("secret", with: "*")
print(str)
The print output:
It's a {
}
****** {
swap = secret
}
Related Topics
How to Make a Swiftui List Scroll Automatically
How to Create Usdz File Using Xcode Converter
Load a Uiview from Nib in Swift
Photopicker Discovery Error: Error Domain=Pluginkit Code=13
Swift Dictionary Get Key For Value
How to Create Array of Unique Object List in Swift
Closure With Generic Parameters
Deleting a Row from a Uitableview in Swift
How to Atomically Increment a Variable in Swift
How to Fix Cocoapod .Modulemap File Not Found
Sending Json Array Via Alamofire
Multiple Sheet(Ispresented:) Doesn't Work in Swiftui
Why Can't I Pass a Protocol.Type to a Generic T.Type Parameter