Skip to content

Commit

Permalink
feat: introduce SKStoreProductViewControllerPresenter
Browse files Browse the repository at this point in the history
  • Loading branch information
OlenaPostindustria committed Feb 18, 2025
1 parent 5e15ca5 commit f7f7a92
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 77 deletions.
14 changes: 13 additions & 1 deletion PrebidMobile.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
537B651E2833A3DA008AE9D1 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537B651D2833A3DA008AE9D1 /* Reachability.swift */; };
537B65402833C091008AE9D1 /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537B653F2833C091008AE9D1 /* NetworkType.swift */; };
537B6581283524BB008AE9D1 /* PrebidInitializationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537B6580283524BB008AE9D1 /* PrebidInitializationStatus.swift */; };
537F3F9C2D648A3000584F9C /* SKStoreProductViewControllerPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537F3F9B2D648A3000584F9C /* SKStoreProductViewControllerPresenter.swift */; };
53842BB829E561750069A4B7 /* PrebidImagesRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53842BB729E561750069A4B7 /* PrebidImagesRepository.swift */; };
53842BBC29E565030069A4B7 /* NSString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53842BBB29E565030069A4B7 /* NSString+Extensions.swift */; };
5388F32627F46A7F00319FE4 /* AdViewButtonDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5388F32527F46A7F00319FE4 /* AdViewButtonDecorator.swift */; };
Expand Down Expand Up @@ -1040,6 +1041,7 @@
537B651D2833A3DA008AE9D1 /* Reachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = "<group>"; };
537B653F2833C091008AE9D1 /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = "<group>"; };
537B6580283524BB008AE9D1 /* PrebidInitializationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrebidInitializationStatus.swift; sourceTree = "<group>"; };
537F3F9B2D648A3000584F9C /* SKStoreProductViewControllerPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SKStoreProductViewControllerPresenter.swift; sourceTree = "<group>"; };
53842BB729E561750069A4B7 /* PrebidImagesRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrebidImagesRepository.swift; sourceTree = "<group>"; };
53842BBB29E565030069A4B7 /* NSString+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSString+Extensions.swift"; sourceTree = "<group>"; };
5388F32527F46A7F00319FE4 /* AdViewButtonDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdViewButtonDecorator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2023,6 +2025,15 @@
path = Parameters;
sourceTree = "<group>";
};
537F3F9A2D6489C800584F9C /* SKAdNetwork */ = {
isa = PBXGroup;
children = (
536469C82D341A8100F50B6D /* PrebidSKAdNetworkHelper.swift */,
537F3F9B2D648A3000584F9C /* SKStoreProductViewControllerPresenter.swift */,
);
path = SKAdNetwork;
sourceTree = "<group>";
};
53A0E7E72CDCCBAB007887F5 /* ImpressionTracking */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -3467,10 +3478,10 @@
FAEE4CFF262DC2B200AD9966 /* Addendum */ = {
isa = PBXGroup;
children = (
537F3F9A2D6489C800584F9C /* SKAdNetwork */,
FAEE4D00262DC2B200AD9966 /* PbWebViewSearchError.swift */,
FAEE4D01262DC2B200AD9966 /* AdViewUtils.swift */,
FAEE4D02262DC2B200AD9966 /* IMAUtils.swift */,
536469C82D341A8100F50B6D /* PrebidSKAdNetworkHelper.swift */,
);
path = Addendum;
sourceTree = "<group>";
Expand Down Expand Up @@ -4440,6 +4451,7 @@
5BC3799E271F1D0000444D5E /* PBMModalPresentationController.m in Sources */,
5BC37A6D271F1D0000444D5E /* PBMORTBBid.m in Sources */,
5BC37A8B271F1D0000444D5E /* BannerEventInteractionDelegate.swift in Sources */,
537F3F9C2D648A3000584F9C /* SKStoreProductViewControllerPresenter.swift in Sources */,
FAEE4D0B262DC2B200AD9966 /* Dispatcher.swift in Sources */,
5BC37A10271F1D0000444D5E /* AdUnitConfig.swift in Sources */,
A908694629E05F7900B37479 /* PrebidRenderer.swift in Sources */,
Expand Down
67 changes: 17 additions & 50 deletions PrebidMobile/AdUnits/Native/NativeAd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,7 @@ public class NativeAd: NSObject, CacheExpiryDelegate {

private let eventManager = EventManager()

private var viewControllerForPresentingModals: UIViewController?

public var privacyUrl: String? {
set {
nativeAdMarkup?.privacy = newValue
}
get {
return nativeAdMarkup?.privacy
}
}
private var productControllerPresenter: SKStoreProductViewControllerPresenter?

// MARK: - Array getters

Expand All @@ -83,6 +74,11 @@ public class NativeAd: NSObject, CacheExpiryDelegate {
return nativeAdMarkup?.eventtrackers
}

public var privacyUrl: String? {
set { nativeAdMarkup?.privacy = newValue }
get { nativeAdMarkup?.privacy }
}

// MARK: - Filtered array getters

/// Returns an array of data objects filtered by the specified data type.
Expand Down Expand Up @@ -363,11 +359,20 @@ public class NativeAd: NSObject, CacheExpiryDelegate {
landingPageString: url
).openHiddenWebView()

presentSKStoreProductViewController(with: productParameters)
if let viewControllerForPresentingModals = viewForTracking?.parentViewController ?? UIApplication.topViewController() {
productControllerPresenter = SKStoreProductViewControllerPresenter()
productControllerPresenter?.present(
from: viewControllerForPresentingModals,
using: productParameters
)
} else {
Log.error("SDK couldn't find a view controller to present the SKStoreProductViewController from.")
}

fireClickTrackers()
}
// Normal clickthrough
else if openURLWithExternalBrowser(url: url) {
else if UIApplication.shared.openExternalURL(url) {
fireClickTrackers()
} else {
Log.debug("Could not open click URL: \(clickUrl)")
Expand All @@ -382,44 +387,6 @@ public class NativeAd: NSObject, CacheExpiryDelegate {

TrackerManager.shared.fireTrackerURLArray(arrayWithURLs: clickTrackersURLs) { _ in }
}

private func presentSKStoreProductViewController(with productParameters: [String: Any]) {
DispatchQueue.main.async {
let skadnController = SKStoreProductViewController()
skadnController.delegate = self
var viewControllerForPresentingModals = UIApplication.topViewController()

if let viewForTracking = self.viewForTracking,
let viewController = viewForTracking.parentViewController {
viewControllerForPresentingModals = viewController
}

self.viewControllerForPresentingModals = viewControllerForPresentingModals

viewControllerForPresentingModals?.present(skadnController, animated: true)
skadnController.loadProduct(withParameters: productParameters) { _, error in
if let error {
Log.error("Error occurred during SKStoreProductViewController product loading: \(error.localizedDescription)")
}
}
}
}

private func openURLWithExternalBrowser(url: URL) -> Bool {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
return true
} else {
return false
}
}
}

extension NativeAd: SKStoreProductViewControllerDelegate {

public func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
viewControllerForPresentingModals?.dismiss(animated: true)
}
}

