Skip to main content

How to Integrate UIKit with SwiftUI

· 4 min read
Full Stack Developer
Last updated on September 2, 2019

swift ui

When you start developing with SwiftUI, you will reach certain points during development where you would like to use some APIs from UIKit. In order to integrate UIKit with SwiftUI, you can use one of two protocols – UIViewControllerRepresentable and UIViewRepresentable. We will soon be migrating all of our iPhone app templates (currently running on UIKit) to SwiftUI, so having them use both UIKit and SwiftUI as a transition state during migration will be a huge advantage in order to not block our customers who are willing to take a stab at SwiftUI early on (even if we don’t recommend it yet!).

The main difference between the two protocols is that UIViewControllerRepresentable is used to represent a UIKit view controller and UIViewRepresentable can be used to represent a UIKit view in your SwiftUI project.

We will use UIViewControllerRepresentable to create a custom ScrollView wrapper. Let’s say we want to create a scrollView that contains Pagination. The default ScrollView that comes with SwiftUI doesn’t have this feature – so we can create a UIViewController that houses a ScrollView with pagination enabled. Then we can integrate this custom UIViewController with our SwiftUI content.

Let’s start by first creating a simple UIViewController

class UIScrollViewController<Content: View>: UIViewController

Then we will create a simple scrollView like so:

lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.delegate = self
view.isPagingEnabled = pagingEnabled
view.showsVerticalScrollIndicator = !hideScrollIndicators
view.showsHorizontalScrollIndicator = !hideScrollIndicators
return view
}()
var hostingController: UIHostingController<Content>! = nil
init(rootView: Content) {
self.hostingController = UIHostingController<Content>(rootView: rootView)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

We have created a scrollView, and also created a hostingController property. This UIHostingController is basically initialized by the content that we have wrapped this wrapper around. So that’s what we have done in the init block. ( more on this later)

Mega Bundle Sale is ON! Get ALL of our iOS App codebases at 90% OFF discount 🔥

Get the Mega Bundle

Make sure to add the scrollView as a subview and assign the constraints using auto layout.

override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
self.makefullScreen(of: self.scrollView, to: self.view) //assiging autolayout to scrollview
// add hostingController as child VC and laying out constraints for the child view controller’s view
self.hostingController.willMove(toParent: self)
self.scrollView.addSubview(self.hostingController.view)
self.makefullScreen(of: self.hostingController.view, to: self.scrollView)
self.hostingController.didMove(toParent: self)
}

Once the ViewController is set up. Now it’s time to implement the protocol. Start by first constructing a struct – I have named mine as SwiftyUIScrollView. It conforms to UIViewControllerRepresentable, so we get methods to which I need to conform to.

The struct has an initializer with one mandatory closure property called content. The remaining are something that you can add yourself. I have added two more including pagingEnabled bool value.

struct SwiftyUIScrollView<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
var pagingEnabled: Bool = false
var hideScrollIndicators: Bool = false

init(pagingEnabled: Bool,
hideScrollIndicators: Bool,
@ViewBuilder content: @escaping () -> Content) {
self.content = content
self.pagingEnabled = pagingEnabled
self.hideScrollIndicators = hideScrollIndicators
}
func makeUIViewController(context: Context) -> UIScrollViewController<Content> {
let vc = UIScrollViewController(rootView: self.content())
vc.pagingEnabled = pagingEnabled
vc.hideScrollIndicators = hideScrollIndicators
return vc
}
func updateUIViewController(_ viewController: UIScrollViewController<Content>, context: Context) {
viewController.hostingController.rootView = self.content()
}
}

Once the initializer is done, we will move to the second method where we will create a UIScrollViewController and return that view controller. We will create a new instance of UIScrollViewController and initialize it with the content. Then we will assign values that we got from the initializer to the properties in the view controller and return the controller.

And that’s about it. Now we can simply use this wrapper where ever we want to show a scrollView and using the initializer property – enable or disable the pagination feature.

This is how you can use this custom wrapper now:

SwiftyUIScrollView(pagingEnabled: true, hideScrollIndicators: true) {
HStack(spacing: 0) {
ForEach(self.contentArray, id: \.id) { item in
TestView(data: item)
.frame(width: g.size.width, height: g.size.height)
}
}
}.frame(width: g.size.width)
Looking for a custom mobile application?

Our team of expert mobile developers can help you build a custom mobile app that meets your specific needs.

Get in Touch

The content closure contains a HStack – all this content is assigned to the hostingViewController that is added as child VC to our custom UIScrollViewController and the child’s view is added as a subview to our scrollView. So now the content automatically sizes up the scrollView and since the scrollView has pagination enabled we get the snap effect when scrolling through.