@@ -60,10 +60,21 @@ private final class ComposerTextAttachment: NSTextAttachment {
6060private final class ComposerTextView : UITextView {
6161 private static let pastedImageDirectoryName = " t3-composer-paste "
6262 private static let stalePastedImageAge : TimeInterval = 60 * 60
63+ private static let readOnlyActions = Set ( [
64+ " cut: " ,
65+ " delete: " ,
66+ " paste: " ,
67+ " redo: " ,
68+ " toggleBoldface: " ,
69+ " toggleItalics: " ,
70+ " toggleUnderline: " ,
71+ " undo: " ,
72+ ] )
6373
6474 var onPasteImages : ( ( [ String ] ) -> Void ) ?
6575 var onAttributedMutation : ( ( ) -> Void ) ?
6676 var onSubmit : ( ( ) -> Void ) ?
77+ var isReadOnly = false
6778
6879 override var keyCommands : [ UIKeyCommand ] ? {
6980 var commands = super. keyCommands ?? [ ]
@@ -83,6 +94,9 @@ private final class ComposerTextView: UITextView {
8394 }
8495
8596 override func canPerformAction( _ action: Selector , withSender sender: Any ? ) -> Bool {
97+ if isReadOnly && Self . readOnlyActions. contains ( NSStringFromSelector ( action) ) {
98+ return false
99+ }
86100 if action == #selector( paste ( _: ) ) {
87101 let pasteboard = UIPasteboard . general
88102 if pasteboard. hasImages ||
@@ -96,6 +110,9 @@ private final class ComposerTextView: UITextView {
96110 }
97111
98112 override func paste( _ sender: Any ? ) {
113+ guard !isReadOnly else {
114+ return
115+ }
99116 let pasteboard = UIPasteboard . general
100117 let imageProviders = pasteboard. itemProviders. filter {
101118 $0. canLoadObject ( ofClass: UIImage . self)
@@ -117,6 +134,9 @@ private final class ComposerTextView: UITextView {
117134 }
118135
119136 override func deleteBackward( ) {
137+ guard !isReadOnly else {
138+ return
139+ }
120140 guard selectedRange. length == 0 , selectedRange. location > 0 else {
121141 super. deleteBackward ( )
122142 return
@@ -160,9 +180,12 @@ private final class ComposerTextView: UITextView {
160180 }
161181
162182 group. notify ( queue: . main) { [ weak self] in
183+ guard let self, !self . isReadOnly else {
184+ return
185+ }
163186 let urls = images. compactMap { $0 } . compactMap ( Self . writeTemporaryImage)
164187 if !urls. isEmpty {
165- self ? . onPasteImages ? ( urls)
188+ self . onPasteImages ? ( urls)
166189 }
167190 }
168191 }
@@ -175,6 +198,9 @@ private final class ComposerTextView: UITextView {
175198 }
176199
177200 override func cut( _ sender: Any ? ) {
201+ guard !isReadOnly else {
202+ return
203+ }
178204 guard isEditable, selectedRange. length > 0 else {
179205 return super. cut ( sender)
180206 }
@@ -306,6 +332,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro
306332 private var contentInsetVertical : CGFloat = 0
307333 private var shouldAutoFocus = false
308334 private var didAutoFocus = false
335+ private var isReadOnly = false
309336 private var isApplyingControlledValue = false
310337 private var nativeEventCount = 0
311338 private var lastContentSize = CGSize . zero
@@ -451,6 +478,11 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro
451478 textView. isEditable = editable
452479 }
453480
481+ func setReadOnly( _ readOnly: Bool ) {
482+ isReadOnly = readOnly
483+ textView. isReadOnly = readOnly
484+ }
485+
454486 func setScrollEnabled( _ scrollEnabled: Bool ) {
455487 textView. isScrollEnabled = scrollEnabled
456488 }
@@ -504,13 +536,16 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro
504536 replacementText text: String
505537 ) -> Bool {
506538 restoreBaseTypingAttributes ( )
507- return true
539+ return !isReadOnly
508540 }
509541
510542 public func textDroppableView(
511543 _ textDroppableView: UIView & UITextDroppable ,
512544 proposalForDrop drop: UITextDropRequest
513545 ) -> UITextDropProposal {
546+ guard !isReadOnly else {
547+ return UITextDropProposal ( operation: . cancel)
548+ }
514549 guard droppedImageProviders ( in: drop) != nil else {
515550 return drop. suggestedProposal
516551 }
@@ -527,6 +562,9 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro
527562 _ textDroppableView: UIView & UITextDroppable ,
528563 willPerformDrop drop: UITextDropRequest
529564 ) {
565+ guard !isReadOnly else {
566+ return
567+ }
530568 guard let imageProviders = droppedImageProviders ( in: drop) else {
531569 return
532570 }
0 commit comments