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 !
}
}
}
UITextView and NSAttributedString is not showing in SwiftUI
This problem is invoked by ScrollView.
My TextView() is wrapped by ScrollView in higher view.
You can find answer in this link -> How to use an NSAttributedString with a ScrollView in SwiftUI?
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")
}
}
SwiftUI: Showing HTML text inside a ScrollView
As WKWebView
has already scroll and you are also wrapped inside the scroll view, so parent scroll view not get the proper size.
You have to disable WKWebView
scrolling. Also, bind the size with webview and update the frame of webview.
Here is the possible demo.
struct AttributedText: UIViewRepresentable {
let htmlContent: String
@Binding var size: CGSize
private let webView = WKWebView()
var sizeObserver: NSKeyValueObservation?
func makeUIView(context: Context) -> WKWebView {
webView.scrollView.isScrollEnabled = false //<-- Here
webView.navigationDelegate = context.coordinator
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.loadHTMLString(htmlContent, baseURL: nil)
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, WKNavigationDelegate {
let parent: AttributedText
var sizeObserver: NSKeyValueObservation?
init(parent: AttributedText) {
self.parent = parent
sizeObserver = parent.webView.scrollView.observe(\.contentSize, options: [.new], changeHandler: { (object, change) in
parent.size = change.newValue ?? .zero
})
}
}
}
For view
@State private var size: CGSize = .zero
var body: some View{
ScrollView {
AttributedText(htmlContent: "<h1>This is HTML String</h1>", size: $size)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, idealHeight: size.height, maxHeight: .infinity)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
Binding with RichTextField (i.e. NSTextView in NSViewRepresentable) - Value resets after redraw, LostFocus, etc
Not sure if it's the correct way to do it, but I got the binding to work with the following code:
import Foundation
import SwiftUI
struct RichTextField: NSViewRepresentable {
typealias NSViewType = NSTextView
@Binding var attributedString: NSAttributedString
var isEditable: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeNSView(context: Context) -> NSTextView {
let textView = NSTextView(frame: .zero)
textView.textStorage?.setAttributedString(self.attributedString)
textView.isEditable = isEditable
textView.delegate = context.coordinator
textView.translatesAutoresizingMaskIntoConstraints = false
textView.autoresizingMask = [.width, .height]
return textView
}
func updateNSView(_ nsView: NSTextView, context: Context) {
nsView.textStorage!.setAttributedString(self.attributedString)
}
// Source: https://medium.com/fantageek/use-xib-de9d8a295757
class Coordinator: NSObject, NSTextViewDelegate {
let parent: RichTextField
init(_ RichTextField: RichTextField) {
self.parent = RichTextField
}
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else { return }
self.parent.attributedString = textView.attributedString()
}
}
}
(I still get the "cycle detected" error when using the RichTextField in a List (non-editable) though..)
Related Topics
Xcode 7.3.1 Hangs on "Copying Swift Standard Libraries"
In Swift 3, What Is a Way to Compare Two Closures
Uiapplication.Shared.Delegate Equivalent for Scenedelegate Xcode11
How to Create an Instance of a Class from a String in Swift
Why Is Manually Setup Root View Controller Showing Black Screen
Masking an Image in Swift Using Calayer and Uiimage
How to Convert Any Generic Numeric into a Double
Swift Make Method Parameter Mutable
How to Create an Importable Module in Swift
Swift: How to Read Standard Output in a Child Process Without Waiting for Process to Finish
Should Conditional Compilation Be Used to Cope With Difference in Cgfloat on Different Architectures
Convert a Swift Array of String to a to a C String Array Pointer
Generating Resource_Bundle_Accessor, Type 'Bundle' Has No Member 'Module'
Making Simple Accordion Tableview in Swift
How to Get the File Creation Date Using Url Resourcevalues Method in Swift 3
Is Swift Dictionary of Indexed for Performance? Even for Exotic Types (Uuid)