UIKit projelerinde SwiftUI View’leri nasıl kullanılır?

Eyup Cimen
4 min readMay 2, 2023

--

Kendi kucuk çalışma ortamım :)

SwiftUI tanıtıldığında birçok iOS Developer gibi bende “vay be” dedim. Şahsen Apple dan böyle bir atak beklemiyordum. Yine Declarative bir yapıya sahip olan React Native yazma ile ilgili çok da hoş olmayan hatıralarım vardı ancak bunun esas sebebi zannımca type script bir dili deneyimliyor olmamdı. Her neyse, Apple SwiftUI ile tanıtınca çok heyecanlandım. Çünkü bu gelişme UI dizayn etme konusunda bir değişimin başlangıcıydı.

SwiftUI Apple ekosisteminde UI dizayn etme konusunda bir değişimin başlangıcı diyebiliriz. UIKit ile kıyas kabul etmeycek kolaylıkta bir konfor sağlıyor. Bu cepte. Ve evet sıfır bir proje açıp orada poliyannacılık oynayabilirsiniz. Ne bileyim view’leri hoplatip ziplatip animasyondan animasyona koşabilirsiniz ama olay SwiftUI’ı mevcut projenizde kullanmaya gelince işte orada işin rengi değişiyor. Tam bu noktada acı gerçeklerle karşılaşıyorsunuz. Bu acı gerçeklerin başında da mecvut UIKit projeniz geliyor :)

2014 de SwiftUI 1.0‘ın ilk tanıtıldığı zamandan bugüne kadar SwiftUI birçok sürümle güncellendi ve sürekli eksiklikleri giderildi. Ancak bana sorarsanız halen bugün dahi şu andaki UIKit’in tüm kabiliyetlerine sahip değil (2023 Mayıs ayı itibariyle) Hatta sizin sıfırdan bir projeye hemde minimum development target’ınızın ios 16 ya destek verdiğini düşünürsek bile bu böyle. Dolayısıyla bu yanında şunu getiriyor evet SwiftUI gibi çok güzel bir API var ancak sektörde SwiftUI’ın yeri ne kadar? Hangi şirket SwiftUI ile sıfırdan uygulama geliştiriyor veya SwiftUI’a ne oranda destek veriyor veyahut SwiftUI’ı mevcut projelerine ne kadar entegre ediyorlar ve yeni feature setlerinin ne kadarını bununla yazıyorlar. Bu soruların tüm cevapları bende yok ancak izlenimlerin sonucu UIKit’in a hala baskın durumda olduğu yönünde. Bu izlenimi edinmemin sebebi büyük şirketlerin ve hali hazırdaki büyük projelerin code base’lerinin UIKit ile yazılmış olması. Şöyle bir düşünün siz bir projeyi başlatacaksınız ve henüz daha oturmamış bir ton eksiği olan bir API’ı kullanarak mı geliştirirsiniz yoksa tüm özellikleriyle tamamlanmış bir yapıyı mı? Eğer development da harcayacak ekstra zamanınız yoksa muhtemelen herkes UIKit diyecektir.

SwiftUI’ı sıfırdan başlayacak projelerde kullanmakta birçok dezavantaj var. En basitinden navigation problemi, textfield focus problemi vs. Yani siz minimum development target’ınız iOS 16 değilse daha aşağıdaki ios sürümlerine destek verecekseniz SwiftUI’ı kullanmanız size epey problem doğuracaktır. Attığınız taş ürküttüğünüz kurbağaya değmeyebilir.

Ha yazamaz mısınız yazarsınız ama karşınıza elinizdeki tasarımı ve business logic’i uygulamaya çalışırken tonlarca aşmanız gereken sorun çıkar. Biz bu fanteziye gireriz diyorsanız tabi karar sizin :)

Peki o zaman mevcut projelerde hani kıyısından köşesinden bu güzellikten nasıl istifade edebiliriz şeklinde düşünüyor olabilirsiniz. Haklısınız da bu yeni API’den istifade etmek lazım hemde çağın yeniliklerin gerisinde kalmamak gerek değil mi?

Evet SwiftUI’ı mevcut projemizde kullanmak istiyoruz fakat nasıl kullanabiliriz yani mevcut UIKit ile yapılmış ekranları da etkilemeden hemde SwiftUI in nimetlerinden faydalanabileceğimiz bir yöntem var mı?

Biz Ozan SuperApp’de SwiftUI’ı mümkün olabilen ve mevcut düzeni bozmayacak şekilde kullanmaya çalışıyoruz. Yeni eklenecek feature’ı kendi başına ayrı bir modül gibi ele alıyoruz ve ekip olarak toplanıp uygulamaya eklenecek yeni feature’ın ekranlarını bir analiz ediyoruz. Eğer SwiftUI ile yapmak bize engel çıkartmayacaksa SwiftUI ile yapmaya karar veriyoruz.

