Статьи

iOS SDK: работа с календарями Google

Из этого туториала вы узнаете, как создать приложение, которое будет взаимодействовать с веб-службой Календаря Google с помощью OAuth 2.0. Читай дальше!


В моем последнем уроке я показал вам, как реализовать протокол OAuth 2.0 для доступа к службам Google. К концу этого урока мы создали полностью функциональный класс для доступа к Службам Google с OAuth 2.0. В этом уроке я собираюсь привести этот класс в действие, реализовав демонстрационное приложение. В частности, я собираюсь показать вам, как взаимодействовать с веб-службой Календаря Google.


Приложение, которое мы создадим в этом руководстве, позволит пользователям подключиться к своей учетной записи Google, загрузить свои календари и создать новое событие с описанием и датой / временем. Новое событие будет опубликовано в календаре, который выберет пользователь.

Что касается структуры нашего приложения, основным представлением будет табличное представление, которое будет содержать три раздела для установки следующих данных:

  • Описание события
  • Дата / время события
  • Целевой календарь

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

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

Наконец, раздел календаря будет многофункциональным. Когда пользователь еще не вошел в свою учетную запись Google, в разделе будет существовать только одна строка с сообщением, в котором пользователю предлагается загрузить свои календари. Когда пользователь выбирает это, процесс авторизации будет приведен в действие. После получения токена доступа будет выполнен вызов API для получения списка календарей от Google. После того, как список календарей был занят, первый календарь в списке заменит сообщение с приглашением в строке и станет выбранным календарем по умолчанию.

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

Давайте начнем!


Запустите Xcode и создайте новый проект. Выберите опцию Single View Application и нажмите Next:

gt6_1_project_template

В поле «Имя продукта» добавьте значение GoogleCalendarPostDemo. Конечно, вы можете выбрать другое имя, если хотите. Также обязательно установите флажок «Использовать автоматический подсчет ссылок» и снимите все остальное. После этого продолжайте.

gt6_2_project_options

Наконец, выберите каталог для хранения проекта и нажмите «Создать».

gt6_3_project_create

Теперь вам нужно добавить файлы классов, которые были реализованы в предыдущем уроке. Этот класс станет механизмом, который будет выполнять всю работу за кулисами. Итак, если у вас есть файлы проекта предыдущего урока, получите эти два:

  • GoogleOAuth.h
  • GoogleOAuth.m

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

gt6_4_class_files

Мы будем использовать Interface Builder для настройки интерфейса. Как вы скоро узнаете, будет добавлено несколько подпредставлений, потому что мы хотим сделать демонстрационное приложение максимально функциональным. Вот серия шагов, которые описывают каждое подпредставление, которое вы должны добавить, вместе со свойствами для каждого:

  1. Откройте файл ViewController.xib чтобы настроить интерфейс и добавить все необходимые подпредставления. Прежде всего, установите для размера представления значение «Нет» на панели «Утилиты»> «Инспектор атрибутов»> «Симулированные метрики», чтобы проект работал должным образом на iPhone до 5.
    gt6_5_size_none
  2. Установите цвет фона представления на белый.
  3. Перетащите подвид UIToolbar в представление. Поместите это внизу экрана.
  4. Добавьте следующие элементы на панель инструментов (в порядке слева направо):
    • Панель кнопок с заголовком: Выйти
    • Гибкая клавиша пробела
    • Панель кнопок с заголовком: сообщение
  5. Добавьте подпредставление UITableView в представление и дайте ему занять все доступное пространство, оставшееся в представлении.
  6. Установите следующие два свойства табличного представления:
    • Стиль: Сгруппированный
    • Фон :: Чистый цвет

Следующее — это то, что вы должны иметь на этом этапе:

gt6_6_ib_sample

Далее нам нужно другое представление, которое будет содержать представление выбора даты. Вот шаги:

  1. Добавьте новый вид за пределы вида по умолчанию и установите для его размера значение Нет (как вы делали раньше). Кроме того, установите его Высота в 460.
  2. Установите цвет фона представления для прокрутки вида текстурированный цвет фона.
  3. Добавьте подпредставление UIDatePicker в представлении и отцентрируйте его в соответствии с центром представления.
  4. Добавьте подпредставление UIToolBar внизу представления.
  5. Добавьте следующие элементы панели кнопок на панель инструментов:
    • Панель кнопок с заголовком: отмена
    • Гибкая клавиша пробела
    • Элемент панели кнопок с заголовком: мероприятие на весь день
    • Гибкая клавиша пробела
    • Панель кнопок с заголовком: Хорошо
gt6_7_ib_datepicker

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

  1. Подвид UIToolBar. Это будет представление вспомогательного ввода для текстового поля, которое будет использоваться для редактирования описания события. Добавьте следующие элементы панели кнопок:
    • Панель кнопок с заголовком: отмена
    • Гибкая клавиша пробела
    • Панель кнопок с заголовком: Хорошо
  2. UIActivityIndicatorView со следующими свойствами:
    • Стиль: Большой Белый
    • Фон: черный цвет
gt6_8_ib_iav
gt6_9_ib_activityindicator

Нам понадобятся несколько свойств IBOutlet, связанных с нашими подпредставлениями, чтобы мы могли изменить их в коде. Чтобы подключить свойство IBOutlet к подпредставлению (а также создать метод IBAction), необходимо иметь файл ViewController.h показанный в помощнике редактора. Итак, нажмите на среднюю кнопку панели инструментов редактора Xcode, чтобы она появилась. Убедитесь, что там отображается содержимое файла ViewController.h .

gt6_10_assistant_editor

Я покажу вам, как создать свойство IBOutlet только для табличного представления. Используйте тот же способ для создания свойств для подпредставлений, о которых я расскажу далее.

На панели «Структура документа» или непосредственно в представлении выполните следующие действия:

  1. Щелкните правой кнопкой мыши или щелкните, удерживая клавишу Control, в представлении таблицы.
  2. В черном всплывающем меню нажмите на кружок рядом с параметром «Новый источник ссылок» и удерживайте кнопку мыши нажатой.
  3. Перетащите и отпустите (и в то же время синяя линия будет следовать за вашей мышью) в окне Assistant Editor.
gt6_11_iboutlet_create

В появившемся новом окне добавьте tblPostData в качестве имени свойства и не трогайте другие параметры. Нажмите кнопку «Подключить» или нажмите кнопку «Возврат» на клавиатуре.

gt6_12_iboutlet_name

