zkit-ui 2.0.4 → 2.0.6
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/README.md +56 -0
- package/android/build.gradle +100 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewLayoutSupport.java +450 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewMediaItem.java +36 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewModule.kt +43 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewPagerAdapter.java +140 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementGeometry.java +25 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementNames.java +39 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementState.java +25 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSourceViewRegistry.java +115 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewTransitionSnapshotView.java +60 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewVideoPlayerView.java +338 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewView.kt +305 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewViewerOverlayView.java +636 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewViewerPageView.java +357 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewViewerTransitionController.java +223 -0
- package/android/src/main/res/layout/native_image_preview_player_view.xml +9 -0
- package/dist/all.d.ts +2 -0
- package/dist/all.d.ts.map +1 -1
- package/dist/all.js +24 -22
- package/dist/ui/NativeImagePreview/NativeImagePreview.d.ts +3 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.js +99 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts +4 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.web.js +150 -0
- package/dist/ui/NativeImagePreview/context.d.ts +14 -0
- package/dist/ui/NativeImagePreview/context.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/context.js +38 -0
- package/dist/ui/NativeImagePreview/index.d.ts +3 -0
- package/dist/ui/NativeImagePreview/index.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/index.js +5 -0
- package/dist/ui/NativeImagePreview/types.d.ts +68 -0
- package/dist/ui/NativeImagePreview/types.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/types.js +2 -0
- package/dist/ui/NativeImagePreview/utils.d.ts +8 -0
- package/dist/ui/NativeImagePreview/utils.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/utils.js +83 -0
- package/dist/ui/NativeImagePreview/web/geometry.d.ts +20 -0
- package/dist/ui/NativeImagePreview/web/geometry.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/geometry.js +121 -0
- package/dist/ui/NativeImagePreview/web/media.d.ts +6 -0
- package/dist/ui/NativeImagePreview/web/media.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/media.js +90 -0
- package/dist/ui/NativeImagePreview/web/styles.css +137 -0
- package/dist/ui/NativeImagePreview/web/transition.d.ts +15 -0
- package/dist/ui/NativeImagePreview/web/transition.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/transition.js +77 -0
- package/dist/ui/NativeImagePreview/web/types.d.ts +74 -0
- package/dist/ui/NativeImagePreview/web/types.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/types.js +3 -0
- package/dist/ui/NativeImagePreview/web/viewer.d.ts +104 -0
- package/dist/ui/NativeImagePreview/web/viewer.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/viewer.js +894 -0
- package/expo-module.config.json +11 -0
- package/ios/NativeImagePreview/NativeImagePreviewImageLoader.swift +73 -0
- package/ios/NativeImagePreview/NativeImagePreviewImageViewInteraction.swift +245 -0
- package/ios/NativeImagePreview/NativeImagePreviewLayoutSupport.swift +143 -0
- package/ios/NativeImagePreview/NativeImagePreviewMedia.swift +28 -0
- package/ios/NativeImagePreview/NativeImagePreviewSourceViewRegistry.swift +80 -0
- package/ios/NativeImagePreview/NativeImagePreviewVideoPlayerView.swift +437 -0
- package/ios/NativeImagePreview/NativeImagePreviewViewerConfiguration.swift +51 -0
- package/ios/NativeImagePreview/NativeImagePreviewViewerController.swift +745 -0
- package/ios/NativeImagePreview/NativeImagePreviewViewerPageView.swift +623 -0
- package/ios/NativeImagePreview/NativeImagePreviewViewerTransitionCoordinator.swift +312 -0
- package/ios/NativeImagePreviewModule.swift +46 -0
- package/ios/NativeImagePreviewView.swift +252 -0
- package/package.json +16 -2
- package/src/all.ts +18 -0
- package/src/services/ImagePreviewService/README.md +19 -0
- package/src/types.d.ts +1 -0
- package/src/ui/NativeImagePreview/NativeImagePreview.tsx +146 -0
- package/src/ui/NativeImagePreview/NativeImagePreview.web.tsx +201 -0
- package/src/ui/NativeImagePreview/README.md +111 -0
- package/src/ui/NativeImagePreview/context.tsx +22 -0
- package/src/ui/NativeImagePreview/index.tsx +18 -0
- package/src/ui/NativeImagePreview/types.ts +94 -0
- package/src/ui/NativeImagePreview/utils.ts +108 -0
- package/src/ui/NativeImagePreview/web/geometry.ts +134 -0
- package/src/ui/NativeImagePreview/web/media.ts +91 -0
- package/src/ui/NativeImagePreview/web/styles.css +137 -0
- package/src/ui/NativeImagePreview/web/transition.ts +111 -0
- package/src/ui/NativeImagePreview/web/types.ts +88 -0
- package/src/ui/NativeImagePreview/web/viewer.ts +1024 -0
- package/zkit-ui.podspec +13 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"platforms": ["apple", "android"],
|
|
3
|
+
"apple": {
|
|
4
|
+
"modules": ["NativeImagePreviewModule"],
|
|
5
|
+
"podspecPath": "zkit-ui.podspec",
|
|
6
|
+
"swiftModuleName": "zkit_ui"
|
|
7
|
+
},
|
|
8
|
+
"android": {
|
|
9
|
+
"modules": ["cn.fontree.zkit.ui.preview.NativeImagePreviewModule"]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
#if canImport(SDWebImage)
|
|
4
|
+
import SDWebImage
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
public protocol NativeImagePreviewImageLoading {
|
|
8
|
+
func loadImage(
|
|
9
|
+
_ url: URL,
|
|
10
|
+
placeholder: UIImage?,
|
|
11
|
+
imageView: UIImageView,
|
|
12
|
+
completion: @escaping (_ image: UIImage?) -> Void
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public struct NativeImagePreviewURLSessionImageLoader: NativeImagePreviewImageLoading {
|
|
17
|
+
public init() {}
|
|
18
|
+
|
|
19
|
+
public func loadImage(
|
|
20
|
+
_ url: URL,
|
|
21
|
+
placeholder: UIImage?,
|
|
22
|
+
imageView: UIImageView,
|
|
23
|
+
completion: @escaping (UIImage?) -> Void
|
|
24
|
+
) {
|
|
25
|
+
if let placeholder = placeholder {
|
|
26
|
+
imageView.image = placeholder
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
DispatchQueue.global(qos: .background).async {
|
|
30
|
+
guard let data = try? Data(contentsOf: url), let image = UIImage(data: data) else {
|
|
31
|
+
completion(nil)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
DispatchQueue.main.async {
|
|
36
|
+
imageView.image = image
|
|
37
|
+
completion(image)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#if canImport(SDWebImage)
|
|
44
|
+
struct NativeImagePreviewSDWebImageLoader: NativeImagePreviewImageLoading {
|
|
45
|
+
func loadImage(
|
|
46
|
+
_ url: URL,
|
|
47
|
+
placeholder: UIImage?,
|
|
48
|
+
imageView: UIImageView,
|
|
49
|
+
completion: @escaping (UIImage?) -> Void
|
|
50
|
+
) {
|
|
51
|
+
imageView.sd_setImage(
|
|
52
|
+
with: url,
|
|
53
|
+
placeholderImage: placeholder,
|
|
54
|
+
options: [],
|
|
55
|
+
progress: nil
|
|
56
|
+
) { image, _, _, _ in
|
|
57
|
+
DispatchQueue.main.async {
|
|
58
|
+
completion(image)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
#endif
|
|
64
|
+
|
|
65
|
+
public enum NativeImagePreviewImageLoaderFactory {
|
|
66
|
+
public static func makeDefault() -> NativeImagePreviewImageLoading {
|
|
67
|
+
#if canImport(SDWebImage)
|
|
68
|
+
return NativeImagePreviewSDWebImageLoader()
|
|
69
|
+
#else
|
|
70
|
+
return NativeImagePreviewURLSessionImageLoader()
|
|
71
|
+
#endif
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import ObjectiveC
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
extension UIImageView {
|
|
5
|
+
private struct NativeImagePreviewAnchorRegistration {
|
|
6
|
+
let previewGroupId: String
|
|
7
|
+
let index: Int
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
private final class NativeImagePreviewTapGestureRecognizer: UITapGestureRecognizer {
|
|
11
|
+
weak var fromViewController: UIViewController?
|
|
12
|
+
var dataSource: NativeImagePreviewDataSource?
|
|
13
|
+
var imageLoader: NativeImagePreviewImageLoading?
|
|
14
|
+
var initialIndex = 0
|
|
15
|
+
var previewGroupId: String?
|
|
16
|
+
var configuration = NativeImagePreviewViewerConfiguration()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private static var anchorRegistrationKey: UInt8 = 0
|
|
20
|
+
private static var gestureRecognizerKey: UInt8 = 0
|
|
21
|
+
|
|
22
|
+
private var topMostHostViewController: UIViewController? {
|
|
23
|
+
let keyWindow: UIWindow? = {
|
|
24
|
+
if #available(iOS 13.0, *) {
|
|
25
|
+
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
26
|
+
let activeScene = scenes.first(where: { $0.activationState == .foregroundActive }) ?? scenes.first
|
|
27
|
+
return activeScene?.windows.first(where: { $0.isKeyWindow }) ?? activeScene?.windows.first
|
|
28
|
+
} else {
|
|
29
|
+
return UIApplication.shared.windows.first(where: { $0.isKeyWindow }) ?? UIApplication.shared.windows.first
|
|
30
|
+
}
|
|
31
|
+
}()
|
|
32
|
+
guard let rootViewController = keyWindow?.rootViewController else { return nil }
|
|
33
|
+
return rootViewController.presentedViewController ?? rootViewController
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public func setupNativeImagePreviewViewer(
|
|
37
|
+
configuration: NativeImagePreviewViewerConfiguration = NativeImagePreviewViewerConfiguration(),
|
|
38
|
+
from viewController: UIViewController? = nil,
|
|
39
|
+
imageLoader: NativeImagePreviewImageLoading? = nil,
|
|
40
|
+
previewGroupId: String? = nil
|
|
41
|
+
) {
|
|
42
|
+
configureNativeImagePreviewViewer(
|
|
43
|
+
dataSource: NativeImagePreviewArrayDataSource(items: [.image(image)]),
|
|
44
|
+
initialIndex: 0,
|
|
45
|
+
configuration: configuration,
|
|
46
|
+
from: viewController,
|
|
47
|
+
imageLoader: imageLoader,
|
|
48
|
+
previewGroupId: previewGroupId
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public func setupNativeImagePreviewViewer(
|
|
53
|
+
url: URL,
|
|
54
|
+
initialIndex: Int = 0,
|
|
55
|
+
placeholder: UIImage? = nil,
|
|
56
|
+
configuration: NativeImagePreviewViewerConfiguration = NativeImagePreviewViewerConfiguration(),
|
|
57
|
+
from viewController: UIViewController? = nil,
|
|
58
|
+
imageLoader: NativeImagePreviewImageLoading? = nil,
|
|
59
|
+
previewGroupId: String? = nil
|
|
60
|
+
) {
|
|
61
|
+
configureNativeImagePreviewViewer(
|
|
62
|
+
dataSource: NativeImagePreviewArrayDataSource(items: [.imageURL(url, placeholder: placeholder)]),
|
|
63
|
+
initialIndex: initialIndex,
|
|
64
|
+
configuration: configuration,
|
|
65
|
+
from: viewController,
|
|
66
|
+
imageLoader: imageLoader,
|
|
67
|
+
previewGroupId: previewGroupId
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public func setupNativeImagePreviewViewer(
|
|
72
|
+
images: [UIImage],
|
|
73
|
+
initialIndex: Int = 0,
|
|
74
|
+
configuration: NativeImagePreviewViewerConfiguration = NativeImagePreviewViewerConfiguration(),
|
|
75
|
+
from viewController: UIViewController? = nil,
|
|
76
|
+
imageLoader: NativeImagePreviewImageLoading? = nil,
|
|
77
|
+
previewGroupId: String? = nil
|
|
78
|
+
) {
|
|
79
|
+
let items = images.map { NativeImagePreviewMediaItem.image($0) }
|
|
80
|
+
configureNativeImagePreviewViewer(
|
|
81
|
+
dataSource: NativeImagePreviewArrayDataSource(items: items),
|
|
82
|
+
initialIndex: initialIndex,
|
|
83
|
+
configuration: configuration,
|
|
84
|
+
from: viewController,
|
|
85
|
+
imageLoader: imageLoader,
|
|
86
|
+
previewGroupId: previewGroupId
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public func setupNativeImagePreviewViewer(
|
|
91
|
+
urls: [URL],
|
|
92
|
+
initialIndex: Int = 0,
|
|
93
|
+
placeholder: UIImage? = nil,
|
|
94
|
+
configuration: NativeImagePreviewViewerConfiguration = NativeImagePreviewViewerConfiguration(),
|
|
95
|
+
from viewController: UIViewController? = nil,
|
|
96
|
+
imageLoader: NativeImagePreviewImageLoading? = nil,
|
|
97
|
+
previewGroupId: String? = nil
|
|
98
|
+
) {
|
|
99
|
+
let items = urls.map { NativeImagePreviewMediaItem.imageURL($0, placeholder: placeholder) }
|
|
100
|
+
configureNativeImagePreviewViewer(
|
|
101
|
+
dataSource: NativeImagePreviewArrayDataSource(items: items),
|
|
102
|
+
initialIndex: initialIndex,
|
|
103
|
+
configuration: configuration,
|
|
104
|
+
from: viewController,
|
|
105
|
+
imageLoader: imageLoader,
|
|
106
|
+
previewGroupId: previewGroupId
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public func setupNativeImagePreviewViewer(
|
|
111
|
+
dataSource: NativeImagePreviewDataSource,
|
|
112
|
+
initialIndex: Int = 0,
|
|
113
|
+
configuration: NativeImagePreviewViewerConfiguration = NativeImagePreviewViewerConfiguration(),
|
|
114
|
+
from viewController: UIViewController? = nil,
|
|
115
|
+
imageLoader: NativeImagePreviewImageLoading? = nil,
|
|
116
|
+
previewGroupId: String? = nil
|
|
117
|
+
) {
|
|
118
|
+
configureNativeImagePreviewViewer(
|
|
119
|
+
dataSource: dataSource,
|
|
120
|
+
initialIndex: initialIndex,
|
|
121
|
+
configuration: configuration,
|
|
122
|
+
from: viewController,
|
|
123
|
+
imageLoader: imageLoader,
|
|
124
|
+
previewGroupId: previewGroupId
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func removeNativeImagePreviewViewerInteraction() {
|
|
129
|
+
if let recognizer = nativeImagePreviewTapGestureRecognizer {
|
|
130
|
+
removeGestureRecognizer(recognizer)
|
|
131
|
+
}
|
|
132
|
+
nativeImagePreviewTapGestureRecognizer = nil
|
|
133
|
+
|
|
134
|
+
if let registration = nativeImagePreviewAnchorRegistration {
|
|
135
|
+
NativeImagePreviewSourceViewRegistry.shared.unregister(
|
|
136
|
+
self,
|
|
137
|
+
previewGroupId: registration.previewGroupId,
|
|
138
|
+
index: registration.index
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
nativeImagePreviewAnchorRegistration = nil
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private func configureNativeImagePreviewViewer(
|
|
145
|
+
dataSource: NativeImagePreviewDataSource?,
|
|
146
|
+
initialIndex: Int,
|
|
147
|
+
configuration: NativeImagePreviewViewerConfiguration,
|
|
148
|
+
from viewController: UIViewController?,
|
|
149
|
+
imageLoader: NativeImagePreviewImageLoading?,
|
|
150
|
+
previewGroupId: String?
|
|
151
|
+
) {
|
|
152
|
+
let recognizer = nativeImagePreviewTapGestureRecognizer ?? {
|
|
153
|
+
let recognizer = NativeImagePreviewTapGestureRecognizer(target: self, action: #selector(showNativeImagePreviewViewer(_:)))
|
|
154
|
+
recognizer.numberOfTouchesRequired = 1
|
|
155
|
+
recognizer.numberOfTapsRequired = 1
|
|
156
|
+
nativeImagePreviewTapGestureRecognizer = recognizer
|
|
157
|
+
return recognizer
|
|
158
|
+
}()
|
|
159
|
+
|
|
160
|
+
isUserInteractionEnabled = true
|
|
161
|
+
clipsToBounds = true
|
|
162
|
+
|
|
163
|
+
recognizer.dataSource = dataSource
|
|
164
|
+
recognizer.imageLoader = imageLoader
|
|
165
|
+
recognizer.initialIndex = initialIndex
|
|
166
|
+
recognizer.previewGroupId = previewGroupId
|
|
167
|
+
recognizer.configuration = configuration
|
|
168
|
+
recognizer.fromViewController = viewController
|
|
169
|
+
|
|
170
|
+
if recognizer.view !== self {
|
|
171
|
+
addGestureRecognizer(recognizer)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
updateNativeImagePreviewAnchorRegistration(previewGroupId: previewGroupId, index: initialIndex)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private var nativeImagePreviewTapGestureRecognizer: NativeImagePreviewTapGestureRecognizer? {
|
|
178
|
+
get {
|
|
179
|
+
objc_getAssociatedObject(self, &Self.gestureRecognizerKey) as? NativeImagePreviewTapGestureRecognizer
|
|
180
|
+
}
|
|
181
|
+
set {
|
|
182
|
+
objc_setAssociatedObject(
|
|
183
|
+
self,
|
|
184
|
+
&Self.gestureRecognizerKey,
|
|
185
|
+
newValue,
|
|
186
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private var nativeImagePreviewAnchorRegistration: NativeImagePreviewAnchorRegistration? {
|
|
192
|
+
get {
|
|
193
|
+
objc_getAssociatedObject(self, &Self.anchorRegistrationKey) as? NativeImagePreviewAnchorRegistration
|
|
194
|
+
}
|
|
195
|
+
set {
|
|
196
|
+
objc_setAssociatedObject(
|
|
197
|
+
self,
|
|
198
|
+
&Self.anchorRegistrationKey,
|
|
199
|
+
newValue,
|
|
200
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@objc
|
|
206
|
+
private func showNativeImagePreviewViewer(_ recognizer: NativeImagePreviewTapGestureRecognizer) {
|
|
207
|
+
guard let sourceView = recognizer.view as? UIImageView else { return }
|
|
208
|
+
let resolvedImageLoader = recognizer.imageLoader ?? NativeImagePreviewImageLoaderFactory.makeDefault()
|
|
209
|
+
let viewerController = NativeImagePreviewViewerController(
|
|
210
|
+
sourceView: sourceView,
|
|
211
|
+
dataSource: recognizer.dataSource,
|
|
212
|
+
imageLoader: resolvedImageLoader,
|
|
213
|
+
configuration: recognizer.configuration,
|
|
214
|
+
initialIndex: recognizer.initialIndex,
|
|
215
|
+
previewGroupId: recognizer.previewGroupId
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
let rootViewController = recognizer.fromViewController ?? topMostHostViewController
|
|
219
|
+
let presenter = rootViewController.flatMap(topMostPresentedController(from:))
|
|
220
|
+
presenter?.present(viewerController, animated: false)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private func topMostPresentedController(from rootViewController: UIViewController) -> UIViewController {
|
|
224
|
+
var controller = rootViewController
|
|
225
|
+
while let presented = controller.presentedViewController {
|
|
226
|
+
controller = presented
|
|
227
|
+
}
|
|
228
|
+
return controller
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private func updateNativeImagePreviewAnchorRegistration(previewGroupId: String?, index: Int) {
|
|
232
|
+
if let existing = nativeImagePreviewAnchorRegistration {
|
|
233
|
+
NativeImagePreviewSourceViewRegistry.shared.unregister(
|
|
234
|
+
self,
|
|
235
|
+
previewGroupId: existing.previewGroupId,
|
|
236
|
+
index: existing.index
|
|
237
|
+
)
|
|
238
|
+
nativeImagePreviewAnchorRegistration = nil
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
guard let previewGroupId = previewGroupId, previewGroupId.isEmpty == false else { return }
|
|
242
|
+
NativeImagePreviewSourceViewRegistry.shared.register(self, previewGroupId: previewGroupId, index: index)
|
|
243
|
+
nativeImagePreviewAnchorRegistration = NativeImagePreviewAnchorRegistration(previewGroupId: previewGroupId, index: index)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
struct NativeImagePreviewSharedElementGeometry {
|
|
4
|
+
let visibleFrameInWindow: CGRect
|
|
5
|
+
let contentFrameInVisibleBounds: CGRect
|
|
6
|
+
let cornerRadius: CGFloat
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
struct NativeImagePreviewSharedElementState {
|
|
10
|
+
let image: UIImage
|
|
11
|
+
let geometry: NativeImagePreviewSharedElementGeometry
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
extension CGRect {
|
|
15
|
+
func nativeImagePreviewPixelAligned(scale: CGFloat) -> CGRect {
|
|
16
|
+
guard scale > 0 else { return self }
|
|
17
|
+
return CGRect(
|
|
18
|
+
x: (origin.x * scale).rounded() / scale,
|
|
19
|
+
y: (origin.y * scale).rounded() / scale,
|
|
20
|
+
width: (size.width * scale).rounded() / scale,
|
|
21
|
+
height: (size.height * scale).rounded() / scale
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func nativeImagePreviewApproximatelyEquals(_ other: CGRect, tolerance: CGFloat = 0.5) -> Bool {
|
|
26
|
+
abs(minX - other.minX) <= tolerance
|
|
27
|
+
&& abs(minY - other.minY) <= tolerance
|
|
28
|
+
&& abs(width - other.width) <= tolerance
|
|
29
|
+
&& abs(height - other.height) <= tolerance
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
extension NativeImagePreviewSharedElementGeometry {
|
|
34
|
+
func nativeImagePreviewPixelAligned(scale: CGFloat) -> NativeImagePreviewSharedElementGeometry {
|
|
35
|
+
NativeImagePreviewSharedElementGeometry(
|
|
36
|
+
visibleFrameInWindow: visibleFrameInWindow.nativeImagePreviewPixelAligned(scale: scale),
|
|
37
|
+
contentFrameInVisibleBounds: contentFrameInVisibleBounds.nativeImagePreviewPixelAligned(scale: scale),
|
|
38
|
+
cornerRadius: (cornerRadius * scale).rounded() / scale
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func approximatelyEquals(_ other: NativeImagePreviewSharedElementGeometry, tolerance: CGFloat = 0.5) -> Bool {
|
|
43
|
+
visibleFrameInWindow.nativeImagePreviewApproximatelyEquals(other.visibleFrameInWindow, tolerance: tolerance)
|
|
44
|
+
&& contentFrameInVisibleBounds.nativeImagePreviewApproximatelyEquals(
|
|
45
|
+
other.contentFrameInVisibleBounds,
|
|
46
|
+
tolerance: tolerance
|
|
47
|
+
)
|
|
48
|
+
&& abs(cornerRadius - other.cornerRadius) <= tolerance
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
extension NativeImagePreviewSharedElementState {
|
|
53
|
+
func approximatelyEquals(_ other: NativeImagePreviewSharedElementState, tolerance: CGFloat = 0.5) -> Bool {
|
|
54
|
+
geometry.approximatelyEquals(other.geometry, tolerance: tolerance)
|
|
55
|
+
&& abs(image.size.width - other.image.size.width) <= tolerance
|
|
56
|
+
&& abs(image.size.height - other.image.size.height) <= tolerance
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
extension UIView {
|
|
61
|
+
func pinToSuperviewEdges(
|
|
62
|
+
top: CGFloat = 0,
|
|
63
|
+
leading: CGFloat = 0,
|
|
64
|
+
trailing: CGFloat = 0,
|
|
65
|
+
bottom: CGFloat = 0
|
|
66
|
+
) {
|
|
67
|
+
guard let superview = superview else { return }
|
|
68
|
+
translatesAutoresizingMaskIntoConstraints = false
|
|
69
|
+
NSLayoutConstraint.activate([
|
|
70
|
+
topAnchor.constraint(equalTo: superview.topAnchor, constant: top),
|
|
71
|
+
leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: leading),
|
|
72
|
+
trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -trailing),
|
|
73
|
+
bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -bottom),
|
|
74
|
+
])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func pinToSuperviewEdges(inset: CGFloat) {
|
|
78
|
+
pinToSuperviewEdges(top: inset, leading: inset, trailing: inset, bottom: inset)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func frameInWindow() -> CGRect {
|
|
82
|
+
convert(bounds, to: nil)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
extension UIImageView {
|
|
87
|
+
func imageContentFrameInWindow() -> CGRect {
|
|
88
|
+
guard let image = image else { return frameInWindow() }
|
|
89
|
+
guard image.size.width > 0, image.size.height > 0 else { return frameInWindow() }
|
|
90
|
+
guard bounds.width > 0, bounds.height > 0 else { return frameInWindow() }
|
|
91
|
+
|
|
92
|
+
let widthRatio = bounds.width / image.size.width
|
|
93
|
+
let heightRatio = bounds.height / image.size.height
|
|
94
|
+
|
|
95
|
+
let scale: CGFloat
|
|
96
|
+
switch contentMode {
|
|
97
|
+
case .scaleAspectFill:
|
|
98
|
+
scale = max(widthRatio, heightRatio)
|
|
99
|
+
default:
|
|
100
|
+
scale = min(widthRatio, heightRatio)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let contentSize = CGSize(
|
|
104
|
+
width: image.size.width * scale,
|
|
105
|
+
height: image.size.height * scale
|
|
106
|
+
)
|
|
107
|
+
let contentFrame = CGRect(
|
|
108
|
+
x: (bounds.width - contentSize.width) * 0.5,
|
|
109
|
+
y: (bounds.height - contentSize.height) * 0.5,
|
|
110
|
+
width: contentSize.width,
|
|
111
|
+
height: contentSize.height
|
|
112
|
+
)
|
|
113
|
+
return convert(contentFrame, to: nil)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func nativeImagePreviewSharedElementState(clippingFrameInWindow: CGRect? = nil) -> NativeImagePreviewSharedElementState? {
|
|
117
|
+
guard let image = image else { return nil }
|
|
118
|
+
guard image.size.width > 0, image.size.height > 0 else { return nil }
|
|
119
|
+
|
|
120
|
+
let clippingFrame = clippingFrameInWindow ?? frameInWindow()
|
|
121
|
+
guard clippingFrame.width > 0, clippingFrame.height > 0 else { return nil }
|
|
122
|
+
|
|
123
|
+
let contentFrame = imageContentFrameInWindow()
|
|
124
|
+
var visibleFrame = clippingFrame.intersection(contentFrame)
|
|
125
|
+
if visibleFrame.isNull || visibleFrame.isEmpty {
|
|
126
|
+
visibleFrame = clippingFrame
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let geometry = NativeImagePreviewSharedElementGeometry(
|
|
130
|
+
visibleFrameInWindow: visibleFrame,
|
|
131
|
+
contentFrameInVisibleBounds: contentFrame.offsetBy(
|
|
132
|
+
dx: -visibleFrame.minX,
|
|
133
|
+
dy: -visibleFrame.minY
|
|
134
|
+
),
|
|
135
|
+
cornerRadius: (clipsToBounds || layer.masksToBounds) ? layer.cornerRadius : 0
|
|
136
|
+
)
|
|
137
|
+
let scale = window?.screen.scale ?? UIScreen.main.scale
|
|
138
|
+
return NativeImagePreviewSharedElementState(
|
|
139
|
+
image: image,
|
|
140
|
+
geometry: geometry.nativeImagePreviewPixelAligned(scale: scale)
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
public enum NativeImagePreviewMediaItem {
|
|
4
|
+
case image(UIImage?)
|
|
5
|
+
case imageURL(URL, placeholder: UIImage?)
|
|
6
|
+
case video(url: URL, poster: URL?)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
public protocol NativeImagePreviewDataSource: AnyObject {
|
|
10
|
+
func numberOfItems() -> Int
|
|
11
|
+
func item(at index: Int) -> NativeImagePreviewMediaItem
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public final class NativeImagePreviewArrayDataSource: NativeImagePreviewDataSource {
|
|
15
|
+
private let items: [NativeImagePreviewMediaItem]
|
|
16
|
+
|
|
17
|
+
public init(items: [NativeImagePreviewMediaItem]) {
|
|
18
|
+
self.items = items
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public func numberOfItems() -> Int {
|
|
22
|
+
items.count
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public func item(at index: Int) -> NativeImagePreviewMediaItem {
|
|
26
|
+
items[index]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
final class NativeImagePreviewSourceViewRegistry {
|
|
4
|
+
static let shared = NativeImagePreviewSourceViewRegistry()
|
|
5
|
+
|
|
6
|
+
private final class WeakImageView {
|
|
7
|
+
weak var value: UIImageView?
|
|
8
|
+
|
|
9
|
+
init(_ value: UIImageView) {
|
|
10
|
+
self.value = value
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private var anchors: [String: [Int: WeakImageView]] = [:]
|
|
15
|
+
|
|
16
|
+
private init() {}
|
|
17
|
+
|
|
18
|
+
func register(_ imageView: UIImageView, previewGroupId: String, index: Int) {
|
|
19
|
+
cleanup()
|
|
20
|
+
var previewGroupAnchors = anchors[previewGroupId] ?? [:]
|
|
21
|
+
previewGroupAnchors[index] = WeakImageView(imageView)
|
|
22
|
+
anchors[previewGroupId] = previewGroupAnchors
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func unregister(_ imageView: UIImageView, previewGroupId: String?, index: Int?) {
|
|
26
|
+
cleanup()
|
|
27
|
+
if let previewGroupId = previewGroupId {
|
|
28
|
+
remove(imageView, from: previewGroupId, index: index)
|
|
29
|
+
} else {
|
|
30
|
+
for previewGroupId in Array(anchors.keys) {
|
|
31
|
+
remove(imageView, from: previewGroupId, index: nil)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
cleanup()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func sourceView(for previewGroupId: String, index: Int) -> UIImageView? {
|
|
38
|
+
cleanup()
|
|
39
|
+
guard
|
|
40
|
+
let imageView = anchors[previewGroupId]?[index]?.value,
|
|
41
|
+
let window = imageView.window,
|
|
42
|
+
!imageView.isHidden
|
|
43
|
+
else {
|
|
44
|
+
return nil
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let frameInWindow = imageView.frameInWindow()
|
|
48
|
+
guard !frameInWindow.isEmpty, frameInWindow.intersects(window.bounds) else {
|
|
49
|
+
return nil
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return imageView
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private func remove(_ imageView: UIImageView, from previewGroupId: String, index: Int?) {
|
|
56
|
+
guard var previewGroupAnchors = anchors[previewGroupId] else { return }
|
|
57
|
+
|
|
58
|
+
if let index = index {
|
|
59
|
+
if let registeredView = previewGroupAnchors[index]?.value, registeredView === imageView {
|
|
60
|
+
previewGroupAnchors.removeValue(forKey: index)
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
previewGroupAnchors = previewGroupAnchors.filter { _, weakImageView in
|
|
64
|
+
guard let registeredView = weakImageView.value else { return false }
|
|
65
|
+
return registeredView !== imageView
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
anchors[previewGroupId] = previewGroupAnchors.isEmpty ? nil : previewGroupAnchors
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private func cleanup() {
|
|
73
|
+
anchors = anchors.compactMapValues { previewGroupAnchors in
|
|
74
|
+
let activeAnchors = previewGroupAnchors.filter { _, weakImageView in
|
|
75
|
+
weakImageView.value != nil
|
|
76
|
+
}
|
|
77
|
+
return activeAnchors.isEmpty ? nil : activeAnchors
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|