lottie-ios 4.4.0 → 4.4.2
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/.github/workflows/main.yml +15 -10
- package/Gemfile +1 -1
- package/Gemfile.lock +25 -25
- package/Lottie.xcodeproj/project.pbxproj +40 -44
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/IDEFindNavigatorScopes.plist +5 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +5 -21
- package/Package.swift +1 -1
- package/README.md +1 -1
- package/Rakefile +5 -17
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +3 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +0 -1
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +0 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +0 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +0 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +0 -1
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +0 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +0 -1
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +0 -2
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +0 -4
- package/Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift +0 -1
- package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +2 -2
- package/Sources/Private/Model/DotLottie/DotLottieUtils.swift +3 -5
- package/Sources/Private/Model/Extensions/Bundle.swift +1 -1
- package/Sources/Private/Utility/Extensions/DataExtension.swift +5 -8
- package/Sources/Private/Utility/Extensions/MathKit.swift +3 -3
- package/Sources/Private/Utility/Helpers/Binding+Map.swift +0 -1
- package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +15 -2
- package/Sources/Public/Animation/LottieAnimationHelpers.swift +0 -1
- package/Sources/Public/Animation/LottieAnimationLayer.swift +8 -8
- package/Sources/Public/Animation/LottieAnimationView.swift +1 -6
- package/Sources/Public/Animation/LottieView.swift +11 -8
- package/Sources/Public/Controls/LottieButton.swift +0 -1
- package/Sources/Public/Controls/LottieSwitch.swift +0 -1
- package/Sources/Public/Controls/LottieViewType.swift +1 -1
- package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +0 -5
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift +1 -1
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +1 -1
- package/Sources/Public/iOS/LottieAnimationViewBase.swift +1 -5
- package/Version.xcconfig +6 -0
- package/lottie-ios.podspec +7 -7
- package/package.json +1 -1
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +0 -46
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +0 -389
|
@@ -118,7 +118,6 @@ extension DotLottieFile {
|
|
|
118
118
|
/// - Parameter bundle: The bundle in which the lottie is located. Defaults to `Bundle.main`
|
|
119
119
|
/// - Parameter subdirectory: A subdirectory in the bundle in which the lottie is located. Optional.
|
|
120
120
|
/// - Parameter dotLottieCache: A cache for holding loaded lotties. Defaults to `LRUDotLottieCache.sharedCache`. Optional.
|
|
121
|
-
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
|
|
122
121
|
public static func named(
|
|
123
122
|
_ name: String,
|
|
124
123
|
bundle: Bundle = Bundle.main,
|
|
@@ -165,7 +164,6 @@ extension DotLottieFile {
|
|
|
165
164
|
/// Loads an DotLottie from a specific filepath.
|
|
166
165
|
/// - Parameter filepath: The absolute filepath of the lottie to load. EG "/User/Me/starAnimation.lottie"
|
|
167
166
|
/// - Parameter dotLottieCache: A cache for holding loaded lotties. Defaults to `LRUDotLottieCache.sharedCache`. Optional.
|
|
168
|
-
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
|
|
169
167
|
public static func loadedFrom(
|
|
170
168
|
filepath: String,
|
|
171
169
|
dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache)
|
|
@@ -204,7 +202,6 @@ extension DotLottieFile {
|
|
|
204
202
|
/// - Parameter name: The name of the lottie file in the asset catalog. EG "StarAnimation"
|
|
205
203
|
/// - Parameter bundle: The bundle in which the lottie is located. Defaults to `Bundle.main`
|
|
206
204
|
/// - Parameter dotLottieCache: A cache for holding loaded lottie files. Defaults to `LRUDotLottieCache.sharedCache` Optional.
|
|
207
|
-
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
|
|
208
205
|
public static func asset(
|
|
209
206
|
named name: String,
|
|
210
207
|
bundle: Bundle = Bundle.main,
|
|
@@ -270,7 +267,6 @@ extension DotLottieFile {
|
|
|
270
267
|
///
|
|
271
268
|
/// - Parameter url: The url to load the animation from.
|
|
272
269
|
/// - Parameter animationCache: A cache for holding loaded animations. Defaults to `LRUAnimationCache.sharedCache`. Optional.
|
|
273
|
-
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
|
|
274
270
|
public static func loadedFrom(
|
|
275
271
|
url: URL,
|
|
276
272
|
session: URLSession = .shared,
|
|
@@ -354,7 +350,6 @@ extension DotLottieFile {
|
|
|
354
350
|
/// - data: The data(`Foundation.Data`) object to load DotLottie from
|
|
355
351
|
/// - filename: The name of the lottie file without the lottie extension. eg. "StarAnimation"
|
|
356
352
|
/// - dispatchQueue: A dispatch queue used to load animations. Defaults to `DispatchQueue.global()`. Optional.
|
|
357
|
-
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
|
|
358
353
|
public static func loadedFrom(
|
|
359
354
|
data: Data,
|
|
360
355
|
filename: String,
|
|
@@ -39,11 +39,7 @@ open class LottieAnimationViewBase: UIView {
|
|
|
39
39
|
|
|
40
40
|
var screenScale: CGFloat {
|
|
41
41
|
#if os(iOS) || os(tvOS)
|
|
42
|
-
|
|
43
|
-
return max(UITraitCollection.current.displayScale, 1)
|
|
44
|
-
} else {
|
|
45
|
-
return UIScreen.main.scale
|
|
46
|
-
}
|
|
42
|
+
max(UITraitCollection.current.displayScale, 1)
|
|
47
43
|
#else // if os(visionOS)
|
|
48
44
|
// We intentionally don't check `#if os(visionOS)`, because that emits
|
|
49
45
|
// a warning when building on Xcode 14 and earlier.
|
package/Version.xcconfig
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Created by Cal Stephens on 3/12/24.
|
|
2
|
+
// Copyright © 2024 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
// The version numbers used when building Lottie.xcframework
|
|
5
|
+
MARKETING_VERSION = 4.4.2
|
|
6
|
+
CURRENT_PROJECT_VERSION = 442 // a three-digit representation of the marketing version, without dots.
|
package/lottie-ios.podspec
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
Pod::Spec.new do |s|
|
|
10
10
|
s.name = 'lottie-ios'
|
|
11
|
-
s.version = '4.4.
|
|
11
|
+
s.version = '4.4.2'
|
|
12
12
|
s.summary = 'A library to render native animations from bodymovin json'
|
|
13
13
|
|
|
14
14
|
s.description = <<-DESC
|
|
@@ -23,14 +23,14 @@ Lottie enables designers to create and ship beautiful animations without an engi
|
|
|
23
23
|
s.source = { :git => 'https://github.com/airbnb/lottie-ios.git', :tag => s.version.to_s }
|
|
24
24
|
|
|
25
25
|
s.swift_version = '5.7'
|
|
26
|
-
s.ios.deployment_target = '
|
|
27
|
-
s.osx.deployment_target = '10.
|
|
28
|
-
s.tvos.deployment_target = '
|
|
26
|
+
s.ios.deployment_target = '13.0'
|
|
27
|
+
s.osx.deployment_target = '10.15'
|
|
28
|
+
s.tvos.deployment_target = '13.0'
|
|
29
|
+
s.visionos.deployment_target = "1.0"
|
|
29
30
|
|
|
30
|
-
s.source_files = 'Sources
|
|
31
|
-
s.exclude_files = 'Sources/**/*.md'
|
|
31
|
+
s.source_files = 'Sources/**/*.swift'
|
|
32
32
|
s.resource_bundles = {
|
|
33
|
-
'
|
|
33
|
+
'LottiePrivacyInfo' => ['Sources/PrivacyInfo.xcprivacy'],
|
|
34
34
|
}
|
|
35
35
|
s.ios.exclude_files = 'Sources/Public/MacOS/**/*'
|
|
36
36
|
s.tvos.exclude_files = 'Sources/Public/MacOS/**/*'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lottie-ios",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.2",
|
|
4
4
|
"description": "Lottie is a mobile library for Android and iOS that parses Adobe After Effects animations exported as json with bodymovin and renders the vector animations natively on mobile and through React Native!",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// Created by eric_horacek on 10/8/21.
|
|
2
|
-
// Copyright © 2021 Airbnb Inc. All rights reserved.
|
|
3
|
-
|
|
4
|
-
#if canImport(SwiftUI) && !os(macOS)
|
|
5
|
-
import SwiftUI
|
|
6
|
-
|
|
7
|
-
// MARK: - EpoxySwiftUIUIHostingController
|
|
8
|
-
|
|
9
|
-
/// A `UIHostingController` that hosts SwiftUI views within an Epoxy container, e.g. an Epoxy
|
|
10
|
-
/// `CollectionView`.
|
|
11
|
-
///
|
|
12
|
-
/// Exposed internally to allow consumers to reason about these view controllers, e.g. to opt
|
|
13
|
-
/// collection view cells out of automated view controller impression tracking.
|
|
14
|
-
///
|
|
15
|
-
/// - SeeAlso: `EpoxySwiftUIHostingView`
|
|
16
|
-
@available(iOS 13.0, tvOS 13.0, *)
|
|
17
|
-
open class EpoxySwiftUIHostingController<Content: View>: UIHostingController<Content> {
|
|
18
|
-
|
|
19
|
-
// MARK: Lifecycle
|
|
20
|
-
|
|
21
|
-
/// Creates a `UIHostingController` that optionally ignores the `safeAreaInsets` when laying out
|
|
22
|
-
/// its contained `RootView`.
|
|
23
|
-
convenience init(rootView: Content, ignoreSafeArea: Bool) {
|
|
24
|
-
self.init(rootView: rootView)
|
|
25
|
-
|
|
26
|
-
// We unfortunately need to call a private API to disable the safe area. We can also accomplish
|
|
27
|
-
// this by dynamically subclassing this view controller's view at runtime and overriding its
|
|
28
|
-
// `safeAreaInsets` property and returning `.zero`. An implementation of that logic is
|
|
29
|
-
// available in this file in the `2d28b3181cca50b89618b54836f7a9b6e36ea78e` commit if this API
|
|
30
|
-
// no longer functions in future SwiftUI versions.
|
|
31
|
-
_disableSafeArea = ignoreSafeArea
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// MARK: Open
|
|
35
|
-
|
|
36
|
-
open override func viewDidLoad() {
|
|
37
|
-
super.viewDidLoad()
|
|
38
|
-
|
|
39
|
-
// A `UIHostingController` has a system background color by default as it's typically used in
|
|
40
|
-
// full-screen use cases. Since we're using this view controller to place SwiftUI views within
|
|
41
|
-
// other view controllers we default the background color to clear so we can see the views
|
|
42
|
-
// below, e.g. to draw highlight states in a `CollectionView`.
|
|
43
|
-
view.backgroundColor = .clear
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
#endif
|
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
// Created by eric_horacek on 9/16/21.
|
|
2
|
-
// Copyright © 2021 Airbnb Inc. All rights reserved.
|
|
3
|
-
|
|
4
|
-
#if canImport(Combine) && canImport(SwiftUI) && !os(macOS)
|
|
5
|
-
import Combine
|
|
6
|
-
import SwiftUI
|
|
7
|
-
|
|
8
|
-
// MARK: - SwiftUIHostingViewReuseBehavior
|
|
9
|
-
|
|
10
|
-
/// The reuse behavior of an `EpoxySwiftUIHostingView`.
|
|
11
|
-
enum SwiftUIHostingViewReuseBehavior: Hashable {
|
|
12
|
-
/// Instances of a `EpoxySwiftUIHostingView` with `RootView`s of same type can be reused within
|
|
13
|
-
/// the Epoxy container.
|
|
14
|
-
///
|
|
15
|
-
/// This is the default reuse behavior.
|
|
16
|
-
case reusable
|
|
17
|
-
/// Instances of a `EpoxySwiftUIHostingView` with `RootView`s of same type can only reused within
|
|
18
|
-
/// the Epoxy container when they have identical `reuseID`s.
|
|
19
|
-
case unique(reuseID: AnyHashable)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// MARK: - CallbackContextEpoxyModeled
|
|
23
|
-
|
|
24
|
-
@available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
|
|
25
|
-
extension CallbackContextEpoxyModeled
|
|
26
|
-
where
|
|
27
|
-
Self: WillDisplayProviding & DidEndDisplayingProviding,
|
|
28
|
-
CallbackContext: ViewProviding & AnimatedProviding
|
|
29
|
-
{
|
|
30
|
-
/// Updates the appearance state of a `EpoxySwiftUIHostingView` in coordination with the
|
|
31
|
-
/// `willDisplay` and `didEndDisplaying` callbacks of this `EpoxyableModel`.
|
|
32
|
-
///
|
|
33
|
-
/// - Note: You should only need to call then from the implementation of a concrete
|
|
34
|
-
/// `EpoxyableModel` convenience vendor method, e.g. `SwiftUI.View.itemModel(…)`.
|
|
35
|
-
func linkDisplayLifecycle<RootView: View>() -> Self
|
|
36
|
-
where
|
|
37
|
-
CallbackContext.View == EpoxySwiftUIHostingView<RootView>
|
|
38
|
-
{
|
|
39
|
-
willDisplay { context in
|
|
40
|
-
context.view.handleWillDisplay(animated: context.animated)
|
|
41
|
-
}
|
|
42
|
-
.didEndDisplaying { context in
|
|
43
|
-
context.view.handleDidEndDisplaying(animated: context.animated)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// MARK: - EpoxySwiftUIHostingView
|
|
49
|
-
|
|
50
|
-
/// A `UIView` that hosts a SwiftUI view within an Epoxy container, e.g. an Epoxy `CollectionView`.
|
|
51
|
-
///
|
|
52
|
-
/// Wraps an `EpoxySwiftUIHostingController` and adds it as a child view controller to the next
|
|
53
|
-
/// ancestor view controller in the hierarchy.
|
|
54
|
-
///
|
|
55
|
-
/// There's a private API that accomplishes this same behavior without needing a `UIViewController`:
|
|
56
|
-
/// `_UIHostingView`, but we can't safely use it as 1) the behavior may change out from under us, 2)
|
|
57
|
-
/// the API is private and 3) the `_UIHostingView` doesn't not accept setting a new `View` instance.
|
|
58
|
-
///
|
|
59
|
-
/// - SeeAlso: `EpoxySwiftUIHostingController`
|
|
60
|
-
@available(iOS 13.0, tvOS 13.0, *)
|
|
61
|
-
final class EpoxySwiftUIHostingView<RootView: View>: UIView, EpoxyableView {
|
|
62
|
-
|
|
63
|
-
// MARK: Lifecycle
|
|
64
|
-
|
|
65
|
-
init(style: Style) {
|
|
66
|
-
// Ignore the safe area to ensure the view isn't laid out incorrectly when being sized while
|
|
67
|
-
// overlapping the safe area.
|
|
68
|
-
epoxyContent = EpoxyHostingContent(rootView: style.initialContent.rootView)
|
|
69
|
-
viewController = EpoxySwiftUIHostingController(
|
|
70
|
-
rootView: .init(content: epoxyContent, environment: epoxyEnvironment),
|
|
71
|
-
ignoreSafeArea: true)
|
|
72
|
-
|
|
73
|
-
dataID = style.initialContent.dataID ?? DefaultDataID.noneProvided as AnyHashable
|
|
74
|
-
|
|
75
|
-
super.init(frame: .zero)
|
|
76
|
-
|
|
77
|
-
epoxyEnvironment.intrinsicContentSizeInvalidator = .init(invalidate: { [weak self] in
|
|
78
|
-
self?.viewController.view.invalidateIntrinsicContentSize()
|
|
79
|
-
|
|
80
|
-
// Inform the enclosing collection view that the size has changed, if we're contained in one,
|
|
81
|
-
// allowing the cell to resize.
|
|
82
|
-
//
|
|
83
|
-
// On iOS 16+, we could call `invalidateIntrinsicContentSize()` on the enclosing collection
|
|
84
|
-
// view cell instead, but that currently causes visual artifacts with `MagazineLayout`. The
|
|
85
|
-
// better long term fix is likely to switch to `UIHostingConfiguration` on iOS 16+ anyways.
|
|
86
|
-
if let enclosingCollectionView = self?.superview?.superview?.superview as? UICollectionView {
|
|
87
|
-
enclosingCollectionView.collectionViewLayout.invalidateLayout()
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
layoutMargins = .zero
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
@available(*, unavailable)
|
|
94
|
-
required init?(coder _: NSCoder) {
|
|
95
|
-
fatalError("init(coder:) has not been implemented")
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// MARK: Internal
|
|
99
|
-
|
|
100
|
-
struct Style: Hashable {
|
|
101
|
-
init(reuseBehavior: SwiftUIHostingViewReuseBehavior, initialContent: Content) {
|
|
102
|
-
self.reuseBehavior = reuseBehavior
|
|
103
|
-
self.initialContent = initialContent
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
var reuseBehavior: SwiftUIHostingViewReuseBehavior
|
|
107
|
-
var initialContent: Content
|
|
108
|
-
|
|
109
|
-
static func == (lhs: Style, rhs: Style) -> Bool {
|
|
110
|
-
lhs.reuseBehavior == rhs.reuseBehavior
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
func hash(into hasher: inout Hasher) {
|
|
114
|
-
hasher.combine(reuseBehavior)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
struct Content: Equatable {
|
|
119
|
-
init(rootView: RootView, dataID: AnyHashable?) {
|
|
120
|
-
self.rootView = rootView
|
|
121
|
-
self.dataID = dataID
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
var rootView: RootView
|
|
125
|
-
var dataID: AnyHashable?
|
|
126
|
-
|
|
127
|
-
static func == (_: Content, _: Content) -> Bool {
|
|
128
|
-
// The content should never be equal since we need the `rootView` to be updated on every
|
|
129
|
-
// content change.
|
|
130
|
-
false
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
override func didMoveToWindow() {
|
|
135
|
-
super.didMoveToWindow()
|
|
136
|
-
|
|
137
|
-
// We'll only be able to discover a valid parent `viewController` once we're added to a window,
|
|
138
|
-
// so we do so here in addition to the `handleWillDisplay(…)` method.
|
|
139
|
-
if window != nil {
|
|
140
|
-
addViewControllerIfNeeded()
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
func setContent(_ content: Content, animated _: Bool) {
|
|
145
|
-
// This triggers a change in the observed `EpoxyHostingContent` object and allows the
|
|
146
|
-
// propagation of the SwiftUI transaction, instead of just replacing the `rootView`.
|
|
147
|
-
epoxyContent.rootView = content.rootView
|
|
148
|
-
dataID = content.dataID ?? DefaultDataID.noneProvided as AnyHashable
|
|
149
|
-
|
|
150
|
-
// The view controller must be added to the view controller hierarchy to measure its content.
|
|
151
|
-
if window != nil {
|
|
152
|
-
addViewControllerIfNeeded()
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// As of iOS 15.2, `UIHostingController` now renders updated content asynchronously, and as such
|
|
156
|
-
// this view will get sized incorrectly with the previous content when reused unless we invoke
|
|
157
|
-
// this semi-private API. We couldn't find any other method to get the view to resize
|
|
158
|
-
// synchronously after updating `rootView`, but hopefully this will become a internal API soon so
|
|
159
|
-
// we can remove this call.
|
|
160
|
-
viewController._render(seconds: 0)
|
|
161
|
-
|
|
162
|
-
// This is required to ensure that views with new content are properly resized.
|
|
163
|
-
viewController.view.invalidateIntrinsicContentSize()
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
override func layoutMarginsDidChange() {
|
|
167
|
-
super.layoutMarginsDidChange()
|
|
168
|
-
|
|
169
|
-
let margins = layoutMargins
|
|
170
|
-
switch effectiveUserInterfaceLayoutDirection {
|
|
171
|
-
case .rightToLeft:
|
|
172
|
-
epoxyEnvironment.layoutMargins = .init(
|
|
173
|
-
top: margins.top,
|
|
174
|
-
leading: margins.right,
|
|
175
|
-
bottom: margins.bottom,
|
|
176
|
-
trailing: margins.left)
|
|
177
|
-
case .leftToRight:
|
|
178
|
-
fallthrough
|
|
179
|
-
@unknown default:
|
|
180
|
-
epoxyEnvironment.layoutMargins = .init(
|
|
181
|
-
top: margins.top,
|
|
182
|
-
leading: margins.left,
|
|
183
|
-
bottom: margins.bottom,
|
|
184
|
-
trailing: margins.right)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Allow the layout margins update to fully propagate through to the SwiftUI View before
|
|
188
|
-
// invalidating the layout.
|
|
189
|
-
DispatchQueue.main.async {
|
|
190
|
-
self.viewController.view.invalidateIntrinsicContentSize()
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
func handleWillDisplay(animated: Bool) {
|
|
195
|
-
guard state != .appeared, window != nil else { return }
|
|
196
|
-
transition(to: .appearing(animated: animated))
|
|
197
|
-
transition(to: .appeared)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
func handleDidEndDisplaying(animated: Bool) {
|
|
201
|
-
guard state != .disappeared else { return }
|
|
202
|
-
transition(to: .disappearing(animated: animated))
|
|
203
|
-
transition(to: .disappeared)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// MARK: Private
|
|
207
|
-
|
|
208
|
-
private let viewController: EpoxySwiftUIHostingController<EpoxyHostingWrapper<RootView>>
|
|
209
|
-
private let epoxyContent: EpoxyHostingContent<RootView>
|
|
210
|
-
private let epoxyEnvironment = EpoxyHostingEnvironment()
|
|
211
|
-
private var dataID: AnyHashable
|
|
212
|
-
private var state: AppearanceState = .disappeared
|
|
213
|
-
|
|
214
|
-
/// Updates the appearance state of the `viewController`.
|
|
215
|
-
private func transition(to state: AppearanceState) {
|
|
216
|
-
guard state != self.state else { return }
|
|
217
|
-
|
|
218
|
-
// See "Handling View-Related Notifications" section for the state machine diagram.
|
|
219
|
-
// https://developer.apple.com/documentation/uikit/uiviewcontroller
|
|
220
|
-
switch (to: state, from: self.state) {
|
|
221
|
-
case (to: .appearing(let animated), from: .disappeared):
|
|
222
|
-
viewController.beginAppearanceTransition(true, animated: animated)
|
|
223
|
-
addViewControllerIfNeeded()
|
|
224
|
-
case (to: .disappearing(let animated), from: .appeared):
|
|
225
|
-
viewController.beginAppearanceTransition(false, animated: animated)
|
|
226
|
-
case (to: .disappeared, from: .disappearing):
|
|
227
|
-
removeViewControllerIfNeeded()
|
|
228
|
-
case (to: .appeared, from: .appearing):
|
|
229
|
-
viewController.endAppearanceTransition()
|
|
230
|
-
case (to: .disappeared, from: .appeared):
|
|
231
|
-
viewController.beginAppearanceTransition(false, animated: true)
|
|
232
|
-
removeViewControllerIfNeeded()
|
|
233
|
-
case (to: .appeared, from: .disappearing(let animated)):
|
|
234
|
-
viewController.beginAppearanceTransition(true, animated: animated)
|
|
235
|
-
viewController.endAppearanceTransition()
|
|
236
|
-
case (to: .disappeared, from: .appearing(let animated)):
|
|
237
|
-
viewController.beginAppearanceTransition(false, animated: animated)
|
|
238
|
-
removeViewControllerIfNeeded()
|
|
239
|
-
case (to: .appeared, from: .disappeared):
|
|
240
|
-
viewController.beginAppearanceTransition(true, animated: false)
|
|
241
|
-
addViewControllerIfNeeded()
|
|
242
|
-
viewController.endAppearanceTransition()
|
|
243
|
-
case (to: .appearing(let animated), from: .appeared):
|
|
244
|
-
viewController.beginAppearanceTransition(false, animated: animated)
|
|
245
|
-
viewController.beginAppearanceTransition(true, animated: animated)
|
|
246
|
-
case (to: .appearing(let animated), from: .disappearing):
|
|
247
|
-
viewController.beginAppearanceTransition(true, animated: animated)
|
|
248
|
-
case (to: .disappearing(let animated), from: .disappeared):
|
|
249
|
-
viewController.beginAppearanceTransition(true, animated: animated)
|
|
250
|
-
addViewControllerIfNeeded()
|
|
251
|
-
viewController.beginAppearanceTransition(false, animated: animated)
|
|
252
|
-
case (to: .disappearing(let animated), from: .appearing):
|
|
253
|
-
viewController.beginAppearanceTransition(false, animated: animated)
|
|
254
|
-
case (to: .appearing, from: .appearing),
|
|
255
|
-
(to: .appeared, from: .appeared),
|
|
256
|
-
(to: .disappearing, from: .disappearing),
|
|
257
|
-
(to: .disappeared, from: .disappeared):
|
|
258
|
-
// This should never happen since we guard on identical states.
|
|
259
|
-
EpoxyLogger.shared.assertionFailure("Impossible state change from \(self.state) to \(state)")
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
self.state = state
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private func addViewControllerIfNeeded() {
|
|
266
|
-
// This isn't great, and means that we're going to add this view controller as a child view
|
|
267
|
-
// controller of a view controller somewhere else in the hierarchy, which the author of that
|
|
268
|
-
// view controller may not be expecting. However there's not really a better pathway forward
|
|
269
|
-
// here without requiring a view controller instance to be passed all the way through, which is
|
|
270
|
-
// both burdensome and error-prone.
|
|
271
|
-
guard let nextViewController = superview?.next(UIViewController.self) else {
|
|
272
|
-
EpoxyLogger.shared.assertionFailure(
|
|
273
|
-
"""
|
|
274
|
-
Unable to add a UIHostingController view, could not locate a UIViewController in the \
|
|
275
|
-
responder chain for view with ID \(dataID) of type \(RootView.self).
|
|
276
|
-
""")
|
|
277
|
-
return
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
guard viewController.parent !== nextViewController else { return }
|
|
281
|
-
|
|
282
|
-
// If in a different parent, we need to first remove from it before we add.
|
|
283
|
-
if viewController.parent != nil {
|
|
284
|
-
removeViewControllerIfNeeded()
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
addViewController(to: nextViewController)
|
|
288
|
-
|
|
289
|
-
state = .appeared
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
private func addViewController(to parent: UIViewController) {
|
|
293
|
-
viewController.willMove(toParent: parent)
|
|
294
|
-
|
|
295
|
-
parent.addChild(viewController)
|
|
296
|
-
|
|
297
|
-
addSubview(viewController.view)
|
|
298
|
-
|
|
299
|
-
// Get the view controller's view to be sized correctly so that we don't have to wait for
|
|
300
|
-
// autolayout to perform a pass to do so.
|
|
301
|
-
viewController.view.frame = bounds
|
|
302
|
-
|
|
303
|
-
viewController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
304
|
-
NSLayoutConstraint.activate([
|
|
305
|
-
viewController.view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
306
|
-
viewController.view.topAnchor.constraint(equalTo: topAnchor),
|
|
307
|
-
viewController.view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
308
|
-
viewController.view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
309
|
-
])
|
|
310
|
-
|
|
311
|
-
viewController.didMove(toParent: parent)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private func removeViewControllerIfNeeded() {
|
|
315
|
-
guard viewController.parent != nil else { return }
|
|
316
|
-
|
|
317
|
-
viewController.willMove(toParent: nil)
|
|
318
|
-
viewController.view.removeFromSuperview()
|
|
319
|
-
viewController.removeFromParent()
|
|
320
|
-
viewController.didMove(toParent: nil)
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// MARK: - AppearanceState
|
|
325
|
-
|
|
326
|
-
/// The appearance state of a `EpoxySwiftUIHostingController` contained within a
|
|
327
|
-
/// `EpoxySwiftUIHostingView`.
|
|
328
|
-
private enum AppearanceState: Equatable {
|
|
329
|
-
case appearing(animated: Bool)
|
|
330
|
-
case appeared
|
|
331
|
-
case disappearing(animated: Bool)
|
|
332
|
-
case disappeared
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// MARK: - UIResponder
|
|
336
|
-
|
|
337
|
-
extension UIResponder {
|
|
338
|
-
/// Recursively traverses the responder chain upwards from this responder to its next responder
|
|
339
|
-
/// until the a responder of the given type is located, else returns `nil`.
|
|
340
|
-
@nonobjc
|
|
341
|
-
fileprivate func next<ResponderType>(_ type: ResponderType.Type) -> ResponderType? {
|
|
342
|
-
self as? ResponderType ?? next?.next(type)
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// MARK: - EpoxyHostingContent
|
|
347
|
-
|
|
348
|
-
/// The object that is used to communicate changes in the root view to the
|
|
349
|
-
/// `EpoxySwiftUIHostingController`.
|
|
350
|
-
@available(iOS 13.0, tvOS 13.0, *)
|
|
351
|
-
final class EpoxyHostingContent<RootView: View>: ObservableObject {
|
|
352
|
-
|
|
353
|
-
// MARK: Lifecycle
|
|
354
|
-
|
|
355
|
-
init(rootView: RootView) {
|
|
356
|
-
_rootView = .init(wrappedValue: rootView)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// MARK: Internal
|
|
360
|
-
|
|
361
|
-
@Published var rootView: RootView
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// MARK: - EpoxyHostingEnvironment
|
|
365
|
-
|
|
366
|
-
/// The object that is used to communicate values to SwiftUI views within an
|
|
367
|
-
/// `EpoxySwiftUIHostingController`, e.g. layout margins.
|
|
368
|
-
@available(iOS 13.0, tvOS 13.0, *)
|
|
369
|
-
final class EpoxyHostingEnvironment: ObservableObject {
|
|
370
|
-
@Published var layoutMargins = EdgeInsets()
|
|
371
|
-
@Published var intrinsicContentSizeInvalidator = EpoxyIntrinsicContentSizeInvalidator(invalidate: { })
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// MARK: - EpoxyHostingWrapper
|
|
375
|
-
|
|
376
|
-
/// The wrapper view that is used to communicate values to SwiftUI views within an
|
|
377
|
-
/// `EpoxySwiftUIHostingController`, e.g. layout margins.
|
|
378
|
-
@available(iOS 13.0, tvOS 13.0, *)
|
|
379
|
-
struct EpoxyHostingWrapper<Content: View>: View {
|
|
380
|
-
@ObservedObject var content: EpoxyHostingContent<Content>
|
|
381
|
-
@ObservedObject var environment: EpoxyHostingEnvironment
|
|
382
|
-
|
|
383
|
-
var body: some View {
|
|
384
|
-
content.rootView
|
|
385
|
-
.environment(\.epoxyLayoutMargins, environment.layoutMargins)
|
|
386
|
-
.environment(\.epoxyIntrinsicContentSizeInvalidator, environment.intrinsicContentSizeInvalidator)
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
#endif
|