понедельник, 19 октября 2015 г.

34. StoryBoard: UINavigationController, UITableViewController

Видео урок находится здесь.
В этом уроке рассказывается как переделать проект файлового менеджера из прошлого урока с помощью StoryBoard.
Первым делом удаляем наш код создания контроллеров из функции didFinishLaunchingWithOptions   класса AppDelegate.
Теперь это будет делаться из StoryBoard.
Если файла StoryBoard не существует, если существует переходим к следующему шагу.
Создаем в storyboard UINavigationController и UITableViewController.
Перетягиваем с одного на другой линию и устанавливаем root контроллер.

Для инициализации меняем сеттер и конструктор, т.к. вызывать конструктор со сторибоард не получиться.

-(void) setPath:(NSString *)path{
    _path=path;
    NSError *error=nil;
    self.contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
    if (error){
        NSLog(@"%@",[error localizedDescription]);
    }

    [self.tableView reloadData];
    self.navigationItem.title=[self.path lastPathComponent];
}
-(id) initWithFolder: (NSString *)path{
    self = [super initWithStyle:UITableViewStyleGrouped];
    if(self){
        self.path=path;
        }
    return self;
}

Все работает как в предыдущем проекте за исключением кнопки возврата в Root контроллер.

Это происходит так как метод viewDidLoad в котором происходит создание кнопки вызывается еще до того, как контроллер создается.
Если перенести код создания кнопки в функцию viewWillAppear, то все работает нормально.
-(void) viewDidAppear:(BOOL)animated{

    [super viewDidAppear:animated];
    self.navigationItem.title=[self.path lastPathComponent];
    if ([self.navigationController.viewControllers count]>1) {
        UIBarButtonItem *item=[[UIBarButtonItem alloc]initWithTitle:@"Root"
                                                              style:UIBarButtonItemStylePlain
                                                             target:self action:@selector(actionRoot:)];
        self.navigationItem.rightBarButtonItem=item;
        
    }

}

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

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

обрабатывающей нажатие на строку заменим

//        TADirectoryViewController *controller=[[TADirectoryViewController alloc] initWithFolder:filePath];
//        [[self navigationController]  pushViewController:controller animated:YES];
 на 
      TADirectoryViewController* vc=[storyboard instantiateViewControllerWithIdentifier:@"TADirectoryViewController"];
        vc.path=filePath;
        [self.navigationController pushViewController:vc animated:YES];


Еще один вариант сделать вызов контроллера из самого контроллера с помощью сторибоард
Нужно поместить кнопку uibarbutton не на сам контроллер, а на панель над контроллером и связать эту кнопку с самим контроллером.
Кстати xCode выдает ворнинг на такой способ добавления, т.е. им лучше не пользоваться

Push segues are deprecated since iOS 8.0

Ссылке Segue даем идентификатор navigateDeep.
Так же добавляем новое property  
@property (strong,nonatomic) NSString * selectedPath;


Заменяем предыдущий абзац кода на

//        UIStoryboard *storyboard=self.storyboard;
//        TADirectoryViewController* vc=[storyboard instantiateViewControllerWithIdentifier:@"TADirectoryViewController"];
//        vc.path=filePath;

//        [self.navigationController pushViewController:vc animated:YES];


        self.selectedPath=filePath;


        [self performSegueWithIdentifier:@"navigateDeep" sender:nil ];

Определяем метод, который вызывается перед переходом по Segue

 -(void) prepareForSegue:(nonnull UIStoryboardSegue *)segue sender:(nullable id)sender{
    NSLog(@"prepareForSegue %@",segue.identifier);
    
    if([segue.identifier isEqualToString:@"navigateDeep"]){
        TADirectoryViewController *destinationController=segue.destinationViewController;
        TADirectoryViewController *sourceController=segue.sourceViewController;
        destinationController.path=sourceController.selectedPath;

    }
    
}

Если определить метод

-(bool) shouldPerformSegueWithIdentifier:(nonnull NSString *)identifier sender:(nullable id)sender{
    NSLog(@"shouldPerformSegueWithIdentifier identifier:%@",identifier);
    return YES;

}

Можно контролировать по каким связям запускать, а по каким нет и что-то можно делать перед запускам в зависимости от типа связи.


Для того, чтобы изменить высоту строки в таблице нужно определить метод

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{

    if ([self isDirectoryAtIndexPath:indexPath]) {
        return 40.f;
    }else{
        return 80.f;
            }
 }

