Skip to content

Commit

Permalink
Paywalls: added support for custom fonts (#2988)
Browse files Browse the repository at this point in the history
This adds a new `PaywallFontProvider` `protocol` as well as 2
implementations:
- `DefaultPaywallFontProvider`: exactly equivalent to the existing font
behavior
- `CustomPaywallFontProvider` allows using a custom font name and still
use dynamic type

![image](https://github.com/RevenueCat/purchases-ios/assets/685609/3659576c-a957-4f33-86ba-e3633520aa17)

![image](https://github.com/RevenueCat/purchases-ios/assets/685609/250304ae-c2e7-4726-888c-bb454651664e)
  • Loading branch information
NachoSoto committed Sep 6, 2023
1 parent d391392 commit c3838ff
Show file tree
Hide file tree
Showing 21 changed files with 358 additions and 63 deletions.
1 change: 1 addition & 0 deletions RevenueCatUI/Data/TemplateViewConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct TemplateViewConfiguration {
let packages: PackageConfiguration
let configuration: PaywallData.Configuration
let colors: PaywallData.Configuration.Colors
let fonts: PaywallFontProvider
let assetBaseURL: URL

}
Expand Down
1 change: 1 addition & 0 deletions RevenueCatUI/Helpers/PreviewHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct PreviewableTemplate<T: TemplateViewType>: View {
self.configuration = offering.paywall!.configuration(
for: offering,
mode: .fullScreen,
fonts: DefaultPaywallFontProvider(),
locale: .current
)
self.presentInSheet = presentInSheet
Expand Down
102 changes: 102 additions & 0 deletions RevenueCatUI/PaywallFontProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//
// PaywallFontProvider.swift
//
//
// Created by Nacho Soto on 8/8/23.
//

import SwiftUI

/// A type that returns a font for a given `Font.TextStyle`.
///
/// You can use one of the provided implementations, or make your own:
/// - ``DefaultPaywallFontProvider``
/// - ``CustomPaywallFontProvider``
public protocol PaywallFontProvider {

/// - Returns: Desired `Font` for the given `Font.TextStyle`.
@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
func font(for textStyle: Font.TextStyle) -> Font

}

/// Default ``PaywallFontProvider`` which uses the system default font
/// supporting dynamic type.
open class DefaultPaywallFontProvider: PaywallFontProvider {

// swiftlint:disable:next missing_docs
public init() {}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
// swiftlint:disable:next cyclomatic_complexity missing_docs
open func font(for textStyle: Font.TextStyle) -> Font {
switch textStyle {
case .largeTitle: return .largeTitle
case .title: return .title
case .title2: return .title2
case .title3: return .title3
case .headline: return .headline
case .subheadline: return .subheadline
case .body: return .body
case .callout: return .callout
case .footnote: return .footnote
case .caption: return .caption
case .caption2: return .caption2
@unknown default: return .body
}
}

}

#if canImport(UIKit)

/// A ``PaywallFontProvider`` implementation that allows you to provide a custom
/// font name, and it will automatically scale up based on the size category.
open class CustomPaywallFontProvider: PaywallFontProvider {

private let fontName: String

/// Creates a ``CustomPaywallFontProvider`` with a name.
public init(fontName: String) {
self.fontName = fontName
}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
// swiftlint:disable:next missing_docs
open func font(for textStyle: Font.TextStyle) -> Font {
return Font.custom(self.fontName,
size: UIFont.preferredFont(forTextStyle: textStyle.style).pointSize,
relativeTo: textStyle)
}

}

#endif

// MARK: - Private

#if canImport(UIKit)

@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
private extension Font.TextStyle {

var style: UIFont.TextStyle {
switch self {
case .largeTitle: return .largeTitle
case .title: return .title1
case .title2: return .title2
case .title3: return .title3
case .headline: return .headline
case .subheadline: return .subheadline
case .body: return .body
case .callout: return .callout
case .footnote: return .footnote
case .caption: return .caption1
case .caption2: return .caption2
@unknown default: return .body
}
}

}

#endif
24 changes: 22 additions & 2 deletions RevenueCatUI/PaywallView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import SwiftUI
public struct PaywallView: View {

private let mode: PaywallViewMode
private let fonts: PaywallFontProvider
private let introEligibility: TrialOrIntroEligibilityChecker?
private let purchaseHandler: PurchaseHandler?

Expand All @@ -26,10 +27,14 @@ public struct PaywallView: View {
/// an error will be displayed.
/// - Warning: `Purchases` must have been configured prior to displaying it.
/// If you want to handle that, you can use ``init(offering:mode:)`` instead.
public init(mode: PaywallViewMode = .default) {
public init(
mode: PaywallViewMode = .default,
fonts: PaywallFontProvider = DefaultPaywallFontProvider()
) {
self.init(
offering: nil,
mode: mode,
fonts: fonts,
introEligibility: .default(),
purchaseHandler: .default()
)
Expand All @@ -39,10 +44,15 @@ public struct PaywallView: View {
/// - Note: if `offering` does not have a current paywall, or it fails to load due to invalid data,
/// a default paywall will be displayed.
/// - Warning: `Purchases` must have been configured prior to displaying it.
public init(offering: Offering?, mode: PaywallViewMode = .default) {
public init(
offering: Offering,
mode: PaywallViewMode = .default,
fonts: PaywallFontProvider = DefaultPaywallFontProvider()
) {
self.init(
offering: offering,
mode: mode,
fonts: fonts,
introEligibility: .default(),
purchaseHandler: .default()
)
Expand All @@ -51,13 +61,15 @@ public struct PaywallView: View {
init(
offering: Offering?,
mode: PaywallViewMode = .default,
fonts: PaywallFontProvider = DefaultPaywallFontProvider(),
introEligibility: TrialOrIntroEligibilityChecker?,
purchaseHandler: PurchaseHandler?
) {
self._offering = .init(initialValue: offering)
self.introEligibility = introEligibility
self.purchaseHandler = purchaseHandler
self.mode = mode
self.fonts = fonts
}

// swiftlint:disable:next missing_docs
Expand All @@ -73,6 +85,7 @@ public struct PaywallView: View {
if let checker = self.introEligibility, let purchaseHandler = self.purchaseHandler {
if let offering = self.offering {
self.paywallView(for: offering,
fonts: self.fonts,
checker: checker,
purchaseHandler: purchaseHandler)
.transition(Self.transition)
Expand Down Expand Up @@ -104,6 +117,7 @@ public struct PaywallView: View {
@ViewBuilder
private func paywallView(
for offering: Offering,
fonts: PaywallFontProvider,
checker: TrialOrIntroEligibilityChecker,
purchaseHandler: PurchaseHandler
) -> some View {
Expand All @@ -112,6 +126,7 @@ public struct PaywallView: View {
offering: offering,
paywall: paywall,
mode: self.mode,
fonts: fonts,
introEligibility: checker,
purchaseHandler: purchaseHandler
)
Expand All @@ -127,6 +142,7 @@ public struct PaywallView: View {
offering: offering,
paywall: .createDefault(with: offering.availablePackages),
mode: self.mode,
fonts: fonts,
introEligibility: checker,
purchaseHandler: purchaseHandler
)
Expand All @@ -147,6 +163,7 @@ struct LoadedOfferingPaywallView: View {
private let offering: Offering
private let paywall: PaywallData
private let mode: PaywallViewMode
private let fonts: PaywallFontProvider

@StateObject
private var introEligibility: IntroEligibilityViewModel
Expand All @@ -160,12 +177,14 @@ struct LoadedOfferingPaywallView: View {
offering: Offering,
paywall: PaywallData,
mode: PaywallViewMode,
fonts: PaywallFontProvider,
introEligibility: TrialOrIntroEligibilityChecker,
purchaseHandler: PurchaseHandler
) {
self.offering = offering
self.paywall = paywall
self.mode = mode
self.fonts = fonts
self._introEligibility = .init(
wrappedValue: .init(introEligibilityChecker: introEligibility)
)
Expand All @@ -176,6 +195,7 @@ struct LoadedOfferingPaywallView: View {
let view = self.paywall
.createView(for: self.offering,
mode: self.mode,
fonts: self.fonts,
introEligibility: self.introEligibility,
locale: self.locale)
.environmentObject(self.introEligibility)
Expand Down
19 changes: 9 additions & 10 deletions RevenueCatUI/Templates/Template1View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SwiftUI
@available(tvOS, unavailable)
struct Template1View: TemplateViewType {

private let configuration: TemplateViewConfiguration
let configuration: TemplateViewConfiguration
private var localization: ProcessedLocalizedConfiguration

@EnvironmentObject
Expand Down Expand Up @@ -34,14 +34,14 @@ struct Template1View: TemplateViewType {
introEligibility: self.introEligibility,
foregroundColor: self.configuration.colors.text1Color
)
.font(self.configuration.mode.offerDetailsFont)
.font(self.font(for: self.configuration.mode.offerDetailsFont))
.multilineTextAlignment(.center)

self.button
.padding(.horizontal)

if case .fullScreen = self.configuration.mode {
FooterView(configuration: self.configuration.configuration,
FooterView(configuration: self.configuration,
color: self.configuration.colors.callToActionBackgroundColor,
purchaseHandler: self.purchaseHandler)
}
Expand All @@ -55,7 +55,7 @@ struct Template1View: TemplateViewType {

Group {
Text(.init(self.localization.title))
.font(self.configuration.mode.titleFont)
.font(self.font(for: self.configuration.mode.titleFont))
.fontWeight(.heavy)
.padding(
self.configuration.mode.displaySubtitle
Expand All @@ -65,7 +65,7 @@ struct Template1View: TemplateViewType {

if self.configuration.mode.displaySubtitle, let subtitle = self.localization.subtitle {
Text(.init(subtitle))
.font(self.configuration.mode.subtitleFont)
.font(self.font(for: self.configuration.mode.subtitleFont))
}
}
.padding(.horizontal, 20)
Expand Down Expand Up @@ -112,10 +112,9 @@ struct Template1View: TemplateViewType {
private var button: some View {
PurchaseButton(
package: self.configuration.packages.single.content,
colors: self.configuration.colors,
localization: self.localization,
configuration: self.configuration,
introEligibility: self.introEligibility,
mode: self.configuration.mode,
purchaseHandler: self.purchaseHandler
)
}
Expand All @@ -142,15 +141,15 @@ private extension PaywallViewMode {
}
}

var titleFont: Font {
var titleFont: Font.TextStyle {
switch self {
case .fullScreen: return .largeTitle
case .card: return .title
case .banner: return .headline
}
}

var subtitleFont: Font {
var subtitleFont: Font.TextStyle {
switch self {
case .fullScreen: return .subheadline
case .card, .banner: return .callout
Expand All @@ -164,7 +163,7 @@ private extension PaywallViewMode {
}
}

var offerDetailsFont: Font {
var offerDetailsFont: Font.TextStyle {
switch self {
case .fullScreen: return .callout
case .card, .banner: return .caption
Expand Down
15 changes: 7 additions & 8 deletions RevenueCatUI/Templates/Template2View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SwiftUI
@available(tvOS, unavailable)
struct Template2View: TemplateViewType {

private let configuration: TemplateViewConfiguration
let configuration: TemplateViewConfiguration
private var localization: [Package: ProcessedLocalizedConfiguration]

@State
Expand Down Expand Up @@ -41,7 +41,7 @@ struct Template2View: TemplateViewType {
.padding(.horizontal)

if case .fullScreen = self.configuration.mode {
FooterView(configuration: self.configuration.configuration,
FooterView(configuration: self.configuration,
color: self.configuration.colors.text1Color,
purchaseHandler: self.purchaseHandler)
}
Expand All @@ -61,13 +61,13 @@ struct Template2View: TemplateViewType {

Text(.init(self.selectedLocalization.title))
.foregroundColor(self.configuration.colors.text1Color)
.font(.largeTitle.bold())
.font(self.font(for: .largeTitle).bold())

Spacer()

Text(.init(self.selectedLocalization.subtitle ?? ""))
.foregroundColor(self.configuration.colors.text1Color)
.font(.title3)
.font(self.font(for: .title3))

Spacer()

Expand Down Expand Up @@ -108,9 +108,9 @@ struct Template2View: TemplateViewType {
alignment: Self.packageButtonAlignment
)
.fixedSize(horizontal: false, vertical: true)
.font(.body)
.font(self.font(for: .body))
}
.font(.body.weight(.medium))
.font(self.font(for: .body).weight(.medium))
.padding()
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: Self.packageButtonAlignment)
Expand Down Expand Up @@ -163,10 +163,9 @@ struct Template2View: TemplateViewType {
private var subscribeButton: some View {
PurchaseButton(
package: self.selectedPackage,
colors: self.configuration.colors,
localization: self.selectedLocalization,
configuration: self.configuration,
introEligibility: self.introEligibility[self.selectedPackage],
mode: self.configuration.mode,
purchaseHandler: self.purchaseHandler
)
}
Expand Down
Loading

0 comments on commit c3838ff

Please sign in to comment.