Новое:Talanto осенью: поиск с цифрами, вакансия по шагам и каталог компаний

+1 164новые вакансии за сутки·93 595вакансий открыто сейчас·Новое:Talanto осенью: поиск с цифрами, вакансия по шагам и каталог компаний

Новые вакансии в TelegramРаботодателям
Talanto
Вакансии
Компании7 266
РезюмеAIПодготовкаСтатьи
BoostТарифыBoost
Войти

Не нашли подходящее?

Пусть нужная вакансия найдёт вас сама.

  1. Подборка в TelegramНовые вакансии по вашему фильтру — сразу в бота.Подключить
  2. Мэтчи по резюмеЗагрузите резюме — покажем, где вы подходите лучше всего.Загрузить
  3. Сохранить фильтрНастройте поиск один раз и возвращайтесь к новым вакансиям.Настроить
Что нового на TalantoTalanto осенью: поиск с цифрами, вакансия по шагам и каталог компаний

Для работодателей

Опубликуйте вакансию — отклики придут в ваш кабинет.

  1. Вакансиябесплатно
  2. Откликиконтакты бесплатно
  3. Наймэтапы и интервью

40 компаний публикуют вакансии напрямую · 4 401 кандидат открыт к предложениям

Разместить вакансию

Популярные направления

Все подборки
  • Python17 121
  • Облачные технологии14 079
  • QA вакансии3 451
  • Backend вакансии2 115
  • Инженер данных1 805
  • Продакт-менеджер1 260

Разработка

  • Android1 862
  • iOS1 700
  • Fullstack вакансии986
  • AI вакансии950
  • Frontend вакансии744
  • Mobile вакансии44

По стеку

  • Go8 659
  • Java6 537
  • Node.js2 020
  • .NET1 721
  • Kotlin1 614
  • Rust1 455
  • C#710
  • PHP681
  • C++571

DevOps и инфраструктура

  • Kubernetes7 772
  • DevOps вакансии6 050
  • Linux-инфраструктура5 488
  • Terraform / IaC4 272
  • CI/CD3 239
  • Мониторинг и Observability1 789
  • SRE567
  • Кибербезопасность452

Аналитика и данные

  • Data Scientist1 140
  • Бизнес-аналитик923
  • Аналитик данных855
  • Системный аналитик152

Другие направления

  • Проджект-менеджер615
  • UI/UX дизайн146
  • Ручное тестирование14

По регионам

  • Вакансии в США44 276
  • Вакансии в Европе23 819
  • Вакансии в Канаде4 471
  • Работа в России3 904

По формату

  • Удалённые вакансии74 928
  • Релокация в США44 273
  • Гибридная работа12 388
  • Офисная работа7 083

По уровню

  • Senior вакансии42 627
  • Middle вакансии20 864
  • Junior вакансии6 721

Сейчас нанимают

Все компании
  • Wise190 вакансий
  • Rakuten189 вакансий
  • ASML166 вакансий
  • Grab137 вакансий
  • Celonis39 вакансий
  • Spotify37 вакансий
  • Adyen28 вакансий
  • Infobip28 вакансий
  • Monzo20 вакансий
  • Darktrace16 вакансий

Вакансии из 344 источников: карьерные страницы компаний и джоб-борды.

КомпанииО насНовостиПубличная офертаУсловия использованияПолитика конфиденциальностиПоддержка
←К теме SwiftUIВсе вопросы
SwiftUIMiddleТеория

Как обрабатывать формы и валидацию пользовательского ввода в SwiftUI?

В закладки
Сначала попробуйте ответить сами30 секунд
Сравнить с ответом ↓

Form + Section организуют поля; валидация — в @MainActor ObservableObject с @Published-ошибками. Inline Text(.caption).foregroundStyle(.red) показывает ошибку под полем. Кнопка Submit дизейблится через .disabled(!isFormValid). Всегда валидируйте повторно в submit().

Полный пример: форма регистрации с валидацией

import SwiftUI
import Combine

// MARK: - ViewModel

