Setelah kita belajar mengenai cara mengintegrasikan UIKit ke dalam SwiftUI pada artikel sebelumnya, sekarang kita akan belajar untuk mengintegrasikan SwiftUI ke dalam UIKit.
Jika kamu belum membaca artikel sebelumnya, kamu bisa membacanya di sini : https://blog.alfin.dev/article/integrasi-uikit-ke-dalam-swiftui
Seperti yang kita tahu, SwiftUI -- framework baru yang diperkenalkan Apple pada WWDC19 mempunyai banyak sekali fitur yang menarik. Jika kita ingin beralih ke SwiftUI namun project kita yang existing masih menggunakan UIKit dan sudah terlalu besar, rasanya tidak mungkin untuk rewrite semuanya ke dalam SwiftUI.
Untuk mengatasi permasalahan tersebut, solusinya kita bisa migrasi ke SwiftUI secara gradually atau secara bertahap dengan cara menggunakan UIHostingController untuk menampilkan SwiftUI View ke dalam UIKit.
1open class UIHostingController<Content> : UIViewController where Content : View { ... }
UIHostingController adalah sebuah class dari module SwiftUI yang dapat kita gunakan untuk menampilkan SwiftUI View ke dalam UIKit. Class ini meng-extends dari UIViewController, sehingga kita bisa langsung tampilkan menggunakan method present atau pushViewController layaknya turunan dari class UIViewController yang lain seperti UITableViewController.
Pertama, kita akan coba menampilkan SwiftUI View sederhana ke dalam UIKit.
Buatlah sebuah project baru kemudian pada bagian "Interface" pilih "Storyboard". Setelah itu buka file "Main.storyboard" kemudian pilih "View Controller" lalu klik menu bar "Editor -> Embed in -> Navigation Controller".
Setelah itu buatlah file SwiftUI View dan beri nama "SwiftUIView.swift", kemudian ubah isinya menjadi seperti ini :
SwiftUIView.swift1import SwiftUI23struct SwiftUIView: View {4 var body: some View {5 ZStack {6 Color.yellow78 VStack {9 Text("SwiftUI View")10 }11 .padding(20)12 }13 .navigationBarTitle("SwiftUI View")14 }15}
SwiftUI View di atas nantinya akan kita tampilkan melalui View Controller di UIKit.
Terakhir, ubahlah isi dari file "ViewController.swift" menjadi seperti ini :
ViewController.swift1import UIKit2import SwiftUI34class ViewController: UIViewController {5 var button: UIButton = {6 let button = UIButton()7 button.backgroundColor = .systemYellow8 button.layer.cornerRadius = 249 button.setTitle("Show SwiftUI View", for: .normal)10 button.setTitleColor(.black, for: .normal)11 button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)12 return button13 }()1415 override func viewDidLoad() {16 super.viewDidLoad()1718 title = "UIKit View"1920 view.addSubview(button)21 }2223 override func viewDidLayoutSubviews() {24 super.viewDidLayoutSubviews()2526 button.frame = CGRect(x: 0, y: 0, width: 200, height: 44)27 button.center = view.center28 }2930 @objc func didTapButton() {32 let vc = UIHostingController(rootView: SwiftUIView())33 navigationController?.pushViewController(vc, animated: true)34 }35}
Code di atas akan menampilkan sebuah button "Show SwiftUI View" dan jika diklik maka akan menampilkan SwiftUI View yang telah kita buat sebelumnya pada file "SwiftUIView.swift"
Untuk mengubah SwiftUI View menjadi sebuah View Controller agar bisa ditampilkan di UIKit, kita bisa lakukan dengan cara me-wrap SwiftUI View tersebut menggunakan UIHostingController :
1let vc = UIHostingController(rootView: SwiftUIView())
Jika code-nya kita jalankan, maka hasilnya akan seperti ini :
Selanjutnya, kita akan coba passing data dari UIKit ke SwiftUI View. Silakan ubah isi dari file "SwiftUIView.swift" menjadi seperti ini :
SwiftUIView.swift1struct SwiftUIView: View {3 var title: String45 var body: some View {6 ZStack {7 Color.yellow89 VStack {11 Text(title)12 }13 .padding(20)14 }16 .navigationBarTitle(title)17 }18}
dan ubah juga method didTapButton yang ada pada class ViewController menjadi seperti ini :
ViewController.swift1@objc func didTapButton() {2 let vc = UIHostingController(3 rootView: SwiftUIView(5 title: "Custom Title"6 )7 )8 navigationController?.pushViewController(vc, animated: true)9}
Jika code di atas kita jalankan, Navigation Title dan Text yang ada di SwiftUI View akan berubah menjadi "Custom Title" :
Terakhir, bagaimana caranya jika kita ingin mengirim balik data dari SwiftUI kepada UIKit? salah satu cara yang bisa kita gunakan adalah dengan menggunakan Closure Callback.
Sebagai contoh, kita akan membuat sebuah aplikasi sederhana yang terdiri dari 2 Screen :
Screen 1 : Dibuat menggunakan UIKit, bertugas untuk menampilkan label "Name"
Screen 2 : Dibuat menggunakan SwiftUI, bertugas untuk mengedit label "Name"
Silakan ubah isi dari file "SwiftUIView.swift" menjadi seperti ini :
SwiftUIView.swift1import SwiftUI23struct SwiftUIView: View {4 var title: String6 var onSave: (String) -> Void78 @Environment(\.presentationMode) var presentationMode9 @State private var name: String = ""1011 var body: some View {12 ZStack {13 Color.yellow1415 VStack {16 TextField("Enter your name here...", text: $name)17 .frame(height: 44)18 .padding(.horizontal, 20)19 .background(.white)20 .cornerRadius(12)2122 Button("Save") {23 presentationMode.wrappedValue.dismiss()25 onSave(name)26 }27 .frame(height: 44)28 .padding(.horizontal, 20)29 .background(.blue)30 .foregroundColor(.white)31 .clipShape(Capsule())3233 Spacer()34 }35 .padding(20)36 }37 .navigationBarTitle(title)38 }39}
Dan ubah juga isi dari file "ViewController.swift" menjadi seperti ini :
ViewController.swift1import UIKit2import SwiftUI34class ViewController: UIViewController {5 var button: UIButton = {6 let button = UIButton()7 button.backgroundColor = .systemYellow8 button.layer.cornerRadius = 249 button.setTitle("Show SwiftUI View", for: .normal)10 button.setTitleColor(.black, for: .normal)11 button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)12 return button13 }()1415 var nameLabel: UILabel = {16 let label = UILabel()17 label.text = "No Name"18 label.font = .systemFont(ofSize: 20)19 label.textAlignment = .center20 label.backgroundColor = .black21 label.textColor = .white22 return label23 }()2425 override func viewDidLoad() {26 super.viewDidLoad()2728 title = "UIKit View"2930 view.addSubview(button)31 view.addSubview(nameLabel)32 }3334 override func viewDidLayoutSubviews() {35 super.viewDidLayoutSubviews()3637 button.frame = CGRect(x: 0, y: 0, width: 200, height: 44)38 button.center = view.center39 nameLabel.frame = CGRect(40 x: 20,41 y: button.frame.origin.y + 100,42 width: view.frame.size.width - 40,43 height: button.frame.size.height44 )45 }4647 @objc func didTapButton() {48 let vc = UIHostingController(49 rootView: SwiftUIView(50 title: "SwiftUI View",52 onSave: { [weak self] name in53 self?.nameLabel.text = "Name: \(name)"54 }56 )57 )58 navigationController?.pushViewController(vc, animated: true)59 }60}
Penjelasan kode :
1Button("Save") {2 presentationMode.wrappedValue.dismiss()3 onSave(name)4}
Ketika button "Save" ditekan, maka akan kembali ke screen utama dan akan memanggil callback onSave sambil membawa data name
1let vc = UIHostingController(2 rootView: SwiftUIView(3 title: "SwiftUI View",4 onSave: { [weak self] name in5 self?.nameLabel.text = "Name: \(name)"6 }7 )8)
Ketika callback onSave dipanggil, kita update text dari nameLabel sesuai dengan parameter name
Kita juga menggunakan Capture List weak pada closure onSave agar tidak terjadi Strong Reference Cycle.
Kamu bisa membaca artikel saya tentang Automatic Reference Counting (ARC), Strong Reference Cycle, dan Capture List pada bahasa pemrograman Swift di sini : https://blog.alfin.dev/article/memory-management-pada-swift
Jika code di atas kita jalankan, 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 UIViewRepresentable dan UIViewControllerRepresentable.
Otomatisasi build, testing, screenshot, dan deployment aplikasi iOS ke Testflight & AppStore.
Tutorial membongkar dan memodifikasi aplikasi iOS 🍎