Вот список всех подпредставлений, которые нам нужны для создания соединений IBOutlet, вместе с их именами. Обязательно следуйте так же, как и раньше, и все будет в порядке.

  • Элемент кнопки barItemPost сообщения: barItemPost
  • Элемент кнопки панели выхода: barItemRevokeAccess
  • Панель инструментов представления аксессуара ввода (единственная панель инструментов): toolbarInputAccessoryView
  • Просмотреть контейнер средства выбора даты: viewDatePicker
  • dpDatePicker даты: dpDatePicker
  • Элемент панели кнопок на весь день: barItemToggleDatePicker
  • Вид индикатора активности: ActivityIndicatorView

Как видите, мы добавили несколько элементов панели кнопок в наши представления. Мы требуем от них реагировать на наши сигналы, поэтому нам нужно создать методы IBAction, чтобы это произошло.

Создание метода IBAction практически такое же, как я продемонстрировал ранее. Только для элемента кнопки «Панель сообщений» приведена подробная процедура:

  1. На панели «Структура документа» или непосредственно в представлении щелкните правой кнопкой мыши или удерживайте нажатой клавишу «Control» на элементе панели кнопок.
  2. В черном всплывающем меню в разделе «Отправленные действия» щелкните кружок рядом с параметром «Выбор» и удерживайте кнопку мыши нажатой.
  3. Перетащите в окно Assistant Editor.
gt6_13_ibaction_create

В новом окне добавьте сообщение в поле Имя и оставьте все остальное как есть. Нажмите «Подключиться» или нажмите кнопку «Возврат» на клавиатуре.

gt6_14_ibaction_name

Теперь следуйте тому же шаблону и создайте следующие методы IBAction для данных подпредставлений:

  • Элемент кнопки панели выхода: revokeAccess
  • Элемент кнопки панели «Окей» на панели инструментов представления аксессуаров ввода: acceptEditingEvent
  • Элемент кнопки «Отмена» на панели инструментов представления аксессуара ввода: cancelEditingEvent
  • Элемент кнопки панели «Окей» на панели инструментов представления « acceptSelectedDate : acceptSelectedDate
  • Элемент кнопки «Отмена» на панели инструментов представления « cancelPickingDate : cancelPickingDate
  • Элемент кнопки на весь день на панели инструментов представления « toggleDatePicker : toggleDatePicker

После добавления всех свойств IBOutlet и всех методов IBAction ваш файл ViewController.h должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITableView *tblPostData;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *barItemPost;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *barItemRevokeAccess;
@property (strong, nonatomic) IBOutlet UIToolbar *toolbarInputAccessoryView;
@property (strong, nonatomic) IBOutlet UIView *viewDatePicker;
@property (weak, nonatomic) IBOutlet UIDatePicker *dpDatePicker;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *barItemToggleDatePicker;
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
 
 
— (IBAction)post:(id)sender;
— (IBAction)revokeAccess:(id)sender;
— (IBAction)acceptEditingEvent:(id)sender;
— (IBAction)cancelEditingEvent:(id)sender;
— (IBAction)acceptSelectedDate:(id)sender;
— (IBAction)cancelPickingDate:(id)sender;
— (IBAction)toggleDatePicker:(id)sender;
 
@end

Теперь, когда интерфейс настроен и настроен, а свойства IBOutlet вместе с методами IBAction созданы и подключены, пришло время приступить к написанию некоторого кода. Ранее мы добавили заголовок GoogleOAuth и файлы реализации в проект, но этого недостаточно, чтобы наш класс работал. Нам также нужно импортировать его в класс контроллера представления и принять его протокол.

Откройте файл ViewController.h и импортируйте файл GoogleOAuth.h вверху файла:

1
2
#import <UIKit/UIKit.h>
#import «GoogleOAuth.h»

В дополнение к протоколу класса GoogleOAuth нам также необходимо принять следующее:

  • UITableViewDelegate: делегат табличного представления.
  • UITableViewDatasource: источник данных табличного представления.
  • UITextFieldDelegate: делегат текстового поля, которое будет использоваться для редактирования описания события.

Таким образом, оставаясь в файле ViewController.h , измените оператор @interface следующим образом:

1
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, GoogleOAuthDelegate>

Есть некоторые свойства и некоторые методы, которые должны быть объявлены в закрытой секции класса и необходимы, чтобы все работало гладко. Эти свойства в основном предназначены для хранения информации о приложении, но некоторые простые флаги также будут использоваться для указания состояния программы.

В следующем фрагменте кода представлены все частные элементы данных, которые вы должны добавить в свой проект (скопируйте и вставьте их, если хотите). Комментарии объясняют все. Не забывайте, что мы работаем сейчас в файле ViewController.m !

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@interface ViewController ()
 
// The string that contains the event description.
// Its value is set every time the event description gets edited and its
// value is displayed on the table view.
@property (nonatomic, strong) NSString *strEvent;
 
// The string that contains the date of the event.
// This is the value that is displayed on the table view.
@property (nonatomic, strong) NSString *strEventDate;
 
// This string is composed right before posting the event on the calendar.
// It’s actually the quick-add string and contains the date data as well.
@property (nonatomic, strong) NSString *strEventTextToPost;
 
// The selected event date from the date picker.
@property (nonatomic, strong) NSDate *dtEvent;
 
// The textfield that is appeared on the table view for editing the event description.
@property (nonatomic, strong) UITextField *txtEvent;
 
// This array is one of the most important properties, as it contains
// all the calendars as NSDictionary objects.
@property (nonatomic, strong) NSMutableArray *arrGoogleCalendars;
 
// This dictionary contains the currently selected calendar.
// It’s the one that appears on the table view when the calendar list
// is collapsed.
@property (nonatomic, strong) NSDictionary *dictCurrentCalendar;
 
// A GoogleOAuth object that handles everything regarding the Google.
@property (nonatomic, strong) GoogleOAuth *googleOAuth;
 
// This flag indicates whether the event description is being edited or not.
@property (nonatomic) BOOL isEditingEvent;
 
// It indicates whether the event is a full-day one.
@property (nonatomic) BOOL isFullDayEvent;
 
// It simply indicates whether the calendar list is expanded or not on the table view.
@property (nonatomic) BOOL isCalendarListExpanded;
 
@end

Кроме того, добавьте следующие объявления методов после свойств и перед оператором @end :

1
2
3
-(void)setupEventTextfield;
-(NSString *)getStringFromDate:(NSDate *)date;
-(void)showOrHideActivityIndicatorView;