@MainActor
final class RegistrationViewModel: ObservableObject {
    // Поля формы
    @Published var email = ""
    @Published var password = ""
    @Published var confirmPassword = ""
    @Published var agreeToTerms = false

    // Ошибки валидации
    @Published var emailError: String?
    @Published var passwordError: String?
    @Published var confirmError: String?

    // Состояние отправки
    @Published var isSubmitting = false
    @Published var submitError: String?
    @Published var isSuccess = false

    // Форма валидна — кнопка активна
    var isFormValid: Bool {
        emailError == nil && passwordError == nil && confirmError == nil
        && !email.isEmpty && !password.isEmpty
        && agreeToTerms
    }

    func validateEmail() {
        let pattern = #"^[A-Z0-9a-z._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$"#
        if email.isEmpty {
            emailError = "Email обязателен"
        } else if email.range(of: pattern, options: .regularExpression) == nil {
            emailError = "Некорректный email"
        } else {
            emailError = nil
        }
    }

    func validatePassword() {
        if password.count < 8 {
            passwordError = "Минимум 8 символов"
        } else if password.rangeOfCharacter(from: .decimalDigits) == nil {
            passwordError = "Нужна хотя бы одна цифра"
        } else {
            passwordError = nil
        }
    }

    func validateConfirm() {
        confirmError = password != confirmPassword ? "Пароли не совпадают" : nil
    }

    func submit() async {
        // Финальная валидация перед отправкой
        validateEmail()
        validatePassword()
        validateConfirm()
        guard isFormValid else { return }

        isSubmitting = true
        defer { isSubmitting = false }

        do {
            // Имитация сетевого запроса
            try await Task.sleep(for: .seconds(1))
            isSuccess = true
        } catch {
            submitError = error.localizedDescription
        }
    }
}

// MARK: - View

struct RegistrationView: View {
    @StateObject private var vm = RegistrationViewModel()
    @FocusState private var focusedField: FormField?

    enum FormField: Hashable {
        case email, password, confirm
    }

    var body: some View {
        NavigationStack {
            Form {
                Section("Учётная запись") {
                    VStack(alignment: .leading, spacing: 4) {
                        TextField("Email", text: $vm.email)
                            .focused($focusedField, equals: .email)
                            .keyboardType(.emailAddress)
                            .textInputAutocapitalization(.never)
                            .autocorrectionDisabled()
                            .submitLabel(.next)
                            .onSubmit { focusedField = .password }
                            .onChange(of: focusedField) { _, f in
                                if f != .email { vm.validateEmail() }
                            }

                        if let err = vm.emailError {
                            Text(err)
                                .font(.caption)
                                .foregroundStyle(.red)
                        }
                    }

                    VStack(alignment: .leading, spacing: 4) {
                        SecureField("Пароль", text: $vm.password)
                            .focused($focusedField, equals: .password)
                            .submitLabel(.next)
                            .onSubmit { focusedField = .confirm }
                            .onChange(of: focusedField) { _, f in
                                if f != .password { vm.validatePassword() }
                            }

                        if let err = vm.passwordError {
                            Text(err)
                                .font(.caption)
                                .foregroundStyle(.red)
                        }
                    }

                    VStack(alignment: .leading, spacing: 4) {
                        SecureField("Повторите пароль", text: $vm.confirmPassword)
                            .focused($focusedField, equals: .confirm)
                            .submitLabel(.done)
                            .onSubmit {
                                focusedField = nil
                                vm.validateConfirm()
                            }
                            .onChange(of: vm.confirmPassword) { _, _ in
                                if !vm.confirmPassword.isEmpty {
                                    vm.validateConfirm()
                                }
                            }

                        if let err = vm.confirmError {
                            Text(err)
                                .font(.caption)
                                .foregroundStyle(.red)
                        }
                    }
                }

                Section {
                    Toggle("Согласен с условиями использования", isOn: $vm.agreeToTerms)
                }

                if let err = vm.submitError {
                    Section {
                        Text(err)
                            .foregroundStyle(.red)
                    }
                }

                Section {
                    Button {
                        Task { await vm.submit() }
                    } label: {
                        if vm.isSubmitting {
                            ProgressView()
                                .frame(maxWidth: .infinity)
                        } else {
                            Text("Зарегистрироваться")
                                .frame(maxWidth: .infinity)
                        }
                    }
                    .disabled(!vm.isFormValid || vm.isSubmitting)
                }
            }
            .navigationTitle("Регистрация")
        }
        .onAppear { focusedField = .email }
    }
}

