Skip to main content

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.

info

Please make sure to follow the Getting Started guidelines before move 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)
}
}
info

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

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? SFWidgetCollectionViewCell {
return sfWidgetCell
}

willDisplayCell for SFWidgetCollectionViewCell

if let sfWidgetCell = cell as? SFWidgetCollectionViewCell {
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

info

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.xcodeprojSwiftUI-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)
}
}