«Программирование мышкой» в Swift ч.2 — навигация
И снова привет, хабаровчане! Развивая тему предыдущей статьи, пишу о программировании навигации между окнами вашего приложения с использованием минимума кода. Сразу хочу упомянуть, статья пишется для новичков, если вы в этом деле опытный разработчик — вряд ли она что-то вам даст.
Итак, приступим.
ПодготовкаПеред тем, как начать писать само приложение, создаем проект Т.к. большинство это и так умеют, обойдемся тут простым видео:[embedded content]Наполнение стартовым содержанием Отлично, проект готов.Мы хотим создать несколько VC с выбором через таб бар, так что зададим цвет главного окна, завернем его в TabViewController и затем добавим туда еще пару VC (=ViewControllers).Для удобства отображения, ужмем наш ViewController в размерах и «покрасим» его в красный [embedded content]«Завернем» главную страничку в Tab Bar Controller Для этого мы выделяем нашу главную страничку, в меню выбираем Editor → Embed In → Tab Bar Controller[embedded content]Ок, теперь у нас уже есть «меню», но пока в нём нет смысла — ведь страничка всего одна. Ну что ж, добавим еще парочку:
Добавим еще 2 ViewControllers Как и в прошлой статье, перетаскиваем их на нашу Storyboard, попутно «сжимая» их в размерах для лучшего восприятия. И зададим цвета — например, желтый и зеленый.[embedded content]Теперь добавим их в наше меню Для этого, зажав правую кнопку мыши на Tab Bar Controller, «перетащите» мышку на то, что хотите добавить в меню, и в появившемся списке выберите Relationship Segue → view controllers[embedded content]Последний шаг Посмотрите сейчас на свой TabView Controller — там будет что-то вроде [Item] [Item] [Item]. Как-то не дружелюбно, так что поменяем названия наших VC в меню — для этого выделяем у VC представляющую его в меню кнопку [Item] внизу и задаем там нужные нам значения. Для примера меняю только название, но тут вы можете поэкспериментировать.[embedded content]Тестирование навигации Итак, мы уже создали полноценное меню, не написав вообще ни одной строчки кода. Для проверки, запустим наше приложение на симуляторе пятого iPhone предварительно закинув Labels с описанием текущей странички для большей наглядности:[embedded content]Как видите, построение простейшего меню не требует от вас почти никаких усилий. Остался только один важный момент: порядок элементов в нашем меню. Если вам хочется поменять местами элементы меню, это тоже делается с минимумом усилий, достаточно перетаскивания с зажатой левой кнопкой мыши в строке меню в Tab Bar Controller:[embedded content]
В принципе, для многих приложений хватит и такого варианта, но что, если мы хотим «завернуть» один из элементов в Navigation Controller?
Вложенные меню Т.к. Navigation Controller и Table View почти всегда используются вместе, рассмотрим именно такой вариант. В таком варианте вам в любом случае придется писать код, если есть хотя бы малейший шанс, что данные таблички не постоянны.
Но т.к. лень — двигатель прогресса, используем уже созданный для нас TableVC внутри нашего VC «Подробнее», при помощи Container View. Выглядеть будет чуть хуже (нагромождение встроенных контроллеров), зато можно писать меньше кода)
Встраивание контроллера в другой контроллер Для начала, нам надо создать контроллер, который будет обрабатывать нашу табличку. Это обычный TableVC, так что будет только видео:[embedded content]Далее встроим наш контроллер таблицы в «желтый» контроллер с помощью Container View. Когда вы создаете в своем VC Container View, ему автоматически создается «дочерний» VC, который нужно удалить.Затем перетаскиваете, зажав правую кнопку мыши, линию от Container View к нашему TableVC, из выпадающего списка выбираете «embed». Теперь ваш «дочерний» VC будет рисоваться в «родительском», пытаясь влезть в его размеры.Теперь момент, который нужно просто «сделать»: растянув Container View на весь View и оставив немного места сверху для другого контента (заголовок, например), выделите ваш «желтый» VC и в меню выбирете Editor → Resolve Auto Layout Issues → All Views → Reset to Suggested Constraints.Костыль необходимый, т.к. в рамках этой статьи Auto Layout мы рассматривать не будем.[embedded content]Так, теперь, когда мы встроили нашу табличку, надо как-то грузить в нее данные. Для начала создадим класс для нашего TableVC.Создаем Swift файл «MyTable.swift» и пишем в нем такой код
Скрытый текст import UIKit
class MyTable: UITableViewController { override func viewDidLoad () { super.viewDidLoad () // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning () { super.didReceiveMemoryWarning () // Dispose of any resources that can be recreated. } } После чего в Stroryboard выбираем наш табличный контроллер и меняем ему класс на MyTable[embedded content]Загрузка данных в MyTable Преимущество нашего TableViewController’a в том, что не надо ничего ни с чем связывать — только переопределить нужные нам функции (кол-во секций, кол-во строк в секции, объект n-ной строки секции и т.п.), в отличие от TableView. И тут нам поможет автодополнение — на словах непонятно, а вот в видео можно хорошо показать:[embedded content]Для таблички нам понадобится образец строки таблицы, его можно создать из storyboard вот так:[embedded content]Теперь заполним наш файл кодом:
MyTable.swift import UIKit
class MyTable: UITableViewController { override func viewDidLoad () { super.viewDidLoad () // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning () { super.didReceiveMemoryWarning () // Dispose of any resources that can be recreated. } override func tableView (tableView: UITableView, numberOfRowsInSection section: Int) → Int { //кол-во элементов для каждой из секций return 10 } override func tableView (tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) → UITableViewCell { //надо вернуть объект для отображения n-ной ячейки таблицы let cell = tableView.dequeueReusableCellWithIdentifier («MyCell») as UITableViewCell cell.textLabel.text = «Строка #\(indexPath.item)» return cell } override func tableView (tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { //а это вызывается если юзер кликнул по какой-то ячейке } override func numberOfSectionsInTableView (tableView: UITableView) → Int { //а здесь нао вернуть кол-во секций (для простой таблицы это 1) return 1 } } Ну теперь у нас должна выводиться табличка из 10 строк, проверим это: Все работает [embedded content]
Итак, у нас есть табличка. На сама по себе она не требует Navigation Controller. Для этого нам надо VC, который будет детально отображать информацию по какому-то из элементов таблички.Создаем класс для контроллера в файле Detail.swift import UIKit
class Detail: UIViewController {
required init (coder aDecoder: NSCoder) { id = 0 super.init (coder: aDecoder) } @IBOutlet weak var idDetail: UILabel!//эта перменная связана с лэйблом с текстом «id…» // получается, как и все, перетаскиванием var id: Int //для передачи id в объект нашего view при переходе override func viewDidLoad () { super.viewDidLoad () // Do any additional setup after loading the view, typically from a nib. idDetail.text = »\(id)» } override func didReceiveMemoryWarning () { super.didReceiveMemoryWarning () // Dispose of any resources that can be recreated. } } Создадим VC, и свяжем его с Detail.swift и с MyTableVC. Связь MyTable → Detail назовём «ToDetail»[embedded content]
Теперь подредактируем файл MyTable:
override func prepareForSegue (segue: UIStoryboardSegue, sender: AnyObject?) {//вызывается перед кадым показом Detail let vc = segue.destinationViewController as Detail let id = sender as Int//сохраним отправителя в поле такого же типа в Detail vc.id = id } override func tableView (tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { //а это вызывается если юзер кликнул по какой-то ячейке performSegueWithIdentifier («ToDetail», sender: indexPath.item)//отправителем мы задали номер ячейки, а можно любой объект } В итоге код будет таким import UIKit
class MyTable: UITableViewController { override func viewDidLoad () { super.viewDidLoad () // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning () { super.didReceiveMemoryWarning () // Dispose of any resources that can be recreated. } override func tableView (tableView: UITableView, numberOfRowsInSection section: Int) → Int { //кол-во элементов для каждой из секций return 10 } override func tableView (tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) → UITableViewCell { //надо вернуть объект для отображения n-ной ячейки таблицы let cell = tableView.dequeueReusableCellWithIdentifier («MyCell») as UITableViewCell cell.textLabel.text = «Строка #\(indexPath.item)» return cell } override func prepareForSegue (segue: UIStoryboardSegue, sender: AnyObject?) {//вызывается перед кадым показом Detail let vc = segue.destinationViewController as Detail let id = sender as Int//сохраним отправителя в поле такого же типа в Detail vc.id = id } override func tableView (tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { //а это вызывается если юзер кликнул по какой-то ячейке performSegueWithIdentifier («ToDetail», sender: indexPath.item)//отправителем мы задали номер ячейки?, а можно любой объект } override func numberOfSectionsInTableView (tableView: UITableView) → Int { //а здесь нао вернуть кол-во секций (для простой таблицы это 1) return 1 } } Демка детального вида Мы уже сделали все, чтобы сымитировать загрузку данных при открытии Detail. Проверим в симуляторе:[embedded content]Как видите, наш detail загружается и получает то, что должен показать, но мы никак не можем вернуться назад к списку вариантов. И тут можно использовать Navigation Controller, а не свой велосипед.Navigation Controller в Tab Bar Сначала самое простое, завернуть наш «желтый» VC в NavigationConctroller:[embedded content]Попутно мы задали заголовок навигации для «желтого» VC и текст кнопки «назад«Уже неплохо, «из коробки» мы можем вернуться назад без написания кода. Но в детальном виде показывается нижнее меню, хотя оно там не нужно, чтобы его убрать ставим у Detail галочку «Hide Bottom Bar on Push»[embedded content]
Проблема в том, что из коробки есть только кнопка «Назад», которая используется только для простого возврата назад, а если мы хотим перед уходом что-то сделать, это надо привязывать на другие кнопки.Представим что юзер может что-то «купить». Добавим два варианта: через верхний бар навигации и через нажатие кнопки.
Сама покупка будет вызываться из функции doIt
func doIt () { //делаем тут что-то с нашим id println (id) //возвращаемся назад navigationController?.popViewControllerAnimated (true) } Обратите внимание на navigationController?.popViewControllerAnimated (true), именно благодаря этой строчке мы возвращаемся назад как если бы была нажата кнопка «назад» в navigation bar.Для кнопки вызов этой функции просто помещаете в обработчик события TouchUpInside кнопки, а вот для кнопки в навигации придется дописать
var rightButton = UIBarButtonItem (title: «Купить», style: .Done, target: self, action: «doIt») self.navigationItem.rightBarButtonItem = rightButton в viewDidLoad.В итоге код будет таким import UIKit
class Detail: UIViewController {
required init (coder aDecoder: NSCoder) { id = 0 super.init (coder: aDecoder) } @IBOutlet weak var idDetail: UILabel!//чтобы показать, что мы получили id var id: Int//для того, чтобы передать этому vc id override func viewDidLoad () { super.viewDidLoad () // Do any additional setup after loading the view, typically from a nib. idDetail.text = »\(id)» var rightButton = UIBarButtonItem (title: «Купить», style: .Done, target: self, action: «doIt») self.navigationItem.rightBarButtonItem = rightButton } override func didReceiveMemoryWarning () { super.didReceiveMemoryWarning () // Dispose of any resources that can be recreated. } @IBAction func buyPressed (sender: AnyObject) { doIt () } func doIt () { //делаем тут что-то с нашим id println (id) //возвращаемся назад navigationController?.popViewControllerAnimated (true) } } Посмотрим на итоговый результат в демке.[embedded content]Вот и всё на данный момент. Образец проекта можно скачать отсюда.Многие моменты пришлось проигнорировать, чтобы не перегружать, но если вы считаете, что что-то нужно непременно добавить — пишите в личку.Надеюсь, что для кого-то эта статья будет полезна.