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.
Files changed (44) hide show
  1. package/.github/workflows/main.yml +15 -10
  2. package/Gemfile +1 -1
  3. package/Gemfile.lock +25 -25
  4. package/Lottie.xcodeproj/project.pbxproj +40 -44
  5. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/IDEFindNavigatorScopes.plist +5 -0
  6. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  7. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +5 -21
  8. package/Package.swift +1 -1
  9. package/README.md +1 -1
  10. package/Rakefile +5 -17
  11. package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +3 -4
  12. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +0 -1
  13. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +0 -4
  14. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +0 -4
  15. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +0 -4
  16. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +0 -1
  17. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +0 -4
  18. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +0 -1
  19. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +0 -2
  20. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +0 -4
  21. package/Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift +0 -1
  22. package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +2 -2
  23. package/Sources/Private/Model/DotLottie/DotLottieUtils.swift +3 -5
  24. package/Sources/Private/Model/Extensions/Bundle.swift +1 -1
  25. package/Sources/Private/Utility/Extensions/DataExtension.swift +5 -8
  26. package/Sources/Private/Utility/Extensions/MathKit.swift +3 -3
  27. package/Sources/Private/Utility/Helpers/Binding+Map.swift +0 -1
  28. package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +15 -2
  29. package/Sources/Public/Animation/LottieAnimationHelpers.swift +0 -1
  30. package/Sources/Public/Animation/LottieAnimationLayer.swift +8 -8
  31. package/Sources/Public/Animation/LottieAnimationView.swift +1 -6
  32. package/Sources/Public/Animation/LottieView.swift +11 -8
  33. package/Sources/Public/Controls/LottieButton.swift +0 -1
  34. package/Sources/Public/Controls/LottieSwitch.swift +0 -1
  35. package/Sources/Public/Controls/LottieViewType.swift +1 -1
  36. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +0 -5
  37. package/Sources/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift +1 -1
  38. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +1 -1
  39. package/Sources/Public/iOS/LottieAnimationViewBase.swift +1 -5
  40. package/Version.xcconfig +6 -0
  41. package/lottie-ios.podspec +7 -7
  42. package/package.json +1 -1
  43. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +0 -46
  44. 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,
@@ -6,7 +6,7 @@
6
6
  //
7
7
 
8
8
  import Foundation
9
- #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst)
9
+ #if canImport(UIKit)
10
10
 
11
11
  /// An Objective-C compatible wrapper around Lottie's AnimationKeypath
12
12
  @objc
@@ -6,7 +6,7 @@
6
6
  //
7
7
 
8
8
  import Foundation
9
- #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst)
9
+ #if canImport(UIKit)
10
10
  import UIKit
11
11
 
12
12
  /// An Objective-C compatible wrapper around Lottie's Animation class.
@@ -39,11 +39,7 @@ open class LottieAnimationViewBase: UIView {
39
39
 
40
40
  var screenScale: CGFloat {
41
41
  #if os(iOS) || os(tvOS)
42
- if #available(iOS 13.0, tvOS 13.0, *) {
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.
@@ -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.
@@ -8,7 +8,7 @@
8
8
 
9
9
  Pod::Spec.new do |s|
10
10
  s.name = 'lottie-ios'
11
- s.version = '4.4.0'
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 = '11.0'
27
- s.osx.deployment_target = '10.12'
28
- s.tvos.deployment_target = '11.0'
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
- 'Lottie_Privacy' => ['Sources/PrivacyInfo.xcprivacy'],
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.0",
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": {
@@ -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