И снова привет, хабаровчане!
Развивая тему предыдущей статьи, пишу о программировании навигации между окнами вашего приложения с использованием минимума кода. Сразу хочу упомянуть, статья пишется для новичков, если вы в этом деле опытный разработчик — вряд ли она что-то вам даст.
Итак, приступим.
Подготовка
Перед тем, как начать писать само приложение, создаем проект
Т.к. большинство это и так умеют, обойдемся тут простым видео:
Наполнение стартовым содержанием
Отлично, проект готов.
Мы хотим создать несколько VC с выбором через таб бар, так что зададим цвет главного окна, завернем его в TabViewController и затем добавим туда еще пару VC(=ViewControllers).
Для удобства отображения, ужмем наш ViewController в размерах и «покрасим» его в красный
«Завернем» главную страничку в Tab Bar Controller
Для этого мы выделяем нашу главную страничку, в меню выбираем Editor -> Embed In -> Tab Bar Controller
Ок, теперь у нас уже есть «меню», но пока в нём нет смысла — ведь страничка всего одна. Ну что ж, добавим еще парочку:
Добавим еще 2 ViewControllers
Как и в прошлой статье, перетаскиваем их на нашу Storyboard, попутно «сжимая» их в размерах для лучшего восприятия. И зададим цвета — например, желтый и зеленый.
Теперь добавим их в наше меню
Для этого, зажав правую кнопку мыши на Tab Bar Controller, «перетащите» мышку на то, что хотите добавить в меню, и в появившемся списке выберите Relationship Segue -> view controllers
Последний шаг
Посмотрите сейчас на свой TabView Controller — там будет что-то вроде [Item] [Item] [Item]. Как-то не дружелюбно, так что поменяем названия наших VC в меню — для этого выделяем у VC представляющую его в меню кнопку [Item] внизу и задаем там нужные нам значения. Для примера меняю только название, но тут вы можете поэкспериментировать.
Тестирование навигации
Итак, мы уже создали полноценное меню, не написав вообще ни одной строчки кода. Для проверки, запустим наше приложение на симуляторе пятого iPhone предварительно закинув Labels с описанием текущей странички для большей наглядности:
Как видите, построение простейшего меню не требует от вас почти никаких усилий. Остался только один важный момент: порядок элементов в нашем меню. Если вам хочется поменять местами элементы меню, это тоже делается с минимумом усилий, достаточно перетаскивания с зажатой левой кнопкой мыши в строке меню в Tab Bar Controller:
В принципе, для многих приложений хватит и такого варианта, но что, если мы хотим «завернуть» один из элементов в Navigation Controller?
Вложенные меню
Т.к. Navigation Controller и Table View почти всегда используются вместе, рассмотрим именно такой вариант. В таком варианте вам в любом случае придется писать код, если есть хотя бы малейший шанс, что данные таблички не постоянны.
Но т.к. лень — двигатель прогресса, используем уже созданный для нас TableVC внутри нашего VC «Подробнее», при помощи Container View. Выглядеть будет чуть хуже(нагромождение встроенных контроллеров), зато можно писать меньше кода )
Встраивание контроллера в другой контроллер
Для начала, нам надо создать контроллер, который будет обрабатывать нашу табличку. Это обычный TableVC, так что будет только видео:
Далее встроим наш контроллер таблицы в «желтый» контроллер с помощью 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 мы рассматривать не будем.
Так, теперь, когда мы встроили нашу табличку, надо как-то грузить в нее данные. Для начала создадим класс для нашего 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
Загрузка данных в MyTable
Преимущество нашего TableViewController'a в том, что не надо ничего ни с чем связывать — только переопределить нужные нам функции(кол-во секций, кол-во строк в секции, объект n-ной строки секции и т.п.), в отличие от TableView. И тут нам поможет автодополнение — на словах непонятно, а вот в видео можно хорошо показать:
Для таблички нам понадобится образец строки таблицы, его можно создать из storyboard вот так:
Теперь заполним наш файл кодом:
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 строк, проверим это:
Итак, у нас есть табличка. На сама по себе она не требует Navigation Controller. Для этого нам надо VC, который будет детально отображать информацию по какому-то из элементов таблички.
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»
Теперь подредактируем файл 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. Проверим в симуляторе:
Как видите, наш detail загружается и получает то, что должен показать, но мы никак не можем вернуться назад к списку вариантов. И тут можно использовать Navigation Controller, а не свой велосипед.
Navigation Controller в Tab Bar
Сначала самое простое, завернуть наш «желтый» VC в NavigationConctroller:
Попутно мы задали заголовок навигации для «желтого» VC и текст кнопки «назад»
Уже неплохо, «из коробки» мы можем вернуться назад без написания кода. Но в детальном виде показывается нижнее меню, хотя оно там не нужно, чтобы его убрать ставим у Detail галочку «Hide Bottom Bar on Push»
Проблема в том, что из коробки есть только кнопка «Назад», которая используется только для простого возврата назад, а если мы хотим перед уходом что-то сделать, это надо привязывать на другие кнопки.
Представим что юзер может что-то «купить». Добавим два варианта: через верхний бар навигации и через нажатие кнопки.
Сама покупка будет вызываться из функции 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)
}
}
Посмотрим на итоговый результат в демке.
Вот и всё на данный момент. Образец проекта можно скачать отсюда.
Многие моменты пришлось проигнорировать, чтобы не перегружать, но если вы считаете, что что-то нужно непременно добавить — пишите в личку.
Надеюсь, что для кого-то эта статья будет полезна.
Автор: MaximChistov