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