После объявления всех этих свойств давайте проведем некоторую инициализацию. Это будет происходить в viewDidLoad: метод. Обратите внимание, что в этом методе мы также устанавливаем self в качестве делегата и источника данных табличного представления.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
— (void)viewDidLoad
{
    [super viewDidLoad];
     
    // Set self as the delegate and datasource of the table view.
    [_tblPostData setDelegate:self];
    [_tblPostData setDataSource:self];
     
    // Set the initial values of the following private properties.
    _strEvent = @»»;
    _strEventDate = @»Pick a date…»;
    _isEditingEvent = NO;
    _isFullDayEvent = NO;
    _isCalendarListExpanded = NO;
     
    // Initialize the googleOAuth object.
    // Pay attention so as to initialize it with the initWithFrame: method, not just init.
    _googleOAuth = [[GoogleOAuth alloc] initWithFrame:self.view.frame];
    // Set self as the delegate.
    [_googleOAuth setGOAuthDelegate:self];
}

Давайте продолжим, написав некоторые источники данных и методы делегатов, связанные с табличным представлением. Пока мы добавим только некоторый стандартный код. По мере продвижения мы добавим еще больше кода, когда это потребуется. Итак, давайте идти вперед. Как я сказал в начале, в табличном представлении будет три раздела:

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

Каждый раздел будет содержать одну строку. Для третьего раздела у нас будет столько строк, сколько поддержит календари. В этом методе вы можете увидеть, как флаг _isCalendarListExpanded используется впервые:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    if (section != 2) {
        return 1;
    }
    else{
        // Depending on whether the calendars are listed in the table view,
        // the respective section will have either one row, or as many as the calendars are.
        if (!_isCalendarListExpanded) {
            return 1;
        }
        else{
            return [_arrGoogleCalendars count];
        }
    }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
    // Set the footer title depending on the section value.
    NSString *footerTitle = @»»;
    if (section == 0) {
        footerTitle = @»Event short description»;
    }
    else if (section == 1){
        footerTitle = @»Event date»;
    }
    else{
        footerTitle = @»Google Calendar»;
    }
     
    return footerTitle;
}

Установите высоту строки для каждой ячейки:

1
2
3
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 50.0;
}

Что касается tableView:cellForRowAtIndexPath: , он будет построен поэтапно, пока мы добавим больше функций в приложение. А пока давайте напишем только основы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *CellIdentifier = @»Cell»;
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier];
     
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
         
        [cell setSelectionStyle:UITableViewCellSelectionStyleGray];
        [cell setAccessoryType:UITableViewCellAccessoryNone];
      
        // Set a font for the cell textLabel.
        [[cell textLabel] setFont:[UIFont fontWithName:@»Trebuchet MS» size:15.0]];
    }
         
    return cell;
}

Наконец, у нас есть только один метод делегата, хорошо известный tableView:didSelectRowAtIndexPath: Как и предыдущий, он также будет построен шаг за шагом. На данный момент, сделайте так, чтобы он удалял только выделение из каждой строки, которая нажата:

1
2
3
4
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    // At first, remove the selection from the tapped cell.
    [[_tblPostData cellForRowAtIndexPath:indexPath] setSelected:NO];
}

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

Прежде чем мы реализуем это поведение, может быть лучше реализовать объявленный нами закрытый метод setupEventTextfield . В этом методе мы будем выполнять следующие задачи:

  • Мы инициализируем текстовое поле, установив фрейм, связанный с фреймом представления содержимого ячейки, и установим стиль.
  • Мы установим содержимое строки strEvent качестве ее текста.
  • Помните (одну) панель инструментов, которую мы добавили ранее в Интерфейсном Разработчике? Мы установим его как вспомогательное представление ввода текстового поля.
  • Мы установим self качестве его делегата, чтобы мы могли обрабатывать клавишу Return на клавиатуре.

Давай увидим это:

01
02
03
04
05
06
07
08
09
10
11
12
13
-(void)setupEventTextfield{
    // Initialize the textfield by setting the following properties.
    // Add or remove properties depending on your demand.
    if (!_txtEvent) {
        _txtEvent = [[UITextField alloc] initWithFrame:CGRectMake(10.0, 10.0,
                                                                  [[_tblPostData cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] contentView].frame.size.width — 20.0,
                                                                  30.0)];
        [_txtEvent setBorderStyle:UITextBorderStyleRoundedRect];
        [_txtEvent setText:_strEvent];
        [_txtEvent setInputAccessoryView:_toolbarInputAccessoryView];
        [_txtEvent setDelegate:self];
    }
}

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

Внутри tableView:didSelectRowAtIndexPath: мы проверим, редактируется ли событие в данный момент. Если нет, то мы вызовем ранее реализованный метод для инициализации текстового поля, изменим статус флага, указывающего, редактируется ли событие, и покажем клавиатуру.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    // At first, remove the selection from the tapped cell.
    [[_tblPostData cellForRowAtIndexPath:indexPath] setSelected:NO];
     
    if ([indexPath section] == 0) {
        // If the row of the first section is tapped, check whether the event description is being edited or not.
        // If not, then setup and show the textfield on the cell.
        if (!_isEditingEvent) {
            [self setupEventTextfield];
        }
        else{
            return;
        }
         
        // Change the value of the isEditingEvent flag.
        _isEditingEvent = !_isEditingEvent;
        // Reload the selected row.
        [_tblPostData reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                            withRowAnimation:UITableViewRowAnimationAutomatic];
         
        // If the textfield has been added as a subview to the cell,
        // then make it the first responder and show the keyboard.
        if (_isEditingEvent) {
            [_txtEvent becomeFirstResponder];
        }
    }
}

