UIHostingController ile Projede Hibrit UIKit ve SwiftUI Kullanımı
Herkese merhaba bu yazımızda UIKit içinde SwiftUI kullanarak nasıl daha verimli biçimde çalıştığımızdan bahsedeceğiz.
Son yıllarda SwiftUI’ın hızlı gelişimi ve komünitenin kabullenme hızı bizi uzun süreli UIKit alışkanlıklarımızı yeniden inceleme gerekliliğini hissettirdi. Hali hazırda UIKit ile yazılmış olan projeleri SwiftUI ile yeniden yazmanın maliyeti fazla olduğu için yapılan bazı geliştirmeleri gerek refactor ederek gerekse sıfırdan SwiftUI ile yazarak projelerin bakımını sağlıyoruz. Bu çalışmalar doğrultusunda UIKit içerisinde SwiftUI’ı daha verimli kullanabileceğimiz üstüne biraz tartıştık. Mevcut süreçte izlediğimiz yol haritasını ve hangi adımlardan geçerek ilerlediğimizi sizlerle paylaşmak istedik.
SwiftUI’ın ya da UIKit’in birbirine karşı güçlü yanları olsada bu kapışmanın mutlak bir galibi yok.
Storyboard ile app geliştirmenin bazı zorlukları var bunlardan en önemlisi de SwiftUI’a göre yeterince esnek bir yapısının olmaması.
Karşıt şekilde SwiftUI Navigation’unun hala yeterli seviyeye ulaşmadığını düşünüyoruz.
İki sisteminde güçlü yanlarını bir araya getirip nasıl daha verimli kullanabileceğimizi sorguladık. Bunun sonucunda UI kısmını tamamen SwiftUI’da, logic ve navigation işlemlerini ise UIKit’te yaptığımız bir yapı oluşturduk. Yapı ve isimlendirmeyi de MVC’ye oldukça benzettik.
SwiftUI’ı UIKit içinde nasıl kullandığımız kısma geçelim. İlk önce bir UIViewController oluşturuyoruz ve bu UIViewController’ı navigation işlemleri için storyboarda eklememiz gerekiyor. Burada UIHostingController ile UIKit sayfasında viewDidLoad() içinde sayfaya SwiftUI’da ki View’ı entegre ediyoruz.
SwiftUI’ı kullanmaya başladığımız ilk dönemde her UIViewController’a aşağıdaki kod satırlarını ekliyorduk çünkü yapının nasıl çalışacağını çözemeden generic bir hale getirmenin bize bir avantajı yoktu.
addChild(hostingView)
view.addSubview(hostingView.view)
hostingView.view.translatesAutoresizingMaskIntoConstraints = false
hostingView.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
hostingView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
hostingView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
hostingView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
Sayfalar arasında data göndermek içinde alttaki kodu yazdık
hostingView.rootView.object = object
Bu yapıda karşılaştığımız ilk sorun hostingView’a değişken göndermek oldu çünkü içinde listedeiki her bir objeye ait bir fonksiyonu tetiklemek istediğimiz listeler vardı. Bu durumu liste ile beraber her birinin kendi fonksiyonunuda göndermesi ile çözdük.
hostingView.rootView.menu = [
.init(title: "Menü", action: {
push(self, identifier: "MenuViewController", storyBoard: .Profile)
}),
.init(title: "Ayarlar", action: {
push(self, identifier: "SettingsViewController", storyBoard: .Profile)
})
]
Projeye yeni sayfalar eklemeye başladıkça ve kullandığımız yapıyada alışınca projede bunu kullanmaya karar verdik ve generic bir hale getirmek için çalışmaya başladık. Bunun için 3 farklı yöntem geliştirdik.
1. UIViewController’a extension yazarak fonksiyonumuzu eklemek
extension UIViewController {
func loadHostView(_ hostingView: UIHostingController<some View>) {
addChild(hostingView)
view.addSubview(hostingView.view)
hostingView.view.translatesAutoresizingMaskIntoConstraints = false
hostingView.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
hostingView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
hostingView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
hostingView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
}
İlk düşündüğümüz yapı yazdığımız kodları extension fonksiyonu haline getirmek oldu ama ilerde yapımıza yeni fonksiyonlar eklemek istediğimizde UIViewController’ın içini doldurmak istemedik. Çünkü böyle bir durumda SwiftUI için yazdığımız fonksiyonları diğerlerininden ayırmakta ve kodun bakımında zorlanacağımızı düşündük.
2. UIViewController’a bir Generic UIViewController yazmak
class GenericSwiftUIHostable<T: View>: UIViewController {
var hostingView: UIHostingController<T>?
override func viewDidLoad() {
super.viewDidLoad()
loadHostView()
}
func loadHostView() {
guard let hostingView = hostingView else {
return
}
addChild(hostingView)
view.addSubview(hostingView.view)
hostingView.view.translatesAutoresizingMaskIntoConstraints = false
hostingView.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
hostingView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
hostingView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
hostingView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
}
İkinci düşündüğümüz yapıda generic bir UIViewController ile SwiftUI’ı kontrol etmek vardı bu sayede SwiftUI için yazdığımız fonksiyonları kolayca tek bir yerde toplamış olacaktık. Ama daha önce yazdığımız projelerde Generic UIViewController eklemenin ilerde bazı karmaşıklıklara yol açtığından başka bir yöntem bulmaya karar verdik.
3. UIViewController’a bir Protocol yazmak
protocol SwiftUIHostable: UIViewController {
associatedtype swiftUIView: View
var hostingView: UIHostingController<swiftUIView> { get set }
}
extension SwiftUIHostable {
func loadHostView() {
addChild(hostingView)
view.addSubview(hostingView.view)
hostingView.view.translatesAutoresizingMaskIntoConstraints = false
hostingView.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
hostingView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
hostingView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
hostingView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
}
Son düşündüğüm yöntem aslında Generic UIViewController kullanmaktan çok farklı bir yöntem değil ikiside aynı işlevi görüyor. Ama protocolde ilerde projeye bir Generic UIViewController eklemek durumunda kalırsak bunun önüne bir engel çıkarmayacağından bunu kullanmayı tercih ettik.
Sonuç
Şimdi gelelim bu yapının bizi nasıl daha verimli hale getirdiğine:
- Öncelikle UIKit içinde SwiftUI kullanmanın navigation konusunda çok büyük bir artısı var çünkü SwiftUI’ın navigation sistemi hala yeterli değil.
- Bir başka önemli bir konu ekip olarak yazdığımız kodları generic hale getirerek ortak bir Helper’da birleştirmeyi ve tekrar tekrar yazmak yerine bu ortak Helper’ı her projeye ekleyerek oradaki fonksiyonları kullanmayı ve ihtiyaç durumunda Helper’a yeni fonksiyonlar eklemeyi tercih ediyoruz. Şu anki yapımızda daha önce UIKit için yazdığımız onlarca fonksiyonu kullanmaya devam edebiliyoruz.
- SwiftUI’da bir sayfa oluşturmak UIKit’e göre oldukça hızlı ve daha esnek olduğu için daha verimli çalışmaya başladık.
- Sektöre yeni giren iOS geliştiricilerin çoğunun SwiftUI ile başladığını düşünürsek(ben SwiftUI ile başlamıştım) yeni geliştiricilerinde hem UIKit hem de SwiftUI ile çalışarak iki taraftada kendini geliştirebileceği bir yöntem olduğunu düşünüyoruz.
SwiftUI ve UIKit biribirleri ile oldukça uyumlu çalışıyor bizde bu yüzden birini seçmek yerine ikisini beraber kullanmayı denemeye karar verdik ve şu ana kadar ikisininde birbirlerinin eksik noktalarını kapattıkları için ikisini bir arada kullanarak SwiftUI yeterli seviyeye gelene kadar kendi yapılarımızı da yavaştan SwiftUI’a taşımaya başlamış olduk.
UIKit içinde SwiftUI kullandığımız makale şimdilik bu kadardı bu yapı hakkındaki düşüncelerinizi önemsiyoruz çünkü bizde her gün nasıl daha verimli kullanabileceğimizi öğreniyoruz ve farklı fikirlere tamamen açığız. Yapıyı geliştirdikçe yeni yazılarla yeni güncellemelerimizden bahsedeceğiz.