Как передать данные между View Controllers в Swift (iOS)?
Проблема в вашем коде
Вы столкнулись с классической ошибкой: в методе prepare(for:sender:) outlet'ы второго View Controller'а ещё не загружены (они инициализируются только после вызова viewDidLoad()). Поэтому попытка обратиться к label.text напрямую приводит к крашу — label равен nil.
Правильное решение: хранить данные в свойстве, а не в UI-элементе
Лучший способ — создать свойство для хранения данных во втором View Controller'е и присваивать его в prepare(for:sender:). А уже в viewDidLoad() обновлять UI.
Вот исправленный код:
// SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
@IBOutlet weak var label: UILabel!
// Свойство для хранения переданных данных
var receivedText: String?
override func viewDidLoad() {
super.viewDidLoad()
// Обновляем UI после загрузки view
label.text = receivedText ?? "Нет данных"
}
}
// FirstViewController.swift
import UIKit
class FirstViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecondVC" {
let secondVC = segue.destination as! SecondViewController
// Передаём данные в свойство, а не в outlet
secondVC.receivedText = textField.text
}
}
}
Почему это работает?
- Свойство
receivedText— это обычная переменная, которая существует сразу после создания экземпляраSecondViewController. - Метод
prepare(for:sender:)вызывается до того, как второй View Controller появляется на экране, но его объект уже создан. Поэтому мы можем безопасно присвоить значение свойству. - В
viewDidLoad()все outlet'ы уже инициализированы, и мы спокойно используемreceivedTextдля обновленияlabel.
Альтернативный вариант: инициализатор (для программного создания VC)
Если вы создаёте View Controller программно (без Storyboard), можно передать данные через кастомный инициализатор:
class SecondViewController: UIViewController {
private let labelText: String
init(text: String) {
self.labelText = text
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
label.text = labelText
}
}
// В FirstViewController:
let secondVC = SecondViewController(text: textField.text ?? "")
navigationController?.pushViewController(secondVC, animated: true)
Дополнительные советы
- Проверяйте опциональные значения: используйте
??(nil-coalescing) илиguard let, чтобы избежать передачиnil. - Используйте enum для идентификаторов segue: это уменьшит ошибки с опечатками в строках.
- Для сложных проектов: рассмотрите паттерны Coordinator, MVVM или передачу данных через замыкания (см. альтернативный ответ).
Этот подход — стандарт для передачи данных в iOS. Освоив его, вы сможете легко перейти к более продвинутым техникам.
<<Передача данных через замыкание (closure)
Ещё один популярный способ — использовать замыкание для обратной связи. Это особенно удобно, когда нужно передать данные обратно из второго View Controller в первый (например, после редактирования).
Пример реализации:
// SecondViewController.swift
class SecondViewController: UIViewController {
@IBOutlet weak var label: UILabel!
var onDataReceived: ((String) -> Void)? // Замыкание для передачи данных обратно
override func viewDidLoad() {
super.viewDidLoad()
// Предположим, пользователь вводит что-то и нажимает кнопку
}
@IBAction func sendBackButtonTapped(_ sender: UIButton) {
onDataReceived?("Данные из второго VC")
dismiss(animated: true)
}
}
// FirstViewController.swift
class FirstViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecondVC" {
let secondVC = segue.destination as! SecondViewController
secondVC.receivedText = textField.text // Передача вперёд
// Замыкание для получения данных обратно
secondVC.onDataReceived = { [weak self] data in
self?.textField.text = data
}
}
}
}
Плюсы и минусы
- Плюсы: гибкость, возможность передавать данные в обе стороны без создания делегата.
- Минусы: риск циклических ссылок (используйте
[weak self]), сложнее отладка при большом количестве замыканий.
Для простой передачи данных вперёд (как в вашем случае) лучше использовать первый метод со свойством. Замыкания пригодятся, когда нужно «вернуть» результат.