Теперь давайте обновим tableView:cellForRowAtIndexPath: так, чтобы он отражал состояние первого раздела в любой момент времени:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    …
    …
    …
    if ([indexPath section] == 0) {
        if (!_isEditingEvent) {
            // If currently the event description is not being edited then just show
            // the value of the strEvent string and let the cell contain a disclosure indicator accessory view.
            // Also, set the gray as the selection style.
            [[cell textLabel] setText:_strEvent];
            [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
            [cell setSelectionStyle:UITableViewCellSelectionStyleGray];
        }
        else{
            // If the event description is being edited, then empty the textLabel text so as to avoid
            // having text behind the textfield.
            // Add the textfield as a subview to the cell’s content view and turn the selection style to none.
            [[cell textLabel] setText:@»»];
            [[cell contentView] addSubview:_txtEvent];
            [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
        }
    }
}

Большой! Если вы запустите приложение сейчас, вы заметите, что текстовое поле появляется в табличном представлении, когда вы нажимаете на строку первого раздела. Однако вы не можете заставить текстовое поле исчезать и сохранять отредактированное значение. Почему? Потому что нам нужно реализовать связанные методы IBAction для элементов кнопок панели вспомогательного ввода ввода.

Давайте начнем с acceptEditingEvent: IBAction. Короче говоря, в этом методе мы сохраним типизированное описание события в строке strEvent , изменим isEditingEvent флага isEditingEvent с клавиатуры и обновим представление таблицы. Вот:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
— (IBAction)acceptEditingEvent:(id)sender {
    // If the strEvent property is already initialized then set its value to nil
    // as it’s going to be re-allocated right after.
    if (_strEvent) {
        _strEvent = nil;
    }
     
    // Keep the text entered in the textfield.
    _strEvent = [[NSString alloc] initWithString:[_txtEvent text]];
 
    // Indicate that no longer the event description is being edited.
    _isEditingEvent = NO;
     
    // Resign the first responder and make the textfield nil.
    [_txtEvent resignFirstResponder];
    [_txtEvent removeFromSuperview];
    _txtEvent = nil;
 
    // Reload the row of the first section of the table view.
    [_tblPostData reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]]
                        withRowAnimation:UITableViewRowAnimationAutomatic];
}

cancelEditingEvent: похоже.

01
02
03
04
05
06
07
08
09
10
11
12
13
— (IBAction)cancelEditingEvent:(id)sender {
    // Indicate that no longer the event description is being edited.
    _isEditingEvent = NO;
     
    // Resign the first responder.
    [_txtEvent resignFirstResponder];
    [_txtEvent removeFromSuperview];
    _txtEvent = nil;
     
    // Reload the first row of the first section of the table view.
    [_tblPostData reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]]
                        withRowAnimation:UITableViewRowAnimationAutomatic];
}

Для того, чтобы быть полностью законченным относительно текстового поля и редактирования описания события в целом, нам осталось сделать еще одну вещь. А именно, мы должны обработать кнопку Return на клавиатуре и заставить ее работать так же, как метод acceptEditingEvent: IBAction. По этой причине реализуйте следующий метод делегата:

1
2
3
4
5
6
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
    // In case the Return button on the keyboard is tapped, call the acceptEditingEvent: method
    // to handle it.
    [self acceptEditingEvent:nil];
    return YES;
}

Как видите, мы просто вызываем метод IBAction и ничего более. Теперь мы на 100% готовы к редактированию событий и манипуляции с текстовым полем.


Одна часть нашего демонстрационного приложения завершена. Давайте продолжим и давайте полностью реализуем функцию выбора даты. В этом случае мы хотим нажать на строку второго раздела в табличном представлении, чтобы показать представление, содержащее средство выбора даты, и через него выбрать дату. После этого мы хотим, чтобы выбранная дата также отображалась в табличном представлении. Особое внимание следует уделить тому факту, что событие может быть установлено на определенное время, но это также может быть событие на весь день, что означает, что выбор времени вообще не имеет смысла. Мы увидим все это дальше.

Давайте начнем с показа представления контейнера средства self.view даты в self.view . Это просто вопрос одной строки, которая должна быть записана в tableView:didSelectRowAtIndexPath: метод делегата, под любым другим контентом, который он имеет до сих пор:

1
2
3
4
5
6
7
8
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    …
    …
    else if ([indexPath section] == 1){
        // If the row of the second section is tapped, just show the view that contains the date picker.
        [self.view addSubview:_viewDatePicker];
    }
}

Хорошо, это было легко. Если вы запустите приложение сейчас и нажмете на строку второго раздела, на экране отобразится вид контейнера выбора даты. Вы заметите, что по умолчанию дата и время представлены в окне выбора. Это хорошо, если мы хотим установить конкретное время для мероприятия. Тем не менее, мы не должны допускать, чтобы время отображалось в окне выбора даты, если речь идет о мероприятии на весь день. Поэтому нам нужно реализовать метод toggleDatePicker: IBAction.

Способ, которым этот метод будет работать, довольно прост. В зависимости от содержимого текущего средства выбора даты мы установим его режим, а также изменим заголовок соответствующего элемента панели кнопок. Не забывайте, что есть также флаг isFullDayEvent , который должен быть изменен соответствующим образом. Ниже приводится реализация:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
— (IBAction)toggleDatePicker:(id)sender {
    if ([_dpDatePicker datePickerMode] == UIDatePickerModeDateAndTime) {
        // If the date picker currently shows both date and time, then set it to show only date
        // and change the title of the barItemToggleDatePicker item.
        // In this case the user selects to make a full-day event.
        [_dpDatePicker setDatePickerMode:UIDatePickerModeDate];
        [_barItemToggleDatePicker setTitle:@»Specific time»];
    }
    else{
        // Otherwise, if only date is shown on the date picker, set it to show time too.
        // The event is no longer a full-day one.
        [_dpDatePicker setDatePickerMode:UIDatePickerModeDateAndTime];
        [_barItemToggleDatePicker setTitle:@»All-day event»];
    }
     
    // Change the flag that indicates whether is a full-day event or not.
    _isFullDayEvent = !_isFullDayEvent;
}

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

Теперь мы знаем, когда событие задано как событие полного дня, но мы все еще не можем сохранить выбранную дату и отобразить ее в виде таблицы. Точно так же мы еще не можем отменить выбор даты и вернуться к нашему представлению.

Итак, давайте работать сейчас над обоими этими методами IBAction.

В acceptSelectedDate: IBAction нам нужно сделать только четыре вещи: (1) сохранить выбранную дату в виде строки, (2) сохранить дату как объект NSDate , (3) удалить представление из суперпредставления и, наконец, (4) перезагрузите наше табличное представление, чтобы показать выбранную дату.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
— (IBAction)acceptSelectedDate:(id)sender
{
    // Keep the selected date as a NSDate object.
    _dtEvent = [_dpDatePicker date];
    // Also, convert it to a string properly formatted depending on whether the event is a full-day one or not
    // by calling the getStringFromDate: method.
    _strEventDate = [[NSString alloc] initWithString:[self getStringFromDate:[_dpDatePicker date]]];
     
    // Remove the view with the date picker from the self.view.
    [_viewDatePicker removeFromSuperview];
     
    // Reload the row of the second section of the table view to reflect the selected date.
    [_tblPostData reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:1]]
                        withRowAnimation:UITableViewRowAnimationAutomatic];
}

В cancelPickingDate: IBAction нам нужно только удалить представление контейнера средства выбора даты из суперпредставления.

