Skip to content

Project 16 — Hot Prospects

Hot Prospects помогает обмениваться контактами на встречах: показывает QR-код, сканирует чужой, разделяет людей на contacted/uncontacted и ставит напоминания.

Перед началом

Готовность к проекту

0%

0 из 6 рекомендуемых тем изучено. Проект всегда доступен — это не ограничение.

Рабочий vertical slice

swift
import SwiftUI
import Observation

struct Prospect: Identifiable {
    let id = UUID()
    var name: String
    var isContacted = false
}

@Observable
final class Prospects {
    var people = [
        Prospect(name: "Анна"),
        Prospect(name: "Борис", isContacted: true)
    ]

    func toggleContacted(for person: Prospect) {
        guard let index = people.firstIndex(where: { $0.id == person.id }) else { return }
        people[index].isContacted.toggle()
    }
}

struct ProspectList: View {
    let prospects: Prospects
    let contacted: Bool

    var body: some View {
        List {
            ForEach(prospects.people.filter { $0.isContacted == contacted }) { person in
                Button {
                    withAnimation {
                        prospects.toggleContacted(for: person)
                    }
                } label: {
                    HStack {
                        Text(person.name)
                        Spacer()
                        Image(systemName: person.isContacted ? "checkmark.circle.fill" : "circle")
                    }
                }
                .buttonStyle(.plain)
                .accessibilityLabel("\(person.name), \(person.isContacted ? "связались" : "ещё не связались")")
                .accessibilityHint("Изменить статус контакта")
            }
        }
    }
}

struct ContentView: View {
    @State private var prospects = Prospects()

    var body: some View {
        TabView {
            ProspectList(prospects: prospects, contacted: false)
                .tabItem { Label("Новые", systemImage: "person.badge.plus") }
            ProspectList(prospects: prospects, contacted: true)
                .tabItem { Label("Связались", systemImage: "checkmark.circle") }
        }
    }
}

Запусти slice, нажми на Анну во вкладке «Новые» и открой «Связались»: Анна исчезнет из первого фильтра и появится во втором. Это видимый эффект одной shared Prospects, которую читают оба tab. После этого добавляй QR и notifications по одному.

Shared data между tabs

В минимальном slice один observable container явно передаётся обоим экранам. Когда экранов станет больше, тот же объект удобно положить в environment для всего TabView. Каждый tab показывает фильтр одного Array, поэтому изменения сразу видны везде.

swift
TabView {
    ProspectsView(filter: .none).tabItem { Label("Everyone", systemImage: "person.3") }
    ProspectsView(filter: .contacted).tabItem { Label("Contacted", systemImage: "checkmark.circle") }
}
.environment(prospects)

Filter и sort

Не заводи три независимых Arrays. Computed property выбирает записи по enum filter, затем сортирует по выбранному правилу.

Context menu и swipe actions

Действия должны работать не только через скрытый long press. Для важных операций добавь видимую кнопку или swipe action и accessibility label.

QR codes

Core Image generator принимает Data и создаёт CIImage; масштабируй его без интерполяции, иначе края станут размытыми и код хуже сканируется.

Сканер требует camera permission и строки NSCameraUsageDescription. До показа камеры проверь authorization status, отдельно обработай denied/restricted и предложи ручной ввод как доступный fallback. QR payload версионируй и валидируй, например swiftcourse:v1:<uuid>: произвольную строку с камеры нельзя сразу считать доверенной моделью контакта. В Simulator показывай явное состояние «Камера недоступна».

Local notifications

Сначала запроси разрешение в контексте понятного действия. Затем создай content, trigger и request с уникальным identifier.

Перед запросом прочитайте notificationSettings(): для .denied покажите путь в Settings, для .notDetermined объясните пользу и запросите разрешение, а для разрешённого состояния планируйте уведомление. Не просите camera и notifications при первом запуске без контекста.

swift
let content = UNMutableNotificationContent()
content.title = "Связаться с \(prospect.name)"
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false)
Самостоятельная работа

Испытание

Задание

Добавь ручное редактирование контакта, отмену reminder и сохранение prospects в SwiftData.

Что уже нужно знать

Arrays, observable state, Codable/SwiftData, context menus и notifications.
Показать подсказку 1
Используй стабильный UUID prospect как identifier notification request.
Показать подсказку 2
При отмене передай тот же identifier в `removePendingNotificationRequests`.
Показать решение
swift
UNUserNotificationCenter.current()
    .removePendingNotificationRequests(withIdentifiers: [prospect.id.uuidString])

Что ты сейчас использовал

SwiftUI Новое в проекте

  • + Observable state
  • + SwiftData
  • + TabView
  • + Notifications
  • + QR codes

Дальше применим gestures к карточкам: Flashzilla →.

Переход между соседними страницами: и вне полей ввода.

ОБСУЖДЕНИЕ

Комментарии

Вопросы, находки и подсказки по этой странице.

Загружаем комментарии…