iOS Bridge (SFWidget)
Intro
SFWidget is a new solution provided by Outbrain to integrate our Web-based solution for SmartLogic\Smartfeed in a native iOS app. This solution should work in either a ScrollView, UICollectionView or UITableView.
The general concept for integrating Outbrain Web-based solution in a native app – is to include SFWidget
which encapsulate WKWebView
which in turn loads the SmartLogic feed in a Web format with a native bridge to pass messages from\to native code.
SFWidget
is a sub-class of UIView
so app developers can either create a new instance from code or via Storyboard with an Outlet variable.
Integration instructions
You will need to register your app’s Outbrain configuration once during the initialization of your app, before calling any other Outbrain method. You can do this by calling Outbrain’s initializeOutbrainWithPartnerKey
method.
Please make sure to follow the Getting Started guidelines before moving foreward.
Common Instructions
In Your ViewController
1) Hold a global variable for the SFWidget:
var sfWidget: SFWidget!
2) Your ViewController should implement SFWidgetDelegate
extension YourVC: SFWidgetDelegate {
func didChangeHeight(_ newHeight: CGFloat) {
// See implementation for UITableView \ UICollectionView \ UIScrollView (at the bottom of this section)
}
// Optional delegate method - should be used for navigating to articles within the app (instead of external browser)
func onOrganicRecClick(_ url: URL) {
// handle click on organic URL - probably some in-app navigation to the article.
}
// Optional delegate method - should be used if the publisher is interested in manually tracking the "widget rendered" event.
func widgetRendered(_ articleUrl: String, widgetId: String, widgetIndex: Int) {
print("App received widgetRendered event: \(articleUrl) \(widgetId) \(widgetIndex)")
}
// Default delegate method - should open the URL in an external browser
func onRecClick(url: URL) {
let safariVC = SFSafariViewController(url: url)
self.navigationController?.present(safariVC, animated: true, completion: nil)
}
}
Notice that onOrganicRecClick
is an optional method. Use this method to handle clicks on organic recommendations (for example, navigate in your app).
3) Configure the SFWidget
in viewDidLoad
Simple Configure()
self.sfWidget.configure(with: self,
url: "https://mobile-demo.outbrain.com",
widgetId: "MB_1",
installationKey: "NANOWDGT01") // also called "Partner Key" or "Installation Key"
Advanced Configure()
/*
* @param url - the current screen "article url"
* @param widgetId - the widget id
* @param widgetIndex - should be 0 by default.
* Only if there are 2 widgets on the same page, the 2nd widget should have idx=1
* @param installationKey - also called "Partner Key" or "Installation Key"
* @param userId - set this property to the value of ["Advertiser ID"](https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614151-advertisingidentifier) only if the user approves the usage of "app tracking" (advertiser ID) or if you want to override Apple Advertiser Id value to some custom user-id (must be used with the user approval.)
* @param darkMode - false by default - set to "true" if feed should be rendered in "dark mode"
*/
self.obSmartfeedWidget.configure(with: self,
url: OBConf.baseURL,
widgetId: OBConf.widgetID,
widgetIndex: 1,
installationKey: OBConf.installationKey,
userId: "F22700D5-1D49-42CC-A183-F36765261112",
darkMode: true)
4) Override method scrollViewDidScroll
SDK version later than 5.2.0 support automatic viewability collection so this step is not needed. The scrollViewDidScroll
method is deprecated since v5.2.0.x
Make sure to call Outbrain SDK inside scrollViewDidScroll
, see example:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
sfWidget.scrollViewDidScroll(scrollView: scrollView)
}
UITableView
SFWidgetDelegate - didChangeHeight
In your implementation for SFWidgetDelegate
add the code below to didChangeHeight
func didChangeHeight(_ newHeight: CGFloat)
tableView.beginUpdates()
tableView.endUpdates()
}
In viewDidLoad
Register SFWidgetTableCell
tableView.register(SFWidgetTableCell.self, forCellReuseIdentifier: "SFWidgetCell")
UITableView methods
numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return originalArticleItemsCount + 1
}
cellForRowAt: indexPath:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell?
switch indexPath.row {
case 0:
cell = self.tableView.dequeueReusableCell(withIdentifier: imageHeaderCellReuseIdentifier) as UITableViewCell?
case 1:
cell = self.tableView.dequeueReusableCell(withIdentifier: textHeaderCellReuseIdentifier) as UITableViewCell?
case 2,3,4:
cell = self.tableView.dequeueReusableCell(withIdentifier: contentCellReuseIdentifier) as UITableViewCell?
default:
if let sfWidgetCell = self.tableView.dequeueReusableCell(withIdentifier: "SFWidgetCell") as? SFWidgetTableViewCell {
cell = sfWidgetCell
}
break;
}
return cell ?? UITableViewCell()
}
willDisplayCell for SFWidgetTableViewCell
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
switch indexPath.row {
case 0:
return
case 1:
....
case 2,3,4:
....
default:
if let sfWidgetCell = cell as? SFWidgetTableCell {
self.sfWidget.willDisplay(sfWidgetCell)
}
break
}
}
heightForRowAt indexPath:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.row {
case 0:
return UIDevice.current.userInterfaceIdiom == .pad ? 400 : 250;
case 1:
return UIDevice.current.userInterfaceIdiom == .pad ? 150 : UITableView.automaticDimension;
case 2, 3, 4:
return UIDevice.current.userInterfaceIdiom == .pad ? 200 : UITableView.automaticDimension;
default:
return self.sfWidget.getCurrentHeight();
}
}
UICollectionView
SFWidgetDelegate - didChangeHeight
In your implementation for SFWidgetDelegate
add the code below to didChangeHeight
func didChangeHeight(_ newHeight: CGFloat)
collectionView.performBatchUpdates(nil, completion: nil)
}
In viewDidLoad
Register SFWidgetTableCell
collectionView.register(SFWidgetCollectionCell.self, forCellWithReuseIdentifier: "SFWidgetCell")
UICollectionView methods
numberOfItemsInSection
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return originalArticleItemsCount + 1
}
cellForItemAt indexPath
For the Bridge item (SFWidget):
if let sfWidgetCell = collectionView.dequeueReusableCell(withReuseIdentifier: "SFWidgetCell", for: indexPath) as? SFWidgetCollectionCell {
return sfWidgetCell
}
willDisplayCell for SFWidgetCollectionCell
if let sfWidgetCell = cell as? SFWidgetCollectionCell {
sfWidget.willDisplaySFWidgetCell(cell: sfWidgetCell)
}
sizeForItemAt indexPath:
For the Bridge item (SFWidget):
return CGSize(width: collectionView.frame.size.width, height: self.sfWidget.getCurrentHeight())
UIScrollView
1) Embed SFWidget inside a ScrollView.
Make sure you create an Outlet to the SFWidget height constraints (see sample app for code example), for example:
@IBOutlet weak var sfWidgetHeightConstraint: NSLayoutConstraint!
2) In viewDidLoad()
You should init SFWidget as explained in “Common Integration”.
3) In your implementation for SFWidgetDelegate:
func didChangeHeight(_ newHeight: CGFloat)
self.sfWidgetHeightConstraint.constant = newHeight
}
SwiftUI Integration
Please follow the instructions in SwiftUI Bridge page
Outbrain provides a sample app with the source code to demonstrate how an app developer might integrate with SFWidget in a SwiftUI framework. Please check out the new target in OutbrainDemo.xcodeproj
– SwiftUI-Bridge
sample app. The source code is available for download on the Download Links page.
Dark Mode
On screen load
When you configure SFWidget on viewDidLoad()
- you can use the advanced configure() to set the dark mode theme on the widget.
self.obSmartfeedWidget.configure(with: self,
url: OBConf.baseURL,
widgetId: OBConf.widgetID,
widgetIndex: 1,
installationKey: OBConf.installationKey,
userId: "F22700D5-1D49-42CC-A183-F36765261112",
darkMode: true)
On User Toggle
If the user toggle darkMode (true/false) while on the screen - you can change the widget theme by calling:
sfWidget.toggleDarkMode(true)
Support Orientation Changes
You should override viewWillTransition
method.
For UITableView
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
sfWidget.viewWillTransition(coordinator: coordinator)
}
For UICollectionView
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
collectionView.collectionViewLayout.invalidateLayout()
sfWidget.viewWillTransition(coordinator: coordinator)
}
For UIScrollView
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
self.sfWidget.viewWillTransition(coordinator: coordinator)
coordinator.animate(alongsideTransition: nil) { _ in
self.scrollView.contentSize = CGSize(width: self.scrollView.frame.size.width, height: self.contentView.frame.size.height)
}
}