Архитектура форм в SwiftUI

SwiftUI предоставляет Form — контейнер, который автоматически адаптирует стиль элементов под платформу (grouped UITableView на iOS, стандартные поля на macOS). Внутри Form используют Section для группировки, TextField, SecureField, Toggle, Picker и другие элементы ввода.

Валидацию удобно вынести в ObservableObject — это отделяет логику проверки от представления и упрощает тестирование.

Live-валидация vs валидация при потере фокуса

В примере выше валидация email и пароля запускается при уходе из поля (onChange of focusedField), а подтверждение пароля — в реальном времени при каждом символе. Это стандартный UX: не ругайте пользователя, пока он ещё печатает первое поле, но сразу показывайте несовпадение при вводе подтверждения.

Подводные камни

  • Кнопка Submit не должна зависеть только от isFormValid без финальной проверки. Пользователь может не трогать поле и оставить его пустым. Всегда вызывайте все validate-методы в submit() перед сетевым запросом.
  • Form на iOS — это ScrollView + List. Если добавить внешний ScrollView, появится двойная прокрутка. Не оборачивайте Form в дополнительный ScrollView.
  • disabled() не блокирует программный вызов submit. Если submit вызывается из другого места (например, по хардварной кнопке Enter), валидация должна быть на стороне ViewModel, а не только в кнопке.
  • @Published-ошибки и @MainActor. Если валидация запускается из фонового потока (например, через debounce в Combine), обновление @Published без @MainActor вызовет предупреждение или краш. Маркируйте ViewModel как @MainActor или используйте await MainActor.run { ... }.
  • Нет встроенного debounce в SwiftUI. Для live-валидации при каждом символе используйте Combine: $email.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main).sink { ... }. Без debounce регулярное выражение запускается на каждое нажатие.
  • SecureField не поддерживает textContentType автоматически на iOS 16-. Явно ставьте .textContentType(.newPassword), иначе Safari не предложит сохранить пароль.
  • Picker внутри Form требует NavigationStack. Без навигационного контейнера стиль .navigationLink (по умолчанию) не откроет список выбора.
  • Inline-ошибки ломают выравнивание Form. Если добавлять Text под полем внутри Form/Section, ячейка динамически меняет высоту — это выглядит хаотично. Рассмотрите overlay или фиксированную высоту для области ошибки.

Частые вопросы на собеседовании

  • Как SwiftUI перерисовывает (re-render) представления и что вызывает обновление view?
  • В чём разница между @State, @Binding, @ObservedObject и @StateObject?
  • Как async/await интегрируется с представлениями SwiftUI через модификатор .task?
  • Как работает NavigationStack в SwiftUI и чем он отличается от NavigationView?

Типичные ошибки

  • Сводить «обрабатывать формы и валидацию пользовательского ввода в SwiftUI» к синтаксису и не объяснять environment.
  • Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии swiftui-18.
  • Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.

Что проверяет интервьюер

  • Формулирует точную модель для «обрабатывать формы и валидацию пользовательского ввода в SwiftUI» и подтверждает ее корректным примером.
  • Умеет связать ответ с main actor, тестированием и отладкой на устройстве.
  • Называет ограничения подхода swiftui-18, включая производительность, память и сопровождение.

Как вы знаете этот вопрос?

Проверить себя по теме SwiftUI →

Источники

  • https://developer.apple.com/documentation/swiftui/form

Связанные темы

SwiftUIframework-api-designMobile-Engineering