All navigation requests within the WebView must be suppressed to prevent external links from opening within the WebView. Instead, use the post message bridge to handle URL opening in the native browser.
All navigation requests within the WebView MUST be suppressed
by returning .cancel in decidePolicyFor navigationAction. This prevents external links from opening within the WebView and ensures they are handled via the post message bridge instead.
Post Message Bridge for URL Handling
URL Opening Pattern:
The integration uses a post message bridge to handle URL opening:
Sovendus content sends messages via window.postMessage() with channel "sovendus:integration"
JavaScript listener catches these messages and forwards them to native code via window.webkit.messageHandlers.sovHandler.postMessage()
Native code receives the message and opens the URL in Safari View Controller
Dynamic Height Management
Height Adjustment Pattern:
The WebView height must dynamically adjust based on content:
ResizeObserver monitors changes to document.body.scrollHeight
Height changes are sent to native code via the JavaScript bridge
Native code updates the height constraint and triggers layout update
Only heights > 100px are applied to avoid layout issues
🔧 Parameter Configuration
Parameter Documentation
For detailed information on all parameters, examples, and requirements, visit: Parameter Documentation
🚀 Testing
Integration Testing: Verify banner loads and displays correctly
Height Adjustment: Test dynamic height changes work properly
URL Handling: Verify external links open in Safari
Error Handling: Test error scenarios and reporting
Memory Management: Ensure no memory leaks in WebView usage
📞 Support
For technical support and parameter configuration, contact your Sovendus account manager or visit the Sovendus Developer Hub .
String
?
let
phone
:
String
?
let
yearOfBirth
:
Int
?
let
dateOfBirth
:
String
?
let
street
:
String
?
let
streetNumber
:
String
?
let
zipcode
:
String
?
let
city
:
String
?
let
country
:
String
?
init
(
salutation
:
String
?
=
nil
,
firstName
:
String
?
=
nil
,
lastName
:
String
?
=
nil
,
email
:
String
?
=
nil
,
phone
:
String
?
=
nil
,
yearOfBirth
:
Int
?
=
nil
,
dateOfBirth
:
String
?
=
nil
,
street
:
String
?
=
nil
,
streetNumber
:
String
?
=
nil
,
zipcode
:
String
?
=
nil
,
city
:
String
?
=
nil
,
country
:
String
?
=
nil
)
{
self
.
salutation
=
salutation
self
.
firstName
=
firstName
self
.
lastName
=
lastName
self
.
email
=
email
self
.
phone
=
phone
self
.
yearOfBirth
=
yearOfBirth
self
.
dateOfBirth
=
dateOfBirth
self
.
street
=
street
self
.
streetNumber
=
streetNumber
self
.
zipcode
=
zipcode
self
.
city
=
city
self
.
country
=
country
}
}
struct
SovendusOrderData
{
let
sessionId
:
String
let
orderId
:
String
let
currencyCode
:
String
let
usedCouponCode
:
String
let
trafficSourceNumber
:
Int
let
trafficMediumNumber
:
Int
let
netOrderValue
:
Double
let
customerData
:
SovendusCustomerData
?
init
(
sessionId
:
String
,
orderId
:
String
,
currencyCode
:
String
,
usedCouponCode
:
String
,
trafficSourceNumber
:
Int
,
trafficMediumNumber
:
Int
,
netOrderValue
:
Double
,
customerData
:
SovendusCustomerData
?
=
nil
)
{
self
.
sessionId
=
sessionId
self
.
orderId
=
orderId
self
.
currencyCode
=
currencyCode
self
.
usedCouponCode
=
usedCouponCode
self
.
trafficSourceNumber
=
trafficSourceNumber
self
.
trafficMediumNumber
=
trafficMediumNumber
self
.
netOrderValue
=
netOrderValue
self
.
customerData
=
customerData
}
}
,
Error
)
->
Void
)
?
init
(
trafficSourceNumber
:
Int
,
trafficMediumNumber
:
Int
,
onError
:
(
(
String
,
Error
)
->
Void
)
?
=
nil
)
{
self
.
trafficSourceNumber
=
trafficSourceNumber
self
.
trafficMediumNumber
=
trafficMediumNumber
self
.
onError
=
onError
}
func
sanitize
(
_
input
:
String
)
->
String
{
do
{
let
jsonData
=
try
JSONSerialization
.
data
(
withJSONObject
:
input
,
options
:
[
]
)
let
jsonString
=
String
(
data
:
jsonData
,
encoding
:
.
utf8
)
??
"\"\""
// Remove surrounding quotes
return
String
(
jsonString
.
dropFirst
(
)
.
dropLast
(
)
)
}
catch
{
reportError
(
"Error sanitizing string input"
,
error
)
return
""
}
}
func
sanitizeOptional
(
_
input
:
String
?
)
->
String
?
{
guard
let
input
=
input
else
{
return
nil
}
return
sanitize
(
input
)
}
func
sanitizeInt
(
_
input
:
Int
)
->
String
{
return
String
(
input
)
}
func
sanitizeIntOptional
(
_
input
:
Int
?
)
->
String
?
{
guard
let
input
=
input
else
{
return
nil
}
return
sanitizeInt
(
input
)
}
func
sanitizeDouble
(
_
input
:
Double
)
->
String
{
if
input
.
isNaN
||
input
.
isInfinite
{
return
"0"
}
return
String
(
input
)
}
private
func
reportError
(
_
message
:
String
,
_
error
:
Error
)
{
onError
?
(
message
,
error
)
// Log error to your application's logging system
}
}
:
NSLayoutConstraint
!
private
let
orderData
:
SovendusOrderData
private
let
onError
:
(
(
String
,
Error
)
->
Void
)
?
static
let
versionNumber
=
"1.0.0"
// Initial height for the WebView
static
let
initialHeight
:
CGFloat
=
348.0
init
(
trafficSourceNumber
:
Int
,
trafficMediumNumber
:
Int
,
sessionId
:
String
=
""
,
orderId
:
String
=
""
,
netOrderValue
:
Double
=
0
,
currencyCode
:
String
=
""
,
usedCouponCode
:
String
=
""
,
customerData
:
SovendusCustomerData
?
=
nil
,
onError
:
(
(
String
,
Error
)
->
Void
)
?
=
nil
)
{
self
.
orderData
=
SovendusOrderData
(
sessionId
:
sessionId
,
orderId
:
orderId
,
currencyCode
:
currencyCode
,
usedCouponCode
:
usedCouponCode
,
trafficSourceNumber
:
trafficSourceNumber
,
trafficMediumNumber
:
trafficMediumNumber
,
netOrderValue
:
netOrderValue
,
customerData
:
customerData
)
self
.
onError
=
onError
super
.
init
(
frame
:
.
zero
)
setupWebView
(
)
loadContent
(
)
}
required
init
?
(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
private
func
setupWebView
(
)
{
let
config
=
WKWebViewConfiguration
(
)
config
.
userContentController
.
add
(
self
,
name
:
"sovHandler"
)
webView
=
WKWebView
(
frame
:
.
zero
,
configuration
:
config
)
webView
.
navigationDelegate
=
self
webView
.
translatesAutoresizingMaskIntoConstraints
=
false
addSubview
(
webView
)
NSLayoutConstraint
.
activate
(
[
webView
.
topAnchor
.
constraint
(
equalTo
:
topAnchor
)
,
webView
.
leadingAnchor
.
constraint
(
equalTo
:
leadingAnchor
)
,
webView
.
trailingAnchor
.
constraint
(
equalTo
:
trailingAnchor
)
,
webView
.
bottomAnchor
.
constraint
(
equalTo
:
bottomAnchor
)
]
)
heightConstraint
=
heightAnchor
.
constraint
(
equalToConstant
:
Self
.
initialHeight
)
heightConstraint
.
isActive
=
true
}
private
func
loadContent
(
)
{
let
html
=
generateHtml
(
)
webView
.
loadHTMLString
(
html
,
baseURL
:
nil
)
}
}
)
->
Void
)
{
// WARNING: Always cancel navigation to prevent links from opening in WebView
// Links will be handled via the post message bridge instead
decisionHandler
(
.
cancel
)
}
func
userContentController
(
_
userContentController
:
WKUserContentController
,
didReceive message
:
WKScriptMessage
)
{
guard
message
.
name
==
"sovHandler"
,
let
messageBody
=
message
.
body
as
?
[
String
:
Any
]
,
let
type
=
messageBody
[
"type"
]
as
?
String
,
let
value
=
messageBody
[
"value"
]
else
{
return
}
switch
type
{
case
"height"
:
// Handle dynamic height updates from WebView content
if
let
heightValue
=
value
as
?
NSNumber
{
updateHeight
(
heightValue
.
doubleValue
)
}
else
if
let
heightString
=
value
as
?
String
,
let
height
=
Double
(
heightString
)
{
updateHeight
(
height
)
}
case
"openUrl"
:
// Handle URL opening requests from the post message bridge
if
let
urlString
=
value
as
?
String
{
openUrlInBrowser
(
urlString
)
}
default
:
onError
?
(
"Unknown message type:
\(
type
)
"
,
NSError
(
domain
:
"SovendusBanner"
,
code
:
1
)
)
}
}
private
func
updateHeight
(
_
height
:
Double
)
{
// Only update height if it's reasonable (> 100px) to avoid layout issues
guard
height
>
100
else
{
return
}
DispatchQueue
.
main
.
async
{
self
.
heightConstraint
.
constant
=
CGFloat
(
height
)
self
.
layoutIfNeeded
(
)
}
}
private
func
openUrlInBrowser
(
_
urlString
:
String
)
{
guard
let
url
=
URL
(
string
:
urlString
)
,
let
viewController
=
findViewController
(
)
else
{
onError
?
(
"Invalid URL or no view controller found"
,
NSError
(
domain
:
"SovendusBanner"
,
code
:
2
)
)
return
}
// Open URL in Safari View Controller for better user experience