|
| 1 | +This is from the Apple UIKit docs |
| 2 | +(https://developer.apple.com/documentation/uikit): |
| 3 | + |
| 4 | +"UIKit works seamlessly with the SwiftUI framework, so you can implement parts of |
| 5 | +your UIKit app in SwiftUI or mix interface elements between the two frameworks. |
| 6 | +For example, you can place UIKit views and view controllers inside SwiftUI |
| 7 | +views, and vice versa." |
| 8 | + |
| 9 | +Could you show me how to do this? |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +Sure, I can help with that. You can use UIKit views in SwiftUI by conforming to |
| 14 | +the `UIViewRepresentable` protocol, and you can use SwiftUI views in UIKit by |
| 15 | +wrapping them in a `UIHostingController`. Here's an example of each. |
| 16 | + |
| 17 | +## UIKit views in SwiftUI |
| 18 | + |
| 19 | +First, let's show how to use a UIKit view in a SwiftUI view. We'll use a |
| 20 | +`UISlider` as an example: |
| 21 | + |
| 22 | +```swift |
| 23 | +import SwiftUI |
| 24 | +import UIKit |
| 25 | + |
| 26 | +struct UIKitSlider: UIViewRepresentable { |
| 27 | + @Binding var value: Float |
| 28 | + |
| 29 | + func makeUIView(context: Context) -> UISlider { |
| 30 | + let slider = UISlider(frame: .zero) |
| 31 | + slider.value = value |
| 32 | + slider.addTarget(context.coordinator, action: #selector(Coordinator.valueChanged(_:)), for: .valueChanged) |
| 33 | + return slider |
| 34 | + } |
| 35 | + |
| 36 | + func updateUIView(_ uiView: UISlider, context: Context) { |
| 37 | + uiView.value = value |
| 38 | + } |
| 39 | + |
| 40 | + func makeCoordinator() -> Coordinator { |
| 41 | + Coordinator(value: $value) |
| 42 | + } |
| 43 | + |
| 44 | + class Coordinator: NSObject { |
| 45 | + var value: Binding<Float> |
| 46 | + |
| 47 | + init(value: Binding<Float>) { |
| 48 | + self.value = value |
| 49 | + } |
| 50 | + |
| 51 | + @objc func valueChanged(_ sender: UISlider) { |
| 52 | + self.value.wrappedValue = sender.value |
| 53 | + } |
| 54 | + } |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +You can then use `UIKitSlider` in your SwiftUI views like this: |
| 59 | + |
| 60 | +```swift |
| 61 | +struct ContentView: View { |
| 62 | + @State private var sliderValue: Float = 0.5 |
| 63 | + |
| 64 | + var body: some View { |
| 65 | + VStack { |
| 66 | + Text("Slider Value: \(sliderValue)") |
| 67 | + UIKitSlider(value: $sliderValue) |
| 68 | + } |
| 69 | + } |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +## SwiftUI views in UIKit |
| 74 | + |
| 75 | +You can also embed SwiftUI views in your UIKit code by using a |
| 76 | +UIHostingController. Here's an example of how to do that: |
| 77 | + |
| 78 | +```swift |
| 79 | +import SwiftUI |
| 80 | +import UIKit |
| 81 | + |
| 82 | +struct SwiftUIView: View { |
| 83 | + var body: some View { |
| 84 | + Text("Hello, SwiftUI!") |
| 85 | + .padding() |
| 86 | + .background(Color.green) |
| 87 | + .foregroundColor(.white) |
| 88 | + .cornerRadius(10) |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +class ViewController: UIViewController { |
| 93 | + override func viewDidLoad() { |
| 94 | + super.viewDidLoad() |
| 95 | + |
| 96 | + let hostingController = UIHostingController(rootView: SwiftUIView()) |
| 97 | + addChild(hostingController) |
| 98 | + hostingController.view.translatesAutoresizingMaskIntoConstraints = false |
| 99 | + view.addSubview(hostingController.view) |
| 100 | + |
| 101 | + NSLayoutConstraint.activate([ |
| 102 | + hostingController.view.centerXAnchor.constraint(equalTo: view.centerXAnchor), |
| 103 | + hostingController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor), |
| 104 | + ]) |
| 105 | + } |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +In this example, `SwiftUIView` is embedded in a `ViewController`. It will |
| 110 | +display a text "Hello, SwiftUI!" with a green background in the center of the |
| 111 | +screen. |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +Could you show me how to place a UIViewController (instead of just a UIView) |
| 116 | +inside SwiftUI views? |
| 117 | + |
| 118 | +--- |
| 119 | + |
| 120 | +## UIKit UIViewControllers in SwiftUI |
| 121 | + |
| 122 | +Yes, absolutely! In order to use a `UIViewController` in a SwiftUI view, you |
| 123 | +need to use the `UIViewControllerRepresentable` protocol. Here's an example of |
| 124 | +how to use a `UIImagePickerController` (which is a subclass of |
| 125 | +`UIViewController`) inside a SwiftUI view: |
| 126 | + |
| 127 | +```swift |
| 128 | +import SwiftUI |
| 129 | +import UIKit |
| 130 | + |
| 131 | +struct ImagePicker: UIViewControllerRepresentable { |
| 132 | + @Binding var selectedImage: UIImage? |
| 133 | + @Environment(\.presentationMode) private var presentationMode |
| 134 | + |
| 135 | + class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { |
| 136 | + var parent: ImagePicker |
| 137 | + |
| 138 | + init(_ parent: ImagePicker) { |
| 139 | + self.parent = parent |
| 140 | + } |
| 141 | + |
| 142 | + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { |
| 143 | + if let uiImage = info[.originalImage] as? UIImage { |
| 144 | + parent.selectedImage = uiImage |
| 145 | + } |
| 146 | + |
| 147 | + parent.presentationMode.wrappedValue.dismiss() |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController { |
| 152 | + let picker = UIImagePickerController() |
| 153 | + picker.delegate = context.coordinator |
| 154 | + return picker |
| 155 | + } |
| 156 | + |
| 157 | + func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) { |
| 158 | + // update function implementation |
| 159 | + } |
| 160 | + |
| 161 | + func makeCoordinator() -> Coordinator { |
| 162 | + Coordinator(self) |
| 163 | + } |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +You can then use `ImagePicker` in your SwiftUI views like this: |
| 168 | + |
| 169 | +```swift |
| 170 | +struct ContentView: View { |
| 171 | + @State private var isShowingImagePicker = false |
| 172 | + @State private var selectedImage: UIImage? |
| 173 | + |
| 174 | + var body: some View { |
| 175 | + VStack { |
| 176 | + Button(action: { |
| 177 | + isShowingImagePicker = true |
| 178 | + }) { |
| 179 | + Text("Open Image Picker") |
| 180 | + } |
| 181 | + |
| 182 | + selectedImage.map { Image(uiImage: $0).resizable().scaledToFit() } |
| 183 | + } |
| 184 | + .sheet(isPresented: $isShowingImagePicker) { |
| 185 | + ImagePicker(selectedImage: $selectedImage) |
| 186 | + } |
| 187 | + } |
| 188 | +} |
| 189 | +``` |
| 190 | + |
| 191 | +In this example, when you press the "Open Image Picker" button, it opens up an |
| 192 | +image picker. Once an image is selected, it is displayed on the screen. |
| 193 | + |
| 194 | +--- |
| 195 | + |
| 196 | +Does SwiftUI render to UIKit on mobile and AppKit on desktop? |
| 197 | + |
| 198 | +--- |
| 199 | + |
| 200 | +In essence, yes. SwiftUI operates at a higher level of abstraction than UIKit or |
| 201 | +AppKit. When you define interfaces in SwiftUI, you describe them in a |
| 202 | +platform-agnostic way, but when those interfaces are rendered on a particular |
| 203 | +platform, SwiftUI uses the native rendering engine of that platform. |
| 204 | + |
| 205 | +On iOS and iPadOS, SwiftUI uses UIKit (more specifically, underlying |
| 206 | +technologies like Core Animation and Core Graphics) for rendering. On macOS, |
| 207 | +SwiftUI uses AppKit. Similarly, on watchOS, it uses WatchKit, and on tvOS, it |
| 208 | +uses TVUIKit. |
| 209 | + |
| 210 | +However, it's important to note that SwiftUI doesn't directly convert your |
| 211 | +SwiftUI views into UIKit or AppKit components. Instead, SwiftUI works directly |
| 212 | +with the underlying rendering and layout engines to create UIs that match the |
| 213 | +capabilities and conventions of each platform. |
| 214 | + |
| 215 | +This means, for instance, that a SwiftUI `Button` does not become a `UIButton` |
| 216 | +on iOS or a `NSButton` on macOS. It remains a SwiftUI `Button`, but it is drawn |
| 217 | +and behaves appropriately for the platform it's running on, thanks to the |
| 218 | +rendering done by SwiftUI using the native technologies. |
0 commit comments