@@ -16,7 +16,7 @@ import R2Shared
1616
1717public enum EditingAction : String {
1818 case copy = " copy: "
19- case share = " _share :"
19+ case share = " shareSelection :"
2020 case lookup = " _lookup: "
2121
2222 public static var defaultActions : [ EditingAction ] {
@@ -36,19 +36,13 @@ protocol EditingActionsControllerDelegate: AnyObject {
3636final class EditingActionsController {
3737
3838 public weak var delegate : EditingActionsControllerDelegate ?
39-
39+
4040 private let actions : [ EditingAction ]
4141 private let license : DRMLicense ?
4242
4343 init ( actions: [ EditingAction ] , license: DRMLicense ? ) {
4444 self . actions = actions
4545 self . license = license
46-
47- NotificationCenter . default. addObserver ( self , selector: #selector( pasteboardDidChange) , name: UIPasteboard . changedNotification, object: nil )
48- }
49-
50- deinit {
51- NotificationCenter . default. removeObserver ( self )
5246 }
5347
5448 func canPerformAction( _ action: Selector ) -> Bool {
@@ -61,62 +55,82 @@ final class EditingActionsController {
6155 }
6256
6357
58+ // MARK: - Selection
59+
60+ /// Current user selection contents and frame in the publication view.
61+ private var selection : ( text: String , frame: CGRect ) ?
62+
63+ /// Peeks into the available selection contents authorized for copy.
64+ /// To be used only when required to have the contents before actually using it (eg. Share dialog). To consume the actual copy, use `copy()`.
65+ var selectionAuthorizedForCopy : ( text: String , frame: CGRect ) ? {
66+ guard canCopy,
67+ var selection = selection else
68+ {
69+ return nil
70+ }
71+ if let license = license {
72+ guard let authorizedText = license. copy ( selection. text, consumes: false ) else {
73+ return nil
74+ }
75+ selection. text = authorizedText
76+ }
77+ return selection
78+ }
79+
80+ /// To be called when the user selection changed.
81+ func selectionDidChange( _ selection: ( text: String , frame: CGRect ) ? ) {
82+ self . selection = selection
83+ }
84+
85+
6486 // MARK: - Copy
6587
6688 /// Returns whether the copy interaction is at all allowed. It doesn't guarantee that the next copy action will be valid, if the license cancels it.
6789 var canCopy : Bool {
6890 return actions. contains ( . copy) && ( license? . canCopy ?? true )
6991 }
70-
71- /// Called when the user attempt to copy the selection. If true is returned, then you may allow the copy .
72- func requestCopy ( ) -> Bool {
92+
93+ /// Copies the authorized portion of the selection text into the pasteboard .
94+ func copy ( ) {
7395 guard canCopy else {
7496 delegate? . editingActionsDidPreventCopy ( self )
75- return false
76- }
77-
78- // We rely on UIPasteboardChanged to notify the copy to the delegate because UIKit sets the selection in the UIPasteboard asynchronously
79- needsCopyCheck = true
80-
81- return true
82- }
83-
84- @objc private func pasteboardDidChange( ) {
85- let pasteboard = UIPasteboard . general
86- guard needsCopyCheck, let text = pasteboard. string else {
8797 return
8898 }
89- needsCopyCheck = false
90-
91- guard let license = license else {
99+ guard var text = selection? . text else {
92100 return
93101 }
94- guard license. canCopy else {
95- pasteboard. items = [ ]
96- return
97- }
98-
99- let authorizedText = license. copy ( text)
100- if authorizedText != text {
101- // We overwrite the pasteboard only if the authorized text is different to avoid erasing formatting
102- pasteboard. string = authorizedText
102+
103+ if let license = license {
104+ guard let authorizedText = license. copy ( text, consumes: true ) else {
105+ return
106+ }
107+ text = authorizedText
103108 }
109+
110+ UIPasteboard . general. string = text
104111 }
105112
106- private var copyTimer : Timer ?
107- private var needsCopyCheck = false {
108- didSet {
109- // A timer is used because we are listening to the event until the content is copied.
110- copyTimer? . invalidate ( )
111- copyTimer = nil
112- if needsCopyCheck {
113- copyTimer = Timer . scheduledTimer ( timeInterval: 1 , target: self , selector: #selector( copyTimerDidFire) , userInfo: nil , repeats: false )
113+
114+ // MARK: - Share
115+
116+ /// Builds a UIActivityViewController to share the authorized contents of the user selection.
117+ func makeShareViewController( from contentsView: UIView ) -> UIActivityViewController ? {
118+ guard canCopy else {
119+ delegate? . editingActionsDidPreventCopy ( self )
120+ return nil
121+ }
122+ guard let selection = selectionAuthorizedForCopy else {
123+ return nil
124+ }
125+ let viewController = UIActivityViewController ( activityItems: [ selection. text] , applicationActivities: nil )
126+ viewController. completionWithItemsHandler = { _, completed, _, _ in
127+ if ( completed) {
128+ self . copy ( )
114129 }
115130 }
116- }
117-
118- @objc private func copyTimerDidFire( ) {
119- needsCopyCheck = false
131+ viewController. popoverPresentationController? . sourceView = contentsView
132+ viewController. popoverPresentationController? . sourceRect = selection. frame
133+ return viewController
120134 }
121135
122136}
0 commit comments