aix 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ios/EditMenuDefaultActions.swift +70 -0
- package/ios/HybridAix.swift +28 -3
- package/ios/HybridAixCellView.swift +0 -1
- package/ios/HybridAixComposer.swift +82 -0
- package/ios/HybridAixDropzone.swift +104 -0
- package/ios/HybridAixInputWrapper.swift +447 -0
- package/ios/InputType.swift +40 -0
- package/ios/PasteFileManager.swift +92 -0
- package/nitro.json +8 -0
- package/nitrogen/generated/android/Aix+autolinking.cmake +8 -0
- package/nitrogen/generated/android/AixOnLoad.cpp +26 -0
- package/nitrogen/generated/android/c++/JAixInputWrapperOnPasteEvent.hpp +70 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_AixInputWrapperOnPasteEvent_.hpp +98 -0
- package/nitrogen/generated/android/c++/JHybridAixComposerSpec.cpp +9 -0
- package/nitrogen/generated/android/c++/JHybridAixComposerSpec.hpp +2 -0
- package/nitrogen/generated/android/c++/JHybridAixDropzoneSpec.cpp +72 -0
- package/nitrogen/generated/android/c++/JHybridAixDropzoneSpec.hpp +66 -0
- package/nitrogen/generated/android/c++/JHybridAixInputWrapperSpec.cpp +144 -0
- package/nitrogen/generated/android/c++/JHybridAixInputWrapperSpec.hpp +74 -0
- package/nitrogen/generated/android/c++/views/JHybridAixComposerStateUpdater.cpp +4 -0
- package/nitrogen/generated/android/c++/views/JHybridAixDropzoneStateUpdater.cpp +56 -0
- package/nitrogen/generated/android/c++/views/JHybridAixDropzoneStateUpdater.hpp +49 -0
- package/nitrogen/generated/android/c++/views/JHybridAixInputWrapperStateUpdater.cpp +72 -0
- package/nitrogen/generated/android/c++/views/JHybridAixInputWrapperStateUpdater.hpp +49 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/AixInputWrapperOnPasteEvent.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/Func_void_std__vector_AixInputWrapperOnPasteEvent_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/HybridAixComposerSpec.kt +6 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/HybridAixDropzoneSpec.kt +67 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/HybridAixInputWrapperSpec.kt +91 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/views/HybridAixDropzoneManager.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/views/HybridAixDropzoneStateUpdater.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/views/HybridAixInputWrapperManager.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/views/HybridAixInputWrapperStateUpdater.kt +23 -0
- package/nitrogen/generated/ios/Aix-Swift-Cxx-Bridge.cpp +42 -0
- package/nitrogen/generated/ios/Aix-Swift-Cxx-Bridge.hpp +112 -0
- package/nitrogen/generated/ios/Aix-Swift-Cxx-Umbrella.hpp +14 -0
- package/nitrogen/generated/ios/AixAutolinking.mm +16 -0
- package/nitrogen/generated/ios/AixAutolinking.swift +30 -0
- package/nitrogen/generated/ios/c++/HybridAixComposerSpecSwift.hpp +7 -0
- package/nitrogen/generated/ios/c++/HybridAixDropzoneSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridAixDropzoneSpecSwift.hpp +80 -0
- package/nitrogen/generated/ios/c++/HybridAixInputWrapperSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridAixInputWrapperSpecSwift.hpp +108 -0
- package/nitrogen/generated/ios/c++/views/HybridAixComposerComponent.mm +5 -0
- package/nitrogen/generated/ios/c++/views/HybridAixDropzoneComponent.mm +96 -0
- package/nitrogen/generated/ios/c++/views/HybridAixInputWrapperComponent.mm +116 -0
- package/nitrogen/generated/ios/swift/AixInputWrapperOnPasteEvent.swift +107 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_AixInputWrapperOnPasteEvent_.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridAixComposerSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridAixComposerSpec_cxx.swift +24 -0
- package/nitrogen/generated/ios/swift/HybridAixDropzoneSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridAixDropzoneSpec_cxx.swift +167 -0
- package/nitrogen/generated/ios/swift/HybridAixInputWrapperSpec.swift +60 -0
- package/nitrogen/generated/ios/swift/HybridAixInputWrapperSpec_cxx.swift +261 -0
- package/nitrogen/generated/shared/c++/AixInputWrapperOnPasteEvent.hpp +88 -0
- package/nitrogen/generated/shared/c++/HybridAixComposerSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridAixComposerSpec.hpp +2 -0
- package/nitrogen/generated/shared/c++/HybridAixDropzoneSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridAixDropzoneSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridAixInputWrapperSpec.cpp +30 -0
- package/nitrogen/generated/shared/c++/HybridAixInputWrapperSpec.hpp +76 -0
- package/nitrogen/generated/shared/c++/views/HybridAixComposerComponent.cpp +12 -0
- package/nitrogen/generated/shared/c++/views/HybridAixComposerComponent.hpp +1 -0
- package/nitrogen/generated/shared/c++/views/HybridAixDropzoneComponent.cpp +87 -0
- package/nitrogen/generated/shared/c++/views/HybridAixDropzoneComponent.hpp +109 -0
- package/nitrogen/generated/shared/c++/views/HybridAixInputWrapperComponent.cpp +135 -0
- package/nitrogen/generated/shared/c++/views/HybridAixInputWrapperComponent.hpp +114 -0
- package/nitrogen/generated/shared/json/AixComposerConfig.json +1 -0
- package/nitrogen/generated/shared/json/AixDropzoneConfig.json +10 -0
- package/nitrogen/generated/shared/json/AixInputWrapperConfig.json +14 -0
- package/package.json +1 -1
- package/src/dropzone.ios.tsx +27 -0
- package/src/dropzone.tsx +10 -0
- package/src/index.ts +3 -0
- package/src/input-wrapper.ios.tsx +30 -0
- package/src/input-wrapper.tsx +17 -0
- package/src/views/aix.nitro.ts +33 -19
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
enum EditMenuDefaultActions: String, CaseIterable {
|
|
4
|
+
case define
|
|
5
|
+
case translate
|
|
6
|
+
case promptForReplace
|
|
7
|
+
case cut
|
|
8
|
+
case copy
|
|
9
|
+
case paste
|
|
10
|
+
case delete
|
|
11
|
+
case select
|
|
12
|
+
case selectAll
|
|
13
|
+
case transliterateChinese
|
|
14
|
+
case insertDrawing
|
|
15
|
+
case captureTextFromCamera
|
|
16
|
+
case toggleBoldface
|
|
17
|
+
case toggleItalics
|
|
18
|
+
case toggleUnderline
|
|
19
|
+
case makeTextWritingDirectionRightToLeft
|
|
20
|
+
case makeTextWritingDirectionLeftToRight
|
|
21
|
+
case showTextFormattingOptions
|
|
22
|
+
case findSelected
|
|
23
|
+
case addShortcut
|
|
24
|
+
case accessibilitySpeak
|
|
25
|
+
case accessibilitySpeakLanguageSelection
|
|
26
|
+
case accessibilityPauseSpeaking
|
|
27
|
+
case share
|
|
28
|
+
|
|
29
|
+
var selectorString: String {
|
|
30
|
+
switch self {
|
|
31
|
+
case .define: return "_define:"
|
|
32
|
+
case .translate: return "_translate:"
|
|
33
|
+
case .promptForReplace: return "_promptForReplace:"
|
|
34
|
+
case .cut: return "cut:"
|
|
35
|
+
case .copy: return "copy:"
|
|
36
|
+
case .paste: return "paste:"
|
|
37
|
+
case .delete: return "delete:"
|
|
38
|
+
case .select: return "select:"
|
|
39
|
+
case .selectAll: return "selectAll:"
|
|
40
|
+
case .transliterateChinese: return "_transliterateChinese:"
|
|
41
|
+
case .insertDrawing: return "_insertDrawing:"
|
|
42
|
+
case .captureTextFromCamera: return "captureTextFromCamera:"
|
|
43
|
+
case .toggleBoldface: return "toggleBoldface:"
|
|
44
|
+
case .toggleItalics: return "toggleItalics:"
|
|
45
|
+
case .toggleUnderline: return "toggleUnderline:"
|
|
46
|
+
case .makeTextWritingDirectionRightToLeft: return "makeTextWritingDirectionRightToLeft:"
|
|
47
|
+
case .makeTextWritingDirectionLeftToRight: return "makeTextWritingDirectionLeftToRight:"
|
|
48
|
+
case .showTextFormattingOptions: return "_showTextFormattingOptions:"
|
|
49
|
+
case .findSelected: return "_findSelected:"
|
|
50
|
+
case .addShortcut: return "_addShortcut:"
|
|
51
|
+
case .accessibilitySpeak: return "_accessibilitySpeak:"
|
|
52
|
+
case .accessibilitySpeakLanguageSelection: return "_accessibilitySpeakLanguageSelection:"
|
|
53
|
+
case .accessibilityPauseSpeaking: return "_accessibilityPauseSpeaking:"
|
|
54
|
+
case .share: return "_share:"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
var selector: Selector {
|
|
59
|
+
Selector(self.selectorString)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static func getSupportedActions(
|
|
63
|
+
forResponder responder: UIResponder,
|
|
64
|
+
withSender sender: Any?
|
|
65
|
+
) -> [Self] {
|
|
66
|
+
Self.allCases.filter {
|
|
67
|
+
responder.canPerformAction($0.selector, withSender: sender)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/ios/HybridAix.swift
CHANGED
|
@@ -91,6 +91,19 @@ extension UIView {
|
|
|
91
91
|
}
|
|
92
92
|
return nil
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
/// Recursively search subviews to find the first UITextField or UITextView
|
|
96
|
+
func findTextInput() -> UIView? {
|
|
97
|
+
if self is UITextField || self is UITextView {
|
|
98
|
+
return self
|
|
99
|
+
}
|
|
100
|
+
for subview in subviews {
|
|
101
|
+
if let input = subview.findTextInput() {
|
|
102
|
+
return input
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return nil
|
|
106
|
+
}
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
// MARK: - HybridAix (Root Context)
|
|
@@ -115,7 +128,15 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
115
128
|
|
|
116
129
|
var scrollOnComposerSizeUpdate: AixScrollOnFooterSizeUpdate?
|
|
117
130
|
|
|
118
|
-
var mainScrollViewID: String?
|
|
131
|
+
var mainScrollViewID: String? {
|
|
132
|
+
didSet {
|
|
133
|
+
guard mainScrollViewID != oldValue else { return }
|
|
134
|
+
removePanGestureObserver()
|
|
135
|
+
removeScrollViewObservers()
|
|
136
|
+
cachedScrollView = nil
|
|
137
|
+
didSetupPanGestureObserver = false
|
|
138
|
+
}
|
|
139
|
+
}
|
|
119
140
|
|
|
120
141
|
func scrollToEnd(animated: Bool?) {
|
|
121
142
|
// Dispatch to main thread since this may be called from RN background thread
|
|
@@ -144,7 +165,11 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
144
165
|
}
|
|
145
166
|
}
|
|
146
167
|
|
|
147
|
-
private var
|
|
168
|
+
private var didScrollToEndInitiallyForId: String? = nil
|
|
169
|
+
|
|
170
|
+
private var didScrollToEndInitially: Bool {
|
|
171
|
+
return didScrollToEndInitiallyForId == (mainScrollViewID ?? "")
|
|
172
|
+
}
|
|
148
173
|
|
|
149
174
|
// MARK: - Inner View
|
|
150
175
|
|
|
@@ -732,7 +757,7 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
732
757
|
scrollToEndInternal(animated: false)
|
|
733
758
|
prevIsScrolledNearEnd = getIsScrolledNearEnd(distFromEnd: distFromEnd)
|
|
734
759
|
}
|
|
735
|
-
|
|
760
|
+
didScrollToEndInitiallyForId = mainScrollViewID ?? ""
|
|
736
761
|
} else {
|
|
737
762
|
applyContentInset()
|
|
738
763
|
applyScrollIndicatorInsets()
|
|
@@ -24,6 +24,15 @@ class HybridAixComposer: HybridAixComposerSpec {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
var fixInput: Bool? = nil {
|
|
28
|
+
didSet {
|
|
29
|
+
cachedTextInput = nil
|
|
30
|
+
if fixInput == true {
|
|
31
|
+
resolveTextInput()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
// MARK: - Inner View
|
|
28
37
|
|
|
29
38
|
/// Custom UIView that notifies owner when layout changes
|
|
@@ -56,6 +65,26 @@ class HybridAixComposer: HybridAixComposerSpec {
|
|
|
56
65
|
}
|
|
57
66
|
}
|
|
58
67
|
|
|
68
|
+
// MARK: - Gesture Target
|
|
69
|
+
|
|
70
|
+
/// NSObject helper to bridge target-action for gesture recognizers,
|
|
71
|
+
/// since HybridAixComposer does not inherit from NSObject.
|
|
72
|
+
private class GestureTarget: NSObject, UIGestureRecognizerDelegate {
|
|
73
|
+
let handler: (UIPanGestureRecognizer) -> Void
|
|
74
|
+
|
|
75
|
+
init(handler: @escaping (UIPanGestureRecognizer) -> Void) {
|
|
76
|
+
self.handler = handler
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@objc func handleGesture(_ gesture: UIPanGestureRecognizer) {
|
|
80
|
+
handler(gesture)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
59
88
|
// MARK: - Properties
|
|
60
89
|
|
|
61
90
|
/// The UIView for this composer
|
|
@@ -67,6 +96,12 @@ class HybridAixComposer: HybridAixComposerSpec {
|
|
|
67
96
|
/// Last reported height (to avoid reporting unchanged heights)
|
|
68
97
|
private var lastReportedHeight: CGFloat = 0
|
|
69
98
|
|
|
99
|
+
/// Cached reference to the text input found inside the footer
|
|
100
|
+
private weak var cachedTextInput: UIView?
|
|
101
|
+
|
|
102
|
+
/// Gesture target for pan-to-focus
|
|
103
|
+
private var panGestureTarget: GestureTarget?
|
|
104
|
+
|
|
70
105
|
// MARK: - Initialization
|
|
71
106
|
|
|
72
107
|
override init() {
|
|
@@ -105,6 +140,9 @@ class HybridAixComposer: HybridAixComposerSpec {
|
|
|
105
140
|
// Initial state
|
|
106
141
|
applyKeyboardTransform(height: ctx.keyboardHeight, heightWhenOpen: ctx.keyboardHeightWhenOpen, animated: false)
|
|
107
142
|
}
|
|
143
|
+
|
|
144
|
+
// Resolve text input once the hierarchy is connected
|
|
145
|
+
resolveTextInput()
|
|
108
146
|
}
|
|
109
147
|
|
|
110
148
|
/// Called when the view is about to be removed from superview
|
|
@@ -128,6 +166,50 @@ class HybridAixComposer: HybridAixComposerSpec {
|
|
|
128
166
|
}
|
|
129
167
|
}
|
|
130
168
|
|
|
169
|
+
// MARK: - Text Input
|
|
170
|
+
|
|
171
|
+
/// Find and cache the text input inside the footer's view hierarchy
|
|
172
|
+
var textInput: UIView? {
|
|
173
|
+
if let cached = cachedTextInput {
|
|
174
|
+
return cached
|
|
175
|
+
}
|
|
176
|
+
let searchRoot = view.superview ?? view
|
|
177
|
+
let input = searchRoot.findTextInput()
|
|
178
|
+
cachedTextInput = input
|
|
179
|
+
return input
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Resolve the text input and apply patches
|
|
183
|
+
private func resolveTextInput() {
|
|
184
|
+
print("[Aix] resolveTextInput: fixInput=\(fixInput), textInput=\(textInput)")
|
|
185
|
+
guard fixInput == true, let input = textInput else { return }
|
|
186
|
+
guard let scrollView = input as? UIScrollView else { return }
|
|
187
|
+
print("[Aix] resolveTextInput: scrollView=\(scrollView)")
|
|
188
|
+
scrollView.showsVerticalScrollIndicator = false
|
|
189
|
+
scrollView.showsHorizontalScrollIndicator = false
|
|
190
|
+
scrollView.bounces = false
|
|
191
|
+
scrollView.alwaysBounceVertical = false
|
|
192
|
+
scrollView.alwaysBounceHorizontal = false
|
|
193
|
+
scrollView.keyboardDismissMode = .interactive
|
|
194
|
+
let target = GestureTarget { [weak self] gesture in
|
|
195
|
+
self?.handlePanToFocus(gesture)
|
|
196
|
+
}
|
|
197
|
+
self.panGestureTarget = target
|
|
198
|
+
let panGesture = UIPanGestureRecognizer(target: target, action: #selector(GestureTarget.handleGesture(_:)))
|
|
199
|
+
panGesture.delegate = target
|
|
200
|
+
scrollView.addGestureRecognizer(panGesture)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// Handle pan to focus the text input
|
|
204
|
+
private func handlePanToFocus(_ gesture: UIPanGestureRecognizer) {
|
|
205
|
+
guard gesture.state == .began else { return }
|
|
206
|
+
guard let input = textInput else { return }
|
|
207
|
+
let velocity = gesture.velocity(in: input)
|
|
208
|
+
if velocity.y < -250.0 && !input.isFirstResponder {
|
|
209
|
+
input.becomeFirstResponder()
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
131
213
|
// MARK: - Keyboard handling
|
|
132
214
|
|
|
133
215
|
/// Apply keyboard transform to move the composer with the keyboard
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
class HybridAixDropzone: HybridAixDropzoneSpec {
|
|
5
|
+
var onDrop: ((_ events: [AixInputWrapperOnPasteEvent]) -> Void)?
|
|
6
|
+
|
|
7
|
+
private let dropDelegate = DropzoneDelegate()
|
|
8
|
+
|
|
9
|
+
// MARK: - Inner View
|
|
10
|
+
private final class InnerView: UIView {
|
|
11
|
+
weak var owner: HybridAixDropzone?
|
|
12
|
+
|
|
13
|
+
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override func didMoveToWindow() {
|
|
18
|
+
super.didMoveToWindow()
|
|
19
|
+
owner?.setupDropInteraction()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let view: UIView
|
|
24
|
+
|
|
25
|
+
// MARK: - Init
|
|
26
|
+
override init() {
|
|
27
|
+
let inner = InnerView()
|
|
28
|
+
self.view = inner
|
|
29
|
+
super.init()
|
|
30
|
+
inner.owner = self
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private var didSetup = false
|
|
34
|
+
|
|
35
|
+
private func setupDropInteraction() {
|
|
36
|
+
guard !didSetup, view.window != nil else { return }
|
|
37
|
+
didSetup = true
|
|
38
|
+
|
|
39
|
+
dropDelegate.onImages = { [weak self] images in
|
|
40
|
+
self?.handleDroppedImages(images)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Add drop interaction to the Fabric container (superview) so it covers the full area
|
|
44
|
+
let target = view.superview ?? view
|
|
45
|
+
target.addInteraction(UIDropInteraction(delegate: dropDelegate))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MARK: - Drop Handling
|
|
49
|
+
private func handleDroppedImages(_ images: [UIImage]) {
|
|
50
|
+
var events: [AixInputWrapperOnPasteEvent] = []
|
|
51
|
+
for image in images {
|
|
52
|
+
if let uri = try? PasteFileManager.save(image: image) {
|
|
53
|
+
let fileURL = URL(fileURLWithPath: uri)
|
|
54
|
+
events.append(AixInputWrapperOnPasteEvent(
|
|
55
|
+
type: "image",
|
|
56
|
+
filePath: uri,
|
|
57
|
+
fileExtension: fileURL.pathExtension,
|
|
58
|
+
fileName: fileURL.lastPathComponent
|
|
59
|
+
))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if !events.isEmpty {
|
|
63
|
+
onDrop?(events)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// MARK: - Drop Delegate
|
|
69
|
+
private final class DropzoneDelegate: NSObject, UIDropInteractionDelegate {
|
|
70
|
+
var onImages: (([UIImage]) -> Void)?
|
|
71
|
+
|
|
72
|
+
func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
|
|
73
|
+
return session.items.contains { $0.itemProvider.canLoadObject(ofClass: UIImage.self) }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
|
|
77
|
+
return UIDropProposal(operation: .copy)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
|
|
81
|
+
let providers = session.items.map { $0.itemProvider }
|
|
82
|
+
var images: [UIImage] = []
|
|
83
|
+
let lock = NSLock()
|
|
84
|
+
let group = DispatchGroup()
|
|
85
|
+
|
|
86
|
+
for provider in providers {
|
|
87
|
+
guard provider.canLoadObject(ofClass: UIImage.self) else { continue }
|
|
88
|
+
group.enter()
|
|
89
|
+
provider.loadObject(ofClass: UIImage.self) { object, _ in
|
|
90
|
+
defer { group.leave() }
|
|
91
|
+
guard let image = object as? UIImage else { return }
|
|
92
|
+
lock.lock()
|
|
93
|
+
images.append(image)
|
|
94
|
+
lock.unlock()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
group.notify(queue: .main) { [weak self] in
|
|
99
|
+
if !images.isEmpty {
|
|
100
|
+
self?.onImages?(images)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|