Not: Biz SwiftUI view’lerini özellikle view controller’lar içine ekleyip mevcut yapıyı bozmadan SwiftUI’ı kullanabiliyoruz. Çünkü içerde Deeplink mekanizması var mevcut routing mekanizması da view controller ve navigation controller’lara bağlı.

UIHostingController

Peki sonra ne mi oluyor? Ben herkesin kolayca örnek alabilmesi için boş bir view controller üzerinden basitçe anlatmaya çalışacağım. Yeni bir view controller’ınız var ve siz buna SwiftUI view’i eklemek istiyorsunuz. Örnek SwiftUI view’ınızı dilediğiniz gibi tasarlayabilirsiniz. Ben aşağıdaki gibi bir örnek view oluşturdum.

import SwiftUI

struct MySwiftUIView: View {
var body: some View {
ZStack {
Color.green.opacity(0.3)
.ignoresSafeArea()
Text("Hello, World!")
.font(.title)
}
}
}

struct MySwiftUIView_Previews: PreviewProvider {
static var previews: some View {
MySwiftUIView()
}
}

Ve View Controller’ınızda da bu SwiftUI view’ini optional olarak çağırın. Burada ben viewDidLoad içerisinde mySwiftUIView’imi create ettim ancak normalde farklı bir ekrandan çağırıyor olmalısınız ve bunu orada create edip oradan setlemeniz yani dışardan vermeniz daha doğru olur. Burada örnek olduğu için mecburen viewDidLoad da create etmiş oldum.

class ViewController: UIViewController {

private var mySwiftUIView: UIHostingController<MySwiftUIView>?

override func viewDidLoad() {
super.viewDidLoad()
mySwiftUIView = UIHostingController(rootView: MySwiftUIView())
addSUIView()
}

private func addSUIView() {
guard let mySwiftUIView = mySwiftUIView else {
return
}
addChild(mySwiftUIView)
mySwiftUIView.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mySwiftUIView.view)
mySwiftUIView.didMove(toParent: self)
NSLayoutConstraint.activate([
mySwiftUIView.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
mySwiftUIView.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
mySwiftUIView.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
mySwiftUIView.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
}
}

Eğer ki UIViewController’a bir extension yazarsanız o zaman tek bir satır ile tüm view controller’larınızda istediğiniz SwiftUI view’ini şöyle çağırabilirsiniz:

import UIKit
import SwiftUI

class ViewController: UIViewController {

private var mySwiftUIView: MySwiftUIView?

override func viewDidLoad() {
super.viewDidLoad()
mySwiftUIView = MySwiftUIView()
addSUIView()
}

private func addSUIView() {
guard let mySwiftUIView = mySwiftUIView else {
return
}
addSubSwiftUIView(mySwiftUIView, to: self.view)
}
}

extension UIViewController {
@discardableResult
func addSubSwiftUIView<Content>(_ swiftUIView: Content, to view: UIView) -> UIHostingController<Content> where Content: View {
let hostingController = UIHostingController(rootView: swiftUIView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.leftAnchor.constraint(equalTo: view.leftAnchor),
view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor),
view.rightAnchor.constraint(equalTo: hostingController.view.rightAnchor)
]
NSLayoutConstraint.activate(constraints)
hostingController.didMove(toParent: self)
return hostingController
}
}

Burada yine MySwiftUIView’ini optional tanımladık. Bu property’leri dışarıdan enjekte etmeyi tavsiye ederim. Örnek olduğu için en basit haliyle yazmaya çalıştım. Bu örneği şu linkten de indirebilirsiniz:

https://github.com/eyupcimen/use-swiftui-in-uikit-example

Tabi buradaki örnek ana proje içerisinde kullandığımızla birebir aynı değil orada View Model’ımızı da bu view controller’ı oluşturuduğumuz yerde oluşturup dışarıdan SwiftUI view’ine enjekte edecek şekilde veriyoruz. Data fetch ve user interaction işlemlerini SwiftUI view üzerinden önce View Model sonra da view controller’a iletiyoruz. Bu mimari yaklaşım ayrı bir yazının konusu. Kimbilir belki onu da yazarız bir gün :)

Not: Türkçe’mizin güzel bir şekilde kullanımına özen göstersem de bazı kelimeleri kendi terim anlamında değilde Türkçe kullandığınızda bir anlam karmaşası oluyor. O yüzden bu tip terim anlamlı kelimeleri kullandığım için kusura bakmayınız.

--

--