SelectableText.swift (3600B)
1 // 2 // SelectableText.swift 3 // damus 4 // 5 // Created by Oleg Abalonski on 2/16/23. 6 // 7 8 import UIKit 9 import SwiftUI 10 11 struct SelectableText: View { 12 13 let attributedString: AttributedString 14 let textAlignment: NSTextAlignment 15 16 @State private var selectedTextHeight: CGFloat = .zero 17 @State private var selectedTextWidth: CGFloat = .zero 18 19 let size: EventViewKind 20 21 init(attributedString: AttributedString, textAlignment: NSTextAlignment? = nil, size: EventViewKind) { 22 self.attributedString = attributedString 23 self.textAlignment = textAlignment ?? NSTextAlignment.natural 24 self.size = size 25 } 26 27 var body: some View { 28 GeometryReader { geo in 29 TextViewRepresentable( 30 attributedString: attributedString, 31 textColor: UIColor.label, 32 font: eventviewsize_to_uifont(size), 33 fixedWidth: selectedTextWidth, 34 textAlignment: self.textAlignment, 35 height: $selectedTextHeight 36 ) 37 .padding([.leading, .trailing], -1.0) 38 .onAppear { 39 if geo.size.width == .zero { 40 self.selectedTextHeight = 1000.0 41 } else { 42 self.selectedTextWidth = geo.size.width 43 } 44 } 45 .onChange(of: geo.size) { newSize in 46 self.selectedTextWidth = newSize.width 47 } 48 } 49 .frame(height: selectedTextHeight) 50 } 51 } 52 53 fileprivate struct TextViewRepresentable: UIViewRepresentable { 54 55 let attributedString: AttributedString 56 let textColor: UIColor 57 let font: UIFont 58 let fixedWidth: CGFloat 59 let textAlignment: NSTextAlignment 60 61 @Binding var height: CGFloat 62 63 func makeUIView(context: UIViewRepresentableContext<Self>) -> UITextView { 64 let view = UITextView() 65 view.isEditable = false 66 view.dataDetectorTypes = .all 67 view.isSelectable = true 68 view.backgroundColor = .clear 69 view.textContainer.lineFragmentPadding = 0 70 view.textContainerInset = .zero 71 view.textContainerInset.left = 1.0 72 view.textContainerInset.right = 1.0 73 view.textAlignment = textAlignment 74 return view 75 } 76 77 func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<Self>) { 78 let mutableAttributedString = createNSAttributedString() 79 uiView.attributedText = mutableAttributedString 80 uiView.textAlignment = self.textAlignment 81 82 let newHeight = mutableAttributedString.height(containerWidth: fixedWidth) 83 84 DispatchQueue.main.async { 85 height = newHeight 86 } 87 } 88 89 func createNSAttributedString() -> NSMutableAttributedString { 90 let mutableAttributedString = NSMutableAttributedString(attributedString) 91 let myAttribute = [ 92 NSAttributedString.Key.font: font, 93 NSAttributedString.Key.foregroundColor: textColor 94 ] 95 96 mutableAttributedString.addAttributes( 97 myAttribute, 98 range: NSRange.init(location: 0, length: mutableAttributedString.length) 99 ) 100 101 return mutableAttributedString 102 } 103 } 104 105 fileprivate extension NSAttributedString { 106 107 func height(containerWidth: CGFloat) -> CGFloat { 108 109 let rect = self.boundingRect( 110 with: CGSize.init(width: containerWidth, height: CGFloat.greatestFiniteMagnitude), 111 options: [.usesLineFragmentOrigin, .usesFontLeading], 112 context: nil 113 ) 114 115 return ceil(rect.size.height) 116 } 117 }