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

Создание 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 принцип такой же, для примера можно использовать этот шаблон.

December 22, 2020