diff --git a/RevenueCatUI/Modifiers/FitToAspectRatio.swift b/RevenueCatUI/Modifiers/FitToAspectRatio.swift index c8db43e3b9..514ccf2d42 100644 --- a/RevenueCatUI/Modifiers/FitToAspectRatio.swift +++ b/RevenueCatUI/Modifiers/FitToAspectRatio.swift @@ -9,7 +9,18 @@ import Foundation import SwiftUI @available(iOS 16.0, macOS 13.0, tvOS 16.0, *) -struct FitToAspectRatio: ViewModifier { +extension Image { + + func fitToAspect(_ aspectRatio: Double, contentMode: SwiftUI.ContentMode) -> some View { + self.resizable() + .scaledToFill() + .modifier(FitToAspectRatio(aspectRatio: aspectRatio, contentMode: contentMode)) + } + +} + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +private struct FitToAspectRatio: ViewModifier { let aspectRatio: Double let contentMode: SwiftUI.ContentMode @@ -23,14 +34,3 @@ struct FitToAspectRatio: ViewModifier { } } - -@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) -extension Image { - - func fitToAspect(_ aspectRatio: Double, contentMode: SwiftUI.ContentMode) -> some View { - self.resizable() - .scaledToFill() - .modifier(FitToAspectRatio(aspectRatio: aspectRatio, contentMode: contentMode)) - } - -} diff --git a/RevenueCatUI/Modifiers/ViewExtensions.swift b/RevenueCatUI/Modifiers/ViewExtensions.swift index ad68dcd09d..fd48937127 100644 --- a/RevenueCatUI/Modifiers/ViewExtensions.swift +++ b/RevenueCatUI/Modifiers/ViewExtensions.swift @@ -54,4 +54,32 @@ extension View { } } + /// Invokes the given closure whethever the view size changes. + func onSizeChange(_ closure: @escaping (CGSize) -> Void) -> some View { + self + .overlay( + GeometryReader { geometry in + Color.clear + .preference(key: ViewSizePreferenceKey.self, + value: geometry.size) + } + ) + .onPreferenceChange(ViewSizePreferenceKey.self, perform: closure) + } + +} + +// MARK: - + +/// `PreferenceKey` for keeping track of view size. +private struct ViewSizePreferenceKey: PreferenceKey { + + typealias Value = CGSize + + static var defaultValue: Value = .init(width: 10, height: 10) + + static func reduce(value: inout Value, nextValue: () -> Value) { + value = nextValue() + } + } diff --git a/RevenueCatUI/Templates/OnePackageStandardTemplate.swift b/RevenueCatUI/Templates/OnePackageStandardTemplate.swift index cdea4f5c69..8bdf8ae45b 100644 --- a/RevenueCatUI/Templates/OnePackageStandardTemplate.swift +++ b/RevenueCatUI/Templates/OnePackageStandardTemplate.swift @@ -54,8 +54,6 @@ struct OnePackageStandardTemplate: TemplateViewType { VStack(spacing: self.configuration.mode.verticalSpacing) { self.headerImage - Spacer() - Group { Text(verbatim: self.localization.title) .font(self.configuration.mode.titleFont) @@ -94,12 +92,7 @@ struct OnePackageStandardTemplate: TemplateViewType { switch self.configuration.mode { case .fullScreen: self.asyncImage - .clipShape( - Circle() - .offset(y: -120) - .scale(3.0) - ) - .padding(.bottom) + .modifier(CircleMaskModifier()) Spacer() @@ -134,7 +127,7 @@ struct OnePackageStandardTemplate: TemplateViewType { return self.introEligibilityViewModel.singleEligibility } - private static let imageAspectRatio = 1.1 + private static let imageAspectRatio: CGFloat = 1.2 } @@ -181,6 +174,35 @@ private extension PaywallViewMode { } +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +private struct CircleMaskModifier: ViewModifier { + + @State + private var size: CGSize = .zero + + func body(content: Content) -> some View { + content + .onSizeChange { self.size = $0 } + .clipShape( + Circle() + .scale(Self.circleScale) + .offset(y: self.circleOffset) + ) + } + + private var aspectRatio: CGFloat { + return self.size.width / self.size.height + } + + private var circleOffset: CGFloat { + return (((self.size.height * Self.circleScale) - self.size.height) / 2.0 * -1) + .rounded(.down) + } + + private static let circleScale: CGFloat = 3 + +} + // MARK: - #if DEBUG