Для настройки ячеек таблицы из storyboard  UITableController настраиваем единственно созданную с контроллером ячейку.
Определяем фотографию папки и идентификатор FolderCell
Затем добавляем новую ячейку на контроллер типа UITableViewCell . 
Определяем 3 Label вставляем фотографию и кнопку.
Определяем идентификатор FileCell.
Создаем новый файл-класс тоже же типа.
Создаем оутлеты, которые соответствуют Label
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *sizeLabel;

@property (weak, nonatomic) IBOutlet UILabel *dateLabel;



Заменяем старый код функции на новый, с учетом того, что ячейки загружаются из сторибоард


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *fileIdentifier=@"FileCell";
    static NSString *folderIdentifier=@"FolderCell";

    NSString *fileName=[self.contents objectAtIndex:indexPath.row];
    if ([self isDirectoryAtIndexPath:indexPath]) {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:folderIdentifier];
        cell.textLabel.text=fileName;
        return cell;

        
    }else{
        NSString *filePath=[self.path stringByAppendingPathComponent:fileName];
        NSDictionary * attributes=[[NSFileManager defaultManager]attributesOfItemAtPath:filePath error:nil];
        TAFileCell* cell = [tableView dequeueReusableCellWithIdentifier:fileIdentifier];

        cell.nameLabel.text=fileName;
        cell.sizeLabel.text=[self fileSizeFromValue:[attributes fileSize]];
        static NSDateFormatter *dateFormatter=nil;
        if (!dateFormatter){
            dateFormatter=[[NSDateFormatter alloc]init];
            [dateFormatter setDateFormat:@"MM/dd/yyyy hh:mm a"];
        }
        cell.dateLabel.text=[dateFormatter stringFromDate:[attributes fileModificationDate]] ;
        
        return cell;

    }

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

-(NSString*) fileSizeFromValue:(unsigned long long) size{
    static NSString *unit[]={@"B",@"KB",@"Mb",@"Gb",@"TB"};
    static  int unitsCount=5;
    int index=0;
    
    double fileSize =(double) size;
    while (fileSize>1024&&index<unitsCount) {
        fileSize/=1024;
        index++;
    }
    return [NSString stringWithFormat:@"%.2f %@",fileSize,unit[index]];
}

Для определения ячейки на которой была нажата кнопка создаем дополнительную категорию к классу UIView

#import "UIView+UITableViewCell.h"

@implementation UIView (UITableViewCell)

-(UITableViewCell*) superCell{

    if(!self.superview) {
        return nil;
       }
    
    if ([self.superview isKindOfClass:[UITableViewCell class]]) {
        return (UITableViewCell*)self.superview;
    }

    return [self.superview superCell]; ;
}


@end

И обработчик  нажатия на кнопку с выводом секции и ряда нажатия

- (IBAction)actionInfo:(UIButton *)sender {
    NSLog(@"ActionInfo");
    UITableViewCell *cell=[sender superCell];
    if (cell) {
        NSIndexPath*indexPath=[self.tableView indexPathForCell:cell];
        NSLog(@"action %@ %@",@(indexPath.section),@(indexPath.row));
        
    }
}

Файл с исходниками к уроку 34.

Файл с тестовым заданием  без часов.
Файл с тестовым заданием с часами.

Домашнее задание к уроку 33-34

Я рекомендую вам немного задержаться на этом уроке и снова попрактиковать таблицы. Также будет довольно таки неплох о если вы углубитесь в NSFileManager

Ученик.

1. Добавьте возможность создавать директории
2. Добавьте возможность удалять файлы и папки

Студент

3. Сортируйте файлы и папки, сверху должны быть папки, снизу файлы, сортировка по имени
4. Не показывайте скрытые файлы

Мастер

5. В detailedTextField каждой ячейки файла, выводите размер файла.

Супермен

6. Тем же способом выводите размер папки (размер папки придется считать рекурсивно :) )

воскресенье, 18 октября 2015 г.

33. UINavigationController, UITableViewController


Видео урок 33 расположен здесь.
В этом уроке продолжаем создавать приложения "в коде" без использования сторибоард. 
Сегодня тема урока - более подробное рассмотрение контроллера навигации UINavigationController  и использование готового контроллера таблиц UITableViewController.
Тестовое приложение для изучение этих контроллеров - файловый менеджер.

Проект создается на базе EmptyApplication. В новом xCode отсутствует, поэтому удаляем ненужные файлы.

Создается класс наследник UITableViewController.
Создается проперти   