private class NativeAdGestureRecognizerRecord : NSObject {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class PrebidSKAdNetworkHelper: NSObject {
private weak var adView: UIView?
private weak var viewControllerForPresentingModals: UIViewController?

private var productControllerPresenter: SKStoreProductViewControllerPresenter?

/// Subscribes to ad click events on the provided ad view and configures it with SKAdN tracking..
/// - Parameters:
/// - adView: The ad view where click events will be tracked.
Expand Down Expand Up @@ -81,12 +83,13 @@ public class PrebidSKAdNetworkHelper: NSObject {
}

private func configureAdViewWithSkadn(using bid: Bid) {
guard let adView = adView else {
guard let adView = adView, let viewControllerForPresentingModals = viewControllerForPresentingModals else {
return
}

guard let skadn = bid.skadn,
let skadnParameters = SkadnParametersManager.getSkadnProductParameters(for: skadn) else {
let productParameters = SkadnParametersManager.getSkadnProductParameters(for: skadn) else {
Log.error("SDK couldn't retrieve SKAdN product parameters from bid response.")
return
}

Expand All @@ -98,34 +101,15 @@ public class PrebidSKAdNetworkHelper: NSObject {
adView.addSubview(overlayView)

overlayView.onClick = { [weak self] in
self?.presentSKStoreProductViewController(with: skadnParameters)
}
}

private func presentSKStoreProductViewController(with productParameters: [String: Any]) {
DispatchQueue.main.async {
let skadnController = SKStoreProductViewController()
skadnController.delegate = self

self.viewControllerForPresentingModals?.present(skadnController, animated: true)
skadnController.loadProduct(withParameters: productParameters) { _, error in
if let error {
Log.error("Error occurred during SKStoreProductViewController product loading: \(error.localizedDescription)")
}
}
self?.productControllerPresenter = SKStoreProductViewControllerPresenter()
self?.productControllerPresenter?.present(
from: viewControllerForPresentingModals,
using: productParameters
)
}
}
}

// MARK: - SKStoreProductViewControllerDelegate

extension PrebidSKAdNetworkHelper: SKStoreProductViewControllerDelegate {

public func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
viewControllerForPresentingModals?.dismiss(animated: true)
}
}

/// A custom overlay view for tracking touch interactions.
private class TouchTrackingOverlayView: UIView {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* Copyright 2018-2025 Prebid.org, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import UIKit
import StoreKit

class SKStoreProductViewControllerPresenter: NSObject {

private weak var presentingViewController: UIViewController?

func present(from viewController: UIViewController, using productParameters: [String: Any]) {
self.presentingViewController = viewController

DispatchQueue.main.async {
let skadnController = SKStoreProductViewController()
skadnController.delegate = self

self.presentingViewController?.present(skadnController, animated: true)
skadnController.loadProduct(withParameters: productParameters) { _, error in
if let error {
Log.error("Error occurred during SKStoreProductViewController product loading: \(error.localizedDescription)")
}
}
}
}
}

// MARK: - SKStoreProductViewControllerDelegate

extension SKStoreProductViewControllerPresenter: SKStoreProductViewControllerDelegate {

func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
presentingViewController?.dismiss(animated: true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@ extension UIApplication {

return topController
}

func openExternalURL(_ url: URL) -> Bool {
guard canOpenURL(url) else { return false }

open(url, options: [:], completionHandler: nil)
return true
}
}

0 comments on commit f7f7a92

Please sign in to comment.