Почему аутлеты (IBOutlet) в UIViewController могут оказаться nil?

Ответ

Аутлеты (@IBOutlet) остаются nil, когда связь между кодом и элементом интерфейса в Storyboard/XIB не установлена или разорвана.

Распространенные причины:

  1. Отсутствует аннотация @IBOutlet в коде.

    // ОШИБКА: Свойство не помечено как IBOutlet
    var myLabel: UILabel?
    
    // ПРАВИЛЬНО: Связь может быть установлена
    @IBOutlet weak var myLabel: UILabel!
  2. Нет связи в Interface Builder. Элемент на storyboard не подключен к свойству в коде (или подключен к другому свойству).

  3. Несоответствие имен. Имя свойства в коде не совпадает с именем аутлета в Interface Builder.

  4. Элемент UI удален из storyboard, но ссылка в коде осталась.

  5. Попытка доступа до загрузки view. Аутлеты инициализируются в момент загрузки view (loadView() / viewDidLoad). Обращение к ним раньше (например, в init) вернет nil.

    override func viewDidLoad() {
        // ОШИБКА: myLabel еще nil, т.к. super.viewDidLoad() не вызван
        // myLabel.text = "Hello"
    
        super.viewDidLoad() // Здесь загружаются аутлеты
    
        // ПРАВИЛЬНО: Теперь myLabel загружен
        myLabel.text = "Hello"
    }
  6. Использование strong вместо weak ссылки (редко, но может мешать освобождению памяти).

Решение: Проверить связи в Interface Builder (правая панель, раздел "Connections Inspector") и убедиться, что view контроллера загружена.

Ответ 18+ 🔞

Эй, слушай, вот эта вся история с аутлетами, которые нихуя не работают и остаются nil — это классика, блядь, ёперный театр! Каждый, кто за iOS хватался, через это проходил. Суть в том, что связь между твоим кодом и этой красивой картинкой в сториборде — порвалась, как последние трусы.

Почему так выходит, блядь?

  1. Ты просто забыл написать @IBOutlet. Ну, бывает, задумался о вечном, а тут — раз, и пиздец.

    // ТАК НЕ РАБОТАЕТ: компилятору похуй на это свойство, он его в интерфейс не пустит.
    var myLabel: UILabel?
    
    // А ТАК РАБОТАЕТ: вот эта волшебная приписка `@IBOutlet` — это как пропуск в закрытый клуб для твоей переменной.
    @IBOutlet weak var myLabel: UILabel!
  2. В сториборде нихуя не подключено. Ты там мышкой поводил, линии какие-то рисовал, а реальной связи — ноль. Или, что ещё веселее, подключил, но к другой переменной. Классический распиздяйский манёвр.

  3. Имена не совпадают. В коде у тебя buttonSubmit, а в сториборде висит связь на buttonSumbit. Одна буква, а волнение ебать — не работает и всё.

  4. Ты удалил кнопку со сториборда, а в коде ссылка осталась. Получился призрак, блядь. Мертвая ссылка, которая ведёт в никуда.

  5. Самое ебнутое — лезешь к аутлетам слишком рано. Они же живые становятся только после того, как вьюха загрузится! Ты в ините (init) или даже в viewDidLoad ДО вызова super пытаешься к ним обратиться — а они тебе: «Пошёл нахуй, мы ещё не родились».

    override func viewDidLoad() {
        // ОШИБКА: myLabel ещё в небытии, он nil! Это как спросить у беременной: "Ну что, твой сын уже на дискотеку пошёл?"
        // myLabel.text = "Hello"
    
        super.viewDidLoad() // Вот тут-то магия и происходит, аутлеты оживают!
    
        // ТЕПЕРЬ ДА: теперь можно. Ребёнок родился, представь.
        myLabel.text = "Hello"
    }
  6. Сильная ссылка (strong) вместо слабой (weak). Редко, но бывает — может создать циклическую зависимость и нахуй сломать всю логику освобождения памяти. Но это уже для продвинутых ебланов.

Что делать, когда пиздец? Открываешь Interface Builder, выбираешь свой контроллер, жмёшь на эту иконку связей (Connections Inspector, обычно справа). Там будет список всех твоих аутлетов. И если напротив какого-то свойства пусто — вот он, твой враг. Или там висит связь, но на другой элемент. Переподключай, блядь, заново. И смотри, чтобы вьюха контроллера уже была загружена к моменту обращения. Всё, больше нехуй мудрить.