«Программирование мышкой» в Swift ч.2 — навигация

в 16:19, , рубрики: iOS, NavigationBar, swift, TabBar, xcode, Xcode 6, разработка под iOS

И снова привет, хабаровчане!

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

Итак, приступим.

Подготовка

Перед тем, как начать писать само приложение, создаем проект

Т.к. большинство это и так умеют, обойдемся тут простым видео:

Наполнение стартовым содержанием

Отлично, проект готов.
Мы хотим создать несколько 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 вот так:

Теперь заполним наш файл кодом:

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 строк, проверим это:

Все работает

Итак, у нас есть табличка. На сама по себе она не требует 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»

Теперь подредактируем файл 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js