Введение: почему ваш UIAlertView не работает (и при чём тут Stack Overflow)
Если вы когда-нибудь гуглили «How would I create a UIAlertView in Swift?» — вы не одиноки. Этот вопрос на Stack Overflow собрал сотни тысяч просмотров. Кажется, что может быть проще? Показать пользователю всплывающее окно с кнопкой «ОК». Но на практике новички (и не только) наступают на одни и те же грабли.
В этой статье мы разберём топ-5 ошибок при создании UIAlertView в Swift, покажем, как их исправить, и дадим готовый код для UIAlertController — современной замены устаревшему API. Если вы хотите писать чистый, работающий код без крашей — оставайтесь с нами.
Ошибка №1: Использование UIAlertView вместо UIAlertController
Самая распространённая ошибка — попытка вызвать UIAlertView в современном Swift. Этот класс был объявлен устаревшим (deprecated) ещё в iOS 8. Если вы пишете:
let alert = UIAlertView(title: "Ошибка", message: "Что-то пошло не так", delegate: nil, cancelButtonTitle: "OK")
alert.show()
…то компилятор, скорее всего, выдаст предупреждение или ошибку. А если проект соберётся — в лучшем случае алерт не будет работать корректно на iOS 13+.
Почему так происходит?
Apple заменила UIAlertView на UIAlertController ещё в iOS 8. Старый класс больше не поддерживается, и его использование может привести к неожиданному поведению.
Как исправить?
Используйте UIAlertController с предпочтительным стилем .alert или .actionSheet. Вот правильный код:
let alert = UIAlertController(title: "Ошибка",
message: "Что-то пошло не так",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
Запомните: в современном Swift UIAlertView — это legacy. Всегда используйте UIAlertController.
Ошибка №2: Забыли вызвать present()
Даже если вы создали UIAlertController, но забыли его показать — ничего не произойдёт. Буквально. Код выполнится, алерт будет создан в памяти, но пользователь его не увидит.
Типичный пример ошибки:
func showAlert() {
let alert = UIAlertController(title: "Привет", message: "Мир", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
// Забыли present!
}
Результат: тишина. Никакой ошибки, никакого алерта. Вы будете гадать, почему код не работает.
Как исправить?
Всегда вызывайте present(_:animated:completion:) на текущем view controller:
func showAlert() {
let alert = UIAlertController(title: "Привет", message: "Мир", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true, completion: nil)
}
Совет: если вы вызываете алерт из замыкания или асинхронного кода, убедитесь, что self (view controller) ещё существует. Иначе получите crash.
Ошибка №3: Неправильная обработка нажатия кнопок
В старом UIAlertView мы использовали делегат alertView(_:clickedButtonAt:). В UIAlertController всё иначе — используются замыкания (closures).
Новички часто пытаются подписать класс на UIAlertViewDelegate и реализовать методы делегата, но это не сработает с UIAlertController.
Неправильно:
class ViewController: UIViewController, UIAlertViewDelegate {
func showAlert() {
let alert = UIAlertController(title: "Вопрос", message: "Вы согласны?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Да", style: .default, handler: nil))
// Где обработка?!
present(alert, animated: true)
}
}
Правильно — используем handler:
func showAlertWithAction() {
let alert = UIAlertController(title: "Вопрос", message: "Вы согласны?", preferredStyle: .alert)
let yesAction = UIAlertAction(title: "Да", style: .default) { _ in
print("Пользователь согласился")
// Здесь ваш код
}
let noAction = UIAlertAction(title: "Нет", style: .cancel) { _ in
print("Пользователь отказался")
}
alert.addAction(yesAction)
alert.addAction(noAction)
present(alert, animated: true)
}
Важно: каждый UIAlertAction может иметь свой обработчик. Не нужно отдельного делегата.
Ошибка №4: Добавление действий после present()
Ещё одна коварная ошибка — модификация алерта после того, как он уже показан. UIAlertController не поддерживает динамическое добавление кнопок после вызова present.
Пример ошибки:
let alert = UIAlertController(title: "Загрузка", message: "Подождите...", preferredStyle: .alert)
present(alert, animated: true)
// Через 2 секунды пытаемся добавить кнопку
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
alert.addAction(UIAlertAction(title: "OK", style: .default)) // ОШИБКА!
}
Это вызовет исключение: “Attempt to add action to alert view after it has been presented”.
Как исправить?
Добавляйте все действия до вызова present. Если нужно показать алерт с задержкой — создайте новый экземпляр:
// Показываем алерт загрузки
let loadingAlert = UIAlertController(title: "Загрузка", message: "Подождите...", preferredStyle: .alert)
present(loadingAlert, animated: true)
// Через 2 секунды скрываем и показываем новый
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
loadingAlert.dismiss(animated: true) {
let successAlert = UIAlertController(title: "Готово", message: "Загрузка завершена", preferredStyle: .alert)
successAlert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(successAlert, animated: true)
}
}
Запомните: алерт — это конечный автомат. После показа его нельзя изменить.
Ошибка №5: Проблемы с потоками (threading)
UIKit не является потокобезопасным. Все операции с UI должны выполняться на главном потоке (main thread). Если вы попытаетесь показать алерт из фонового потока — приложение может упасть.
Типичный сценарий:
DispatchQueue.global().async {
// Какой-то тяжёлый код
let alert = UIAlertController(title: "Ошибка", message: "Сеть недоступна", preferredStyle: .alert)
alert.add