UIKit-проект без Storyboard

December 22, 2020

Создание UIKit-проекта без использования Storyboard в первый раз кажется чем-то сложным, на самом же деле основные проблемы при попытке запустить проект без сторибордов могут появиться при неправильном установке, удалении ненужных файлов и зависимостей.

Зачем вообще делать проект без Storyboard? Я вижу две причины: 

  1. Более удобная работа с git. Storyboard - такой же текстовый файл, как и другой код, только любое малейшее изменение внутри сториборда приведёт к изменению текста файла, и такие изменения совсем нечитаемые
  2. Полный контроль над UI. Мы не разделяем настройки UI-элемента на те, что в сториборде, и те, что в коде. Всё находится в одном месте, и это проще контролировать.

Стоит учесть, что несмотря на эти минусы, на всех серьёзных проектах, где я работал, использовали Storyboard, расставляя все элементы, а основная настройка UI была в коде. В каком-то смысле это упрощает жизнь и уменьшает количество кода. Тем не менее, вот как настроить проект на работу с программным UI:

Создадим проект (как ни странно) с Interface: Storyboard

Сразу же удаляем файл Main.storyboard

В настройках Target удалим Main из поля Main Interface, оно остаётся пустым. 

В Info.plist удалим всю строчку Application Scene Manifest

SceneDelegate.swift можно удалить (он может понадобиться для определённых целей, но в данном случае он не нужен). Спойлер: до iOS 13 "входом" в приложение был AppDelegate, там находилась логика запуска приложения и инициализация. С iOS13 ответственность разделилась:

  • AppDelegate.swift отвечает за жизненный цикл приложения и первоначальные установки
  • SceneDelegate.swift отвечает за то, что будет показано на экране

Это вызвано новым функционалом iPadOS и поддержкой мультиэкранов. Подробнее про AppDelegate и SceneDelegate можно прочитать здесь.

Далее изменим два файла:

AppDelegate.swift

import UIKit

@main

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        

        window = UIWindow(frame: UIScreen.main.bounds)

        window?.rootViewController = ViewController()

        window?.makeKeyAndVisible()

        

        return true

    }

}

ViewController.swift

import UIKit

final class ViewController: UIViewController {

    

    private let someLabel : UILabel = {

        let label = UILabel()

        label.text = "Label title"

        label.textAlignment = .center

        label.translatesAutoresizingMaskIntoConstraints = false

        return label

    }()

    

    override func viewDidLoad() {

        super.viewDidLoad()

        setupSubviews()

    }

    

    private func setupSubviews() {

        view.addSubview(someLabel)

        

        /// Constraints

        let margins = view.layoutMarginsGuide

        NSLayoutConstraint.activate([

            someLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),

            someLabel.leadingAnchor.constraint(equalTo: margins.leadingAnchor),

            someLabel.trailingAnchor.constraint(equalTo: margins.trailingAnchor),

            someLabel.widthAnchor.constraint(equalTo: margins.widthAnchor),

        ])

    }

    

}

Готово, запускаем проект и радуемся. 

Для Objective-C принцип такой же, для примера можно использовать этот шаблон.