When the SwiftUI announcement came out at WWDC 2019, you were buzzing about the new framework. Finally, a new way to build our interfaces. Out with ViewControllers, out with storyboards, in with declarative, programmatic UIs!
Now that we’ve all had some time to play with the framework, we’ve found out what we’re really trading away when we use SwiftUI, at least for now. One of the major things? The vast array of components and configurability available through UIKit.
You’ve probably found through experimenting with the new framework that there are things missing. Entire UIKit components are missing, and things like the broad number of properties you could set on TextFields, and the common pull-to-refresh control are missing.
You really don’t want to continue down the UIKit road. You want to go all in on SwiftUI now; it’s the future of development for all Apple platforms. “But how am I going to get back these features?”
This is where UIViewRepresentable
comes in. Apple knew that SwiftUI was not going to be a complete port of UIKit for its first version, and so they provided this protocol which you can implement on a View struct to wrap a UIKit control, making it usable in SwiftUI. Let’s see an example how to use this: getting the pull-to-refresh functionality on a scroll view.
UIViewRepresentable
works very similarly to a standard SwiftUI View, but specifies additional methods to implement:
makeUIView(context:)
updateUIView(uiView:context:)
makeCoordinator:
The first two methods are required, and construct the UIView
, and update it when the view’s state changes, respectively.
For some components, this is sufficient, if you don’t need to handle any events. If you need an object that will receive events, you will need what UIViewRepresentable
calls a coordinator to receive those events. That is where makeCoordinator
comes in; it constructs the object that will receive those events, which you will configure the control to use in makeUIView
.
With that in mind, here is our sample UIViewRepresentable
, which wraps a UIKit UIScrollView
:
struct LegacyScrollView : UIViewRepresentable {
// any data state, like @State/@Binding/etc, if needed
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIScrollView {
let control = UIScrollView()
control.refreshControl = UIRefreshControl()
control.refreshControl?.addTarget(context.coordinator, action:
#selector(Coordinator.handleRefreshControl),
for: .valueChanged)
// Simply to give some content to see in the app
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 30))
label.text = "Scroll View Content"
control.addSubview(label)
return control
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
// code to update scroll view from view state, if needed
}
class Coordinator: NSObject {
var control: LegacyScrollView
init(_ control: LegacyScrollView) {
self.control = control
}
@objc func handleRefreshControl(sender: UIRefreshControl) {
// handle the refresh event
sender.endRefreshing()
}
}
}
makeUIView
is where the UIScrollView
is constructed with the UIRefreshControl
. In this example, there is a hardcoded UILabel
, but you can put any children for the UIScrollView
in here. (Aside: You could even make a constructor for LegacyScrollView
which would take SwiftUI Views, and wrap them in a UIHostingController
, allowing you to use SwiftUI Views in your wrapped UIKit view, but that is beyond the scope of this article.)
Note that makeUIView
makes use of the context argument passed in. The context argument contains a few properties, but the most important one is the coordinator, which SwiftUI set earlier by calling the makeCoordinator
function on our class. So now we can set the handleRefreshControl
to handle the refresh control when it is pulled down, triggering the refresh logic, and then ending the refresh cycle.
If this control had any state variables, then updateUIView
would be the place to update the UIScrollView
to reflect them on the screen. updateUIView
should get called anytime the state variables on your control are changed, just like the SwiftUI View body property.
SwiftUI is going to be growing for a long time, slowly integrating pieces of UIKit functionality over that time. But if you’re ready to use SwiftUI now, but still want some of that missing functionality from UIKit, UIViewRepresentable
s are going to be your way to get both with (relatively) little fuss.
Comments are closed.