@property(strong,nonatomic) NSString *path;
@property(strong,nonatomic) NSArray *contents;

Заменяется конструктор  - (instancetype)initWithStyle:(UITableViewStyle)style  на

-(id) initWithFolder: (NSString *)path{
    self = [super initWithStyle:UITableViewStyleGrouped];
    if(self){
        NSError *error=nil;
        self.contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
        if (error){
            NSLog(@"%@",[error localizedDescription]);
        }
        self.path=path;
    }
    return self;

}

Стандартно определяем методы из UITableDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.contents.count;
}

Для удобства определим функцию определяющую на файл или директорию указывает indexPath:
-(BOOL) isDirectoryAtIndexPath: (NSIndexPath*) indexPath{
    NSString *fileName=[self.contents objectAtIndex:indexPath.row];
    NSString *filePath=[self.path stringByAppendingPathComponent:fileName];
//    NSLog(@"self.path=%@,filePath=%@,fileName=%@",self.path,filePath,fileName);
    
    BOOL isDirectory=NO;

    ;
    if (!  [[NSFileManager defaultManager]fileExistsAtPath:filePath isDirectory:&isDirectory]) NSLog(@"fileExistsAtPath No");
    return isDirectory;


}

Используя эту функцию выводим картинку папку или файл в ячейку вместе с именем

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *identifier=@"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    if (!cell) {
        cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    }

    NSString *fileName=[self.contents objectAtIndex:indexPath.row];
    cell.textLabel.text=fileName;
    if ([self isDirectoryAtIndexPath:indexPath]) {
        cell.imageView.image=[UIImage imageNamed:@"folder.png"];
    }
    else{
        cell.imageView.image=[UIImage imageNamed:@"file.png"];

    }
    return cell;

}

В viewDidLoad определяем заголовок навигационной панельки 

    self.navigationItem.title=[self.path lastPathComponent];

lastPathComponent - Метод возвращает последний компонент (папку) в строке с дисковым путем.


Создаем наш контроллер.
 В классе AppDelegate в методе didFinishLaunchingWithOptions
    TADirectoryViewController *controller=[[TADirectoryViewController alloc] initWithFolder:@"/"];

Создаем навигационный контроллер

    UINavigationController *navigation=[[UINavigationController alloc] initWithRootViewController:controller]; 

Устанавливаем в свойствах окна root контроллер

    self.window.rootViewController=navigation;

Для перемещения по папкам необходимо реализовать метод выделения строк

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    if([self isDirectoryAtIndexPath:indexPath]){
        NSString *fileName=[self.contents objectAtIndex:indexPath.row];
        NSString *filePath=[self.path stringByAppendingPathComponent:fileName];
        TADirectoryViewController *controller=[[TADirectoryViewController alloc] initWithFolder:filePath];
        [[self navigationController]  pushViewController:controller animated:YES];
    }
}

При нажатии на ячейку если содержимое является директория, создается и инициализируется путем новый контроллер и помещается на стек навигационного контроллера.

Для просмотра количества контроллеров в стеке навигационного контроллера и определения индекса текущего контроллера:

    NSLog(@"Path=%@. ViewController=%@. Index Current ViewController:%@",
          self.path,
          @([self.navigationController.viewControllers count]),
          @([self.navigationController.viewControllers indexOfObject:self]));

В viewDidLoad добавляем кнопку ссылку на начальный каталог - Root

    if ([self.navigationController.viewControllers count]>1) {
        UIBarButtonItem *item=[[UIBarButtonItem alloc]initWithTitle:@"Root"
                                                              style:UIBarButtonItemStylePlain
                                                             target:self action:@selector(actionRoot:)];
    self.navigationItem.rightBarButtonItem=item;

    }

И обработчик для кнопки выталкивает все контроллеры со стека навигационного контроллера до Root- контроллера

-(void) actionRoot:(UIBarButtonItem*) sender{
    [self.navigationController popToRootViewControllerAnimated:YES];

}

Ссылка на исходник тестового проекта.


 Домашнее задание к уроку 33-34


Я рекомендую вам немного задержаться на этом уроке и снова попрактиковать таблицы. Также будет довольно таки неплох о если вы углубитесь в NSFileManager

Ученик.

1. Добавьте возможность создавать директории
2. Добавьте возможность удалять файлы и папки

Студент

3. Сортируйте файлы и папки, сверху должны быть папки, снизу файлы, сортировка по имени
4. Не показывайте скрытые файлы

Мастер

5. В detailedTextField каждой ячейки файла, выводите размер файла.

Супермен

6. Тем же способом выводите размер папки (размер папки придется считать рекурсивно :) )



Выполнил домашнее задание "в коде" без использования storyboard

Исходник к домашнему заданию 33.



пятница, 16 октября 2015 г.

32. UITableView Part 2

В этом уроке продолжаем тему редактируемых таблиц.
Видео урок 32 находится здесь.

Для дополнительной панели создадим контроллер навигации
    UINavigationController *navigationController=[[UINavigationController alloc] initWithRootViewController:controller];
    self.window.rootViewController=navigationController;

Инициализируем его нашим ранее созданным контроллером UIViewController и затем указываем, что главный контроллер для окна - navigationController.

У нас появляется дополнительная панель сверху.
В каждом View Controller есть объект navigationItem.
Чтобы задать заголовок для нашего контроллера

self.navigationItem.title=@"Students";

Для того, чтобы создать кнопку на навигационном контроллере
создаем системную кнопку edit и указываем обработчик событий actionEdit 

    UIBarButtonItem * editButton= [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:@selector(actionEdit:)];

Добавляем созданную кнопку на self.navigationItem

self.navigationItem.rightBarButtonItem=editButton;

Так выглядит обработчик события нажатия кнопки Edit

-(void) actionEdit:(UIBarButtonItem*) sender{
    BOOL isEditing=self.tableView.editing;
    [self.tableView  setEditing:!isEditing animated:YES];
    UIBarButtonSystemItem item=UIBarButtonSystemItemEdit;
    if (self.tableView.editing) {
        item=UIBarButtonSystemItemDone;
    }
    UIBarButtonItem * editButton= [[UIBarButtonItem alloc]initWithBarButtonSystemItem:item
                                                                               target:self
                                                                               action:@selector(actionEdit:)];
    //self.navigationItem.rightBarButtonItem=editButton;
    [self.navigationItem setRightBarButtonItem:editButton animated:YES];
  

Обработчик меняет режим редактирования таблицы (включает/отключает)  и меняет системную кнопку в зависимости от режима.

Кнопку удаления мы убрали из таблицы, но отступ слева в режиме редактирования все равно остается
Для того, чтобы  убрать этот отступ нужно переопределить метод UITableViewDelegate

- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath{
    return NO;
}

Аналогично кнопке Edit добавляем кнопку системную кнопку Add слева для добавления секций
    UIBarButtonItem * addButton= [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(actionAdd:)];
    self.navigationItem.leftBarButtonItem=addButton;

Обработчик нажатия Add

-(void) actionAdd:(UIBarButtonItem*) sender{
    TAGroup *group=[[TAGroup alloc ]init];
    group.name=[NSString stringWithFormat:@"Group %@",@(self.groupsArray.count+1)];
    
    NSMutableArray *array=[NSMutableArray array];
    for (int j=0; j<(arc4random()%3+1); j++) {
        [array addObject:[TAStudent randomStudent]];
    }
    group.students=array;
    NSInteger newSectionIndex=0;
    [self.groupsArray insertObject:group atIndex:newSectionIndex];
    
    NSIndexSet *insertSections=[NSIndexSet indexSetWithIndex:newSectionIndex];
    [self.tableView beginUpdates];
    [self.tableView insertSections:insertSections withRowAnimation:(self.groupsArray.count%2)?UITableViewRowAnimationLeft:UITableViewRowAnimationRight];
    [self.tableView endUpdates];
}

В обработчике меняются сначала данные, а затем анимационно добавляется секция.
Анимация при добавлении секций или рядов должна быть заключена в 

   [self.tableView beginUpdates];

    [self.tableView endUpdates];

Создаем на основе индекса NSIndexSet для вставки секции

    NSIndexSet *insertSections=[NSIndexSet indexSetWithIndex:newSectionIndex];

Вставляем в таблицу новую секцию с анимацией  различной анимацией 

       [self.tableView insertSections:insertSections withRowAnimation:(self.groupsArray.count%2)?UITableViewRowAnimationLeft:UITableViewRowAnimationRight];

Чтобы запретить слишком быстрое добавление (анимация не успевает и могут произойти какие-нибудь проблемы), мы можем добавить следующий блок который на 0.3 секунды перестает принимать события от интерфейса

    [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        if ([[UIApplication sharedApplication] isIgnoringInteractionEvents]) {
            [[UIApplication sharedApplication ]endIgnoringInteractionEvents];
        }
    });

