Передача данных между экранами в iOS: полный гайд

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Передача данных между экранами в iOS: от новичка до профи

Каждый, кто начинал писать под iOS, рано или поздно упирался в стену: "Как мне передать строку, массив или объект на другой экран?". Вопрос настолько популярен, что на Stack Overflow он собрал тысячи просмотров и десятки вариантов ответов. И это неспроста — архитектура iOS построена так, что каждый экран (ViewController) живет своей жизнью, и данные между ними нужно передавать осознанно.

В этой статье мы разберем 5 основных способов передачи данных между view controllers на Swift. Вы узнаете, когда использовать segue, а когда — делегаты или замыкания, и почему NotificationCenter — это не всегда зло. Поехали!

Почему это вообще проблема?

iOS использует строгую архитектуру MVC (Model-View-Controller). Каждый ViewController отвечает за свой кусочек экрана и не имеет прямого доступа к данным соседнего контроллера. Это сделано для безопасности и модульности, но для разработчика это означает, что нужно явно "протянуть" данные из точки А в точку Б.

Представьте: у вас есть список пользователей, и вы хотите показать детальную информацию при нажатии на ячейку. Вы нажимаете — открывается новый экран. Как передать ID пользователя? Вот тут и начинается магия.

Способ 1: Передача данных через Storyboard Segue (самый простой)

Самый прямолинейный способ — использовать prepare(for:sender:). Вы просто подготавливаете данные перед переходом. Идеально для простых приложений и прототипов.

// В исходном ViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "showDetail" {
        guard let destinationVC = segue.destination as? DetailViewController else { return }
        destinationVC.userID = 42 // передаем число
        destinationVC.userName = "Анна" // передаем строку
    }
}

// В DetailViewController
var userID: Int = 0
var userName: String = ""

Плюсы: Минимум кода, всё в одном месте. Минусы: Жесткая привязка к Storyboard, сложно тестировать, легко запутаться в идентификаторах.

Способ 2: Программная навигация с передачей данных (продвинутый уровень)

Когда вы устали от segues и хотите контролировать всё из кода, используйте прямой инстанс контроллера.

// В исходном ViewController
func showDetail() {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    guard let detailVC = storyboard.instantiateViewController(withIdentifier: "DetailVC") as? DetailViewController else { return }
    
    detailVC.userID = 42
    detailVC.userName = "Анна"
    
    navigationController?.pushViewController(detailVC, animated: true)
}

Этот способ даёт полный контроль: вы можете загрузить данные из сети, проверить их и только потом перейти. Отлично для сложной логики.

Способ 3: Делегаты (Delegate Pattern) — обратная связь

Что делать, если нужно передать данные обратно? Например, пользователь выбрал дату на втором экране, и мы хотим вернуть её на первый. Тут на помощь приходит делегат.

Шаг 1: Создаем протокол

protocol DatePickerDelegate: AnyObject {
    func didSelectDate(_ date: Date)
}

Шаг 2: Добавляем делегат в дочерний контроллер

class DatePickerViewController: UIViewController {
    weak var delegate: DatePickerDelegate?
    
    @IBAction func saveButtonTapped(_ sender: UIButton) {
        let selectedDate = datePicker.date
        delegate?.didSelectDate(selectedDate)
        dismiss(animated: true)
    }
}

Шаг 3: Реализуем делегат в родительском контроллере

class MainViewController: UIViewController, DatePickerDelegate {
    func didSelectDate(_ date: Date) {
        print("Получена дата: \(date)")
        // обновляем UI
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let datePickerVC = segue.destination as? DatePickerViewController {
            datePickerVC.delegate = self
        }
    }
}

Делегаты — стандартный паттерн в iOS. UIKit сам использует его повсеместно (например, UITableViewDelegate). Он безопасен, тестируем и понятен другим разработчикам.

Способ 4: Замыкания (Closures) — современный и лаконичный

Если делегаты кажутся вам слишком многословными, используйте замыкания. Это тот же callback, но без отдельного протокола.

// В дочернем контроллере
class EditorViewController: UIViewController {
    var onSave: ((String) -> Void)?
    
    @IBAction func saveTapped(_ sender: UIButton) {
        onSave?(textField.text ?? "")
        dismiss(animated: true)
    }
}

// В родительском контроллере
let editorVC = EditorViewController()
editorVC.onSave = { [weak self] text in
    self?.label.text = text
}
present(editorVC, animated: true)

Обратите внимание на [weak self] — это важно, чтобы избежать retain cycle (утечки памяти). Замыкания особенно хороши для одноразовых действий: "сделай что-то и забудь".

Способ 5: NotificationCenter — глобальная шина событий

Иногда данные нужно передать между контроллерами, которые никак не связаны навигацией. Или когда один объект должен узнать о событии, произошедшем в другом месте. Тут в игру вступает NotificationCenter.

// Отправляем уведомление
extension Notification.Name {
    static let userLoggedIn = Notification.Name("userLoggedIn")
}

NotificationCenter.default.post(name: .userLoggedIn, object: nil, userInfo: ["userID": 42])

// Подписываемся на уведомление (например, в viewDidLoad)
NotificationCenter.default.addObserver(
    self,
    selector: #selector(handleUserLogin),
    name: .userLoggedIn,
    object: nil
)

@objc func handleUserLogin(_ notification: Notification) {
    if let userID = notification.userInfo?["userID"] as? Int {
        print("Пользователь с ID \(userID) вошел в систему")
    }
}

Важно: Не забывайте отписываться от уведомлений, иначе будет утечка памяти. В современных проектах лучше использовать Combine или сторонние библиотеки, но для быстрых решений NotificationCenter — спасение.

Бонус: MVVM и реактивная передача данных

Если вы пишете не просто учебный проект, а серьёзное приложение, присмотритесь к архитектуре MVVM (Model-View-ViewModel). Вместо того чтобы передавать данные напрямую, вы используете наблюдаемые свойства (например, через Combine или сторонние библиотеки вроде RxSwift).

// ViewModel
class UserViewModel: ObservableObject {
    @Published var userName: String = ""
    @Published var userAge: Int = 0
}

// Первый ViewController
let viewModel = UserViewModel()
viewModel.userName = "Анна"

// Второй ViewController получает тот же viewModel
// и автоматически обновляется при изменениях

Этот подход разделяет логику и UI, делая код тестируемым и переиспользуемым. Но требует больше boilerplate-кода на старте.

Какой способ выбрать? Шпаргалка

  • Простой переход

Блоги

Рекомендаций по книгам