Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall/Superwall-iOS/releases) on GitHub.

## 4.15.1

### Enhancements

- Adds an `onCustomCallback` parameter to `getPaywall`.

## 4.15.0

### Enhancements
Expand Down
3 changes: 2 additions & 1 deletion Sources/SuperwallKit/Dependencies/DependencyContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ extension DependencyContainer: ViewControllerFactory {
webView: webView,
webEntitlementRedeemer: webEntitlementRedeemer,
cache: cache,
paywallArchiveManager: paywallArchiveManager
paywallArchiveManager: paywallArchiveManager,
customCallbackRegistry: customCallbackRegistry
)

webView.delegate = paywallViewController
Expand Down
3 changes: 2 additions & 1 deletion Sources/SuperwallKit/Graveyard/SuperwallGraveyard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ extension Superwall {
),
webEntitlementRedeemer: dependencyContainer.webEntitlementRedeemer,
cache: dependencyContainer.makeCache(),
paywallArchiveManager: dependencyContainer.paywallArchiveManager
paywallArchiveManager: dependencyContainer.paywallArchiveManager,
customCallbackRegistry: dependencyContainer.customCallbackRegistry
)
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SuperwallKit/Misc/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ let sdkVersion = """
*/

let sdkVersion = """
4.15.0
4.15.1
"""
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ extension Superwall {
/// be dropped.
/// - paywallOverrides: An optional ``PaywallOverrides`` object whose parameters override the paywall defaults. Use this to override products and presentation style. Defaults to `nil`.
/// - delegate: A delegate responsible for handling user interactions with the retrieved ``PaywallViewController``.
/// - onCustomCallback: An optional async block invoked when the paywall webview triggers a custom callback.
/// Return a ``CustomCallbackResult`` to report success or failure back to the webview. Defaults to `nil`, in which
/// case the webview receives a failure result for any custom callbacks.
/// - completion: A completion block accepting an optional ``PaywallViewController``, an optional
/// ``PaywallSkippedReason`` and an optional `Error`. If the ``PaywallViewController`` couldn't be retrieved
/// because its presentation should be skipped, the ``PaywallSkippedReason`` will be non-`nil`. Any errors
Expand All @@ -35,6 +38,7 @@ extension Superwall {
params: [String: Any]? = nil,
paywallOverrides: PaywallOverrides? = nil,
delegate: PaywallViewControllerDelegate,
onCustomCallback: ((CustomCallback) async -> CustomCallbackResult)? = nil,
completion: @escaping (PaywallViewController?, PaywallSkippedReason?, Error?) -> Void
) {
Task { @MainActor in
Expand All @@ -43,7 +47,8 @@ extension Superwall {
forPlacement: placement,
params: params,
paywallOverrides: paywallOverrides,
delegate: delegate
delegate: delegate,
onCustomCallback: onCustomCallback
)
completion(paywallViewController, nil, nil)
} catch let reason as PaywallSkippedReason {
Expand All @@ -68,6 +73,9 @@ extension Superwall {
/// be dropped.
/// - paywallOverrides: An optional ``PaywallOverrides`` object whose parameters override the paywall defaults. Use this to override products and presentation style. Defaults to `nil`.
/// - delegate: A delegate responsible for handling user interactions with the retrieved ``PaywallViewController``.
/// - onCustomCallback: An optional async block invoked when the paywall webview triggers a custom callback.
/// Return a ``CustomCallbackResult`` to report success or failure back to the webview. Defaults to `nil`, in which
/// case the webview receives a failure result for any custom callbacks.
///
/// - Returns: A ``PaywallViewController`` object.
/// - Throws: An `Error` explaining why it couldn't get the view controller. If the ``PaywallViewController`` couldn't be retrieved
Expand All @@ -80,15 +88,17 @@ extension Superwall {
forPlacement placement: String,
params: [String: Any]? = nil,
paywallOverrides: PaywallOverrides? = nil,
delegate: PaywallViewControllerDelegate
delegate: PaywallViewControllerDelegate,
onCustomCallback: ((CustomCallback) async -> CustomCallbackResult)? = nil
) async throws -> PaywallViewController {
return try await internallyGetPaywall(
forPlacement: placement,
params: params,
paywallOverrides: paywallOverrides,
delegate: .init(
swiftDelegate: delegate,
objcDelegate: nil
objcDelegate: nil,
onCustomCallback: onCustomCallback
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ final class PaywallViewControllerDelegateAdapter {
weak var swiftDelegate: PaywallViewControllerDelegate?
weak var objcDelegate: PaywallViewControllerDelegateObjc?

/// An optional handler invoked when the paywall webview triggers a custom callback.
let onCustomCallback: ((CustomCallback) async -> CustomCallbackResult)?

var hasObjcDelegate: Bool {
return objcDelegate != nil
}

init(
swiftDelegate: PaywallViewControllerDelegate?,
objcDelegate: PaywallViewControllerDelegateObjc?
objcDelegate: PaywallViewControllerDelegateObjc?,
onCustomCallback: ((CustomCallback) async -> CustomCallbackResult)? = nil
) {
self.swiftDelegate = swiftDelegate
self.objcDelegate = objcDelegate
self.onCustomCallback = onCustomCallback
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ public class PaywallViewController: UIViewController, LoadingDelegate {
}
}

var delegate: PaywallViewControllerDelegateAdapter?
var delegate: PaywallViewControllerDelegateAdapter? {
didSet {
syncCustomCallbackRegistration()
}
}

typealias Factory = TriggerFactory
& RestoreAccessFactory
Expand Down Expand Up @@ -215,6 +219,7 @@ public class PaywallViewController: UIViewController, LoadingDelegate {
private unowned let storage: Storage
private unowned let deviceHelper: DeviceHelper
private unowned let webEntitlementRedeemer: WebEntitlementRedeemer
private unowned let customCallbackRegistry: CustomCallbackRegistry
private weak var cache: PaywallViewControllerCache?
private weak var paywallArchiveManager: PaywallArchiveManager?

Expand All @@ -231,7 +236,8 @@ public class PaywallViewController: UIViewController, LoadingDelegate {
webView: SWWebView,
webEntitlementRedeemer: WebEntitlementRedeemer,
cache: PaywallViewControllerCache?,
paywallArchiveManager: PaywallArchiveManager?
paywallArchiveManager: PaywallArchiveManager?,
customCallbackRegistry: CustomCallbackRegistry
) {
self.cache = cache
self.paywallArchiveManager = paywallArchiveManager
Expand All @@ -248,15 +254,38 @@ public class PaywallViewController: UIViewController, LoadingDelegate {
self.webView = webView
self.introOfferTokenManager = IntroOfferTokenManager(network: network)
self.webEntitlementRedeemer = webEntitlementRedeemer
self.customCallbackRegistry = customCallbackRegistry

presentationStyle = paywall.presentation.style
super.init(nibName: nil, bundle: nil)
syncCustomCallbackRegistration()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

/// Registers or unregisters the delegate's custom callback handler with
/// ``CustomCallbackRegistry`` so the paywall webview can invoke it.
///
/// `register()` manages registration around its own present/dismiss lifecycle, so this
/// path is the one that wires up handlers supplied via
/// ``Superwall/getPaywall(forPlacement:params:paywallOverrides:delegate:onCustomCallback:)``.
private var hasRegisteredCallback = false

private func syncCustomCallbackRegistration() {
if let handler = delegate?.onCustomCallback {
customCallbackRegistry.register(
paywallIdentifier: paywall.identifier,
handler: handler
)
hasRegisteredCallback = true
} else if hasRegisteredCallback {
customCallbackRegistry.unregister(paywallIdentifier: paywall.identifier)
hasRegisteredCallback = false
}
}

public override func viewDidLoad() {
super.viewDidLoad()
configureUI()
Expand All @@ -266,6 +295,9 @@ public class PaywallViewController: UIViewController, LoadingDelegate {

deinit {
introOfferTokenManager.stopObservingAppLifecycle()
if hasRegisteredCallback {
customCallbackRegistry.unregister(paywallIdentifier: paywall.identifier)
}
}
Comment thread
yusuftor marked this conversation as resolved.

private func configureUI() {
Expand Down
2 changes: 1 addition & 1 deletion SuperwallKit.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|

s.name = "SuperwallKit"
s.version = "4.15.0"
s.version = "4.15.1"
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"

Expand Down
4 changes: 4 additions & 0 deletions SuperwallKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@
B15607185B9E4229C6C4F240 /* SK2StoreTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3B96E2A1A289D96267EC0BC /* SK2StoreTransaction.swift */; };
B294572426111EC04F225289 /* MockExternalPurchaseControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9C9132109020FA03D1D5C7 /* MockExternalPurchaseControllerFactory.swift */; };
B2AB1E9283FDE2D544C8BCA8 /* MockReceiptData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B81D88316F06C0C2757F10 /* MockReceiptData.swift */; };
B2AC4436371BC96FAA4FB5B3 /* CustomCallbackRegistryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CFC75AD1252D05D2033D7B0 /* CustomCallbackRegistryTests.swift */; };
B2B5684F46FB49AB9E3C1BE0 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E47DA89C9F4FBD7FA038F5 /* Cache.swift */; };
B3E6E82C0240EE6048360C9B /* RawWebMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C39015ED9E8F5D9F0F78F1E /* RawWebMessageHandler.swift */; };
B456ED8E41AD35A0EF5C295F /* ProductVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8730F0D05BFDFA12AA6309 /* ProductVariable.swift */; };
Expand Down Expand Up @@ -874,6 +875,7 @@
8BAEECE2DBFEB8817E6C36DA /* PaywallViewControllerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallViewControllerCache.swift; sourceTree = "<group>"; };
8BDA9D233EACAB5090B0657D /* StorePresentationObjectsOperatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePresentationObjectsOperatorTests.swift; sourceTree = "<group>"; };
8CA2E0A3532CE2F0AFD6F20B /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; };
8CFC75AD1252D05D2033D7B0 /* CustomCallbackRegistryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCallbackRegistryTests.swift; sourceTree = "<group>"; };
8D45DC0981EE9BBE3BF56C48 /* PurchaseController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseController.swift; sourceTree = "<group>"; };
8D5F8BE7E93645C0FCA49E4A /* PopupTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupTransition.swift; sourceTree = "<group>"; };
8D9545633A97A2E63FEDF78A /* SWWebViewLoadingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWWebViewLoadingHandler.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2076,6 +2078,7 @@
75743C9AAFCAB9D91984F19C /* Presentation */ = {
isa = PBXGroup;
children = (
8CFC75AD1252D05D2033D7B0 /* CustomCallbackRegistryTests.swift */,
A3F306D67A9F3A43D082DD83 /* PresentationIdTests.swift */,
43F99E26CAFD72F228189A4D /* Audience Logic */,
B2C3E282003472D3326477C4 /* Internal Presentation */,
Expand Down Expand Up @@ -3189,6 +3192,7 @@
A1621A749D8F05959A486ACE /* CoreDataManagerMock.swift in Sources */,
C7AB21123540550E513AD28A /* CoreDataManagerTests.swift in Sources */,
ABC17AE96AD396607E3CAB17 /* CoreDataStackMock.swift in Sources */,
B2AC4436371BC96FAA4FB5B3 /* CustomCallbackRegistryTests.swift in Sources */,
2517FC60F3A7288C5FE34A73 /* CustomProductTests.swift in Sources */,
85728EABBC5C73193AC5F876 /* CustomURLSessionMock.swift in Sources */,
37FDB46DD55E649FA10D753C /* CustomerInfoDecodingTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,8 @@ struct TrackingLogicTests {
webView: webView,
webEntitlementRedeemer: dependencyContainer.webEntitlementRedeemer,
cache: nil,
paywallArchiveManager: nil
paywallArchiveManager: nil,
customCallbackRegistry: dependencyContainer.customCallbackRegistry
)

let outcome = await TrackingLogic.canTriggerPaywall(
Expand Down
Loading
Loading