Introduction SFWidgetObservable.swift CustomSFWidgetDelegate.swift ContentView


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 encapsulates 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.

Important:
Currently our SDK does not officially support SwiftUI. Please consider the following instructions as experimental. You or Outbrain might experience unexpected behaviours when using SwiftUI together with the Outbrain SDK. In case of such issues we might need you to move to the fully supported UIKit instead of SwiftUI.


Copy\Paste SFWidgetObservable.swift

Please copy\paste the following content to a file named SFWidgetObservable.swift and add it to your app module in Xcode. This file contains the following:

  1. SFWidgetObservable – which is an ObservableObject which is shared between the SDK and the app code (to receive the URL of the recommendation being clicked for example.

  2. SFWidgetWrapper – which is an UIViewRepresentable which basically wraps SFWidget and contains the mandatory parameters needed.

  3. OBSafariView – which is an UIViewRepresentable which basically wraps SFSafariViewController with the URL to open.


    import Foundation 
    import SafariServices 
    import SwiftUI 
    import OutbrainSDK`

    // Our observable object class class SFWidgetObservable: ObservableObject { @Published var showSafari:Bool = false @Published var url:URL? @Published var widgetHeight:CGFloat = 800.0 }

    struct SFWidgetWrapper: UIViewRepresentable { @EnvironmentObject var sfWidgetObservable: SFWidgetObservable

        let widgetId:String
        let baseURL:String
        let installationKey:String
        let myCustomDelegate = CustomSFWidgetDelegate()

        func updateUIView(_ uiView: SFWidget, context: Context) {
            uiView.configure(with: myCustomDelegate, url: baseURL, widgetId: widgetId, widgetIndex: 0, installationKey: installationKey, userId: nil, darkMode: false, isSwiftUI: true);
            myCustomDelegate.sfWidgetObservable = sfWidgetObservable
        }

        func makeUIView(context: Context) -> SFWidget {
            SFWidget()
        }


    }

    struct OBSafariView: UIViewControllerRepresentable { let url: URL

        func makeUIViewController(context: UIViewControllerRepresentableContext<OBSafariView>) -> SFSafariViewController {
            return SFSafariViewController(url: url)
        }

        func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext<OBSafariView>) {

        }
    }


Copy\Paste CustomSFWidgetDelegate.swift

Please copy\paste the following content to a file named CustomSFWidgetDelegate.swift and add it to your app module in Xcode. This file contains a simple working example of implementing the necessary SFWidgetDelegate (which is responsible for handling clicks for example)

import Foundation
import OutbrainSDK

// Our CustomSFWidgetDelegate implementation class that expects to find a SFWidgetObservable object
// in the environment, and set if needed.
class CustomSFWidgetDelegate : NSObject {
    var sfWidgetObservable: SFWidgetObservable?
}

extension CustomSFWidgetDelegate : SFWidgetDelegate {

    func onRecClick(_ url: URL) {
        if let sfWidgetObservable = self.sfWidgetObservable {
            sfWidgetObservable.url = url
            sfWidgetObservable.showSafari = true
        }
    }

    func didChangeHeight(_ newHeight: CGFloat) {
        print("didChangeHeight \(newHeight)")
        sfWidgetObservable?.widgetHeight = newHeight
    }
}


Integrate SFWidgetWrapper in your app ContentView

This is the SwiftUI file where the app developer wants to include Outbrain Bridge (SFWidget) in the views hirarechy.

  1. Please note where we place SFWidgetWrapper – this View will contain the entire Outbrain feed.

  2. init() method should call initializeOutbrain() with the partner key (given by Outbrain Account Manager).

  3. SFWidgetWrapper – make sure to pass it the “article URL”, “widget id” and “installationKey” (partner key).

  4. SFWidgetWrapper – make sure to set the frame height according to sfWidgetObservable.widgetHeight which dynamically changes as content is loaded into the feed.

  5. .fullScreenCover with OBSafariView to allow SafariViewController to display the recommendation URL on click event.

  6. .environmentObject(sfWidgetObservable) – to configure the environmentObject which will be shared between the SDK and the app.

See complete example below:

import SwiftUI
import OutbrainSDK


struct ContentView: View {
    @StateObject var sfWidgetObservable = SFWidgetObservable()


    let widgetId = "MB_1"
    let baseURL = "https://mobile-demo.outbrain.com"
    let installationKey = "NANOWDGT01"

    init() {
        Outbrain.initializeOutbrain(withPartnerKey: "iOSSampleApp2014")

    }

    var body: some View {
            ScrollView(.vertical) {
                VStack(spacing:0) {
                    Image("article_image", bundle: Bundle.main)
                        .resizable()
                        .aspectRatio(16/9, contentMode: .fill)
                    Text("The Guardian")
                        .padding()
                        .font(.headline)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .frame(height: 80.0)
                        .background(.blue)
                        .foregroundColor(.white)

                    Text("Suarez: Messi Was Born Great, Ronaldo Made Himself Great")
                        .font(.system(size: 24))
                        .fontWeight(.medium)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding(EdgeInsets(top: 20, leading: 5, bottom: 20, trailing: 0    ))

                    ArticleBody()
                    ArticleBody()
                    ArticleBody()

                    SFWidgetWrapper(widgetId: widgetId, baseURL: baseURL, installationKey: installationKey)
                        .frame(height: sfWidgetObservable.widgetHeight)
                        .padding(EdgeInsets(top: 20, leading: 10, bottom: 20, trailing: 10))
                }
            }
            .fullScreenCover(isPresented: $sfWidgetObservable.showSafari) {
                OBSafariView(url: sfWidgetObservable.url!)
            }
            .environmentObject(sfWidgetObservable)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct ArticleBody: View {
    let loremIpsem = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda."

    var body: some View {
        Text(loremIpsem)
            .font(.body)
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(EdgeInsets(top: 20, leading: 5, bottom: 0, trailing: 5    ))
    }
}