Playbooks
Moments Prefetch Integration O...
iOS β Moments Prefetch Integration Guide
21 min
overview this guide explains how to integrate the momentscience moments solution into your ios app using swift and swiftui the sdk offers two prefetching modes that help preload offers in advance, ensuring a fast, responsive checkout experience this guide covers two integration modes, both designed to preload and display offers in a webview sdk prefetch mode api prefetch mode both options enable a high performing native experience while keeping the integration lightweight and flexible integration modes sdk prefetch mode this is the lightest touch integration a hidden 0Γ0 wkwebview is embedded before checkout (e g , on the cart screen) it loads the moments sdk and begins prefetching and caching offers in the background offers are later rendered instantly at checkout using this cached data api prefetch mode this approach gives your app full control over how and when offers are fetched your app sends a native https request to the moments api before the checkout screen the sdk is then initialized with the response payload at checkout in both modes, offers are rendered inside a fullscreen webview at checkout because the sdk prefetches offers before rendering, the offer ui loads quickly and smoothly, even if no offers are returned if no offers are found, you can simply skip showing the offer component to see a full implementation, check out the momentscience ios demo on github which includes examples for offer fetching, event handling, and webview integration requirements to integrate the moments sdk into your ios app, ensure the following prerequisites are met a valid momentscience sdk id , which you can obtain by following these steps environment swift version 5 ui framework swiftui minimum ios version 15 integration steps step 1 add dependencies add html assets the moments sdk renders offers inside a wkwebview using local html templates these templates must be bundled with your app required files add the following html files to your xcode project webpage template html prefetch template html you can place these files anywhere in your project in the momentscience ios demo app , they are located at the root of the xcode project add and install dependencies no third party libraries are required to use the moments sdk however, because offer experiences are rendered inside a webview, you must import the webkit framework import webkit this enables your app to use wkwebview for loading the offer templates and rendering personalized offers during checkout step 2 prefetch offers (sdk or api) you can prefetch offers using one of two approaches sdk prefetch uses a hidden 0Γ0 webview to silently fetch and cache offers before checkout api prefetch uses a native api call to fetch offers, which are then injected into the sdk display template in both modes, you may skip showing the offer screen if no offers are returned option 1 sdk prefetch this is the lightest integration path it uses a hidden webview to load the moments sdk in prefetch mode when offers are found, the sdk emits an ads found event, which your app can use to determine whether to show the offer screen listen for sdk events via javascript handler use wkscriptmessagehandler to listen for sdk events and capture results (e g , whether offers were found) this callback is triggered when the sdk finishes prefetching you can then decide whether to show the offers or skip it func usercontentcontroller( usercontentcontroller wkusercontentcontroller, didreceive message wkscriptmessage) { // handle sdk messages such as "ads found" } load the html template and inject data read and modify prefetch template html by injecting values like sdkid , adpxuser payload, and launcher script url func generateprefetchhtml() throws > string { guard let htmlpath = bundle main path(forresource "prefetch template", oftype "html") else { throw htmltemplateerror templatenotfound } var htmlcontent = try string(contentsoffile htmlpath, encoding utf8) let escapedsdkid = sdkid replacingoccurrences(of "'", with "\\\\'") htmlcontent = htmlcontent replacingoccurrences(of "{{sdk id}}", with escapedsdkid) if let payload = userpayload, !payload isempty { let payloaddata = try jsonserialization data(withjsonobject payload) let payloadjsonstring = string(data payloaddata, encoding utf8) ?? "{}" let escapedpayload = payloadjsonstring replacingoccurrences(of "'", with "\\\\'") htmlcontent = htmlcontent replacingoccurrences( of "window\ adpxuser = {}", with "window\ adpxuser = \\(escapedpayload)" ) } htmlcontent = htmlcontent replacingoccurrences( of "{{launcher script url}}", with appconfig webview\ launcherscripturl ) return htmlcontent } see offerview\ swift , offersviewmodel swift and the wkscriptmessagehandler implementation in the demo app for a complete working example option 2 prefetch with api in this method, your app fetches offers directly from the moments api before checkout and injects the response into the sdk using javascript for complete details on moments api, refer to the moments api documentation https //docs adspostx com/moments api#rqin send a post request to the moments api moments api endpoint post https //api adspostx com/native/v4/offers json build the request parameters include the following api key as a query parameter a user payload in the request body custom user agent in the request headers validate optional parameters (if used) loyaltyboost must be " 0 ", " 1 ", or " 2 " creative must be " 0 " or " 1 " prepare and send the request use the following example code to construct and send the request fetchoffers func fetchoffers( sdkid string, isdevelopment bool = false, payload \[string string]? = nil, loyaltyboost string?, creative string?, campaignid string? = nil ) async throws > \[string any] { // validate parameters if let loyaltyboost = loyaltyboost, !\["0", "1", "2"] contains(loyaltyboost) { throw networkerror invalidparameter("loyaltyboost must be 0, 1, or 2") } if let creative = creative, !\["0", "1"] contains(creative) { throw networkerror invalidparameter("creative must be 0 or 1") } // build query params guard var urlcomponents = urlcomponents(string baseurl) else { throw networkerror invalidurl } var queryitems = \[urlqueryitem(name "api key", value sdkid)] if let loyaltyboost = loyaltyboost { queryitems append(urlqueryitem(name "loyaltyboost", value loyaltyboost)) } if let creative = creative { queryitems append(urlqueryitem(name "creative", value creative)) } if let campaignid = campaignid { queryitems append(urlqueryitem(name "campaignid", value campaignid)) } urlcomponents queryitems = queryitems guard let url = urlcomponents url else { throw networkerror invalidurl } // build request body var requestbody \[string any] = isdevelopment ? \["dev" "1"] \[ ] payload? foreach { key, value in requestbody\[key] = value } // create request var request = urlrequest(url url) request httpmethod = "post" request addvalue("application/json", forhttpheaderfield "content type") let useragent = payload?\["ua"] ?? useragentservice shared useragent request addvalue(useragent, forhttpheaderfield "user agent") request httpbody = try jsonserialization data(withjsonobject requestbody) // perform request let (data, response) = try await urlsession shared data(for request) guard let httpresponse = response as? httpurlresponse else { throw networkerror servererror("invalid server response") } switch httpresponse statuscode { case 200 299 guard let json = try jsonserialization jsonobject(with data) as? \[string any] else { throw networkerror decodingerror("invalid json format") } return json default throw networkerror servererror("unexpected error \\(httpresponse statuscode)") } } fetchoffers usage do { let response = try await fetchoffers( sdkid sdkid, isdevelopment true, payload userpayload, loyaltyboost "0", creative "0" ) } catch { // handle any errors from the api call } payload @published var userpayload \[string string]? = \[ "ua" "\<user agent value>", // user agent string use system default or provide a custom one if needed "placement" "checkout", //page/section identifier where the offer was triggered "pub user id" "\<unique value>", //a unique, non pii identifier for the end user "themeid" "demo", // pass valid themeid value here "adpx fp" "\<unique value>"//unique user identifier ] save the api response locally store the json response from the api to use it when initializing the sdk see networkservice swift and useragentservice swift implementation in the demo app for a complete working example step 3 show offers in fullscreen webview once youβve prefetched offers using the moments api or sdk , display them in a fullscreen webview on your checkout screen navigate to the offer webview use navigationlink (or your appβs navigation method) to transition to a view that renders the offer experience youβll need to pass relevant properties such as the api response, load mode, and user payload navigationlink( destination webpageview( sdkid viewmodel sdkid, momentsapiresponse viewmodel checkoutapiresponse, loadmode viewmodel prefetching ?? prefetchapi, // you may not need this parameter as you are going to use only one of the approach offercount viewmodel offerscount, // optional, require only if you want to show no of offers in ui userpayload viewmodel userpayload ) ) render offers in wkwebview inside webpageview , use a fullscreen wkwebview to render html from a template you'll need to load webpage template html from the app bundle replace placeholders (e g , {{sdk id}} , {{auto config}} ) with actual values inject the modified html into the webview load and prepare html hereβs a simplified example of how the html template is loaded and populated private func loadhtmltemplate() throws > string { guard let htmlpath = bundle main path(forresource "webpage template", oftype "html") else { throw htmltemplateerror templatenotfound } do { return try string(contentsoffile htmlpath, encoding utf8) } catch { print("error loading html template \\(error)") throw error } } private var htmlcontent string { let htmltemplate string do { htmltemplate = try loadhtmltemplate() } catch { return "\<html>\<body>\<h1>error\</h1>\<p>\\(error localizeddescription)\</p>\</body>\</html>" } // serialize the moments api response let responsejson = (try? jsonserialization data(withjsonobject momentsapiresponse ?? \[ ])) flatmap { string(data $0, encoding utf8) } ?? "{}" let escapedresponse = webpageviewmodel escapeforjsstring(responsejson) // serialize user payload let payloadjson = (try? jsonserialization data(withjsonobject userpayload ?? \[ ])) flatmap { string(data $0, encoding utf8) } ?? "{}" let escapedpayload = webpageviewmodel escapeforjsstring(payloadjson) // determine webview configuration based on prefetch method let (autoconfig, responsehandling, callsetresponse) (string, string, string) = { switch loadmode { case prefetchapi return ( "autoshow true,\n autoload false", """ try { const responsedata = json parse('\\(escapedresponse)'); if (responsedata && typeof responsedata === 'object') { settimeout(() => window\ adpx setapiresponse(responsedata), 100); } } catch (error) { console error('error parsing response data ', error message); } """, "await setresponse();" ) case prefetchwebsdk return ( "autoshow true,\n autoload true,\n prefetch true", "// websdk prefetch mode no additional response handling needed", "// no need to call setresponse" ) } }() let offercountstr = string(offercount ?? 0) let escapedsdkid = webpageviewmodel escapeforjsstring(sdkid) return htmltemplate replacingoccurrences(of "{{sdk id}}", with escapedsdkid) replacingoccurrences(of "{{launcher script url}}", with appconfig webview\ launcherscripturl) replacingoccurrences(of "{{offers count}}", with offercountstr) replacingoccurrences(of "{{auto config}}", with autoconfig) replacingoccurrences(of "{{response handling}}", with responsehandling) replacingoccurrences(of "{{call set response}}", with callsetresponse) replacingoccurrences(of "window\ adpxuser = {}", with "window\ adpxuser = \\(escapedpayload)") } see webpageviewmodel swift and webpage template html in the demo app for a complete example step 4 open offer links in an external browser to ensure a smooth user experience, all offer links (such as cta clicks) should open in the userβs default browser, not inside the embedded wkwebview this behavior requires customizing navigation handling using wknavigationdelegate , wkuidelegate , and wkscriptmessagehandler intercept navigation requests implement wknavigationdelegate to inspect and control url navigation events func webview( webview wkwebview, decidepolicyfor navigationaction wknavigationaction, decisionhandler @escaping (wknavigationactionpolicy) > void) { if let url = navigationaction request url { decisionhandler(viewmodel shouldopenurl(url) ? allow cancel) } else { decisionhandler( allow) } } handle new window requests use wkuidelegate to intercept any links that attempt to open in a new window (e g , using target=" blank" ) instead of opening in the webview, redirect them externally func webview( webview wkwebview, createwebviewwith configuration wkwebviewconfiguration, for navigationaction wknavigationaction, windowfeatures wkwindowfeatures) > wkwebview? { if let url = navigationaction request url, viewmodel shouldopenurl(url) { viewmodel openexternal(url url) } return nil } openexternal func openexternal(url url) { uiapplication shared open(url, options \[ ], completionhandler nil) } handle javascript events ( postmessage ) some offer interactions are dispatched from the sdk using window\ webkit messagehandlers adpxcallback postmessage( ) to respond to these events, implement wkscriptmessagehandler wkscriptmessagehandler func usercontentcontroller( usercontentcontroller wkusercontentcontroller, didreceive message wkscriptmessage ) { if message name == "adpxcallback", let messagedict = message body as? \[string any], let event = messagedict\["event"] as? string, let payload = messagedict\["payload"] as? \[string any] { viewmodel handlemessage(event event, payload payload) } } inside handlemessage , inspect for url clicked event type and open the target url using if event == "url clicked", let urlstring = payload\["target url"] as? string, let url = url(string urlstring) { uiapplication shared open(url) } see webpageviewmodel swift and webpageview\ swift in the demo app for implementation conclusion congratulations! you've successfully integrated the moments sdk into your ios app using swift and swiftui whether youβre using sdk prefetch mode or api prefetch mode, your app is now equipped to deliver personalized offers directly within the user experience this setup enables dynamic monetization while preserving control over when and how offers are displayed π’ if you're running into any issues while going through the integration process, feel free to contact us at help\@m omentscience com