1
2
3
4
— (IBAction)cancelPickingDate:(id)sender {
    // Just remove the view with the date picker from the superview.
    [_viewDatePicker removeFromSuperview];
}

В acceptSelectedDate: мы сделали вызов метода getStringFromDate: private, который объявлен, но еще не реализован. Пришло время работать с этим методом. Прежде чем представить код, я должен сделать замечание.

Цель метода getStringFromDate: состоит в том, чтобы получить указанную нами дату (выбранную дату в нашем примере) и вернуть эту дату в виде строки и отформатировать так, как мы хотим. Однако есть два вида форматов строк, которые нам нужны, в зависимости от того, является ли событие полным днем ​​или нет. Если дата события имеет определенное время, мы хотим, чтобы это время присутствовало в строке. Это означает, что в нашем методе будет условие, которое будет определять формат выходной строки. Итак, прояснив наше намерение, введите следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
-(NSString *)getStringFromDate:(NSDate *)date{
    // Create a NSDateFormatter object to handle the date.
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
     
    if (!_isFullDayEvent) {
        // If it’s not a full-day event, then set the date format in a way that contains the time too.
        [formatter setDateFormat:@»EEE, MMM dd, yyyy, HH:mm»];
    }
    else{
        // Otherwise keep just the date.
        [formatter setDateFormat:@»EEE, MMM dd, yyyy»];
    }
     
    // Return the formatted date as a string value.
    return [formatter stringFromDate:date];
}

Выше приведено что-то вроде понедельника, 12 августа 2013 г., 17:32.

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

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

Пока все хорошо: осталось сделать только одно. Добавьте некоторый код в tableView:cellForRowAtIndexPath: метод, чтобы отображалась выбранная дата. Под всем другим содержимым метода добавьте следующий фрагмент:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    …
    …
    …
    else if ([indexPath section] == 1){
        // In the event date cell just show the strEventDate string which either prompts the user
        // to pick a date, or contains the selected date as a string.
        // Also, add a disclosure indicator view.
        [[cell textLabel] setText:_strEventDate];
        [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
    }
 
    return cell;
}

Если вы уже запустили приложение или сейчас, вы заметите, что строка третьего раздела в табличном представлении содержит сообщение Загрузить календари …. Конечно, ничего не происходит, когда вы нажимаете на этот метод. То, что мы хотели бы сделать на касании, это позвонить в Google через API и запросить необходимую нам информацию. Но перед этим мы должны авторизоваться против службы Google и получить токен доступа, который будет использоваться для обмена данными. На самом деле, все это будет сделано классом GoogleOAuth, который мы включили в самом начале. Все, что нужно сделать, это предоставить этому классу идентификатор клиента, секрет клиента и область действия. Итак, давайте посетим веб-сайт разработчиков Google и получим все необходимые нам значения.

Перейдите на сайт разработчика Google . Нажмите на кнопку «Войти», которая существует в верхней правой части веб-страницы, чтобы войти в систему.

gt6_15_sign_in
gt6_16_sign_in_2

После входа прокрутите страницу вниз, пока не найдете значок консоли API.

gt6_17_api_console

Нажмите на нее, и вы будете переведены на панель инструментов, где вы будете обрабатывать все свои проекты. Если необходимо, создайте проект сейчас, в противном случае выберите проект, созданный вами из предыдущего урока этой серии. В меню в левой части веб-страницы выберите параметр доступа к API.

gt6_18_api_access_option

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

gt6_19_client_info

Далее нам нужно сообщить Google, что мы хотим использовать сервис Календари. Для этого нажмите «Сервис» в меню в левой части веб-страницы. Появится список всех предоставляемых услуг. Найдите элемент календаря API. Нажмите кнопку «Выкл.», Чтобы включить конкретный сервис для текущего проекта.

gt6_20_services

То, что мы еще не нашли, — это значение объема для получения необходимой нам календарной информации. Напоминаю, что область действия указывает на API, к которому приложение запрашивает доступ. Лучший способ найти то, что вы хотите, это использовать поисковую систему для веб-сайта разработчиков Google. Итак, перейдите на главную страницу сайта разработчиков Google и найдите термин Google Calendar API. На странице результатов нажмите на первый результат.

gt6_22_search_box

посмотрите вокруг, если хотите, и изучите меню. Для этого урока вы должны перейти в Справочник> Список календарей> список. Загружена новая веб-страница, содержащая информацию о вызове API списка календаря. Найдите область авторизации внизу страницы. Там вы можете найти необходимую область: https://www.googleapis.com/auth/calendar .

gt6_21_scopes_calendarlist

