SnapKit 18: UIKit + SwiftUI Interop — SnapKit bilan qurilgan UI ichida SwiftUI va aksincha
🧩 SnapKit — Zero to Hero
SnapKit 17: UIKit + SwiftUI Interop — SnapKit bilan qurilgan UI ichida SwiftUI va aksincha
Holat: 2026-yil oktabr. SwiftUI va UIKit aralashmasi — bu faqat vaqtinchalik o'tish davri emas, balki zamonaviy iOS dasturlashning ajralmas qismidir. Aksariyat yirik kompaniyalar millionlab qator UIKit (va SnapKit) legacy kodlariga ega, ammo yangi feature'larni SwiftUI yordamida yozishni xohlashadi. Shu bilan birga, SwiftUI yetishmovchiliklari sababli ba'zi qismlar hali ham UIKit bilan samaraliroq yoziladi. Professional iOS dasturchi har ikkala freymvorkni puxta bilishi va ularni birgalikda, uzluksiz ishlatishni uddalashi shart.
Mundarija
- Nima uchun aralash ishlatamiz?
- SwiftUI View'ni UIKit ichiga qo'yish — UIHostingController
- Amaliy misol 1: UIKit ekran ichida SwiftUI chart
- UIHostingConfiguration (iOS 16+) — Cell ichida SwiftUI
- Amaliy misol 2: SnapKit Settings ekran + SwiftUI toggle row
- UIKit View'ni SwiftUI ichiga qo'yish — UIViewRepresentable
- UIViewControllerRepresentable
- Amaliy misol 3: SwiftUI app ichida SnapKit-based custom view
- Data Flow — UIKit ↔ SwiftUI sinxronlash
- Amaliy misol 4: To'liq Hybrid App arxitekturasi
- sizeThatFits va Layout
- Performance va Best Practices
- Anti-patterns (Qilmang!)
- iOS 26 yangiliklari
- Xulosa
- Manbalar
1. Nima uchun aralash ishlatamiz? {#why-mix}
SwiftUI juda kuchli va tez, biroq hammasini ham o'z ichiga olmagan.
| UIKit (SnapKit) Afzalliklari | SwiftUI Afzalliklari |
|---|---|
| Murakkab animatsiyalar va custom gesture'lar qulay. | Declarative va reaktiv UI. |
| CollectionView'lar bilan maxsus layout'lar qilish qulay. | Prototip yaratish juda tez (Live Previews). |
| Katta va eski loyihalarda tayyor infratuzilma mavjud. | Yangi dizayn tizimlarini qisqa kod bilan yozish oson. |
| UI elementlar ustidan 100% past darajadagi nazorat. | Animatsiyalar oddiyroq va state'ga bog'langan. |
Hatto Apple ham o'zining ko'pgina ilovalarida (Settings, Maps, Music) UIKit hamda SwiftUI ni bitta ekran ichida aralash ishlatadi.
2. SwiftUI View'ni UIKit ichiga qo'yish — UIHostingController {#hosting-controller}
SwiftUI View ni UIKit olamiga olib kiruvchi asosiy "ko'prik" bu — UIHostingController. Bu xuddi oddiy UIViewController kabi ishlaydi, faqat uning view xususiyati sizning SwiftUI view'ingizni o'zida saqlaydi.
UIKit ierarxiyasiga UIHostingController'ni to'g'ri qo'shish tartibi quyidagicha (SnapKit bilan):
import UIKit
import SnapKit
import SwiftUI
class DashboardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
// 1. SwiftUI View'ni tayyorlash
let swiftUIView = Text("Salom, SwiftUI!")
.font(.largeTitle)
.padding()
.background(Color.blue.cornerRadius(10))
.foregroundColor(.white)
// 2. UIHostingController yaratish
let hostingController = UIHostingController(rootView: swiftUIView)
// 3. Child View Controller sifatida qo'shish (Juda muhim!)
addChild(hostingController)
view.addSubview(hostingController.view)
// 4. SnapKit yordamida joylashuvni belgilash
hostingController.view.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(250)
make.height.equalTo(100)
}
// 5. Ota-onaga qo'shilganligini tasdiqlash
hostingController.didMove(toParent: self)
}
}
Muhim:
addChild→addSubview→SnapKit constraints→didMove(toParent:)tartibi hech qachon buzilmasligi kerak. Aks holda touch event'lar yoki lifecycle (viewWillAppear va boshqalar) to'g'ri ishlamaydi.
3. Amaliy misol 1: UIKit ekran ichida SwiftUI chart {#example-chart}
Zamonaviy ilovalarda diagrammalar (charts) kerak bo'lganda iOS 16 da chiqqan Charts framework juda qulay. Agar sizning asosingiz UIKit bo'lsa, uni shu tarzda joylaysiz:
import UIKit
import SnapKit
import SwiftUI
import Charts
// 1. SwiftUI Chart Component
struct WeeklySalesChart: View {
var body: some View {
Chart {
BarMark(x: .value("Day", "Mon"), y: .value("Sales", 120))
BarMark(x: .value("Day", "Tue"), y: .value("Sales", 200))
BarMark(x: .value("Day", "Wed"), y: .value("Sales", 150))
BarMark(x: .value("Day", "Thu"), y: .value("Sales", 250))
BarMark(x: .value("Day", "Fri"), y: .value("Sales", 300))
}
.foregroundStyle(.blue.gradient)
}
}
// 2. UIKit ViewController bilan SnapKit
class AnalyticsViewController: UIViewController {
private let headerLabel: UILabel = {
let lbl = UILabel()
lbl.text = "Haftalik Savdolar"
lbl.font = .boldSystemFont(ofSize: 24)
return lbl
}()
private let chartContainer = UIView()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupChart()
}
private func setupUI() {
view.backgroundColor = .systemGroupedBackground
view.addSubview(headerLabel)
view.addSubview(chartContainer)
headerLabel.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(20)
make.leading.equalToSuperview().offset(20)
}
chartContainer.snp.makeConstraints { make in
make.top.equalTo(headerLabel.snp.bottom).offset(20)
make.leading.trailing.equalToSuperview().inset(20)
make.height.equalTo(300)
}
}
private func setupChart() {
let chartVC = UIHostingController(rootView: WeeklySalesChart())
chartVC.view.backgroundColor = .clear // Oq fon bo'lmasligi uchun
addChild(chartVC)
chartContainer.addSubview(chartVC.view)
chartVC.view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
chartVC.didMove(toParent: self)
}
}
4. UIHostingConfiguration (iOS 16+) — Cell ichida SwiftUI {#hosting-config}
TableView yoki CollectionView cell'larini layout qilish SnapKit'da biroz zerikarli bo'lishi mumkin (ayniqsa murakkab state'lar bo'lsa). iOS 16 dan boshlab UIHostingConfiguration chiqdi. Bu API sizga cell'ning kontentini to'liq SwiftUI'da yozib, lekin jadvalni (UITableView) o'zini UIKit'da boshqarish imkonini beradi.
import UIKit
import SnapKit
import SwiftUI
// SwiftUI Cell View
struct UserCellView: View {
let name: String
let role: String
var body: some View {
HStack(spacing: 16) {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 50, height: 50)
.foregroundColor(.blue)
VStack(alignment: .leading, spacing: 4) {
Text(name)
.font(.headline)
Text(role)
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(.gray)
}
.padding(.vertical, 8)
}
}
Endi shuni UITableView ichida ishlatamiz:
class UsersListViewController: UIViewController, UITableViewDataSource {
private let tableView = UITableView(frame: .zero, style: .insetGrouped)
private let users = [("Ali", "Admin"), ("Vali", "Moderator"), ("Sami", "User")]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let user = users[indexPath.row]
// ✨ Mana haqiqiy sehr:
cell.contentConfiguration = UIHostingConfiguration {
UserCellView(name: user.0, role: user.1)
}
return cell
}
}
Senior eslatmasi:
UIHostingConfigurationcell qayta ishlatilganda (reuse) juda yaxshi optimallashtirilgan. Oldingiday har bir cell uchun alohidaUIHostingControlleryaratish shart emas va xotira sizib chiqishi (memory leak) xavfi ham kam.
5. Amaliy misol 2: SnapKit Settings ekran + SwiftUI toggle row {#example-settings}
Keling endi state (holat) o'zgarishini UIKit va SwiftUI o'rtasida bog'laymiz.
import UIKit
import SnapKit
import SwiftUI
// Shared ViewModel (Sinxronlash uchun)
class SettingsViewModel: ObservableObject {
@Published var isDarkModeEnabled: Bool = false
@Published var notificationsEnabled: Bool = true
}
// SwiftUI Toggle Row
struct SettingsToggleRow: View {
let title: String
let icon: String
@Binding var isOn: Bool
var body: some View {
HStack {
Image(systemName: icon)
.foregroundColor(.white)
.padding(6)
.background(Color.blue.cornerRadius(8))
Toggle(title, isOn: $isOn)
}
}
}
// UIKit Controller
class AppSettingsViewController: UIViewController, UITableViewDataSource {
private let tableView = UITableView(frame: .zero, style: .insetGrouped)
private var viewModel = SettingsViewModel() // State source of truth
override func viewDidLoad() {
super.viewDidLoad()
title = "Settings"
view.addSubview(tableView)
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if indexPath.row == 0 {
cell.contentConfiguration = UIHostingConfiguration {
// SwiftUI state UIKit ViewModel ga Binding orqali uzatilyapti
SettingsToggleRow(
title: "Dark Mode",
icon: "moon.fill",
isOn: Binding(
get: { self.viewModel.isDarkModeEnabled },
set: { self.viewModel.isDarkModeEnabled = $0 }
)
)
}
} else {
cell.contentConfiguration = UIHostingConfiguration {
SettingsToggleRow(
title: "Notifications",
icon: "bell.fill",
isOn: Binding(
get: { self.viewModel.notificationsEnabled },
set: { self.viewModel.notificationsEnabled = $0 }
)
)
}
}
return cell
}
}
6. UIKit View'ni SwiftUI ichiga qo'yish — UIViewRepresentable {#view-representable}
Endi tangani ikkinchi tarafiga qaraymiz. Sizda faqat SwiftUI loyiha bor, lekin siz eskidan yozib qo'ygan SnapKit orqali yozilgan kuchli CustomCardView ni ishlatmoqchisiz. Buning uchun UIViewRepresentable protokoli yordamga keladi.
import SwiftUI
import UIKit
import SnapKit
// 1. Bizning UIKit (SnapKit) bilan yozilgan custom view'imiz
class CustomCardView: UIView {
private let titleLabel = UILabel()
private let subtitleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
private func setupViews() {
backgroundColor = .systemTeal
layer.cornerRadius = 16
titleLabel.font = .boldSystemFont(ofSize: 20)
titleLabel.textColor = .white
subtitleLabel.font = .systemFont(ofSize: 14)
subtitleLabel.textColor = .white.withAlphaComponent(0.8)
addSubview(titleLabel)
addSubview(subtitleLabel)
titleLabel.snp.makeConstraints { make in
make.top.leading.equalToSuperview().offset(16)
make.trailing.equalToSuperview().offset(-16)
}
subtitleLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(8)
make.leading.trailing.equalTo(titleLabel)
make.bottom.lessThanOrEqualToSuperview().offset(-16)
}
}
func configure(title: String, subtitle: String) {
titleLabel.text = title
subtitleLabel.text = subtitle
}
}
// 2. SwiftUI Wrapper
struct SnapKitCardView: UIViewRepresentable {
var title: String
var subtitle: String
// a. UIView ni yaratish
func makeUIView(context: Context) -> CustomCardView {
let view = CustomCardView()
// MUHIM: SnapKit constraint'lar shu yerda EMAS, balki UIView o'zining initida qilingani ma'qul.
return view
}
// b. SwiftUI state o'zgarganda UIView ni yangilash
func updateUIView(_ uiView: CustomCardView, context: Context) {
uiView.configure(title: title, subtitle: subtitle)
}
}
7. UIViewControllerRepresentable {#vc-representable}
Agar siz bitta UIView'ni emas, butun bir UIViewController'ni SwiftUI ichiga olib kirmoqchi bo'lsangiz UIViewControllerRepresentable kerak bo'ladi. Bu ko'pincha tizimning tayyor kontrollerlari (UIImagePickerController, SFSafariViewController) yoki o'zingizning to'liq SnapKit arxitekturangizni chaqirish uchun ishlatiladi.
import SwiftUI
import UIKit
struct ImagePickerWrapper: UIViewControllerRepresentable {
@Binding var selectedImage: UIImage?
@Environment(\.presentationMode) var presentationMode
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator // Delegate handling
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
// Yangilanishlar shu yerda
}
// Delegate event'larini SwiftUI olamiga uzatish uchun Coordinator
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: ImagePickerWrapper
init(_ parent: ImagePickerWrapper) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.originalImage] as? UIImage {
parent.selectedImage = image
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}
8. Amaliy misol 3: SwiftUI app ichida SnapKit-based custom view {#example-custom-view}
Endi yuqorida yasagan SnapKitCardView imizni haqiqiy SwiftUI Content View ichida qo'llaymiz.
import SwiftUI
struct HomeContentView: View {
@State private var cardTitle = "SnapKit Card"
@State private var counter = 0
var body: some View {
VStack(spacing: 30) {
Text("SwiftUI App Asosi")
.font(.largeTitle)
.bold()
// UIKit (SnapKit) Custom View shu yerda yotibdi
SnapKitCardView(
title: cardTitle,
subtitle: "Ushbu karta to'liq SnapKit'da yozilgan. Bosilgan: \(counter) marta."
)
.frame(height: 120)
.padding(.horizontal)
Button("Karta ma'lumotini yangilash") {
counter += 1
cardTitle = "Yangilandi! 🔥"
}
.buttonStyle(.borderedProminent)
Spacer()
}
.padding(.top)
}
}
Kodni e'tibor bilan kuzatsangiz: SwiftUI Button bosilganda @State o'zgaradi -> updateUIView avtomatik ishlaydi -> SnapKit View dagi yozuv darhol o'zgaradi!
9. Data Flow — UIKit ↔ SwiftUI sinxronlash {#data-flow}
Ma'lumotlar oqimini ikki olam o'rtasida to'g'ri tashkil qilish juda muhim:
- UIKit → SwiftUI:
@ObservableObject(yoki yangi Swift 6/iOS 17+ larda@Observablemacro) class yarating. Uni o'zgartirsangiz SwiftUI avtomatik yangilanadi. YokiUIHostingControllerning root view xususiyatlarini yangilash mumkin. - SwiftUI → UIKit: Custom
delegateyoki block klosure'larni ishlatish eng qulay. Yoki@Bindingishlatib SwiftUI state'iniupdateUIVieworqali UIkit'ga push qilasiz.
10. Amaliy misol 4: To'liq Hybrid App arxitekturasi {#hybrid-architecture}
Quyida bitta dastur ichida UIKit TabBar bilan SwiftUI va SnapKit interfeyslari mukammal aralashib ishlagan keng qamrovli misol.
import UIKit
import SwiftUI
import SnapKit
// Shared Model
@Observable
class AppState {
var globalCounter = 0
}
// ==========================================
// Tab 1: UIKit + SnapKit + SwiftUI Embed
// ==========================================
class FirstTabViewController: UIViewController {
let appState: AppState
init(state: AppState) {
self.appState = state
super.init(nibName: nil, bundle: nil)
title = "Hybrid Home"
tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "house"), tag: 0)
}
required init?(coder: NSCoder) { fatalError() }
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
// SwiftUI Counter Widget ni UIKit ichiga o'rnatamiz
let widget = CounterSwiftUIView(state: appState)
let hostingController = UIHostingController(rootView: widget)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.snp.makeConstraints { make in
make.center.equalToSuperview()
}
hostingController.didMove(toParent: self)
}
}
// SwiftUI Widget
struct CounterSwiftUIView: View {
var state: AppState
var body: some View {
VStack {
Text("SwiftUI Widget")
.font(.headline)
Text("Global Counter: \(state.globalCounter)")
Button("Oshirish") {
state.globalCounter += 1
}
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}
// ==========================================
// Asosiy TabBarController (Entry Point)
// ==========================================
@MainActor
class MainTabBarController: UITabBarController {
let sharedState = AppState()
override func viewDidLoad() {
super.viewDidLoad()
let tab1 = FirstTabViewController(state: sharedState)
let tab1Nav = UINavigationController(rootViewController: tab1)
// Tab 2: To'liq SwiftUI View ni HostingController orqali TabBar'ga tiqish
let tab2View = SettingsSwiftUIView(state: sharedState)
let tab2VC = UIHostingController(rootView: tab2View)
tab2VC.title = "SwiftUI Settings"
tab2VC.tabBarItem = UITabBarItem(title: "Settings", image: UIImage(systemName: "gear"), tag: 1)
self.viewControllers = [tab1Nav, tab2VC]
}
}
struct SettingsSwiftUIView: View {
var state: AppState
var body: some View {
Form {
Section(header: Text("App State Info")) {
Text("Current Counter: \(state.globalCounter)")
Button("Nolga tushirish (Reset)") {
state.globalCounter = 0
}
}
}
}
}
Bu kod 2026-yilda yirik kompaniyalarda (masalan, Yandex yoki Telegram'ning yangi modullarida) ko'p uchraydigan naqsh (pattern) hisoblanadi.
11. sizeThatFits va Layout {#size-that-fits}
Eng ko'p qilinadigan xato — SwiftUI content o'lchami o'zgarganda UIKit UIHostingController.view o'lchamini bilmay qolishi. Agar siz SwiftUI View'ning o'ziga xos aniq o'lchamini (intrinsic size) olmoqchi bo'lsangiz:
let hostingView = hostingController.view!
// AutoresizingMask o'chirilgan bo'lishi shart
hostingView.translatesAutoresizingMaskIntoConstraints = false
// SwiftUI o'zini eng optimal o'lchamini hisoblaydi
let fittingSize = hostingController.sizeThatFits(in: CGSize(width: view.bounds.width, height: UIView.layoutFittingExpandedSize.height))
hostingView.snp.makeConstraints { make in
make.width.equalTo(fittingSize.width)
make.height.equalTo(fittingSize.height)
}
// Keyinchalik SwiftUI state o'zgarsa, UI ni qayta chizish uchun:
hostingView.invalidateIntrinsicContentSize()
12. Performance va Best Practices {#best-practices}
- #Preview (iOS 17+) UIKit va SnapKit uchun:
SnapKit bilan yozilgan UIKit kodingizni tekshirish uchun simulyatorni har safar yurgizish shart emas. Yangi Makro tizimi orqali uni to'g'ridan to'g'ri Xcode Previews da ko'rishingiz mumkin:
#Preview("SnapKit Card View") {
let view = CustomCardView()
view.configure(title: "Preview Test", subtitle: "Working perfectly")
return view
}
- UIHostingController og'irligi: U xotira uchun unchalik "qimmat" emas, lekin cell'lar ichida har safar
new UIHostingController()yaratish sekinlashtiradi. Buning o'rniga iOS 16 dan beri tavsiya qilinganUIHostingConfigurationni ishlating.
13. Anti-patterns (Qilmang!) {#anti-patterns}
| Yomon tajriba (Anti-pattern) | Nima sababdan? | Yechim |
|---|---|---|
makeUIView ichida .snp.makeConstraints yozish. | SwiftUI uni qayta-qayta chaqirishi mumkin, duplicate constraints vujudga keladi. | Constraint'larni faqat UIView'ning init() metodida 1 marta qiling. |
UIKit background thread'dan @State yoki @Published ni o'zgartirish. | SwiftUI state'lari faqatgina Main Thread'da xavfsiz o'zgarishi kerak. | Doim @MainActor yoki DispatchQueue.main.async ishlating. |
UIHostingController.view ga translatesAutoresizingMaskIntoConstraints = true qoldirish. | SnapKit constraint'lari UIKit autoresizing mask bilan urishib, ziddiyat (Conflict) beradi. | SnapKit snp.makeConstraints o'zi avtomatik buni false qiladi, qo'lda true ga o'zgartirmang! |
14. iOS 26 yangiliklari (2026-yil holatiga) {#ios26}
2026-yilga kelib SwiftUI va UIKit orasidagi chegara deyarli yo'qolgan. Yangi UIHostingConfiguration API'larida Liquid Glass va Depth Modifiers to'g'ridan-to'g'ri UIKit view'laridan o'qilishi qo'shilgan. Endi siz UIKit ichida animatsiyalarni boshqarib, uni SwiftUI withAnimation bloki ichida uzluksiz tutishingiz mumkin. Bu SnapKit arxitekturasi bilan ishlaydigan har bir iOS dasturchi uchun ulkan erkinlik beradi.
Xulosa {#xulosa}
Zamonaviy iOS dasturchi "Men faqat SwiftUI da yozaman" yoki "UIKit dan boshqasi bekor" deb aytishga haqli emas. 2026-yilda mukammal ilovalar yaratish — bu to'g'ri joyda to'g'ri asbobni tanlay bilish san'atidir. Murakkab formalar, oddiy holat (state) boshqaruvi uchun SwiftUI ni ishlatsangiz, murakkab layout, past darajali optimizatsiya va katta arxitekturalar uchun SnapKit yordamida UIKit dan foydalanish eng to'g'ri tanlovdir.