lottie-ios 4.2.0 → 4.3.0
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 +117 -11
- package/Gemfile +1 -0
- package/Gemfile.lock +5 -0
- package/Lottie.xcodeproj/project.pbxproj +1542 -194
- package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (iOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (macOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (tvOS).xcscheme +4 -5
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (visionOS).xcscheme +66 -0
- package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +18 -0
- package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 -11
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +346 -102
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +16 -5
- package/Package.resolved +2 -2
- package/Package.swift +16 -9
- package/README.md +2 -2
- package/Rakefile +122 -25
- package/Sources/Private/CoreAnimation/Animations/DropShadowAnimation.swift +160 -0
- package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +7 -7
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +2 -2
- package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
- package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
- package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
- package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
- package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
- package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/README.md +27 -0
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
- package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
- package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
- package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
- package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
- package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
- package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
- package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
- package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
- package/Sources/Private/Model/Assets/Asset.swift +8 -0
- package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
- package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
- package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
- package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
- package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
- package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
- package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
- package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
- package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
- package/Sources/Private/Model/Objects/Marker.swift +1 -1
- package/Sources/Private/Model/Objects/Transform.swift +1 -2
- package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
- package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
- package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
- package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
- package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
- package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
- package/Sources/Private/Model/Text/Font.swift +2 -2
- package/Sources/Private/Model/Text/Glyph.swift +1 -1
- package/Sources/Private/RootAnimationLayer.swift +2 -2
- package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
- package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
- package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
- package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
- package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
- package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
- package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
- package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
- package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
- package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
- package/Sources/Public/Animation/LottieAnimation.swift +21 -2
- package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
- package/Sources/Public/Animation/LottieAnimationView.swift +258 -735
- package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
- package/Sources/Public/Animation/LottieView.swift +462 -0
- package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
- package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
- package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
- package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
- package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
- package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
- package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
- package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
- package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
- package/Sources/Public/Controls/LottieButton.swift +122 -0
- package/Sources/Public/Controls/LottieSwitch.swift +144 -0
- package/Sources/Public/Controls/LottieViewType.swift +79 -0
- package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
- package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
- package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
- package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
- package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
- package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
- package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
- package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
- package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
- package/Sources/Public/Keyframes/Keyframe.swift +6 -0
- package/Sources/Public/Primitives/Vectors.swift +2 -2
- package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
- package/Sources/Public/iOS/AnimationSubview.swift +1 -1
- package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +2 -2
- package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
- package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
- package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
- package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
- package/lottie-ios.podspec +2 -1
- package/package.json +1 -1
- package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/URL+ZIP.swift +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Created by eric_horacek on 6/22/22.
|
|
2
|
+
// Copyright © 2022 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import SwiftUI
|
|
5
|
+
|
|
6
|
+
// MARK: - MeasuringViewRepresentable
|
|
7
|
+
|
|
8
|
+
/// A `UIViewRepresentable` that uses a `SwiftUIMeasurementContainer` wrapping its represented
|
|
9
|
+
/// `UIView` to report its size that fits a proposed size to SwiftUI.
|
|
10
|
+
///
|
|
11
|
+
/// Supports iOS 13-15 using the private `_overrideSizeThatFits(…)` method and iOS 16+ using the
|
|
12
|
+
/// `sizeThatFits(…)` method.
|
|
13
|
+
///
|
|
14
|
+
/// - SeeAlso: ``SwiftUIMeasurementContainer``
|
|
15
|
+
@available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
|
|
16
|
+
protocol MeasuringViewRepresentable: ViewRepresentableType
|
|
17
|
+
where
|
|
18
|
+
RepresentableViewType == SwiftUIMeasurementContainer<Content>
|
|
19
|
+
{
|
|
20
|
+
/// The `UIView` content that's being measured by the enclosing `SwiftUIMeasurementContainer`.
|
|
21
|
+
associatedtype Content: ViewType
|
|
22
|
+
|
|
23
|
+
/// The sizing strategy of the represented view.
|
|
24
|
+
///
|
|
25
|
+
/// To configure the sizing behavior of the `View` instance, call `sizing` on this `View`, e.g.:
|
|
26
|
+
/// ```
|
|
27
|
+
/// myView.sizing(.intrinsicSize)
|
|
28
|
+
/// ```
|
|
29
|
+
var sizing: SwiftUIMeasurementContainerStrategy { get set }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// MARK: Extensions
|
|
33
|
+
|
|
34
|
+
@available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
|
|
35
|
+
extension MeasuringViewRepresentable {
|
|
36
|
+
/// Returns a copy of this view with its sizing strategy updated to the given `sizing` value.
|
|
37
|
+
func sizing(_ strategy: SwiftUIMeasurementContainerStrategy) -> Self {
|
|
38
|
+
var copy = self
|
|
39
|
+
copy.sizing = strategy
|
|
40
|
+
return copy
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// MARK: Defaults
|
|
45
|
+
|
|
46
|
+
#if os(iOS) || os(tvOS)
|
|
47
|
+
@available(iOS 13.0, tvOS 13.0, *)
|
|
48
|
+
extension MeasuringViewRepresentable {
|
|
49
|
+
func _overrideSizeThatFits(
|
|
50
|
+
_ size: inout CGSize,
|
|
51
|
+
in proposedSize: _ProposedSize,
|
|
52
|
+
uiView: UIViewType)
|
|
53
|
+
{
|
|
54
|
+
uiView.strategy = sizing
|
|
55
|
+
|
|
56
|
+
// Note: this method is not double-called on iOS 16, so we don't need to do anything to prevent
|
|
57
|
+
// extra work here.
|
|
58
|
+
let children = Mirror(reflecting: proposedSize).children
|
|
59
|
+
|
|
60
|
+
// Creates a `CGSize` by replacing `nil`s with `UIView.noIntrinsicMetric`
|
|
61
|
+
uiView.proposedSize = .init(
|
|
62
|
+
width: children.first { $0.label == "width" }?.value as? CGFloat ?? ViewType.noIntrinsicMetric,
|
|
63
|
+
height: children.first { $0.label == "height" }?.value as? CGFloat ?? ViewType.noIntrinsicMetric)
|
|
64
|
+
|
|
65
|
+
size = uiView.measuredFittingSize
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#if swift(>=5.7) // Proxy check for being built with the iOS 15 SDK
|
|
69
|
+
@available(iOS 16.0, tvOS 16.0, macOS 13.0, *)
|
|
70
|
+
func sizeThatFits(
|
|
71
|
+
_ proposal: ProposedViewSize,
|
|
72
|
+
uiView: UIViewType,
|
|
73
|
+
context _: Context)
|
|
74
|
+
-> CGSize?
|
|
75
|
+
{
|
|
76
|
+
uiView.strategy = sizing
|
|
77
|
+
|
|
78
|
+
// Creates a size by replacing `nil`s with `UIView.noIntrinsicMetric`
|
|
79
|
+
uiView.proposedSize = .init(
|
|
80
|
+
width: proposal.width ?? ViewType.noIntrinsicMetric,
|
|
81
|
+
height: proposal.height ?? ViewType.noIntrinsicMetric)
|
|
82
|
+
|
|
83
|
+
return uiView.measuredFittingSize
|
|
84
|
+
}
|
|
85
|
+
#endif
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#elseif os(macOS)
|
|
89
|
+
@available(macOS 10.15, *)
|
|
90
|
+
extension MeasuringViewRepresentable {
|
|
91
|
+
func _overrideSizeThatFits(
|
|
92
|
+
_ size: inout CGSize,
|
|
93
|
+
in proposedSize: _ProposedSize,
|
|
94
|
+
nsView: NSViewType)
|
|
95
|
+
{
|
|
96
|
+
nsView.strategy = sizing
|
|
97
|
+
|
|
98
|
+
let children = Mirror(reflecting: proposedSize).children
|
|
99
|
+
|
|
100
|
+
// Creates a `CGSize` by replacing `nil`s with `UIView.noIntrinsicMetric`
|
|
101
|
+
nsView.proposedSize = .init(
|
|
102
|
+
width: children.first { $0.label == "width" }?.value as? CGFloat ?? ViewType.noIntrinsicMetric,
|
|
103
|
+
height: children.first { $0.label == "height" }?.value as? CGFloat ?? ViewType.noIntrinsicMetric)
|
|
104
|
+
|
|
105
|
+
size = nsView.measuredFittingSize
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Proxy check for being built with the macOS 13 SDK.
|
|
109
|
+
#if swift(>=5.7.1)
|
|
110
|
+
@available(macOS 13.0, *)
|
|
111
|
+
func sizeThatFits(
|
|
112
|
+
_ proposal: ProposedViewSize,
|
|
113
|
+
nsView: NSViewType,
|
|
114
|
+
context _: Context)
|
|
115
|
+
-> CGSize?
|
|
116
|
+
{
|
|
117
|
+
nsView.strategy = sizing
|
|
118
|
+
|
|
119
|
+
// Creates a size by replacing `nil`s with `UIView.noIntrinsicMetric`
|
|
120
|
+
nsView.proposedSize = .init(
|
|
121
|
+
width: proposal.width ?? ViewType.noIntrinsicMetric,
|
|
122
|
+
height: proposal.height ?? ViewType.noIntrinsicMetric)
|
|
123
|
+
|
|
124
|
+
return nsView.measuredFittingSize
|
|
125
|
+
}
|
|
126
|
+
#endif
|
|
127
|
+
}
|
|
128
|
+
#endif
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
// Created by Bryn Bodayle on 1/24/22.
|
|
2
|
+
// Copyright © 2022 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import SwiftUI
|
|
5
|
+
|
|
6
|
+
// MARK: - SwiftUIMeasurementContainer
|
|
7
|
+
|
|
8
|
+
/// A view that has an `intrinsicContentSize` of the `uiView`'s `systemLayoutSizeFitting(…)` and
|
|
9
|
+
/// supports double layout pass sizing and content size category changes.
|
|
10
|
+
///
|
|
11
|
+
/// This container view uses an injected proposed width to measure the view and return its ideal
|
|
12
|
+
/// height through the `SwiftUISizingContext` binding.
|
|
13
|
+
///
|
|
14
|
+
/// - SeeAlso: ``MeasuringViewRepresentable``
|
|
15
|
+
@available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
|
|
16
|
+
final class SwiftUIMeasurementContainer<Content: ViewType>: ViewType {
|
|
17
|
+
|
|
18
|
+
// MARK: Lifecycle
|
|
19
|
+
|
|
20
|
+
init(content: Content, strategy: SwiftUIMeasurementContainerStrategy) {
|
|
21
|
+
self.content = content
|
|
22
|
+
self.strategy = strategy
|
|
23
|
+
|
|
24
|
+
// On iOS 15 and below, passing zero can result in a constraint failure the first time a view
|
|
25
|
+
// is displayed, but the system gracefully recovers afterwards. On iOS 16, it's fine to pass
|
|
26
|
+
// zero.
|
|
27
|
+
let initialSize: CGSize
|
|
28
|
+
if #available(iOS 16, tvOS 16, macOS 13, *) {
|
|
29
|
+
initialSize = .zero
|
|
30
|
+
} else {
|
|
31
|
+
initialSize = .init(width: 375, height: 150)
|
|
32
|
+
}
|
|
33
|
+
super.init(frame: .init(origin: .zero, size: initialSize))
|
|
34
|
+
|
|
35
|
+
addSubview(content)
|
|
36
|
+
setUpConstraints()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@available(*, unavailable)
|
|
40
|
+
required init?(coder _: NSCoder) {
|
|
41
|
+
fatalError("init(coder:) has not been implemented")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// MARK: Internal
|
|
45
|
+
|
|
46
|
+
/// The most recently measured fitting size of the `uiView` that fits within the current
|
|
47
|
+
/// `proposedSize`.
|
|
48
|
+
///
|
|
49
|
+
/// Contains `proposedSize`/`bounds.size` fallbacks for dimensions with no intrinsic size, as
|
|
50
|
+
/// compared to `intrinsicContentSize` which has `UIView.noIntrinsicMetric` fields in the case of
|
|
51
|
+
/// no intrinsic size.
|
|
52
|
+
var measuredFittingSize: CGSize {
|
|
53
|
+
_measuredFittingSize ?? measureView()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// The `UIView` content that's being measured by this container.
|
|
57
|
+
var content: Content {
|
|
58
|
+
didSet {
|
|
59
|
+
guard content !== oldValue else { return }
|
|
60
|
+
oldValue.removeFromSuperview()
|
|
61
|
+
addSubview(content)
|
|
62
|
+
// Invalidate the strategy since it's derived from this view.
|
|
63
|
+
_resolvedStrategy = nil
|
|
64
|
+
// Re-configure the constraints since they depend on the resolved strategy.
|
|
65
|
+
setUpConstraints()
|
|
66
|
+
// Finally, we need to re-measure the view.
|
|
67
|
+
_measuredFittingSize = nil
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// The proposed size at the time of the latest measurement.
|
|
72
|
+
///
|
|
73
|
+
/// Has a side-effect of updating the `measuredIntrinsicContentSize` if it's changed.
|
|
74
|
+
var proposedSize = CGSize.noIntrinsicMetric {
|
|
75
|
+
didSet {
|
|
76
|
+
guard oldValue != proposedSize else { return }
|
|
77
|
+
// The proposed size is only used by the measurement, so just re-measure.
|
|
78
|
+
_measuredFittingSize = nil
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// The measurement strategy of this container.
|
|
83
|
+
///
|
|
84
|
+
/// Has a side-effect of updating the `measuredIntrinsicContentSize` if it's changed.
|
|
85
|
+
var strategy: SwiftUIMeasurementContainerStrategy {
|
|
86
|
+
didSet {
|
|
87
|
+
guard oldValue != strategy else { return }
|
|
88
|
+
// Invalidate the resolved strategy since it's derived from this strategy.
|
|
89
|
+
_resolvedStrategy = nil
|
|
90
|
+
// Then, re-measure the view.
|
|
91
|
+
_measuredFittingSize = nil
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override var intrinsicContentSize: CGSize {
|
|
96
|
+
_intrinsicContentSize
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#if os(macOS)
|
|
100
|
+
override func layout() {
|
|
101
|
+
super.layout()
|
|
102
|
+
|
|
103
|
+
// We need to re-measure the view whenever the size of the bounds changes, as the previous size
|
|
104
|
+
// may now be incorrect.
|
|
105
|
+
if latestMeasurementBoundsSize != nil, bounds.size != latestMeasurementBoundsSize {
|
|
106
|
+
// This will trigger SwiftUI to re-measure the view.
|
|
107
|
+
super.invalidateIntrinsicContentSize()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
#else
|
|
111
|
+
override func layoutSubviews() {
|
|
112
|
+
super.layoutSubviews()
|
|
113
|
+
|
|
114
|
+
// We need to re-measure the view whenever the size of the bounds changes, as the previous size
|
|
115
|
+
// may now be incorrect.
|
|
116
|
+
if latestMeasurementBoundsSize != nil, bounds.size != latestMeasurementBoundsSize {
|
|
117
|
+
// This will trigger SwiftUI to re-measure the view.
|
|
118
|
+
super.invalidateIntrinsicContentSize()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
#endif
|
|
122
|
+
|
|
123
|
+
override func invalidateIntrinsicContentSize() {
|
|
124
|
+
super.invalidateIntrinsicContentSize()
|
|
125
|
+
|
|
126
|
+
// Invalidate the resolved strategy in case it changes with the re-measurement as it relies on
|
|
127
|
+
// the intrinsic size.
|
|
128
|
+
_resolvedStrategy = nil
|
|
129
|
+
_measuredFittingSize = nil
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// MARK: Private
|
|
133
|
+
|
|
134
|
+
/// The most recently measured intrinsic content size of the `uiView`, else `noIntrinsicMetric` if
|
|
135
|
+
/// it has not yet been measured.
|
|
136
|
+
///
|
|
137
|
+
/// Contains `UIView.noIntrinsicMetric` fallbacks for dimensions with no intrinsic size,
|
|
138
|
+
/// as compared to `measuredFittingSize` which has `proposedSize`/`bounds.size` fallbacks.
|
|
139
|
+
private var _intrinsicContentSize = CGSize.noIntrinsicMetric
|
|
140
|
+
|
|
141
|
+
/// The bounds size at the time of the latest measurement.
|
|
142
|
+
private var latestMeasurementBoundsSize: CGSize?
|
|
143
|
+
|
|
144
|
+
/// The most recently updated set of constraints constraining `uiView` to `self`.
|
|
145
|
+
private var uiViewConstraints = [NSLayoutConstraint.Attribute: NSLayoutConstraint]()
|
|
146
|
+
|
|
147
|
+
/// The cached `resolvedStrategy` to prevent unnecessary re-measurements.
|
|
148
|
+
private var _resolvedStrategy: ResolvedSwiftUIMeasurementContainerStrategy?
|
|
149
|
+
|
|
150
|
+
/// The cached `measuredFittingSize` to prevent unnecessary re-measurements.
|
|
151
|
+
private var _measuredFittingSize: CGSize?
|
|
152
|
+
|
|
153
|
+
/// The resolved measurement strategy.
|
|
154
|
+
private var resolvedStrategy: ResolvedSwiftUIMeasurementContainerStrategy {
|
|
155
|
+
if let resolvedStrategy = _resolvedStrategy {
|
|
156
|
+
return resolvedStrategy
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let resolved: ResolvedSwiftUIMeasurementContainerStrategy
|
|
160
|
+
switch strategy {
|
|
161
|
+
case .automatic:
|
|
162
|
+
// Perform an intrinsic size measurement pass, which gives us valid values for
|
|
163
|
+
// `UILabel.preferredMaxLayoutWidth`.
|
|
164
|
+
let intrinsicSize = content.systemLayoutFittingIntrinsicSize()
|
|
165
|
+
|
|
166
|
+
// If the view has a intrinsic width and contains a double layout pass subview, give it the
|
|
167
|
+
// proposed width to allow the label content to gracefully wrap to multiple lines.
|
|
168
|
+
if intrinsicSize.width > 0, content.containsDoubleLayoutPassSubviews() {
|
|
169
|
+
resolved = .intrinsicHeightProposedWidth
|
|
170
|
+
} else {
|
|
171
|
+
let zero = CGFloat(0)
|
|
172
|
+
switch (width: intrinsicSize.width, height: intrinsicSize.height) {
|
|
173
|
+
case (width: ...zero, height: ...zero):
|
|
174
|
+
resolved = .proposed
|
|
175
|
+
case (width: ...zero, height: zero.nextUp...):
|
|
176
|
+
resolved = .intrinsicHeightProposedWidth
|
|
177
|
+
case (width: zero.nextUp..., height: ...zero):
|
|
178
|
+
resolved = .intrinsicWidthProposedHeight
|
|
179
|
+
default:
|
|
180
|
+
resolved = .intrinsic(intrinsicSize)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
case .proposed:
|
|
184
|
+
resolved = .proposed
|
|
185
|
+
case .intrinsicHeightProposedWidth:
|
|
186
|
+
resolved = .intrinsicHeightProposedWidth
|
|
187
|
+
case .intrinsicWidthProposedHeight:
|
|
188
|
+
resolved = .intrinsicWidthProposedHeight
|
|
189
|
+
case .intrinsic:
|
|
190
|
+
resolved = .intrinsic(content.systemLayoutFittingIntrinsicSize())
|
|
191
|
+
}
|
|
192
|
+
_resolvedStrategy = resolved
|
|
193
|
+
return resolved
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private func setUpConstraints() {
|
|
197
|
+
content.translatesAutoresizingMaskIntoConstraints = false
|
|
198
|
+
|
|
199
|
+
let leading = content.leadingAnchor.constraint(equalTo: leadingAnchor)
|
|
200
|
+
let top = content.topAnchor.constraint(equalTo: topAnchor)
|
|
201
|
+
let trailing = content.trailingAnchor.constraint(equalTo: trailingAnchor)
|
|
202
|
+
let bottom = content.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
203
|
+
let newConstraints: [NSLayoutConstraint.Attribute: NSLayoutConstraint] = [
|
|
204
|
+
.leading: leading, .top: top, .trailing: trailing, .bottom: bottom,
|
|
205
|
+
]
|
|
206
|
+
// Start with the lowest priority constraints so we aren't measuring the view too early, the
|
|
207
|
+
// priorities will be updated later on.
|
|
208
|
+
prioritizeConstraints(newConstraints, strategy: .intrinsic(.zero))
|
|
209
|
+
|
|
210
|
+
NSLayoutConstraint.deactivate(Array(uiViewConstraints.values))
|
|
211
|
+
uiViewConstraints = newConstraints
|
|
212
|
+
NSLayoutConstraint.activate(Array(uiViewConstraints.values))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Prioritizes the given constraints based on the provided resolved strategy.
|
|
216
|
+
private func prioritizeConstraints(
|
|
217
|
+
_ constraints: [NSLayoutConstraint.Attribute: NSLayoutConstraint],
|
|
218
|
+
strategy: ResolvedSwiftUIMeasurementContainerStrategy)
|
|
219
|
+
{
|
|
220
|
+
// Give a required constraint in the dimensions that are fixed to the bounds, otherwise almost
|
|
221
|
+
// required.
|
|
222
|
+
switch strategy {
|
|
223
|
+
case .proposed:
|
|
224
|
+
constraints[.trailing]?.priority = .required
|
|
225
|
+
constraints[.bottom]?.priority = .required
|
|
226
|
+
case .intrinsicHeightProposedWidth:
|
|
227
|
+
constraints[.trailing]?.priority = .required
|
|
228
|
+
constraints[.bottom]?.priority = .almostRequired
|
|
229
|
+
case .intrinsicWidthProposedHeight:
|
|
230
|
+
constraints[.trailing]?.priority = .almostRequired
|
|
231
|
+
constraints[.bottom]?.priority = .required
|
|
232
|
+
case .intrinsic:
|
|
233
|
+
constraints[.trailing]?.priority = .almostRequired
|
|
234
|
+
constraints[.bottom]?.priority = .almostRequired
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#if os(macOS)
|
|
238
|
+
// On macOS, views default to having required constraints setting their height / width
|
|
239
|
+
// equal to their intrinsic content size. These have to be disabled in favor of the constraints
|
|
240
|
+
// we create here.
|
|
241
|
+
content.isVerticalContentSizeConstraintActive = false
|
|
242
|
+
content.isHorizontalContentSizeConstraintActive = false
|
|
243
|
+
#endif
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Measures the `uiView`, storing the resulting size in `measuredIntrinsicContentSize`.
|
|
247
|
+
private func measureView() -> CGSize {
|
|
248
|
+
latestMeasurementBoundsSize = bounds.size
|
|
249
|
+
prioritizeConstraints(uiViewConstraints, strategy: resolvedStrategy)
|
|
250
|
+
|
|
251
|
+
var measuredSize: CGSize
|
|
252
|
+
let proposedSizeElseBounds = proposedSize.replacingNoIntrinsicMetric(with: bounds.size)
|
|
253
|
+
|
|
254
|
+
switch resolvedStrategy {
|
|
255
|
+
case .proposed:
|
|
256
|
+
measuredSize = .noIntrinsicMetric
|
|
257
|
+
|
|
258
|
+
case .intrinsicHeightProposedWidth:
|
|
259
|
+
measuredSize = content.systemLayoutFittingIntrinsicHeightFixedWidth(proposedSizeElseBounds.width)
|
|
260
|
+
measuredSize.width = ViewType.noIntrinsicMetric
|
|
261
|
+
|
|
262
|
+
case .intrinsicWidthProposedHeight:
|
|
263
|
+
measuredSize = content.systemLayoutFittingIntrinsicWidthFixedHeight(proposedSizeElseBounds.height)
|
|
264
|
+
measuredSize.height = ViewType.noIntrinsicMetric
|
|
265
|
+
|
|
266
|
+
case .intrinsic(let size):
|
|
267
|
+
measuredSize = size
|
|
268
|
+
|
|
269
|
+
// If the measured size exceeds an available width or height, set the measured size to
|
|
270
|
+
// `noIntrinsicMetric` to ensure that the component can be compressed, otherwise it will
|
|
271
|
+
// overflow beyond the proposed size.
|
|
272
|
+
// - If the previous intrinsic content size is the same as the new proposed size, we don't
|
|
273
|
+
// do this as SwiftUI sometimes "proposes" the same intrinsic size back to the component and
|
|
274
|
+
// we don't want that scenario to prevent size changes when there is actually more space
|
|
275
|
+
// available.
|
|
276
|
+
if
|
|
277
|
+
proposedSize.width != ViewType.noIntrinsicMetric,
|
|
278
|
+
measuredSize.width > proposedSizeElseBounds.width,
|
|
279
|
+
_intrinsicContentSize.width != proposedSize.width
|
|
280
|
+
{
|
|
281
|
+
measuredSize.width = ViewType.noIntrinsicMetric
|
|
282
|
+
}
|
|
283
|
+
if
|
|
284
|
+
proposedSize.height != ViewType.noIntrinsicMetric,
|
|
285
|
+
measuredSize.height > proposedSizeElseBounds.height,
|
|
286
|
+
_intrinsicContentSize.height != proposedSize.height
|
|
287
|
+
{
|
|
288
|
+
measuredSize.height = ViewType.noIntrinsicMetric
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
_intrinsicContentSize = measuredSize
|
|
293
|
+
|
|
294
|
+
let measuredFittingSize = measuredSize.replacingNoIntrinsicMetric(with: proposedSizeElseBounds)
|
|
295
|
+
_measuredFittingSize = measuredFittingSize
|
|
296
|
+
return measuredFittingSize
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// MARK: - SwiftUIMeasurementContainerStrategy
|
|
301
|
+
|
|
302
|
+
/// The measurement strategy of a `SwiftUIMeasurementContainer`.
|
|
303
|
+
enum SwiftUIMeasurementContainerStrategy {
|
|
304
|
+
/// The container makes a best effort to correctly choose the measurement strategy of the view.
|
|
305
|
+
///
|
|
306
|
+
/// The best effort is based on a number of heuristics:
|
|
307
|
+
/// - The `uiView` will be given its intrinsic width and/or height when measurement in that
|
|
308
|
+
/// dimension produces a positive value, while zero/negative values will result in that
|
|
309
|
+
/// dimension receiving the available space proposed by the parent.
|
|
310
|
+
/// - If the view contains `UILabel` subviews that require a double layout pass as determined by
|
|
311
|
+
/// a `preferredMaxLayoutWidth` that's greater than zero after a layout, then the view will
|
|
312
|
+
/// default to `intrinsicHeightProposedWidth` to allow the labels to wrap.
|
|
313
|
+
///
|
|
314
|
+
/// If you would like to opt out of automatic sizing for performance or to override the default
|
|
315
|
+
/// behavior, choose another strategy.
|
|
316
|
+
case automatic
|
|
317
|
+
|
|
318
|
+
/// The `uiView` is sized to fill the area proposed by its parent.
|
|
319
|
+
///
|
|
320
|
+
/// Typically used for views that should expand greedily in both axes, e.g. a background view.
|
|
321
|
+
case proposed
|
|
322
|
+
|
|
323
|
+
/// The `uiView` is sized with its intrinsic height and expands horizontally to fill the width
|
|
324
|
+
/// proposed by its parent.
|
|
325
|
+
///
|
|
326
|
+
/// Typically used for views that have a height that's a function of their width, e.g. a row with
|
|
327
|
+
/// text that can wrap to multiple lines.
|
|
328
|
+
case intrinsicHeightProposedWidth
|
|
329
|
+
|
|
330
|
+
/// The `uiView` is sized with its intrinsic width and expands vertically to fill the height
|
|
331
|
+
/// proposed by its parent.
|
|
332
|
+
///
|
|
333
|
+
/// Typically used for views that are free to grow vertically but have a fixed width, e.g. a view
|
|
334
|
+
/// in a horizontal carousel.
|
|
335
|
+
case intrinsicWidthProposedHeight
|
|
336
|
+
|
|
337
|
+
/// The `uiView` is sized to its intrinsic width and height.
|
|
338
|
+
///
|
|
339
|
+
/// Typically used for components with a specific intrinsic size in both axes, e.g. controls or
|
|
340
|
+
/// inputs.
|
|
341
|
+
case intrinsic
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// MARK: - ResolvedSwiftUIMeasurementContainerStrategy
|
|
345
|
+
|
|
346
|
+
/// The resolved measurement strategy of a `SwiftUIMeasurementContainer`, matching the cases of the
|
|
347
|
+
/// `SwiftUIMeasurementContainerStrategy` without the automatic case.
|
|
348
|
+
private enum ResolvedSwiftUIMeasurementContainerStrategy {
|
|
349
|
+
case proposed, intrinsicHeightProposedWidth, intrinsicWidthProposedHeight, intrinsic(CGSize)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// MARK: - UILayoutPriority
|
|
353
|
+
|
|
354
|
+
extension LayoutPriorityType {
|
|
355
|
+
/// An "almost required" constraint, useful for creating near-required constraints that don't
|
|
356
|
+
/// error when unable to be satisfied.
|
|
357
|
+
@nonobjc
|
|
358
|
+
fileprivate static var almostRequired: LayoutPriorityType { .init(rawValue: required.rawValue - 1) }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// MARK: - UIView
|
|
362
|
+
|
|
363
|
+
extension ViewType {
|
|
364
|
+
/// The `systemLayoutSizeFitting(…)` of this view with a compressed size and fitting priorities.
|
|
365
|
+
@nonobjc
|
|
366
|
+
fileprivate func systemLayoutFittingIntrinsicSize() -> CGSize {
|
|
367
|
+
#if os(macOS)
|
|
368
|
+
intrinsicContentSize
|
|
369
|
+
#else
|
|
370
|
+
systemLayoutSizeFitting(
|
|
371
|
+
UIView.layoutFittingCompressedSize,
|
|
372
|
+
withHorizontalFittingPriority: .fittingSizeLevel,
|
|
373
|
+
verticalFittingPriority: .fittingSizeLevel)
|
|
374
|
+
#endif
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/// The `systemLayoutSizeFitting(…)` of this view with a compressed height with a fitting size
|
|
378
|
+
/// priority and with the given fixed width and fitting priority.
|
|
379
|
+
@nonobjc
|
|
380
|
+
fileprivate func systemLayoutFittingIntrinsicHeightFixedWidth(
|
|
381
|
+
_ width: CGFloat,
|
|
382
|
+
priority: LayoutPriorityType = .almostRequired)
|
|
383
|
+
-> CGSize
|
|
384
|
+
{
|
|
385
|
+
#if os(macOS)
|
|
386
|
+
return CGSize(width: width, height: intrinsicContentSize.height)
|
|
387
|
+
#else
|
|
388
|
+
let targetSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height)
|
|
389
|
+
|
|
390
|
+
return systemLayoutSizeFitting(
|
|
391
|
+
targetSize,
|
|
392
|
+
withHorizontalFittingPriority: priority,
|
|
393
|
+
verticalFittingPriority: .fittingSizeLevel)
|
|
394
|
+
#endif
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/// The `systemLayoutSizeFitting(…)` of this view with a compressed width with a fitting size
|
|
398
|
+
/// priority and with the given fixed height and fitting priority.
|
|
399
|
+
@nonobjc
|
|
400
|
+
fileprivate func systemLayoutFittingIntrinsicWidthFixedHeight(
|
|
401
|
+
_ height: CGFloat,
|
|
402
|
+
priority: LayoutPriorityType = .almostRequired)
|
|
403
|
+
-> CGSize
|
|
404
|
+
{
|
|
405
|
+
#if os(macOS)
|
|
406
|
+
return CGSize(width: intrinsicContentSize.width, height: height)
|
|
407
|
+
#else
|
|
408
|
+
let targetSize = CGSize(width: UIView.layoutFittingCompressedSize.width, height: height)
|
|
409
|
+
|
|
410
|
+
return systemLayoutSizeFitting(
|
|
411
|
+
targetSize,
|
|
412
|
+
withHorizontalFittingPriority: .fittingSizeLevel,
|
|
413
|
+
verticalFittingPriority: priority)
|
|
414
|
+
#endif
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/// Whether this view or any of its subviews has a subview that has a double layout pass `UILabel`
|
|
418
|
+
/// as determined by a non-zero `preferredMaxLayoutWidth`, which implies that it should get a
|
|
419
|
+
/// `intrinsicHeightProposedWidth` sizing strategy to allow the label to wrap and grow.
|
|
420
|
+
@nonobjc
|
|
421
|
+
fileprivate func containsDoubleLayoutPassSubviews() -> Bool {
|
|
422
|
+
#if os(macOS)
|
|
423
|
+
return false
|
|
424
|
+
#else
|
|
425
|
+
var contains = false
|
|
426
|
+
if let label = self as? UILabel, label.preferredMaxLayoutWidth > 0 {
|
|
427
|
+
contains = true
|
|
428
|
+
}
|
|
429
|
+
for subview in subviews {
|
|
430
|
+
contains = contains || subview.containsDoubleLayoutPassSubviews()
|
|
431
|
+
}
|
|
432
|
+
return contains
|
|
433
|
+
#endif
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// MARK: - CGSize
|
|
438
|
+
|
|
439
|
+
extension CGSize {
|
|
440
|
+
/// A `CGSize` with `noIntrinsicMetric` for both its width and height.
|
|
441
|
+
fileprivate static var noIntrinsicMetric: CGSize {
|
|
442
|
+
.init(width: ViewType.noIntrinsicMetric, height: ViewType.noIntrinsicMetric)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/// Returns a `CGSize` with its width and/or height replaced with the corresponding field of the
|
|
446
|
+
/// provided `fallback` size if they are `UIView.noIntrinsicMetric`.
|
|
447
|
+
fileprivate func replacingNoIntrinsicMetric(with fallback: CGSize) -> CGSize {
|
|
448
|
+
.init(
|
|
449
|
+
width: width == ViewType.noIntrinsicMetric ? fallback.width : width,
|
|
450
|
+
height: height == ViewType.noIntrinsicMetric ? fallback.height : height)
|
|
451
|
+
}
|
|
452
|
+
}
|