When iOS 13 was introduced, Apple came up with a new UI paradigm called SwiftUI. SwiftUI is a declarative style of UI similar to Flutter or React. As such the UI elements need to be handled differently. Xcode was redesigned to support SwiftUI by adding the concept of an automatic preview so you can actually see the UI update as you write code as well as see the code update as you interact with the preview pane. This is a really cool feature. I decided to find out how I could use SwiftUI and still use our spot classes (SASCollectorUIAdView). What I found was that working with SwiftUI was very different but there were ways to make things work.
One of the issues with the SASCollectorUIAdview is that the code for MRAID support requires access to the hosting view controller. MRAID provides an industry standard way for content providers to add rich UI elements to their spot content. One such element is to present an overlaying window of an external web page. Before SwiftUI, you could use InterfaceBuilder to drag a connection from the hostController outlet to the hosting view controller. This is very convenient. In code, you could construct the SASCollectorUIAdView and pass the host controller in. In SwiftUI, though, the drag to connect concept is gone so you have to do it in code. With the declarative style of coding, though, you don’t have access to the host controller.
SwiftUI comes with support for many of the normal UI elements available in iOS. There are some that are notably missing. These include WKWebView and MKMapView and, of course, SASCollectorUIAdView. To allow the use of UIViews in SwiftUI, the designers provided a struct of type UIViewRepresentable. This struct has 2 required methods, makeUIView(context:) and updateUIView(_:context:). makeUIView is the method that constructs the object that will appear on the screen. updateUIView is the method that is called when the state of the object changes. With that in mind I created the following struct to represent the SASCollectorUIAdView:
import SwiftUI
import SASCollector
struct AdView: UIViewRepresentable {
typealias UIViewType = SASCollectorUIAdView
var spotID = ""
func makeUIView(context: Context) -> SASCollectorUIAdView {
let adView = SASCollectorUIAdView(frame: CGRect.zero)
return adView
}
func updateUIView(_ uiView: SASCollectorUIAdView, context: Context) {
uiView.spotID = spotID
uiView.load()
}
mutating func reload(_ spotID: String) {
self.spotID = spotID
}
}
This struct provides the simple ability to add a spotID and reload content if it changes. I then created the following SwiftUI class to host my new AdView:
struct ContentView: View {
@State var spotID = ""
@State var adView = AdView()
var body: some View {
VStack {
adView.onAppear() {
self.adView.reload(self.spotID)
}
HStack {
Spacer()
Text("spotID:")
TextField("", text: $spotID)
Spacer()
}
Button(action: {
self.adView.reload( self.spotID)
}) {
Text("Reload").padding(.trailing, 10)
.padding(.leading, 10)
.padding(.top, 4)
.padding(.bottom, 4)
.overlay (
RoundedRectangle(cornerRadius: 4, style: .circular)
.stroke(Color.gray, lineWidth: 0.5)
)
}
Spacer()
}
}
}
This produces the following application. Note that the content I am using uses MRAID to pop up a window with a webpage hosted in it:
There is just one small problem. Clicking on the SAS button is supposed to put up a new window containing the SAS Homepage. Nothing happens when you click on it. Turns out we are missing the connection to the hosting view controller so there is no place to show the popup from. SwiftUI is a very different paradigm and the view controller containing the SASCollectorUIAdView is not readily available. The solution is to make a UIView extenstion that walks up the Responder chain until it finds a view controller.
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder?.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
Now we can modify our AdView struct to set the hostController property every time the UI is updated. This actually allows the object to be moved from controller to controller but I don’t expect that to happen very often. Here is the updated code:
struct AdView: UIViewRepresentable {
typealias UIViewType = SASCollectorUIAdView
var spotID = ""
func makeUIView(context: Context) -> SASCollectorUIAdView {
let adView = SASCollectorUIAdView(frame: CGRect.zero)
return adView
}
func updateUIView(_ uiView: SASCollectorUIAdView, context: Context) {
if let parentViewController = uiView.parentViewController {
uiView.hostViewController = parentViewController
}
uiView.spotID = spotID
uiView.load()
}
mutating func reload(_ spotID: String) {
self.spotID = spotID
}
}
Now when we click on the SAS button we get a popup showing the SAS Home Page! Success!
... View more