Pada event WWDC tahun 2019, Apple memperkenalkan sebuah framework baru untuk membuat User Interface pada aplikasi iOS, watchOS, tvOS, dan macOS bernama SwiftUI.
Sangat mungkin di masa depan nanti framework ini akan menggantikan framework pendahulunya yaitu UIKit -- seperti halnya bahasa pemrograman Swift yang telah menggantikan Objective C. Karena ada banyak sekali kelebihan yang dimiliki oleh SwiftUI, misalnya Syntax-nya yang declarative (tidak seperti UIKit yang imperative), fitur Live Preview, dan masih banyak lagi.
Meskipun begitu, SwiftUI tentunya mempunyai limitasi-limitasi karena framework ini memang masih sangat baru. Salah satu limitasi yang paling krusial menurut saya adalah karena UI Components di SwiftUI tidak selengkap di UIKit. Setidaknya sampai artikel ini ditulis, Di SwiftUI belum ada Component yang sepadan dengan UIImagePickerController, UISearchController, dll. padahal Component-Component tersebut akan sangat sering kita gunakan dalam membuat aplikasi.
kamu bisa cek lebih lengkapnya di sini : https://fuckingswiftui.com
Untuk mengatasi masalah tersebut, untungnya di SwiftUI kita tetap bisa menggunakan UI Component dari UIKit! 😃 yaitu dengan cara wrapping View yang berasal dari UIKit menggunakan UIViewRepresentable atau UIViewControllerRepresentable untuk View Controller. Setelah di-wrap, nanti kita bisa menampilkan View-nya seperti biasa ke dalam body SwiftUI.
UIViewRepresentable adalah protocol yang dapat kita gunakan untuk me-wrap View yang berasal dari UIKit agar bisa ditampilkan ke dalam SwiftUI.
Pertama, kita akan coba menampilkan View sederhana dari UIKit ke dalam SwiftUI. Perhatikan code berikut :
Menampilkan UIKit View1import SwiftUI23struct ContentView: View {4 var body: some View {5 UIKitView()6 .frame(width: 100, height: 100)7 }8}910struct UIKitView: UIViewRepresentable {11 func makeUIView(context: Context) -> some UIView {12 let view = UIView()13 view.backgroundColor = .systemOrange14 return view15 }1617 func updateUIView(_ uiView: UIViewType, context: Context) {}18}
Ada 2 method yang harus dipenuhi jika kita ingin menggunakan protocol UIViewRepresentable, yaitu :
makeUIView : Digunakan untuk membuat View dari UIKit yang ingin kita tampilkan.
updateUIView : Digunakan untuk melakukan update ke UIKit View jika terjadi perubahan data. akan saya jelaskan lebih lanjut nanti di step nomor 2.
Jika code di atas kita jalankan, maka hasilnya akan seperti ini :
Selanjutnya, kita akan coba mengirim data dari SwiftUI ke UIKit View, sebagai contoh kita akan membuat sebuah 2 TextField, yang pertama adalah TextField dari SwiftUI dan yang satunya adalah TextField dari UIKit.
Jika TextField di SwiftUI berubah value-nya, maka akan mengupdate juga value dari TextField yang di UIKit.
Caranya seperti ini :
Passing data dari SwiftUI ke UIKit1import SwiftUI23struct ContentView: View {4 @State private var text: String = ""56 var body: some View {7 VStack {8 Text("Text : \(text)")9 .frame(maxWidth: .infinity)10 .padding(20)11 .background(.yellow)1213 Spacer()14 .frame(height: 50)1516 Text("SwiftUI TextField :")17 TextField("Type here...", text: $text)18 .frame(height: 44)19 .padding(.horizontal, 10)20 .border(.gray, width: 2)2122 Text("UIKit TextField :")23 UIKitTextField(text: $text)24 .frame(height: 44)25 .padding(.horizontal, 10)26 .border(.gray, width: 2)27 }28 .padding(20)29 }30}313233struct UIKitTextField: UIViewRepresentable {34 @Binding var text: String3536 func makeUIView(context: Context) -> UITextField {37 let textField = UITextField()38 textField.placeholder = "Type here..."39 return textField40 }4142 func updateUIView(_ uiView: UITextField, context: Context) {43 uiView.text = text44 }45}
Penjelasan Kode :
Pertama, kita buat properti text pada struct UIKitTextField
1@Binding var text: String
Kemudian kita berikan value untuk properti text tersebut dari state text yang ada pada SwiftUI.
1@State private var text: String = ""2...3UIKitTextField(text: $text)
Terakhir, kita gunakan method updateUIView untuk mengupdate value dari UITextField jika terjadi perubahan pada state text
1func updateUIView(_ uiView: UITextField, context: Context) {2 uiView.text = text3}
Jika kita jalankan code-nya maka hasilnya akan seperti ini :
Sampai di sini kita telah berhasil untuk membuat agar value dari TextField di UIKit ikut berubah jika value dari TextField di SwiftUI berubah.
Namun jika value dari TextField di UIKit berubah, value dari TextField di SwiftUI tidak ikut berubah. 🤔
Lalu bagaimana cara untuk memecahkan masalah tersebut? kita bisa membuat sebuah Coordinator menggunakan method makeCoordinator, Coordinator ini bertugas untuk membuat delegasi pada UIKit View. Pada case di atas, kita akan membuat delegasi dari UITextFieldDelegate karena kita ingin menggunakan method textFieldDidChangeSelection, method ini akan dipanggil ketika value dari TextField di UIKit berubah, dan di dalam method tersebut juga kita akan meng-update value dari TextField di SwiftUI sesuai dengan value dari TextField di UIkit.
Passing data dari UIKit ke SwiftUI1struct UIKitTextField: UIViewRepresentable {2 @Binding var text: String34 func makeUIView(context: Context) -> UITextField {5 let textField = UITextField()6 textField.placeholder = "Type here..."8 textField.delegate = context.coordinator9 return textField10 }1112 func updateUIView(_ uiView: UITextField, context: Context) {13 uiView.text = text14 }1517 func makeCoordinator() -> Coordinator {18 return Coordinator(text: $text)19 }2021 class Coordinator: NSObject, UITextFieldDelegate {22 @Binding var text: String2324 init(text: Binding<String>) {25 self._text = text26 }2728 func textFieldDidChangeSelection(_ textField: UITextField) {29 text = textField.text ?? ""30 }31 }33}
Penjelasan kode :
Kita buat dulu sebuah Coordinator menggunakan method makeCoordinator
Kemudian kita delegasikan textField.delegate kepada context.coordinator, context.coordinator ini adalah hasil kembalian dari method makeCoordinator yang telah kita buat sebelumnya.
Sekarang, jika kita mengubah value dari TextField yang ada di UIKit, maka value dari TextField yang ada di SwiftUI pun akan ikut berubah :
Terakhir, kita akan coba membuat Custom Modifier bernama textColorModifier pada struct UIKitTextField. modifier ini berfungsi untuk mengubah warna font pada TextField di UIKit.
Caranya seperti ini :
Custom Modifier1import SwiftUI23struct ContentView: View {4 @State private var text: String = ""6 @State private var color: UIColor = .black78 var body: some View {9 VStack {10 Text("Text : \(text)")11 .frame(maxWidth: .infinity)12 .padding(20)13 .background(.yellow)1415 Spacer()16 .frame(height: 50)1718 Text("SwiftUI TextField :")19 TextField("Type here...", text: $text)20 .frame(height: 44)21 .padding(.horizontal, 10)22 .border(.gray, width: 2)232425 Text("UIKit TextField :")26 UIKitTextField(text: $text)28 .textColorModifier(color)29 .frame(height: 44)30 .padding(.horizontal, 10)31 .border(.gray, width: 2)3233 Spacer()34 .frame(height: 50)3537 HStack {38 Button("RED") {39 color = .systemRed40 }41 .frame(maxWidth: .infinity, minHeight: 44)42 .foregroundColor(.white)43 .background(.red)4445 Button("GREEN") {46 color = .systemGreen47 }48 .frame(maxWidth: .infinity, minHeight: 44)49 .foregroundColor(.white)50 .background(.green)5152 Button("BLUE") {53 color = .systemBlue54 }55 .frame(maxWidth: .infinity, minHeight: 44)56 .foregroundColor(.white)57 .background(.blue)58 }60 }61 .padding(20)62 }63}646566struct UIKitTextField: UIViewRepresentable {67 @Binding var text: String69 var textColor: UIColor = .black7071 func makeUIView(context: Context) -> UITextField {72 let textField = UITextField()73 textField.placeholder = "Type here..."75 textField.textColor = textColor76 textField.delegate = context.coordinator77 return textField78 }7980 func updateUIView(_ uiView: UITextField, context: Context) {81 uiView.text = text83 uiView.textColor = textColor84 }8586 func makeCoordinator() -> Coordinator {87 return Coordinator(text: $text)88 }8990 class Coordinator: NSObject, UITextFieldDelegate {91 @Binding var text: String9293 init(text: Binding<String>) {94 self._text = text95 }9697 func textFieldDidChangeSelection(_ textField: UITextField) {98 text = textField.text ?? ""99 }100 }101}102104extension UIKitTextField {105 func textColorModifier(_ color: UIColor) -> UIKitTextField {106 var textField = self107 textField.textColor = color108 return textField109 }110}
Yang perlu kita perhatikan adalah di bagian ini :
1extension UIKitTextField {2 func textColorModifier(_ color: UIColor) -> UIKitTextField {3 var textField = self4 textField.textColor = color5 return textField6 }7}
Jadi, Kita membuat sebuah method textColorModifier yang mengembalikan tipe data UIKitTextField, method ini menerima 1 paramater yaitu color. kemudian di dalam method ini juga kita ubah properti textColor yang ada pada UIKitTextField. Sekarang, kita bisa pakai Custom Modifier yang telah kita buat barusan :
1UIKitTextField(text: $text)2 .textColorModifier(color)
Jika code di atas kita jalankan, maka hasilnya akan seperti ini :
UIViewControllerRepresentable adalah protocol yang dapat kita gunakan untuk me-wrap View Controller yang berasal dari UIKit agar bisa ditampilkan ke dalam SwiftUI.
Pertama, kita akan coba menampilkan View Controller sederhana dari UIKit ke dalam SwiftUI. Perhatikan code berikut :
Menampilkan UIKit View Controller1import SwiftUI23struct ContentView: View {4 @State private var isPresented: Bool = false56 var body: some View {7 VStack {8 Button("Show UIKit VC") {9 isPresented = true10 }11 }12 .sheet(isPresented: $isPresented, content: {13 UIKitVC()14 })15 }16}1718struct UIKitVC: UIViewControllerRepresentable {19 func makeUIViewController(context: Context) -> some UIViewController {20 let vc = UIViewController()21 vc.view.backgroundColor = .systemYellow22 return vc23 }2425 func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}26}
Mirip dengan UIViewRepresentable, pada UIViewControllerRepresentable juga terdapat 2 method yang harus dipenuhi jika kita ingin menggunakan protocol tersebut, yaitu :
makeUIViewController : Digunakan untuk membuat View Controller yang ingin kita tampilkan.
updateUIViewController : Digunakan untuk melakukan update ke View Controller jika terjadi perubahan data.
Jika code di atas kita jalankan, maka hasilnya akan seperti ini :
Selanjutnya, kita akan coba mengirim data dari SwiftUI ke UIKit View Controller. caranya sangat mudah :
Passing data dari SwiftUI ke UIKit1import SwiftUI23struct ContentView: View {4 @State private var isPresented: Bool = false56 var body: some View {7 VStack {8 Button("Show UIKit VC") {9 isPresented = true10 }11 }12 .sheet(isPresented: $isPresented, content: {14 UIKitVC(text: "From SwiftUI")15 })16 }17}1819struct UIKitVC: UIViewControllerRepresentable {21 let text: String2223 func makeUIViewController(context: Context) -> some UIViewController {24 let vc = UIViewController()25 vc.view.backgroundColor = .systemYellow2627 let label = UILabel()28 label.frame = vc.view.bounds29 label.textAlignment = .center31 label.text = text32 vc.view.addSubview(label)3334 return vc35 }3637 func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}38}
Jika kita jalankan code-nya, dan kita klik button "Show UIKit VC" maka akan muncul text "From SwiftUI" seperti ini :
Pada contoh kali ini, kita akan membuat Image Picker menggunakan UIKit dan setelah user selesai memilih foto, maka foto tersebut akan dikirim lagi ke SwiftUI untuk ditampilkan kepada user.
Caranya seperti ini :
Passing data dari UIKit ke SwiftUI1import SwiftUI23struct ContentView: View {4 @State private var isPresented: Bool = false5 @State private var image: UIImage?67 var body: some View {8 VStack {10 if image != nil {11 Image(uiImage: image!)12 .resizable()13 .scaledToFit()14 .frame(width: 250, height: 250)15 } else {16 Text("No Image")17 }1920 Spacer()21 .frame(height: 50)2223 Button("Choose Image") {24 isPresented = true25 }26 }27 .sheet(isPresented: $isPresented, content: {28 UIKitImagePicker(image: $image)29 })30 }31}3233struct UIKitImagePicker: UIViewControllerRepresentable {34 @Binding var image: UIImage?3537 func makeUIViewController(context: Context) -> some UIImagePickerController {38 let vc = UIImagePickerController()39 vc.allowsEditing = false40 vc.delegate = context.coordinator41 return vc42 }4445 func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}4647 func makeCoordinator() -> Coordinator {48 return Coordinator(image: $image)49 }5051 class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {52 @Binding var image: UIImage?5354 init(image: Binding<UIImage?>) {55 self._image = image56 }5759 func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {60 guard let newImage = info[.originalImage] as? UIImage else { return }61 image = newImage62 }64 }65}
Penjelasan Kode :
Kita buat dulu Image Picker dari UIKit menggunakan method makeUIViewController
1func makeUIViewController(context: Context) -> some UIImagePickerController {2 let vc = UIImagePickerController()3 vc.allowsEditing = false4 vc.delegate = context.coordinator5 return vc6}
Lalu kita buat method didFinishPickingMediaWithInfo, method ini akan dipanggil ketika user selesai memilih foto. dan kemudian kita kirim data image-nya kepada SwiftUI.
1func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {2 guard let newImage = info[.originalImage] as? UIImage else { return }3 image = newImage4}
Terakhir, jika user selesai memilih foto (nilai dari image tidak nil), maka kita tampilkan foto tersebut kepada user :
1if image != nil {2 Image(uiImage: image!)3 .resizable()4 .scaledToFit()5 .frame(width: 250, height: 250)6} else {7 Text("No Image")8}
Jika kita jalankan code di atas, maka hasilnya akan seperti ini :
Yang terakhir, kita akan belajar bagaimana cara menggunakan Storyboard pada SwiftUI.
Silakan buat file Storyboard baru dengan cara klik File -> New -> File -> lalu pilih Storyboard.
Ubah Storyboard ID pada "Identity Inspector", menjadi "ExampleVC" misalnya.
Lalu centang "Is Initial View Controller" pada "Attributes Inspector", dan silakan ubah UI dari View Controller menjadi apa aja yang kamu mau.
Kemudian, ubah code pada file "ContentView.swift" menjadi seperti ini :
Menggunakan Storyboard di SwiftUI1import SwiftUI23struct ContentView: View {4 @State private var isPresented: Bool = false56 var body: some View {7 VStack {8 Button("Show Storyboard") {9 isPresented = true10 }11 }12 .sheet(isPresented: $isPresented, content: {13 ExampleVC()14 })15 }16}1718struct ExampleVC: UIViewControllerRepresentable {19 func makeUIViewController(context: Context) -> some UIViewController {21 let storyboard = UIStoryboard(name: "Storyboard", bundle: Bundle.main)22 let vc = storyboard.instantiateViewController(identifier: "ExampleVC")24 return vc25 }2627 func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}28}
Yang perlu diperhatikan dari code di atas adalah bagian ini :
1let storyboard = UIStoryboard(name: "Storyboard", bundle: Bundle.main)
Silakan ganti "Storyboard" dengan nama dari file Storyboard kamu.
1let vc = storyboard.instantiateViewController(identifier: "ExampleVC")
Ganti juga "ExampleVC" sesuai dengan Storyboard ID yang kamu berikan pada step 2 di atas.
Dan jika kita jalankan code-nya maka hasilnya akan seperti ini :
Oke mungkin itu saja yang bisa saya bagikan kali ini, kalau kamu merasa artikel ini bermanfaat silakan Like & Share artikel ini ke teman-teman kamu atau jika kamu punya pertanyaan, tulis aja di kolom komentar, Thank you! 😁 🙏
Always open to new ideas. 🕊️
Articles that you might want to read.
Tutorial penggunaan UIHostingController.
Otomatisasi build, testing, screenshot, dan deployment aplikasi iOS ke Testflight & AppStore.
Tutorial membongkar dan memodifikasi aplikasi iOS 🍎