diff --git a/CHANGELOG.md b/CHANGELOG.md index 9acd565130..d06cfe70fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall/Superwall-iOS/releases) on GitHub. +## 4.16.2 + +### Enhancements + +- Adds the user's system-wide text size (Dynamic Type) as three device attributes for use in paywalls and audience filters: `fontScale`, `fontSize`, and `preferredContentSizeCategory`. + ## 4.16.1 ### Enhancements diff --git a/Sources/SuperwallKit/Misc/Constants.swift b/Sources/SuperwallKit/Misc/Constants.swift index e38ade56b0..20f05d6af4 100644 --- a/Sources/SuperwallKit/Misc/Constants.swift +++ b/Sources/SuperwallKit/Misc/Constants.swift @@ -18,5 +18,5 @@ let sdkVersion = """ */ let sdkVersion = """ -4.16.1 +4.16.2 """ diff --git a/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift index ec3e453d40..3c1c1a2494 100644 --- a/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift +++ b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift @@ -207,6 +207,53 @@ class DeviceHelper { #endif } + var fontSize: Int { + #if os(visionOS) + return 16 + #else + return Int(UIFontMetrics.default.scaledValue(for: 16.0).rounded()) + #endif + } + + var fontScale: Double { + #if os(visionOS) + return 1.0 + #else + let scale = UIFontMetrics.default.scaledValue(for: 16.0) / 16.0 + return (scale * 100).rounded() / 100 + #endif + } + + var preferredContentSizeCategory: String { + #if os(visionOS) + return "unspecified" + #else + let category = UIApplication.sharedApplication?.preferredContentSizeCategory + ?? UIScreen.main.traitCollection.preferredContentSizeCategory + return Self.contentSizeCategoryToken(for: category) + #endif + } + + /// Maps a `UIContentSizeCategory` to the fixed dashboard token string used by + /// backend audience filters. These tokens are a backend contract and must not change. + static func contentSizeCategoryToken(for category: UIContentSizeCategory) -> String { + switch category { + case .extraSmall: return "xSmall" + case .small: return "small" + case .medium: return "medium" + case .large: return "large" + case .extraLarge: return "xLarge" + case .extraExtraLarge: return "xxLarge" + case .extraExtraExtraLarge: return "xxxLarge" + case .accessibilityMedium: return "accessibilityMedium" + case .accessibilityLarge: return "accessibilityLarge" + case .accessibilityExtraLarge: return "accessibilityXLarge" + case .accessibilityExtraExtraLarge: return "accessibilityXXLarge" + case .accessibilityExtraExtraExtraLarge: return "accessibilityXXXLarge" + default: return "unspecified" + } + } + var platformWrapper: String? var platformWrapperVersion: String? @@ -606,6 +653,9 @@ class DeviceHelper { timezoneOffset: Int(TimeZone.current.secondsFromGMT()), radioType: radioType, interfaceStyle: interfaceStyle, + fontSize: fontSize, + fontScale: fontScale, + preferredContentSizeCategory: preferredContentSizeCategory, isLowPowerModeEnabled: isLowPowerModeEnabled == "true", isApplePayAvailable: true, bundleId: bundleId, diff --git a/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/DeviceTemplate.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/DeviceTemplate.swift index 3849d7f1a0..8a77c694b9 100644 --- a/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/DeviceTemplate.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/DeviceTemplate.swift @@ -29,6 +29,9 @@ struct DeviceTemplate: Codable { var timezoneOffset: Int var radioType: String var interfaceStyle: String + var fontSize: Int + var fontScale: Double + var preferredContentSizeCategory: String var isLowPowerModeEnabled: Bool var isApplePayAvailable: Bool var bundleId: String diff --git a/SuperwallKit.podspec b/SuperwallKit.podspec index 3115a9e217..d18d4169e7 100644 --- a/SuperwallKit.podspec +++ b/SuperwallKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SuperwallKit" - s.version = "4.16.1" + s.version = "4.16.2" s.summary = "Superwall: In-App Paywalls Made Easy" s.description = "Paywall infrastructure for mobile apps :) we make things like editing your paywall and running price tests as easy as clicking a few buttons. superwall.com" diff --git a/Tests/SuperwallKitTests/Network/DeviceHelperTests.swift b/Tests/SuperwallKitTests/Network/DeviceHelperTests.swift index c285e73c09..905cfc199f 100644 --- a/Tests/SuperwallKitTests/Network/DeviceHelperTests.swift +++ b/Tests/SuperwallKitTests/Network/DeviceHelperTests.swift @@ -8,6 +8,7 @@ import Testing import Combine +import UIKit @testable import SuperwallKit struct DeviceHelperTests { @@ -58,4 +59,25 @@ struct DeviceHelperTests { let paddedVersion = DeviceHelper.makePaddedVersion(using: version) #expect(paddedVersion == "003") } + + // These tokens are a backend/audience-filter contract and must not change. + @Test func contentSizeCategoryToken_mapsEveryCategory() { + #expect(DeviceHelper.contentSizeCategoryToken(for: .extraSmall) == "xSmall") + #expect(DeviceHelper.contentSizeCategoryToken(for: .small) == "small") + #expect(DeviceHelper.contentSizeCategoryToken(for: .medium) == "medium") + #expect(DeviceHelper.contentSizeCategoryToken(for: .large) == "large") + #expect(DeviceHelper.contentSizeCategoryToken(for: .extraLarge) == "xLarge") + #expect(DeviceHelper.contentSizeCategoryToken(for: .extraExtraLarge) == "xxLarge") + #expect(DeviceHelper.contentSizeCategoryToken(for: .extraExtraExtraLarge) == "xxxLarge") + #expect(DeviceHelper.contentSizeCategoryToken(for: .accessibilityMedium) == "accessibilityMedium") + #expect(DeviceHelper.contentSizeCategoryToken(for: .accessibilityLarge) == "accessibilityLarge") + #expect(DeviceHelper.contentSizeCategoryToken(for: .accessibilityExtraLarge) == "accessibilityXLarge") + #expect(DeviceHelper.contentSizeCategoryToken(for: .accessibilityExtraExtraLarge) == "accessibilityXXLarge") + #expect(DeviceHelper.contentSizeCategoryToken(for: .accessibilityExtraExtraExtraLarge) == "accessibilityXXXLarge") + } + + @Test func contentSizeCategoryToken_unknownCategoryIsUnspecified() { + #expect(DeviceHelper.contentSizeCategoryToken(for: .unspecified) == "unspecified") + #expect(DeviceHelper.contentSizeCategoryToken(for: UIContentSizeCategory(rawValue: "someUnknownValue")) == "unspecified") + } }