В предыдущих шагах я показал вам, как вы можете включить службу в Google и как найти то, что вам нужно, на веб-сайте разработчика. Теперь мы можем продолжать строить приложение.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    …
    …
    …
    else if ([indexPath section] == 2){
        if (_arrGoogleCalendars == nil || [_arrGoogleCalendars count] == 0) {
            // If the arrGoogleCalendars array is nil or contains nothing, then the calendars should be
            // downloaded from Google.
            // So, show the activity indicator view and authorize the user by calling the the next
            // method of our custom-made class.
            [self showOrHideActivityIndicatorView];
            [_googleOAuth authorizeUserWithClienID:@»YOUR_CLIENT_ID»
                                   andClientSecret:@»YOUR_CLIENT_SECRET»
                                     andParentView:self.view
                                         andScopes:[NSArray arrayWithObject:@»https://www.googleapis.com/auth/calendar»]];
        }
}

Вы заметите, что выше мы также отображаем индикатор. Мы делаем это, потому что мы не знаем, сколько времени потребуется, чтобы получить разрешение. Не забудьте установить свои собственные значения для идентификатора клиента и его секрета!

Когда вы используете приложение в первый раз, появится встроенное веб-представление. Вы должны ввести свои учетные данные и войти в свою учетную запись Google, чтобы приложение могло получить доступ к вашим календарям. Если все будет хорошо, вы без проблем авторизуетесь.


На этом этапе нам нужно реализовать некоторые методы делегата GoogleOAuth. Давайте начнем с метода authorizationWasSuccessful , где мы обработаем успешную авторизацию, сделав вызов API для получения списка календаря.

Однако нам нужна строка URL API, если мы хотим продолжить. Перейдите на соответствующую страницу списка календарей в своей учетной записи разработчика Google. В верхней части страницы вы найдете URL-адрес вместе с методом HTTP, который следует использовать.

gt6_23_api_calendarlist

Теперь мы можем реализовать метод делегата:

1
2
3
4
5
6
7
8
9
-(void)authorizationWasSuccessful{
    // If user authorization is successful, then make an API call to get the calendar list.
    // For more infomation about this API call, visit:
    // https://developers.google.com/google-apps/calendar/v3/reference/calendarList/list
    [_googleOAuth callAPI:@»https://www.googleapis.com/calendar/v3/users/me/calendarList»
           withHttpMethod:httpMethod_GET
       postParameterNames:nil
      postParameterValues:nil];
}

Если все работает согласно плану, метод responseFromServiceWasReceived:andResponseJSONAsData: делегат будет вызван классом GoogleOAuth. Мы несем ответственность за проверку того, ответил ли Google желаемые результаты, а затем за сохранение только тех данных, которые нам нужны.

Давайте немного поговорим о том, что содержится в ответе и как мы собираемся управлять данными. Сначала нам нужно преобразовать ответные данные JSON в объект NSDictionary. Если вы NSLog этот словарь, мы увидим, как формируются возвращенные данные. Смотрите следующий пример:

1
2
3
4
5
6
7
8
9
{
etag = «\»AaJWGrGt8CrZSonQa3iAA4QAo_s/oZDiRXBvAIXr3JkNwKQRZZfQzQ4\»»;
    items = (
                { … }
        { … }
        { … }
    );
    kind = «calendar#calendarList»;
}

Внутри каждой фигурной скобки есть блок, содержащий кучу информации о каждом календаре, который вы создали в Google Calendars. Объект items эквивалентен массиву, который содержит словари в качестве объектов. Другими словами, мы NSArray объект items как NSArray и обработаем каждый его объект как объект NSDictionary .

Давайте вернемся на правильный путь.Как только мы получим все элементы как NSArrayобъекты, мы пройдем цикл, чтобы получить доступ к деталям каждого календаря, и мы сохраним только те значения, которые мы хотим для целей этого примера. На самом деле, мы собираемся создать пары ключ-значение с этими значениями с целью создания новых NSDictionaries, которые будут храниться в arrGoogleCalendarsмассиве. Этот массив является списком календаря для нашего приложения. Также dictCurrentCalendarсловарь будет инициализирован содержимым первого календаря из списка. Как только это будет сделано, элементы Post и Logout станут активными. Мы также скроем представление индикатора активности и обновим таблицу, чтобы показать выбранный календарь.

Вот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
-(void)responseFromServiceWasReceived:(NSString *)responseJSONAsString andResponseJSONAsData🙁NSData *)responseJSONAsData{
    NSError *error;
     
    if ([responseJSONAsString rangeOfString:@"calendarList"].location != NSNotFound) {
        // If the response from Google contains the "calendarList" literal, then the calendar list
        // has been downloaded.
         
        // Get the JSON data as a dictionary.
        NSDictionary *calendarInfoDict = [NSJSONSerialization JSONObjectWithData:responseJSONAsData options:NSJSONReadingMutableContainers error:&error];
         
        if (error) {
            // This is the case that an error occured during converting JSON data to dictionary.
            // Simply log the error description.
            NSLog(@»%@», [error localizedDescription]);
        }
        else{
            // Get the calendars info as an array.
            NSArray *calendarsInfo = [calendarInfoDict objectForKey:@"items"];
 
            // If the arrGoogleCalendars array is nil then initialize it so to store each calendar as a NSDictionary object.
            if (_arrGoogleCalendars == nil) {
                _arrGoogleCalendars = [[NSMutableArray alloc] init];
            }
             
            // Make a loop and get the next data of each calendar.
            for (int i=0; i<[calendarsInfo count]; i++) {
                // Store each calendar in a temporary dictionary.
                NSDictionary *currentCalDict = [calendarsInfo objectAtIndex:i];
                                 
                 
                // Create an array which contains only the desired data.
                NSArray *values = [NSArray arrayWithObjects:[currentCalDict objectForKey:@"id"],
                                   [currentCalDict objectForKey:@"summary"],
                                   nil];
                // Create an array with keys regarding the values on the previous array.
                NSArray *keys = [NSArray arrayWithObjects:@"id", @"summary", nil];
                 
                // Add key-value pairs in a dictionary and then add this dictionary into the arrGoogleCalendars array.
                [_arrGoogleCalendars addObject:
                 [[NSMutableDictionary alloc] initWithObjects:values forKeys:keys]];
            }
             
            // Set the first calendar as the selected one.
            _dictCurrentCalendar = [[NSDictionary alloc] initWithDictionary:[_arrGoogleCalendars objectAtIndex:0]];
             
            // Enable the post and the sign out bar button items.
            [_barItemPost setEnabled:YES];
            [_barItemRevokeAccess setEnabled:YES];
             
            // Stop the activity indicator view.
            [self showOrHideActivityIndicatorView];
             
            // Reload the table view section.
            [_tblPostData reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:2]]
                                withRowAnimation:UITableViewRowAnimationAutomatic];
        }
    }
}

Теперь, когда мы сделали все вышеперечисленное, мы хотим иметь возможность нажать на название календаря и развернуть полный список, что позволит нам выбрать другой календарь. Это будет сделано в tableView:didSelectRowAtIndexPath:методе. Добавьте следующий фрагмент кода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        …
        …
        …
        else{
            // In this case the calendars exist in the arrGoogleCalendars array.
            if (_isCalendarListExpanded) {
                // If the calendar list is shown on the table view, then the tapped one shoule become the selected calendar.
                // Re-initialize the dictCurrentCalendar dictionary so it contains the information regarding the selected one.
                _dictCurrentCalendar = nil;
                _dictCurrentCalendar = [[NSDictionary alloc] initWithDictionary:[_arrGoogleCalendars objectAtIndex:[indexPath row]]];
            }
             
            // Change the value of the isCalendarListExpanded which indicates whether only the selected calendar is shown, or the
            // whole list.
            _isCalendarListExpanded = !_isCalendarListExpanded;
 
            // Finally, reload the section.
            [_tblPostData reloadSections:[NSIndexSet indexSetWithIndex:2]
                        withRowAnimation:UITableViewRowAnimationAutomatic];
        }
    }
     
}

Наконец, нам нужно обновить tableView:cellForRowAtIndexPath:метод, чтобы отобразить все, что мы сделали. Добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    …
    …
    …
    else if ([indexPath section] == 2){
        // This is the case where either the selected calendar is shown, or a list with all of them.
        if (!_isCalendarListExpanded) {
            // If the calendar list is not expanded and only the selected calendar is shown,
            // then if the arrGoogleCalendars array is nil or it doesn't have any contents at all prompt
            // the user to download them now.
            // Otherwise show the summary (title) of the selected calendar along with a disclosure indicator.
            if (![_arrGoogleCalendars count] || [_arrGoogleCalendars count] == 0) {
                [[cell textLabel] setText:@"Download calendars..."];
            }
            else{
                [[cell textLabel] setText:[_dictCurrentCalendar objectForKey:@"summary"]];
            }
             
            [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
        }
        else{
            // This is the case where all the calendars should be listed.
            // Note that each calendar is represented as a NSDictionary which is read from the
            // arrGoogleCalendars array.
            // If the calendar that is shown in the current cell is the already selected one,
            // then add the checkmark accessory type to the cell, otherwise set the accessory type to none.
            NSDictionary *tempDict = [_arrGoogleCalendars objectAtIndex:[indexPath row]];
            [[cell textLabel] setText:[tempDict objectForKey:@"summary"]];
             
            if ([tempDict isEqual:_dictCurrentCalendar]) {
                [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
            }
            else{
                [cell setAccessoryType:UITableViewCellAccessoryNone];
            }
        }
    }
     
    return cell;
}

Вот и все.Иди и попробуй. Смотрите свои календари на Симуляторе и поиграйте некоторое время, расширив список календарей и выбрав календарь!


Давайте посмотрим, как нам удастся добавить событие в выбранный календарь. Первое, что мы должны сделать, это убедиться, что описание события и дата события были установлены. Чтобы убедиться в этом, мы проверим их значения и покажем предупреждение, если что-то не так.

Наша работа теперь будет проходить по post:методу IBAction.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (IBAction)post:(id)sender {
    // Before posting the event, check if the event description is empty or a date has not been selected.
    if ([_strEvent isEqualToString:@""]) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@""
                                                        message:@"Please enter an event description."
                                                       delegate:self
                                              cancelButtonTitle:nil
                                              otherButtonTitles:@"Okay", nil];
        [alert show];
        return;
    }
     
    if ([_strEventDate isEqualToString:@"Pick a date..."]) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@""
                                                        message:@"Please select a date for the event."
                                                       delegate:self
                                              cancelButtonTitle:nil
                                              otherButtonTitles:@"Okay", nil];
        [alert show];
        return;
    }
 
    …
}

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

Как я уже сказал, в этом проекте мы на самом деле реализуем функцию быстрого добавления, которую поддерживает Календарь Google. Однако нам нужно знать формат строки события, потому что есть некоторые правила, которые применяются. Например, существуют специальные способы добавления даты и времени события в конце строки описания события, чтобы Google знал точную дату / время события. Конечно, если вы уже использовали онлайн-функцию быстрого добавления, то вы наверняка знаете, о чем я говорю.

К счастью, Google предоставляет помощь и примеры по этой проблеме. Нам просто нужно посетить документацию Quick Add . Зайдите на этот сайт и ознакомьтесь с этой функцией.

В нашем случае, если у нас есть событие полного дня, мы просто должны добавить дату, используя косую черту в конце строки описания события (например, «Это событие 08/12/2013»). Если у нас нет мероприятия на целый день, мы также добавим время, используя «at» между датой и временем (например, «Это событие 08/12/2013 в 21:40») ,

Кроме того, нам нужно знать URL API, который мы хотим вызвать. Как и в случае со списком календарей, мы должны найти информацию о быстром добавлении в документации по API Календаря Google. Если вы не хотите заморачиваться искать его прямо сейчас, это то , где вы можете найти его. Мы должны искать следующую информацию:

  • Запрос: это URL API, который мы хотим вызвать, так как он появился в верхней части страницы. Также обратите внимание, что нам нужно использовать метод POST HTTP.
  • Параметры POST: вот параметры, которые мы должны отправить с помощью метода POST. Есть только два обязательных параметра: идентификатор календаря, в который мы хотим добавить событие, и текст события (конечно, отформатированный с датой, как я указывал ранее).
  • Авторизация: это та область, которую мы должны авторизовать. В этом случае область действия равна области списка календаря, поэтому нам не нужно заботиться об этом. Однако, если это было другое значение, мы должны включить его в массив области действия во время авторизации.

Итак, давайте вернемся к нашему методу, чтобы закончить вещи. Обновите наш код следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (IBAction)post:(id)sender {
    …
    …
    …
    // Create the URL string of API needed to quick-add the event into the Google calendar.
    // Note that we specify the id of the selected calendar.
    NSString *apiURLString = [NSString stringWithFormat:@"https://www.googleapis.com/calendar/v3/calendars/%@/events/quickAdd",
                              [_dictCurrentCalendar objectForKey:@"id"]];
 
    // Build the event text string, composed by the event description and the date (and time) that should happen.
    // Break the selected date into its components.
    NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
    dateComponents = [[NSCalendar currentCalendar] components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit
                                                     fromDate:_dtEvent];
     
    if (_isFullDayEvent) {
        // If a full-day event was selected (meaning without specific time), then add at the end of the string just the date.
        _strEventTextToPost = [NSString stringWithFormat:@"%@ %d/%d/%d", _strEvent, [dateComponents month], [dateComponents day], [dateComponents year]];
    }
    else{
        // Otherwise, append both the date and the time that the event should happen.
        _strEventTextToPost = [NSString stringWithFormat:@"%@ %d/%d/%d at %d.%d", _strEvent, [dateComponents month], [dateComponents day], [dateComponents year], [dateComponents hour], [dateComponents minute]];
    }
 
    // Show the activity indicator view.
    [self showOrHideActivityIndicatorView];
     
    // Call the API and post the event on the selected Google calendar.
    // Visit https://developers.google.com/google-apps/calendar/v3/reference/events/quickAdd for more information about the quick-add event API call.
    [_googleOAuth callAPI:apiURLString
           withHttpMethod:httpMethod_POST
       postParameterNames:[NSArray arrayWithObjects:@"calendarId", @"text", nil]
      postParameterValues:[NSArray arrayWithObjects:[_dictCurrentCalendar objectForKey:@"id"], _strEventTextToPost, nil]];
}

Если вы запустите приложение сейчас и нажмете кнопку «Опубликовать», событие будет опубликовано. Вы можете убедиться в этом, проверив свой календарь в веб-браузере. Однако пока мы не обработаем ответ от Google, мы не сможем узнать, было ли событие успешно добавлено или нет.

Каждый раз, когда событие публикуется, Google создает соответствующий объект, который содержит несколько свойств и данных. После успешного добавления Google отправляет эти данные, чтобы мы могли получить любую необходимую нам информацию о недавно созданном событии. В нашем случае мы не будем делать что-то необычное. Мы просто покажем представление предупреждений, которое будет содержать следующие значения, поэтому мы уверены, что событие было успешно опубликовано.

  • Идентификатор: каждое новое событие получает уникальное значение идентификатора, которое мы собираемся отобразить в представлении предупреждений.
  • Дата создания: дата создания события.
  • Резюме: само описание события.

Конечно, в реальном приложении вы должны обрабатывать данные события по-другому. Всегда учитывайте потребности приложения.

Мы responseFromServiceWasReceived:andResponseJSONAsData:снова будем работать в методе. Обратите внимание, что данные JSON снова преобразуются в NSDictionaryобъект.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-(void)responseFromServiceWasReceived:(NSString *)responseJSONAsString andResponseJSONAsData🙁NSData *)responseJSONAsData{
    NSError *error;
    …
    …
    …
    else if ([responseJSONAsString rangeOfString:@"calendar#event"].location != NSNotFound){
        // If the Google response contains the "calendar#event" literal then the event has been added to the selected calendar
        // and Google returns data related to the new event.
         
        // Get the response JSON as a dictionary.
        NSDictionary *eventInfoDict = [NSJSONSerialization JSONObjectWithData:responseJSONAsData options:NSJSONReadingMutableContainers error:&error];
         
        if (error) {
            // This is the case that an error occured during converting JSON data to dictionary.
            // Simply log the error description.
            NSLog(@»%@», [error localizedDescription]);
            return;
        }
 
        // An alert view with some information regarding the just added event will be shown.
        // Keep only the information that will be shown to the alert view.
        // Look at the https://developers.google.com/google-apps/calendar/v3/reference/events#resource for a complete list of the
        // data fields that Google returns.
        NSString *eventID = [eventInfoDict objectForKey:@"id"];
        NSString *created = [eventInfoDict objectForKey:@"created"];
        NSString *summary = [eventInfoDict objectForKey:@"summary"];
         
        // Build the alert message.
        NSString *alertMessage = [NSString stringWithFormat:@"ID: %@\n\nCreated:%@\n\nSummary:%@", eventID, created, summary];
         
        // Stop the activity indicator view.
        [self showOrHideActivityIndicatorView];
         
        // Show the alert view.
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"New event"
                                                        message:alertMessage
                                                       delegate:self
                                              cancelButtonTitle:nil
                                              otherButtonTitles:@"Great", nil];
        [alert show];
    }
}

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


Выход из учетной записи Google — это опция, которая всегда должна предоставляться пользователям, хотя рекомендуется держать их подключенными для более быстрого доступа к онлайн-сервисам. Для нашего приложения это всего лишь одна строка кода, которую мы добавим в revokeAccess:метод IBAction.

1
2
3
4
- (IBAction)revokeAccess:(id)sender {
    // Revoke the access token.
    [_googleOAuth revokeAccessToken];
}

revokeAccessTokenМетод будет сделать вызов к accessTokenWasRevokedметоду делегата , чтобы сообщить нашему классу , что маркер доступа был отменен. Это будет реализовано дальше.


До сих пор мы использовали только два метода делегата класса GoogleOAuth — authorizationWasSuccessfulи responseFromServiceWasReceived:andResponseJSONAsData:. Есть еще три из них, которые мы должны реализовать. Нам нужен метод делегата для обработки отзыва доступа, другой делегат для обработки любой ошибки, которая может возникнуть, и последний метод для обработки любых сообщений об ошибках, которые могут существовать в ответах Google.

Что касается отзыва доступа, нам нужно сделать только три вещи. Во-первых, нам нужно удалить все календари из arrGoogleCalendarsмассива. Далее нам нужно отключить кнопки «Опубликовать» и «Выйти». Наконец, нам нужно перезагрузить табличное представление, чтобы поддерживать его в актуальном состоянии.

01
02
03
04
05
06
07
08
09
10
11
12
-(void)accessTokenWasRevoked{
    // Remove all calendars from the array.
    [_arrGoogleCalendars removeAllObjects];
    _arrGoogleCalendars = nil;
     
    // Disable the post and sign out bar button items.
    [_barItemPost setEnabled:NO];
    [_barItemRevokeAccess setEnabled:NO];
     
    // Reload the Google calendars section.
    [_tblPostData reloadSections:[NSIndexSet indexSetWithIndex:2] withRowAnimation:UITableViewRowAnimationAutomatic];
}

Для следующих двух методов делегата обработки ошибок мы не будем много делать. Мы просто зарегистрируем сообщения об ошибках и ничего больше. Очевидно, что в реальном приложении вам придется обрабатывать ошибки надлежащим образом и находить обходные пути, которые обрабатывают неожиданные ситуации. На данный момент вот наши реализации заглушки:

01
02
03
04
05
06
07
08
09
10
-(void)errorOccuredWithShortDescription:(NSString *)errorShortDescription andErrorDetails🙁NSString *)errorDetails{
    // Just log the error messages.
    NSLog(@"%@", errorShortDescription);
    NSLog(@"%@", errorDetails);
}
 
-(void)errorInResponseWithBody:(NSString *)errorMessage{
    // Just log the error message.
    NSLog(@"%@", errorMessage);
}

В ходе реализации проекта мы сделали несколько вызовов showOrHideActivityIndicatorViewчастного метода, который был объявлен и вызван, но еще не создан. Давайте разберемся с этим сейчас.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
-(void)showOrHideActivityIndicatorView{
    // If the activity indicator view is not currently animating (spinning),
    // then set its view center equal to self view's center, add it as a subview and start animating.
    // Otherwise stop animating and remove it from the superview.
    if (![_activityIndicatorView isAnimating]) {
        [_activityIndicatorView setCenter:self.view.center];
        [self.view addSubview:_activityIndicatorView];
        [_activityIndicatorView startAnimating];
    }
    else{
        [_activityIndicatorView stopAnimating];
        [_activityIndicatorView removeFromSuperview];
    }
 
}

Это было последнее, что мы должны были сделать. Наконец-то наше приложение закончено! Попробуйте все поддерживаемые функции.


В этом уроке я продемонстрировал, как работать с Google Calendars, используя OAuth 2.0. Хотя реализация веб-службы в приложении — это большая работа, я надеюсь, что вы найдете, что усилия стоили того! Спасибо за чтение, и не стесняйтесь оставлять любые вопросы или отзывы в комментариях ниже!