13. [Demo Easy] SPM bilan ToDo App (Local Package & Resurslar)
Arxitektura
SPM bilan ToDo App — [Demo: Easy]
Qiyinlik darajasi: Oson (Easy) Texnologiyalar: Swift 6.0, Xcode 16/27, Local Swift Package, Observation Framework (
@Observable),Bundle.moduleresurslari, Izolyatsiyalangan Unit Testlar.
1. Loyihaning Maqsadi va Arxitekturasi
Ushbu amaliy loyihada biz Xcode ilovasi ichida to'liq mustaqil Local Swift Package yaratamiz. Maqsad — ilovaning biznes logikasi (Model, ViewModel, Storage) va maxsus resurslarini (ikonkalar, ranglar) asosiy ilovadan ajratib, alohida SPM moduliga joylash.
TodoListSPM/ (Asosiy Loyiha)
├── TodoListSPM.xcodeproj
├── TodoListSPM/ (App Target)
│ ├── TodoListSPMApp.swift
│ └── ContentView.swift (Faqat UI render)
└── Packages/
└── TodoLogicPackage/ (Lokal SPM Paketi)
├── Package.swift
├── Sources/
│ └── TodoLogicPackage/
│ ├── Models/
│ │ └── TodoItem.swift
│ ├── Storage/
│ │ └── TodoStorage.swift
│ ├── Resources/
│ │ └── Assets.xcassets (Status ikonkalari va ranglar)
│ └── ViewModels/
│ └── TodoViewModel.swift
└── Tests/
└── TodoLogicPackageTests/
└── TodoViewModelTests.swift
Nega hatto kichik ToDo ilovada ham SPM kerak?
- Tezkor Unit Testlar:
TodoViewModelTestsni ishga tushirish uchun og'ir iOS Simulyatorini (Simulator boot) kutish shart emas! SPM unit testlari to'g'ridan-to'g'ri macOS platformasida soniyaning o'ndan birida (0.1 soniya) ishlab tugaydi. - Inkapsulyatsiya: Ma'lumotlarni saqlash mexanizmi (
TodoStorage) faqat paket ichida ko'rinadi, tashqi UI uni to'g'ridan-to'g'ri buzib qo'ya olmaydi. - Qayta ishlatuvchanlik: Xuddi shu
TodoLogicPackagepaketini ertaga macOS yoki watchOS ilovasida 100% o'zgarishsiz ishlatish mumkin.
2. 1-Qadam: Xcode'da Loyiha va Local Package Yaratish
2.1. Asosiy App yaratish
- Xcode ni oching -> File -> New -> Project -> iOS App.
- Product Name:
TodoListSPM. - Interface: SwiftUI, Language: Swift.
2.2. Local Swift Package yaratish
- Xcode menyusidan: File -> New -> Package...
- Nomiga:
TodoLogicPackagedeb yozing. - Saqlash joyi (Location):
TodoListSPMpapkasi ichini tanlang. - Muhim: Pastdagi "Add to:" va "Group:" parametrlarida
TodoListSPMtanlangan bo'lishi kerak. - Create tugmasini bosing. Xcode Project Navigator'da paket ramzi bilan
TodoLogicPackagepaydo bo'ladi.
3. 2-Qadam: Package.swift Manifestini Sozlash
Paket ildizidagi Package.swift faylini ochib, zamonaviy Swift 6 standartlariga moslaymiz:
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "TodoLogicPackage",
defaultLocalization: "en",
platforms: [
.iOS(.v17),
.macOS(.v14)
],
products: [
// Asosiy ilovaga eksport qilinadigan kutubxona
.library(
name: "TodoLogicPackage",
targets: ["TodoLogicPackage"]
)
],
targets: [
.target(
name: "TodoLogicPackage",
dependencies: [],
resources: [
// Ikonka va ranglarni o'z ichiga olgan Asset Catalog
.process("Resources")
],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
.swiftLanguageMode(.v6)
]
),
.testTarget(
name: "TodoLogicPackageTests",
dependencies: ["TodoLogicPackage"]
)
]
)
4. 3-Qadam: Biznes Logika va Modellarni Yozish
Paket ichida Sources/TodoLogicPackage/ ostida quyidagi fayllarni yaratamiz:
4.1. Model (Models/TodoItem.swift)
import Foundation
public struct TodoItem: Identifiable, Codable, Sendable, Equatable {
public let id: UUID
public var title: String
public var isCompleted: Bool
public let createdAt: Date
public init(
id: UUID = UUID(),
title: String,
isCompleted: Bool = false,
createdAt: Date = Date()
) {
self.id = id
self.title = title
self.isCompleted = isCompleted
self.createdAt = createdAt
}
}
4.2. Local Storage (Storage/TodoStorage.swift)
Biz saqlash logikasini paket tashqarisidan yashiramiz (package yoki internal):
import Foundation
// Paket ichidagi targetlar uchun ochiq, ammo App ko'rmaydi
package actor TodoStorage {
private let userDefaultsKey = "saved_todo_items_cache"
package init() {}
package func save(items: [TodoItem]) throws {
let data = try JSONEncoder().encode(items)
UserDefaults.standard.set(data, forKey: userDefaultsKey)
}
package func load() -> [TodoItem] {
guard let data = UserDefaults.standard.data(forKey: userDefaultsKey),
let items = try? JSONDecoder().decode([TodoItem].self, from: data) else {
return []
}
return items
}
}
4.3. ViewModel (ViewModels/TodoViewModel.swift)
Swift 6 va iOS 17+ ning zamonaviy @Observable makrosi bilan yozamiz:
import SwiftUI
import Observation
@Observable
@MainActor
public final class TodoViewModel {
public private(set) var items: [TodoItem] = []
public var newItemTitle: String = ""
public var errorMessage: String? = nil
private let storage = TodoStorage()
public init() {
Task {
await loadInitialItems()
}
}
public func addItem() {
let trimmed = newItemTitle.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
let item = TodoItem(title: trimmed)
items.insert(item, at: 0)
newItemTitle = ""
persistItems()
}
public func toggleItem(_ item: TodoItem) {
if let index = items.firstIndex(where: { $0.id == item.id }) {
items[index].isCompleted.toggle()
persistItems()
}
}
public func deleteItem(at offsets: IndexSet) {
items.remove(atOffsets: offsets)
persistItems()
}
private func loadInitialItems() async {
self.items = await storage.load()
}
private func persistItems() {
let currentItems = self.items
Task {
do {
try await storage.save(items: currentItems)
} catch {
await MainActor.run {
self.errorMessage = "Saqlashda xatolik yuz berdi"
}
}
}
}
}
5. 4-Qadam: Bundle.module Orqali Resurslarni Boshqarish
Paket ichida Sources/TodoLogicPackage/Resources/Assets.xcassets yarating va unga quyidagi rangni qo'shing:
- Name:
TodoAccentColor(Masalan, yoqimli zangori rang).
Endi paket ichida yordamchi UI extension yozamiz:
// Sources/TodoLogicPackage/UI/Theme.swift
import SwiftUI
public extension Color {
static var todoAccent: Color {
// DIQQAT: Bundle.module orqali olinadi!
Color("TodoAccentColor", bundle: .module)
}
}
6. 5-Qadam: Asosiy App Targetiga Bog'lash
- Xcode da eng yuqoridagi TodoListSPM ko'k target ikonkasiga bosing.
- General bo'limiga o'ting -> Frameworks, Libraries, and Embedded Content.
+tugmasini bosib, ro'yxatdanTodoLogicPackageni tanlang.ContentView.swiftni oching:
import SwiftUI
import TodoLogicPackage // SPM modulimizni import qilamiz!
struct ContentView: View {
@State private var viewModel = TodoViewModel()
var body: some View {
NavigationStack {
VStack(spacing: 16) {
// Yangi vazifa qo'shish qatori
HStack {
TextField("Yangi vazifa yozing...", text: $viewModel.newItemTitle)
.textFieldStyle(.roundedBorder)
Button {
viewModel.addItem()
} label: {
Image(systemName: "plus.circle.fill")
.font(.title2)
.foregroundColor(.todoAccent)
}
}
.padding(.horizontal)
// Vazifalar ro'yxati
List {
ForEach(viewModel.items) { item in
HStack {
Image(systemName: item.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(item.isCompleted ? .green : .gray)
.onTapGesture {
viewModel.toggleItem(item)
}
Text(item.title)
.strikethrough(item.isCompleted, color: .secondary)
.foregroundColor(item.isCompleted ? .secondary : .primary)
}
}
.onDelete(perform: viewModel.deleteItem)
}
.listStyle(.insetGrouped)
}
.navigationTitle("SPM ToDo")
}
}
}
7. 6-Qadam: Izolyatsiyalangan Unit Testlar (0.1 soniyalik tezlik)
Paket ichidagi Tests/TodoLogicPackageTests/TodoViewModelTests.swift faylida test yozamiz:
import XCTest
@testable import TodoLogicPackage
@MainActor
final class TodoViewModelTests: XCTestCase {
func testAddItem_SuccessfullyAppends() {
// Given
let vm = TodoViewModel()
vm.newItemTitle = "SPM Masterclass o'rganish"
// When
vm.addItem()
// Then
XCTAssertEqual(vm.items.count, 1)
XCTAssertEqual(vm.items.first?.title, "SPM Masterclass o'rganish")
XCTAssertFalse(vm.items.first?.isCompleted ?? true)
}
func testToggleItem_InvertsStatus() {
// Given
let vm = TodoViewModel()
vm.newItemTitle = "Non sotib olish"
vm.addItem()
guard let item = vm.items.first else {
XCTFail("Element topilmadi")
return
}
// When
vm.toggleItem(item)
// Then
XCTAssertTrue(vm.items.first?.isCompleted ?? false)
}
}
Testni ishga tushirish:
Terminalda paket papkasiga kiring va buyruq bering:
cd Packages/TodoLogicPackage
swift test
Natija:
Test Suite 'All tests' passed at 2026-10-15 23:45:00.012.
Executed 2 tests, with 0 failures in 0.042 seconds.
Xulosa: Simulyator ochmasdan, butun iOS ilovasini build qilmasdan, 0.04 sekundda barcha biznes logika tekshirildi! Bu SPM ning kichik ilovalarda ham beradigan eng katta yutug'idir.