В последнее время я видел много статей, в которых выступает против использования раскадровок при создании приложений для iOS. Наиболее часто упоминаемые аргументы заключаются в том, что раскадровки не читаются человеком, они медленные и вызывают конфликты git. Все это обоснованные опасения, но их можно избежать. Я хочу рассказать вам, как мы используем раскадровки в нетривиальных проектах, и как вы можете избежать этих проблем и при этом получить те приятные вещи, которые дают вам раскадровки.

Зачем нужны раскадровки?

Одна картинка стоит тысячи слов.

Люди мыслят визуально. Подавляющее большинство информации, которую мы получаем, мы получаем через глаза, а наш мозг - это невероятно сложные машины для сопоставления визуальных образов, которые помогают нам понимать окружающий мир.

Раскадровки дают вам обзор экрана в вашем приложении, не имеющий себе равных по представлению кода, будь то XML или простой Swift. Когда вы открываете раскадровку, вы можете увидеть все представления, их позиции и их иерархию за секунду. Для каждого представления вы можете увидеть все ограничения, которые на него влияют, и то, как оно взаимодействует с другими представлениями. Эффективность передачи информации визуально не может сравниться с текстом.

Еще одно преимущество раскадровки заключается в том, что она делает автоматическую компоновку более интуитивно понятной. Автоматическая верстка по своей сути является визуальной системой. Это может быть набор математических уравнений под капотом, но мы так не думаем. Мы думаем, что «это мнение всегда должно быть рядом с этим». Выполнение автоматической компоновки визуально - это естественный вариант.

Кроме того, автоматическая компоновка в раскадровках обеспечивает некоторую безопасность во время компиляции. Большинство отсутствующих или неоднозначных ограничений обнаруживаются во время создания макета, а не при открытии приложения. Это означает, что меньше времени тратится на отслеживание неоднозначных макетов или выяснение того, почему представление отсутствует на экране.

Как вам это сделать

Одна раскадровка для каждого UIViewController

Вы бы не написали все свое приложение внутри одного UIViewController. То же самое и с раскадровкой. Каждый контроллер представления заслуживает отдельной раскадровки. Это дает несколько преимуществ.

1. Конфликты Git возникают только в том случае, если два разработчика работают над одним и тем же UIViewController в раскадровке одновременно. По моему опыту, это случается не часто, и исправить это несложно. .

2. Раскадровка больше не загружается медленно, так как загружается только один UIViewController.

3. Вы можете создать экземпляр любого UIViewController любым способом, просто получив начальный контроллер представления раскадровки. Независимо от того, используете ли вы переходы или проталкиваете их через код.

Когда я создаю новый экран, первым делом я создаю UIViewController. Сделав это, я создаю раскадровку с тем же именем, что и только что созданный мной контроллер представления. Это позволяет сделать довольно интересную вещь: создать экземпляры UIViewControllers безопасным для типов способом, без жестко запрограммированных строк.

let feed = FeedViewController.instance()
// `feed` is of type `FeedViewController`

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

extension UIViewController {
    class func instance() -> Self {
        let storyboardName = String(describing: self)
        let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
        return storyboard.initialViewController()
    }
}
extension UIStoryboard {
    func initialViewController<T: UIViewController>() -> T {
        return self.instantiateInitialViewController() as! T
    }
}

Я знаю, как используются СИБ. Но формат NIB устарел, и некоторые функции (например, создание UITableViewCells в фактическом пере UIViewController) не поддерживаются в редакторе .xib. У меня такое чувство, что список неподдерживаемых функций будет только расти, и поэтому я использую раскадровки вместо перьев.

Никаких переходов
Поначалу переходы кажутся крутыми, но как только вам приходится передавать данные с одного экрана на другой, это становится проблемой. Вы должны где-то сохранить данные в какой-то временной переменной, а затем установить это значение внутри метода prepare (for segue :, sender:).

class UsersViewController: UIViewController, UITableViewDelegate {
    
  private enum SegueIdentifier {
    static let showUserDetails = "showUserDetails"
  }
    
  var usernames: [String] = ["Marin"]
    
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    usernameToSend = usernames[indexPath.row]
    performSegue(withIdentifier: SegueIdentifier.showUserDetails, sender: nil)
  }
    
    
  private var usernameToSend: String?
    
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
    switch segue.identifier {
      case SegueIdentifier.showUserDetails?:
            
        guard let usernameToSend = usernameToSend else {
          assertionFailure("No username provided!")
          return
        }
            
        let destination = segue.destination as! UserDetailViewController
        destination.username = usernameToSend
            
      default:
        break
    }     
  }
}

В этом коде много проблем. `prepare (for: sender:)` не является чистой функцией, так как она зависит от временной переменной, определенной над ней. Хуже того, эта переменная является необязательной, и непонятно, что должно произойти, если она равна нулю.

Не забудьте вручную установить свойство usernameToSend, которое добавляет изменяемое состояние в наш код. Вам также необходимо привести место назначения перехода к ожидаемому типу. Это много шаблонов и более чем одна точка отказа.

Я бы предпочел иметь функцию, которая принимает необязательное значение и подталкивает следующий контроллер представления с этим значением. Просто и легко.

class UsersViewController: UIViewController, UITableViewDelegate {
    
  var usernames: [String] = ["Marin"]
    
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let username = usernames[indexPath.row]
    showDetail(withUsername: username)
  }
    
  private func showDetail(withUsername username: String) {
    let detail = UserDetailViewController.instance()
    detail.username = username
    navigationController?.pushViewController(detail, animated: true)
  }
}

Этот код намного безопаснее, читабельнее и лаконичнее.

Все свойства задаются в коде

Оставьте все значения раскадровки по умолчанию. Если у метки должен быть другой текст или у представления должен быть цвет фона, это делается в коде. Это особенно относится ко всем маленьким галочкам в инспекторе свойств.

Причина в том, что вы не хотите жестко кодировать шрифты, цвета и тексты. У вас могут быть константы для них и единое место, где они хранятся, так что у вас будет единое место для изменения, когда вам нужно внести изменения в дизайн.

Кроме того, сканировать код на предмет свойств представления проще, чем пытаться найти, какие галочки отмечены в раскадровке.

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

Какие раскадровки для меня

Возможно, вы читаете эту статью и думаете: «Этот парень говорит, что раскадровки - это здорово, а потом говорит, что не использует половину функций!», И вы правы!

У раскадровок действительно есть проблемы, и я их избегаю. Я считаю раскадровки очень полезными для того, что я хочу с ними делать: создавать иерархию представлений и ограничения. Ни больше ни меньше.

Я хочу сказать, что нельзя игнорировать целую технологию, потому что вам не нравится один ее аспект. Вы можете выбирать, какие части вы хотите использовать. Это не все или ничего.

Так что для тех из вас, кто хочет получить преимущества или раскадровку, но хочет свести к минимуму недостатки, это наш подход, который до сих пор работал очень хорошо. Если у вас есть какие-либо комментарии, не стесняйтесь оставлять ответ или напишите мне на @marinbenc в Twitter.

Марин - разработчик iOS в COBE, блогер на marinbenc.com и студент компьютерных наук в FERIT, Осиек. Ему нравится программировать, узнавать о вещах, а потом писать о них, кататься на велосипедах и пить кофе. В основном, однако, он просто вызывает сбои SourceKit. У него есть пухлый кот по имени Амиго. Не он сам писал эту биографию.

Если вам понравился этот, ознакомьтесь с другими статьями моей команды: