вторник, 27 октября 2015 г.

36 UIPopoverPresentationController, NSUserDefaults, UIDatePicker

Видео урок  36 посвящен описанию класса UIPopoverController.
Popover представляет собой всплывающее модальное окно со стрелочкой - якорем указывающим на элемент к которому относится Popover.
 В новой версии xCode класс UIPopoverController уже deprecated (в IOS 9.0).
Поэтому подробно описывать урок смысла нет (т.к. UIPopoverController использовать уже не нельзя).
В сообщении об ошибке говорится, что теперь popover включен в презентации UIViewController. Используйте модальный стиль презентаций  UIModalPresentationPopover и UIPopoverPresentationController.
Попробую домашнее задание реализовать с учетом нововведений в  Xcode.

Нашел вот такой пример создания Popover в коде:

Declare a property of UIPopoverPresentationController:
@property(nonatomic,retain)UIPopoverPresentationController *dateTimePopover8;
Use the following method to present the popover from UIButton:
- (IBAction)btnSelectDatePressed:(id)sender
{
    UINavigationController *destNav = [[UINavigationController alloc] initWithRootViewController:dateVC];/*Here dateVC is controller you want to show in popover*/
    dateVC.preferredContentSize = CGSizeMake(280,200);
    destNav.modalPresentationStyle = UIModalPresentationPopover;
    _dateTimePopover8 = destNav.popoverPresentationController;
    _dateTimePopover8.delegate = self;
    _dateTimePopover8.sourceView = self.view;
    _dateTimePopover8.sourceRect = sender.frame;
    destNav.navigationBarHidden = YES;
    [self presentViewController:destNav animated:YES completion:nil];
}

Use the following method to present the popover from UIBarButtonItem:

- (IBAction)btnSelectDatePressed:(id)sender
{
    UINavigationController *destNav = [[UINavigationController alloc] initWithRootViewController:dateVC];/*Here dateVC is controller you want to show in popover*/
    dateVC.preferredContentSize = CGSizeMake(280,200);
    destNav.modalPresentationStyle = UIModalPresentationPopover;
    _dateTimePopover8 = destNav.popoverPresentationController;
    _dateTimePopover8.delegate = self;
    _dateTimePopover8.sourceView = self.view;
     CGRect frame = [[sender valueForKey:@"view"] frame];
    frame.origin.y = frame.origin.y+20;
    _dateTimePopover8.sourceRect = frame;
    destNav.navigationBarHidden = YES;
    [self presentViewController:destNav animated:YES completion:nil];
}

Implement this delegate method too in your view controller:

- (UIModalPresentationStyle) adaptivePresentationStyleForPresentationController: (UIPresentationController * ) controller {
    return UIModalPresentationNone;
}
To dismiss this popover, simply dismiss the view controller. Below is the code to dismiss the view controller:
-(void)hideIOS8PopOver
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

В процессе выполнения домашнего задания разобрался с разными моментами создания Popover (т.к. методы указанные в уроке уже устарели).

Popover легко можно создать в StoryBoard. Создаем UIButton или  UIBarButtonItem, контроллер который будет отображаться как Popover.
От кнопки тянем к новому контроллеру Segue и выбираем PopoverPresentation.
В принципе на Ipad уже будет все работать.
Чтобы и на айфоне нормально выводился поповер, необходимо добавить код:

-(void) prepareForSegue:(nonnull UIStoryboardSegue *)segue sender:(nullable id)sender{
    if ([segue.identifier isEqualToString:@"showView"]) {
        
        UINavigationController * nvc = segue.destinationViewController;
     
        UIPresentationController * pc = nvc.presentationController;
        pc.delegate=self;
    }
}

Так же обязательно присутствие метода для всех типов создания поперев (из кода или storyboard)

- (UIModalPresentationStyle) adaptivePresentationStyleForPresentationController: (UIPresentationController * ) controller {
    return UIModalPresentationNone;
}

Если segue начинается не с кнопки, а просто связывает два контроллера, то можно в action кнопки указать использовать нужный segue:

- (IBAction)actionButton:(UIButton *)sender {

    [self performSegueWithIdentifier:@"showView" sender:self];
}

Так же нужно прописать, что наш класс поддерживает протокол

@interface ViewController ()<UIPopoverPresentationControllerDelegate>

Для создания Popover из кода:
Создаем property
@property(nonatomic,retain)UIPopoverPresentationController *popoverPresentation;

В обработчик кнопки добавляем код

    TAPopoverController *dateVC=[[TAPopoverController alloc]init];
// это контроллер который будут отображен в Popover окне
    
    UINavigationController *destNav = [[UINavigationController alloc] initWithRootViewController:dateVC];/*Here dateVC is controller you want to show in popover*/
    dateVC.preferredContentSize = CGSizeMake(150,150);
    destNav.modalPresentationStyle = UIModalPresentationPopover;
    self.popoverPresentation = destNav.popoverPresentationController;
    self.popoverPresentation.delegate = self;
    self.popoverPresentation.sourceView = self.view;
    CGRect frame = [[sender valueForKey:@"view"] frame];
    frame.origin.y = frame.origin.y+20;
    self.popoverPresentation.sourceRect = frame;
    destNav.navigationBarHidden = YES;
    [self presentViewController:destNav animated:YES completion:nil];

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





Объявляем протокол:

@protocol DataPopoverDelegate <NSObject>
-(void) dataFromPopover:(NSDate*) date;
-(void) currentEducationSelected:(NSInteger) currentEducation;
@end

Родительский контроллер поддерживает этот протокол и реализует методы этого протокола 

@interface ViewController : UITableViewController <UIAdaptivePresentationControllerDelegate,DataPopoverDelegate>

В дочернем контроллере объявляем член класса - делегат 

@interface TADatePickerController : UIViewController
@property (weak, nonatomic) IBOutlet UIDatePicker *birthdayDataPicker;
@property (strong,nonatomic) NSDate *dateBirthday;
@property(weak,nonatomic) id<DataPopoverDelegate> delegate;
@end


Реализация метедов протокола в родительском классе

-(void) dataFromPopover:(NSDate*) date{
    self.dateBirthday=date;
    [self displayBirthday];
}
-(void) currentEducationSelected:(NSInteger) currentEducation{
    self.educationTextField.text=educationArray[currentEducation];
    self.currentEducationSelected=currentEducation;
}

При изменении данных в дочернем классе вызываем метод делегата

- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath {
    self.currentSelection=indexPath.row;
    [tableView reloadData];
    [self.delegate  currentEducationSelected:self.currentSelection];
}

Так же в этом домашнем задании реализовано сохранения и загрузка данных через NSUserDefaults


- (IBAction)actionSaveData:(UIBarButtonItem *)sender {
    NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults];
    [userDefault setObject:self.firstNameTextField.text forKey:kSettingsFirstName];
    [userDefault setObject:self.lastNameTextField.text forKey:kSettingsLastName];
    [userDefault setInteger:self.sexSegmentControl.selectedSegmentIndex forKey:kSettingsSex];
    [userDefault setObject:self.dateBirthday forKey:kSettingsBirthDay];
    [userDefault setInteger:self.currentEducationSelected forKey:kSettingsEducation];
    [userDefault synchronize];

}


- (IBAction)actionLoadData:(id)sender {
    NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults];
    self.firstNameTextField.text=[userDefault objectForKey:kSettingsFirstName];
    self.lastNameTextField.text=[userDefault objectForKey:kSettingsLastName];
    self.sexSegmentControl.selectedSegmentIndex=[userDefault integerForKey:kSettingsSex];
    self.dateBirthday=[userDefault objectForKey:kSettingsBirthDay];
    [self displayBirthday];
    self.currentEducationSelected=[userDefault integerForKey:kSettingsEducation];
    self.educationTextField.text=educationArray[self.currentEducationSelected];
}

Подробное описание   модального диалога Alert - UIAlertcontroller
Домашнее задание

Ученик

1. Создайте универсальное приложение (айпад / айфон)
2. Первый контроллер должен быть статической таблицей с навигейшн баром
3. В правом углу на навигейшине должна быть кнопка инфо, если на нее нажать, то вылазит поповер с объяснением, что это такое за приложение :)

Студент

4. В таблице создайте классические ячейки:
имя + текстфилд
фамилия + текстфилд
пол + сегментед контрол (мужской/женский)
Дата рождения + текстфилд
Образование + текстфилд

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

Мастер

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

7. Подсказка. Вам надо сделать контроллер с дейт пикером, а дейт пикер это наследник от UIControl, то есть у него есть акшин valueChanged или типо того. У контроллера нужно создать проперти делегат, по которому мы будем отправлять данные, полученные с барабана. То есть по простому: контроллер следит за барабаном и отправляет изменения своему делегату. Не забудьте установить делегат перед создания поповера.

Супермен

8. Тоже самое сделать с образованием. Образование это список типа, неполное среднее, среднее, неполное высшее, высшее и тд, то есть если делать в сегментед контролах, то не поместится.

9. Когда нажимаем на образование, появляется поповер с контроллером и таблицей. Причем, выбранное образование должно быть отмечено чекбоксом. (кстати выбранная дата рождения в мастере тоже должна стоять по умолчанию в новом поповере)

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

Выполнены все задания к уроку 36 Popover

Исходник  проекта

суббота, 24 октября 2015 г.

35. UITableView UISearchBar NSOperation

В уроке 35 рассматривается организация поиска в таблице UITableView с помощью UISearchBar.
 Рассматривается выделение поиска и обработки данных в фоновый процесс.
Создается класс- наследник UITableViewController. В storyBoard создается NavigationController и UITableViewController.
Созданный класс связывается с контроллером из storyBoard.
Так же создается UISearchBar, аутлет и делегат указывает на базовый контроллер.
Создается класс секций состоящий из названия секции и массива строк входящих в секцию.

@interface TASection : NSObject

@property (strong,nonatomic) NSString *sectionName;
@property(strong,nonatomic) NSMutableArray *itemArray;


@end

Создается массив случайных строк (используется категория-расширения NSString).

@implementation NSString (Random)
+ (NSString *)randomAlphanumericString
{
    NSInteger length=arc4random()%10+5;

    return [self randomAlphanumericStringWithLength:length];
}
+ (NSString *)randomAlphanumericStringWithLength:(NSInteger)length
{
    NSString *letters = @"abcdefghijklmnopqrstuvwxyz";
    NSMutableString *randomString = [NSMutableString stringWithCapacity:length];
    
    for (int i = 0; i < length; i++) {
        [randomString appendFormat:@"%C", [letters characterAtIndex:arc4random() % [letters length]]];
    }
    
    return randomString;

}

Сортируется этот массив строк с помощью NSSortDescriptor

    NSSortDescriptor *sortDescriptor=[[NSSortDescriptor alloc] initWithKey:@"self" ascending:YES];
    [array sortUsingDescriptors:@[sortDescriptor]];

Создается функция, создающая секции (первая буква строки) и включающая в каждую секцию строки начинающиеся с этой буквы. Так же функция использует фильтр, по которому исключается добавление строк не содержащихся в фильтре.

-(NSArray*) generateSectionsFromArray: (NSArray*) array withFilter: (NSString*) filterString{
    NSString *currentLetter=nil;
    NSMutableArray* sectionArray=[NSMutableArray array];
    
    for (NSString *str in array) {
        if(([filterString length]>0)&&[str rangeOfString:filterString].location==NSNotFound){
            continue;
        }
        NSString *firstletter=[str substringToIndex:1];
        TASection *section=nil;
        if(![currentLetter isEqualToString:firstletter] )
        {
            section=[[TASection alloc] init];
            section.sectionName=firstletter;
            currentLetter=firstletter;
            section.itemArray=[NSMutableArray array];
            [sectionArray addObject:section];
        }
        else
        {
            section= [sectionArray lastObject];;
        }
        [section.itemArray addObject:str];
        
    }
    
    return sectionArray;
}

Для проведения ресурсоемкий расчетов в фоновом процессе и возможности отменить старую операцию (при поступлении новой), добавляем 
@property (strong,nonatomic) NSOperation *currentOperation;

Метод для фоновых вычислений с использованием блоков.

-(void) generateSectionsInBackgroundFromArray: (NSArray*) array withFilter: (NSString*) filterString{
    [self.currentOperation cancel];
    __weak ViewController * weakSelf=self;
    self. currentOperation=[NSBlockOperation blockOperationWithBlock:^{
        NSArray *sectionsArray=[weakSelf generateSectionsFromArray:array withFilter:filterString];
        dispatch_async(dispatch_get_main_queue(), ^{
            weakSelf.sectionArray=sectionsArray;
            [weakSelf.tableView reloadData];
            self.currentOperation=nil;
        });
    }];
    [self.currentOperation start];
}

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


- (nullable NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    NSMutableArray *array=[NSMutableArray array];
    for (TASection * section in self.sectionArray){
        [array addObject:section.sectionName];
    }
    return array;
}

а так же определить делегата для UISearchBar в storyboard.

@interface ViewController : UITableViewController <UISearchBarDelegate>

Для  показа кнопки Отмены при начале редактирования, определяем метод

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar{
    [searchBar setShowsCancelButton:YES animated:YES];
}

Для обработки нажатия клавиши Отмена (например убрать клавиатуру с экрана) определяется следующий метод

-( void) searchBarCancelButtonClicked:(nonnull UISearchBar *)searchBar{
    [searchBar resignFirstResponder];
    [searchBar setShowsCancelButton:NO animated:YES];
}

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

-(void) searchBar:(UISearchBar*) searchBar textDidChange:(nonnull NSString *)searchText{
    [self generateSectionsInBackgroundFromArray:self.arrayNames withFilter:searchText];
}

Исходник с тестовым проектом урока 35.

Домашнее задание к уроку 35:

Ученик.

1. Создайте класс студента. У него должны быть свойства: имя, фамилия и год рождения.
2. Генерируйте случайное количество студентов и отобразите их в вашей таблице. (слева имя и фамилия, а справа дата рождения)

Студент.

3. Сгрупируйте студентов по секциям месяцев рождения, то есть все кто родился в январе в одной секции, а если в феврале никто не родился, то и секции такой нет.
4. Внутри секции студенты должны быть отсортированы по имени по алфавиту, а если имена одинаковы, то и по фамилии (подсказка, лучше отсортировать массив вначале по 3 всем параметрам: дата, имя и фамилия)
5. Добавьте индекс бар для быстрого перехода по секциям

Мастер.

6. Добавьте серчбар как в видео, чтобы кнопочка кенсел анимировано добавлялась/уезжала и тд
7. Фильтруйте студентов каждый раз, когда вводится новая буква, причем совпадения ищите как в имени так и в фамилии

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

Ссылка на исходник программы.