Introduction

Common Integration Instructions

UITableView Integration

UICollectionView Integration

UIScrollView Integration

SwiftUI Integration

Support Orientation Changes


Introduction

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.


Common Integration Instructions


Registering App Configuration

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. This method takes a single appKey parameter, which is a string that contains the application key you’ve received from your Outbrain account manager.

Here is an example of how to call initializeOutbrainWithPartnerKey:

Objective C

[Outbrain initializeOutbrainWithPartnerKey:@"MyPartnerKey"];

Swift

Outbrain.initializeOutbrain(withPartnerKey: "MyPartnerKey")


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:

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
    sfWidget.scrollViewDidScroll(scrollView: scrollView)
}


UITableView


SFWidgetDelegate

In your implementation for SFWidgetDelegate:

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:

This method will remove all “sub-views” from cell.contentView and then will this SFWidget as a sub-view with constraints to “fill parent”.


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

In your implementation for SFWidgetDelegate:

func didChangeHeight(_ newHeight: CGFloat)
  collectionView.performBatchUpdates(nil, completion: nil)
}


In viewDidLoad

Register SFWidgetCollectionCell:

collectionView.register(SFWidgetCollectionCell.self, forCellWithReuseIdentifier: "SFWidgetCell")


UICollectionView methods

numberOfItemsInSection:

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return originalArticleItemsCount + 1
}

cellForItemAt indexPath, cell for last item should be:

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 last 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

Sample app 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 main page with all the SDK links .


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