SwiftUI Bridge (SFWidget)
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
Please make sure to read iOS Bridge and Getting Started pages before moving forward.
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:
-
SFWidgetObservable
– which is anObservableObject
which is shared between the SDK and the app code (to receive the URL of the recommendation being clicked for example. -
SFWidgetWrapper
– which is anUIViewRepresentable
which basically wrapsSFWidget
and contains the mandatory parameters needed. -
OBSafariView
– which is anUIViewRepresentable
which basically wrapsSFSafariViewController
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>) {
// No-op
}
}
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) {
guard let sfWidgetObservable else { return }
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.
-
Please note where we place
SFWidgetWrapper
- this View will contain the entire Outbrain feed. -
init() method should call
initializeOutbrain()
with the partner key (given by Outbrain Account Manager). -
SFWidgetWrapper
- make sure to pass it the “article URL”, “widget id” and “installationKey” (partner key). -
SFWidgetWrapper
- make sure to set the frame height according to sfWidgetObservable.widgetHeight which dynamically changes as content is loaded into the feed. -
.fullScreenCover
with OBSafariView to allow SafariViewController to display the recommendation URL on click event. -
.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(.horizontal, 5)
.padding(.top, 20)
}
}