|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Use UIKit components in SwiftUI: UIViewControllerRepresentable and UIViewRepresentable" |
| 4 | +description: "Do you know that it is possible to use UKit components in SwiftUI? Let's see how you can use UIViewRepresentable and UIViewControllerRepresentable to use your UIKit based component or to fill the gap for missing SwiftUI API." |
| 5 | +date: 2020-06-08 |
| 6 | +image: /assets/images/posts/XXXX |
| 7 | +tags: [computer graphics] |
| 8 | +comments: true |
| 9 | +math: false |
| 10 | +seo: |
| 11 | + - type: "BlogPosting" |
| 12 | +authors: [fabrizio_duroni] |
| 13 | +--- |
| 14 | + |
| 15 | +*Do you know that it is possible to use UKit components in SwiftUI? Let's see how you can use UIViewRepresentable and UIViewControllerRepresentable to use your UIKit based component or to fill the gap for missing SwiftUI API.* |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +SwiftUI has been around for almost year now. With its reactive paradigm approach it is a big step forward when compared to the UIKit imperative approach. But as a consequence of the fact that UIKit has been with us [for almost twelve years](https://en.wikipedia.org/wiki/IOS_SDK) and there are millions of apps already publish on the app store, a lot of developers have tons of UKit based library and custom components. Another interesting point is the fact that at the moment of this writing a lot of UIKit components from the iOS SDK are [missing a counterpart in SwiftUI](https://www.hackingwithswift.com/quick-start/swiftui/answering-the-big-question-should-you-learn-swiftui-uikit-or-both "uikit missing swiftui"). |
| 20 | +Anyway, you are just starting to develop a new app and you want to create it in SwiftUI (targeting only for iOS 13 because, you know, SwiftUI is compatible only with it :laughing:) without losing the ability to speed up you development by reusing your UIKit based libraries and components. Is there a solution to this problem? Yes!! :relaxed: |
| 21 | +In this post I will show you how you can leverage the power of `UIViewRepresentable` and `UIViewControllerRepresentable` protocols to expose your UIKit views and controller as standard SwiftUI components. Before going deeper with an example let's see the definition of this two protocol from the official Apple documentation. Let's start from `UIViewRepresentable`: |
| 22 | + |
| 23 | +>UIViewRepresentable. A wrapper for a UIKit view that you use to integrate that view into your SwiftUI view hierarchy...Adopt this protocol in one of your app's custom instances, and use its methods to create, update, and tear down your view. The creation and update processes parallel the behavior of SwiftUI views, and you use them to configure your view with your app's current state information... |
| 24 | + |
| 25 | +And here we have the other one for `UIViewControllerRepresentable`: |
| 26 | + |
| 27 | +>UIViewControllerRepresentable. A view that represents a UIKit view controller...Adopt this protocol in one of your app's custom instances, and use its methods to create, update, and tear down your view controller. The creation and update processes parallel the behavior of SwiftUI views, and you use them to configure your view controller with your app's current state information...The system doesn't automatically communicate changes occurring within your view controller to other parts of your SwiftUI interface. When you want your view controller to coordinate with other SwiftUI views, you must provide a Coordinator instance to facilitate those interactions. For example, you use a coordinator to forward target-action and delegate messages from your view controller to any SwiftUI views. |
| 28 | +
|
| 29 | +There are a lot of concepts here: view lifecycle, notification, delegation and communication with Coordinator :cold_sweat: But don't worry, with an example you will see how easy it is to use `UIViewRepresentable` and `UIViewControllerRepresentable`. |
| 30 | + |
| 31 | +#### Implementation |
| 32 | + |
| 33 | +In this example we will create a simple app that will let the user select a document using and instance of the UIKit based controller `UIDocumentPickerViewController` and we will print the name of file selected using a UIKit `UILabel`. ... |
| 34 | + |
| 35 | + |
| 36 | +```swift |
| 37 | +struct DocumentNameLabel: UIViewRepresentable { |
| 38 | + @Binding var content: String |
| 39 | + |
| 40 | + func makeUIView(context: Context) -> UILabel { |
| 41 | + let label = UILabel() |
| 42 | + label.backgroundColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1) |
| 43 | + label.layer.borderColor = #colorLiteral(red: 0.2745098174, green: 0.4862745106, blue: 0.1411764771, alpha: 1) |
| 44 | + label.layer.borderWidth = 2 |
| 45 | + return label |
| 46 | + } |
| 47 | + |
| 48 | + func updateUIView(_ uiView: UILabel, context: Context) { |
| 49 | + uiView.text = content |
| 50 | + } |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +```swift |
| 55 | +struct DocumentPickerViewController: UIViewControllerRepresentable { |
| 56 | + var callback: (URL) -> () |
| 57 | + |
| 58 | + func makeCoordinator() -> Coordinator { |
| 59 | + return Coordinator(documentController: self) |
| 60 | + } |
| 61 | + |
| 62 | + func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, |
| 63 | + context: UIViewControllerRepresentableContext<DocumentPickerViewController>) { |
| 64 | + } |
| 65 | + |
| 66 | + func makeUIViewController(context: Context) -> UIDocumentPickerViewController { |
| 67 | + let controller = UIDocumentPickerViewController(documentTypes: [String(kUTTypeText)], in: .open) |
| 68 | + controller.delegate = context.coordinator |
| 69 | + return controller |
| 70 | + } |
| 71 | + |
| 72 | + class Coordinator: NSObject, UIDocumentPickerDelegate { |
| 73 | + var documentController: DocumentPickerViewController |
| 74 | + |
| 75 | + init(documentController: DocumentPickerViewController) { |
| 76 | + self.documentController = documentController |
| 77 | + } |
| 78 | + |
| 79 | + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { |
| 80 | + guard let url = urls.first, url.startAccessingSecurityScopedResource() else { return } |
| 81 | + defer { url.stopAccessingSecurityScopedResource() } |
| 82 | + documentController.callback(urls[0]) |
| 83 | + } |
| 84 | + } |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +```swift |
| 89 | +struct ContentView: View { |
| 90 | + @State var isDocumentPickerPresented: Bool = false |
| 91 | + @State var documentUrl: String = "" |
| 92 | + |
| 93 | + var body: some View { |
| 94 | + VStack{ |
| 95 | + Spacer() |
| 96 | + DocumentNameLabel(content: self.$documentUrl) |
| 97 | + .frame(height: 40) |
| 98 | + Button(action: { |
| 99 | + self.isDocumentPickerPresented.toggle() |
| 100 | + }, label: { Text("Document selection") }) |
| 101 | + .frame(height: 40, alignment: .center) |
| 102 | + .sheet(isPresented: self.$isDocumentPickerPresented, content: { |
| 103 | + DocumentPickerViewController { url in |
| 104 | + self.documentUrl = url.lastPathComponent |
| 105 | + } |
| 106 | + }) |
| 107 | + Spacer() |
| 108 | + } |
| 109 | + .padding(10) |
| 110 | + } |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +#### Conclusion |
| 115 | + |
0 commit comments