Чтобы запретить перемещение строки например перед первой строкой (в первой строке располагается добавление студентов), нужно определить метод

- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath{
    if (proposedDestinationIndexPath.row==0) {
        return sourceIndexPath;
    }
    else{
        return proposedDestinationIndexPath;
    }
}
Запрет добавлять в 0 ряд.

Для обработки нажатия (выделения) строки/ряда нужно определить метод

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

//снимаем выделение
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
//если нажата 0 строка (добавить студента)
    if (indexPath.row==0) {
        TAGroup *group=self.groupsArray[indexPath.section];
        NSMutableArray *array;
//если студентов в группе нет, инициазируем массив, если есть, то на основе него инициализируем
        if (group.students.count==0) {
            array=[NSMutableArray array];
        }
        else{
            array=[NSMutableArray arrayWithArray:group.students];

        }

        //Создаем и добавляем студента в группу

        TAStudent *student=[TAStudent randomStudent];
        [array insertObject:student atIndex:0];
        group.students=array;
//Начинаем  обновлять таблицу         
        [self.tableView beginUpdates];
//создаем новый indexpath куда будем добавлять строку

        NSIndexPath *newIndewxPath=[NSIndexPath indexPathForItem:1 inSection:indexPath.section];
//добавляем 
        [self.tableView insertRowsAtIndexPaths:@[newIndewxPath] withRowAnimation:UITableViewRowAnimationFade];
        [self.tableView endUpdates];

//задержка 0.3 секунды для завершения анимации

        [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            if ([[UIApplication sharedApplication] isIgnoringInteractionEvents]) {
                [[UIApplication sharedApplication ]endIgnoringInteractionEvents];
            }
        });
    }

}

Для срабатывания нажатия (выделения) во время редактирование необходимо установить проперти allowsSelectionDuringEditing в YES

    self.tableView.allowsSelectionDuringEditing=YES;

Для появления клавиши удаление на каждой строке кроме первой, меняем метод

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
    if (indexPath.row==0 ){
     return UITableViewCellEditingStyleNone;
    }
    else{
        return UITableViewCellEditingStyleDelete;
    }

}

Для изменения подписи на кнопке удаления определяем метод

- (nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
    return @"Remove";

}

Для определения действий кнопок подтверждений удаления, добавления  определяем метод 

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{

    if (editingStyle==UITableViewCellEditingStyleDelete) {
        TAGroup *group=self.groupsArray[indexPath.section];
        TAStudent *student=group.students[indexPath.row-1];
        NSMutableArray *array=[NSMutableArray arrayWithArray:group.students];
        [array removeObject:student];
        group.students=array;

        [self.tableView beginUpdates];
        NSIndexPath *newIndewxPath=[NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section];
        [self.tableView deleteRowsAtIndexPaths: @[newIndewxPath] withRowAnimation:UITableViewRowAnimationRight];
        [self.tableView endUpdates];
        [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            if ([[UIApplication sharedApplication] isIgnoringInteractionEvents]) {
                [[UIApplication sharedApplication ]endIgnoringInteractionEvents];
            }
        });
    }

}

Ссылка на исходники к уроку.

Выполняя домашнее задание задание для индивидуального дизайна заголовков секций используется следующий метод

- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
    UIView *view=[[UIView alloc] initWithFrame:CGRectMake(0, 0,  CGRectGetWidth( tableView.frame), 18)];
    UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(10,5,CGRectGetWidth(tableView.frame), 18)];
    [label setFont:[UIFont boldSystemFontOfSize:16]];
    TAModel *model=self.arrayModel[section];
    [label setText:model.modelName];
    label.textColor=[UIColor whiteColor];
    label.textAlignment=NSTextAlignmentCenter;
    [view addSubview:label];
    [view setBackgroundColor:[UIColor grayColor]];
    return view;
}



Домашнее задание к уроку 31-32:

Ученик

1. Создайте контроллер с таблицей в коде без сторибордов.
2. Заполните таблицу данными на свое усмотрение
3. Объедините данные в группы (секции)

Студент.

4. Реализуйте механизм перемещения данных между рядами и секциями
5. Вы должны четко понимать что и как работает и в какой последовательности поэтому повторяйте задание пока вы полностью не освоите этот механизм

Мастер.

6. Реализуйте удаление рядов

Супермен

7. Реализуйте механзм добавления секций и рядов на ваше усмотрение

Выполнены все задания

Ссылка на исходник домашнего задания 31-32 UITableView.