Skip to main content

SwiftUI Bridge (SFWidget)

caution

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.

Intro

info

Please make sure to read iOS Bridge and Getting Started pages before moving foreword.

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: "iOSSampleApp2023")

}

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