Создание приложений iOS Objective-C с применением Microsoft Graph
В этом руководстве рассказывается о создании приложения React Native, которое использует API microsoft Graph для получения сведений о календаре для пользователя.
Совет
Если вы предпочитаете просто скачать завершенный учебник, вы можете скачать или клонировать GitHub репозиторий.
Предварительные требования
Перед началом этого учебного пособия на компьютере разработки должно быть установлено следующее.
Вы также должны иметь личную учетную запись Майкрософт с почтовым ящиком на Outlook.com или учетную запись Microsoft work или school. Если у вас нет учетной записи Майкрософт, существует несколько вариантов получения бесплатной учетной записи:
- Вы можете зарегистрироваться на новую личную учетную запись Майкрософт.
- Вы можете зарегистрироваться в программе Microsoft 365 разработчика, чтобы получить бесплатную Microsoft 365 подписку.
Примечание
Этот учебник был написан с помощью Xcode версии 12.3 и CocoaPods версии 1.10.1. Действия в этом руководстве могут работать с другими версиями, но они не были проверены.
Отзывы
Обратите внимание на этот учебник в репозитории GitHub.
Создание приложения iOS Objective-C
Начните с создания нового проекта Objective-C.
Откройте Xcode. В меню File выберите New, а затем Project.
Выберите шаблон Приложения и выберите Далее.
Установите имя продукта и
GraphTutorial
язык в Objective-C.Заполните оставшиеся поля и выберите Далее.
Выберите расположение для проекта и выберите Create.
Установка зависимостей
Прежде чем двигаться дальше, установите дополнительные зависимости, которые вы будете использовать позже.
- Библиотека проверки подлинности Майкрософт (MSAL) для iOS для проверки подлинности в Azure AD.
- Microsoft Graph SDK for Objective C для звонков в Microsoft Graph.
- Microsoft Graph модели SDK для objective C для сильно типных объектов, представляющих microsoft Graph ресурсов, таких как пользователи или события.
Выйти из Xcode.
Откройте терминал и измените каталог на расположение вашего проекта GraphTutorial.
Запустите следующую команду, чтобы создать Podfile.
pod init
Откройте Podfile и добавьте следующие строки сразу после
use_frameworks!
строки.pod 'MSAL', '~> 1.1.13' pod 'MSGraphClientSDK', ' ~> 1.0.0' pod 'MSGraphClientModels', '~> 1.3.0'
Сохраните podfile и запустите следующую команду для установки зависимостей.
pod install
После завершения команды откройте вновь созданное пространство GraphTutorial.xcworkspace в Xcode.
Проектирование приложения
В этом разделе будут создаваться представления для приложения: вход на страницу, навигатор панели вкладок, страница приветствия и страница календаря. Вы также создайте наложение индикатора активности.
Создание знака на странице
Расширь папку GraphTutorial в Xcode, а затем выберите файл ViewController.m.
В файловом инспекторе измените имя файла на
SignInViewController.m
.Откройте SignInViewController.m и замените его содержимое следующим кодом.
#import "SignInViewController.h" @interface SignInViewController () @end @implementation SignInViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. } - (IBAction)signIn { [self performSegueWithIdentifier: @"userSignedIn" sender: nil]; } @end
Выберите файл ViewController.h.
В файловом инспекторе измените имя файла на
SignInViewController.h
.Откройте SignInViewController.h и измените все экземпляры
ViewController
наSignInViewController
.Откройте файл Main.storyboard.
Развяви сцену контроллера представления, выберите контроллер представления.
Выберите инспектор удостоверений, а затем измените выпадание класса на SignInViewController.
Выберите библиотеку, затем перетащите кнопку на контроллер "Вход в поле зрения".
Выбрав выбранную кнопку, выберите инспектор атрибутов и измените название кнопки на
Sign In
.Выбрав выбранную кнопку, выберите кнопку Выравнивание в нижней части сюжетного доски. Выберите горизонтально в контейнере и вертикально в контейнерных ограничениях, оставьте их значения как 0, а затем добавьте 2 ограничения.
Выберите контроллер Вход в представлении, а затем выберите инспектор подключений.
В соответствии с полученными действиями перетащите незаполненным кругом рядом, чтобы подписатьIn на кнопку. Выберите Touch Up Inside в всплывающее меню.
Создание панели вкладок
Выберите библиотеку, затем перетащите контроллер панели вкладок на доску.
Выберите контроллер Вход в представлении, а затем выберите инспектор подключений.
В статье Triggered Segues перетащите неисполненный круг рядом с вручную на контроллер панели вкладок на доске. Выберите Present Modally в всплывающее меню.
Выберите только что добавленную сегу, а затем инспектор атрибутов. Установите поле Идентификатор и установите
userSignedIn
презентацию на полный экран.Выберите элемент 1 Сцена, а затем выберите инспектор подключений.
В статье Triggered Segues перетащите неисполненный круг рядом с вручную на контроллер просмотра sign in the storyboard. Выберите Present Modally в всплывающее меню.
Выберите только что добавленную сегу, а затем инспектор атрибутов. Установите поле Идентификатор и установите
userSignedOut
презентацию на полный экран.
Создание страницы приветствия
Выберите файл Assets.xcassets.
В меню редактора выберите Добавить новый актив, а затем набор изображений.
Выберите новый актив Image и используйте инспектор атрибутов, чтобы установить его имя.
DefaultUserPhoto
Добавьте любое изображение, которое вы хотите выполнять в качестве фотографии профиля пользователя по умолчанию.
Создайте новый файл класса касания какао в папке GraphTutorial с именем
WelcomeViewController
. Выберите UIViewController в подклассе поля.Откройте WelcomeViewController.h и добавьте в декларацию следующий
@interface
код.@property (nonatomic) IBOutlet UIImageView *userProfilePhoto; @property (nonatomic) IBOutlet UILabel *userDisplayName; @property (nonatomic) IBOutlet UILabel *userEmail;
Откройте WelcomeViewController.m и замените содержимое следующим кодом.
#import "WelcomeViewController.h" @interface WelcomeViewController () @end @implementation WelcomeViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. // TEMPORARY self.userProfilePhoto.image = [UIImage imageNamed:@"DefaultUserPhoto"]; self.userDisplayName.text = @"Default User"; [self.userDisplayName sizeToFit]; self.userEmail.text = @"default@contoso.com"; [self.userEmail sizeToFit]; } - (IBAction)signOut { [self performSegueWithIdentifier: @"userSignedOut" sender: nil]; } @end
Откройте main.storyboard. Выберите сцену Элемента 1, а затем выберите инспектора удостоверений. Измените значение Класса на WelcomeViewController.
С помощью библиотеки добавьте следующие элементы в сцену Item 1.
- Одно представление изображения
- Две метки
- Одна кнопка
С помощью инспектора подключений сделайте следующие подключения.
- Связать выход userDisplayName с первой меткой.
- Ссылка розетки userEmail на вторую метку.
- Привязывай выход userProfilePhoto к представлению изображения.
- Ссылка действия signOut на кнопку Touch Up Inside.
Выберите представление изображения, а затем выберите инспектор размера.
Установите ширину и высоту до 196.
Используйте кнопку Выравнивание, чтобы добавить ограничение горизонтально в контейнере со значением 0.
Используйте кнопку Добавить новые ограничения (рядом с кнопкой Выравнивание), чтобы добавить следующие ограничения:
- Выравнивание сверху до: Сейф области, значение: 0
- Нижнее пространство: имя отображения пользователя, значение: Стандартный
- Высота, значение: 196
- Ширина, значение: 196
Выберите первую метку, а затем используйте кнопку Выравнивание, чтобы добавить ограничение горизонтально в контейнере со значением 0.
Используйте кнопку Добавить новые ограничения, чтобы добавить следующие ограничения:
- Верхнее пространство для: Фотография профиля пользователя, значение: Стандартный
- Нижнее пространство: электронная почта пользователя, значение: Стандартный
Выберите вторую метку, а затем выберите инспектор атрибутов.
Измените цвет на темно-серый и измените шрифт на System 12.0.
Используйте кнопку Выравнивание, чтобы добавить ограничение горизонтально в контейнере со значением 0.
Используйте кнопку Добавить новые ограничения, чтобы добавить следующие ограничения:
- Верхнее пространство: имя отображения пользователя, значение: Стандартный
- Нижнее пространство: Выход, значение: 14
Выберите кнопку, а затем выберите инспектор атрибутов.
Измените название
Sign Out
на .Используйте кнопку Выравнивание, чтобы добавить ограничение горизонтально в контейнере со значением 0.
Используйте кнопку Добавить новые ограничения, чтобы добавить следующие ограничения:
- Верхнее пространство: электронная почта пользователя, значение: 14
Выберите элемент панели вкладок в нижней части сцены, а затем выберите инспектор атрибутов. Измените название
Me
на .В меню редактора выберите разрешить проблемы с автостройкой, а затем выберите Добавить отсутствующие ограничения под всеми представлениями в контроллере welcome View.
Приветствие должно выглядеть примерно так же, как и после этого.
Создание страницы календаря
Создайте новый файл класса касания какао в папке GraphTutorial с именем
CalendarViewController
. Выберите UIViewController в подклассе поля.Откройте CalendarViewController.h и добавьте в декларацию следующий
@interface
код.@property (nonatomic) IBOutlet UITextView *calendarJSON;
Откройте CalendarViewController.m и замените его содержимое следующим кодом.
#import "CalendarViewController.h" @interface CalendarViewController () @end @implementation CalendarViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. // TEMPORARY self.calendarJSON.text = @"Calendar"; [self.calendarJSON sizeToFit]; } @end
Откройте main.storyboard. Выберите сцену Элемента 2, а затем выберите инспектора удостоверений. Измените значение Класса на CalendarViewController.
С помощью библиотеки добавьте текстовое представление в элемент 2 Scene.
Выберите только что добавленное текстовое представление. В редакторе выберите встраить, а затем просмотреть прокрутку.
С помощью инспектора подключений соедините выход calendarJSON с текстовым представлением.
Выберите элемент панели вкладок в нижней части сцены, а затем выберите инспектор атрибутов. Измените название
Calendar
на .В меню редактора выберите разрешить проблемы с автостройкой, а затем выберите Добавить отсутствующие ограничения под всеми представлениями в контроллере welcome View.
После этого сцена календаря должна выглядеть примерно так же.
Создание индикатора активности
Создайте новый файл класса касания какао в папке GraphTutorial с именем
SpinnerViewController
. Выберите UIViewController в подклассе поля.Откройте SpinnerViewController.h и добавьте в декларацию следующий
@interface
код.- (void) startWithContainer:(UIViewController*) container; - (void) stop;
Откройте SpinnerViewController.m и замените содержимое следующим кодом.
#import "SpinnerViewController.h" @interface SpinnerViewController () @property (nonatomic) UIActivityIndicatorView* spinner; @end @implementation SpinnerViewController - (void)viewDidLoad { [super viewDidLoad]; _spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleLarge]; self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.7]; [self.view addSubview:_spinner]; _spinner.translatesAutoresizingMaskIntoConstraints = false; [_spinner startAnimating]; [_spinner.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = true; [_spinner.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = true; } - (void) startWithContainer:(UIViewController *)container { [container addChildViewController:self]; self.view.frame = container.view.frame; [container.view addSubview:self.view]; [self didMoveToParentViewController:container]; } - (void) stop { [self willMoveToParentViewController:nil]; [self.view removeFromSuperview]; [self removeFromParentViewController]; } @end
Тестирование приложения
Сохраните изменения и запустите приложение. Вы должны иметь возможность перемещаться между экранами с помощью кнопок Вход и Выход и вкладка.
Регистрация приложения на портале
В этом упражнении будет создаваться новое родной приложение Azure AD с Azure Active Directory центра администрирования.
Откройте браузер и перейдите в Центр администрирования Azure Active Directory и войдите с помощью личной учетной записи (т.е. учетной записи Майкрософт) или рабочей или учебной учетной записи.
Выберите Azure Active Directory на панели навигации слева, затем выберите Регистрация приложений в разделе Управление.
Выберите Новая регистрация. На странице Зарегистрировать приложение задайте необходимые значения следующим образом.
- Введите имя
iOS Objective-C Graph Tutorial
. - Введите поддерживаемые типы учетных записей для учетных записей в любом каталоге организаций и личных учетных записей Microsoft.
- Оставьте поле URI перенаправления пустым.
- Введите имя
Нажмите Зарегистрировать. На странице учебника Graph цель-C iOS скопируйте значение ID приложения (клиента) и сохраните его, оно потребуется на следующем шаге.
Выберите пункт Проверка подлинности в разделе Управление. Выберите Добавить платформу, затем iOS / macOS.
Введите пакетный ID приложения и выберите Настройка, а затем выберите Готово.
Добавление проверки подлинности с помощью Azure AD
В этом упражнении вы расширит приложение от предыдущего упражнения для поддержки проверки подлинности с помощью Azure AD. Это необходимо для получения необходимого маркера доступа OAuth для вызова microsoft Graph. Для этого в приложение будет интегрирована библиотека проверки подлинности Microsoft (MSAL) для iOS.
Создайте новый файл Списка свойств в проекте GraphTutorial с именем AuthSettings.plist.
Добавьте в файл в словаре Root следующие элементы.
Key Тип Значение AppId
String ID приложения с портала Azure GraphScopes
Массив Три значения строки: User.Read
MailboxSettings.Read
иCalendars.ReadWrite
Важно!
Если вы используете источник управления, например git, то сейчас самое время исключить файл AuthSettings.plist из источника управления, чтобы избежать случайной утечки вашего ID приложения.
Реализация входа в систему
В этом разделе вы настроите проект для MSAL, создайте класс диспетчера проверки подлинности и обновим приложение, чтобы войти и выйти.
Настройка проекта для MSAL
Добавьте новую группу ключей в возможности проекта.
- Выберите проект GraphTutorial, а затем & возможности.
- Выберите + возможность, затем дважды щелкните Keychain Sharing.
- Добавьте группу keychain со значением
com.microsoft.adalcache
.
Управление щелкните Info.plist и выберите Open As, а затем исходный код.
Добавьте в элемент
<dict>
следующее.<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>msauth.$(PRODUCT_BUNDLE_IDENTIFIER)</string> </array> </dict> </array> <key>LSApplicationQueriesSchemes</key> <array> <string>msauthv2</string> <string>msauthv3</string> </array>
Откройте AppDelegate.m и добавьте следующее утверждение импорта в верхней части файла.
#import <MSAL/MSAL.h>
Добавьте к классу
AppDelegate
следующую функцию:- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { return [MSALPublicClientApplication handleMSALResponse:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]]; }
Создание диспетчера проверки подлинности
Создайте новый класс Касания какао в проекте GraphTutorial с именем AuthenticationManager. Выберите NSObject в подклассе поля.
Откройте проверку подлинностиManager.h и замените содержимое следующим кодом.
#import <Foundation/Foundation.h> #import <MSAL/MSAL.h> #import <MSGraphClientSDK/MSGraphClientSDK.h> NS_ASSUME_NONNULL_BEGIN typedef void (^GetTokenCompletionBlock)(NSString* _Nullable accessToken, NSError* _Nullable error); @interface AuthenticationManager : NSObject<MSAuthenticationProvider> + (id) instance; - (void) getTokenInteractivelyWithParentView: (UIViewController*) parentView andCompletionBlock: (GetTokenCompletionBlock)completionBlock; - (void) getTokenSilentlyWithCompletionBlock: (GetTokenCompletionBlock)completionBlock; - (void) signOut; - (void) getAccessTokenForProviderOptions:(id<MSAuthenticationProviderOptions>)authProviderOptions andCompletion:(void (^)(NSString *, NSError *))completion; @end NS_ASSUME_NONNULL_END
Откройте проверку подлинностиManager.m и замените содержимое следующим кодом.
#import "AuthenticationManager.h" @interface AuthenticationManager() @property NSString* appId; @property NSArray<NSString*>* graphScopes; @property MSALPublicClientApplication* publicClient; @end @implementation AuthenticationManager + (id) instance { static AuthenticationManager *singleInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { singleInstance = [[self alloc] init]; }); return singleInstance; } - (id) init { if (self = [super init]) { // Get app ID and scopes from AuthSettings.plist NSString* authConfigPath = [NSBundle.mainBundle pathForResource:@"AuthSettings" ofType:@"plist"]; NSDictionary* authConfig = [NSDictionary dictionaryWithContentsOfFile:authConfigPath]; self.appId = authConfig[@"AppId"]; self.graphScopes = authConfig[@"GraphScopes"]; // Create the MSAL client self.publicClient = [[MSALPublicClientApplication alloc] initWithClientId:self.appId error:nil]; } return self; } - (void) getAccessTokenForProviderOptions:(id<MSAuthenticationProviderOptions>) authProviderOptions andCompletion:(void (^)(NSString * _Nonnull, NSError * _Nonnull)) completion { [self getTokenSilentlyWithCompletionBlock:completion]; } - (void) getTokenInteractivelyWithParentView:(UIViewController *) parentView andCompletionBlock:(GetTokenCompletionBlock) completionBlock { MSALWebviewParameters* webParameters = [[MSALWebviewParameters alloc] initWithAuthPresentationViewController:parentView]; MSALInteractiveTokenParameters* interactiveParameters = [[MSALInteractiveTokenParameters alloc] initWithScopes:self.graphScopes webviewParameters:webParameters]; // Call acquireToken to open a browser so the user can sign in [self.publicClient acquireTokenWithParameters:interactiveParameters completionBlock:^(MSALResult * _Nullable result, NSError * _Nullable error) { // Check error if (error) { completionBlock(nil, error); return; } // Check result if (!result) { NSMutableDictionary* details = [NSMutableDictionary dictionary]; [details setValue:@"No result was returned" forKey:NSDebugDescriptionErrorKey]; completionBlock(nil, [NSError errorWithDomain:@"AuthenticationManager" code:0 userInfo:details]); return; } NSLog(@"Got token interactively: %@", result.accessToken); completionBlock(result.accessToken, nil); }]; } - (void) getTokenSilentlyWithCompletionBlock:(GetTokenCompletionBlock)completionBlock { // Check if there is an account in the cache NSError* msalError; MSALAccount* account = [self.publicClient allAccounts:&msalError].firstObject; if (msalError || !account) { NSMutableDictionary* details = [NSMutableDictionary dictionary]; [details setValue:@"Could not retrieve account from cache" forKey:NSDebugDescriptionErrorKey]; completionBlock(nil, [NSError errorWithDomain:@"AuthenticationManager" code:0 userInfo:details]); return; } MSALSilentTokenParameters* silentParameters = [[MSALSilentTokenParameters alloc] initWithScopes:self.graphScopes account:account]; // Attempt to get token silently [self.publicClient acquireTokenSilentWithParameters:silentParameters completionBlock:^(MSALResult * _Nullable result, NSError * _Nullable error) { // Check error if (error) { completionBlock(nil, error); return; } // Check result if (!result) { NSMutableDictionary* details = [NSMutableDictionary dictionary]; [details setValue:@"No result was returned" forKey:NSDebugDescriptionErrorKey]; completionBlock(nil, [NSError errorWithDomain:@"AuthenticationManager" code:0 userInfo:details]); return; } NSLog(@"Got token silently: %@", result.accessToken); completionBlock(result.accessToken, nil); }]; } - (void) signOut { NSError* msalError; NSArray* accounts = [self.publicClient allAccounts:&msalError]; if (msalError) { NSLog(@"Error getting accounts from cache: %@", msalError.debugDescription); return; } for (id account in accounts) { [self.publicClient removeAccount:account error:nil]; } } @end
Добавление входных и входных
Откройте файл SignInViewController.m и замените его содержимое следующим кодом.
#import "SignInViewController.h" #import "SpinnerViewController.h" #import "AuthenticationManager.h" @interface SignInViewController () @property SpinnerViewController* spinner; @end @implementation SignInViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.spinner = [SpinnerViewController alloc]; [self.spinner startWithContainer:self]; [AuthenticationManager.instance getTokenSilentlyWithCompletionBlock:^(NSString * _Nullable accessToken, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ [self.spinner stop]; if (error || !accessToken) { // If there is no token or if there's an error, // no user is signed in, so stay here return; } // Since we got a token, user is signed in // Go to welcome page [self performSegueWithIdentifier: @"userSignedIn" sender: nil]; }); }]; } - (IBAction)signIn { [self.spinner startWithContainer:self]; [AuthenticationManager.instance getTokenInteractivelyWithParentView:self andCompletionBlock:^(NSString * _Nullable accessToken, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ [self.spinner stop]; if (error || !accessToken) { // Show the error and stay on the sign-in page UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Error signing in" message:error.debugDescription preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* okButton = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; [alert addAction:okButton]; [self presentViewController:alert animated:true completion:nil]; return; } // Since we got a token, user is signed in // Go to welcome page [self performSegueWithIdentifier: @"userSignedIn" sender: nil]; }); }]; } @end
Откройте WelcomeViewController.m и добавьте следующее
import
утверждение в верхнюю часть файла.#import "AuthenticationManager.h"
Замените имеющуюся функцию
signOut
указанным ниже кодом.- (IBAction)signOut { [AuthenticationManager.instance signOut]; [self performSegueWithIdentifier: @"userSignedOut" sender: nil]; }
Сохраните изменения и перезапустите приложение в Simulator.
При входе в приложение необходимо увидеть маркер доступа, отображаемый в окне вывода в Xcode.
Получение сведений о пользователе
В этом разделе вы создадим класс помощников для удержания всех вызовов в Microsoft Graph и обновим его, чтобы использовать этот новый класс для входа в WelcomeViewController
систему пользователя.
Создайте новый класс Касания какао в проекте GraphTutorial с именем GraphManager. Выберите NSObject в подклассе поля.
Откройте GraphManager.h и замените содержимое следующим кодом.
#import <Foundation/Foundation.h> #import <MSGraphClientSDK/MSGraphClientSDK.h> #import <MSGraphClientModels/MSGraphClientModels.h> #import <MSGraphClientModels/MSCollection.h> #import "AuthenticationManager.h" NS_ASSUME_NONNULL_BEGIN typedef void (^GetMeCompletionBlock)(MSGraphUser* _Nullable user, NSError* _Nullable error); @interface GraphManager : NSObject + (id) instance; - (void) getMeWithCompletionBlock: (GetMeCompletionBlock) completion; @end NS_ASSUME_NONNULL_END
Откройте GraphManager.m и замените содержимое следующим кодом.
#import "GraphManager.h" @interface GraphManager() @property MSHTTPClient* graphClient; @end @implementation GraphManager + (id) instance { static GraphManager *singleInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { singleInstance = [[self alloc] init]; }); return singleInstance; } - (id) init { if (self = [super init]) { // Create the Graph client self.graphClient = [MSClientFactory createHTTPClientWithAuthenticationProvider:AuthenticationManager.instance]; } return self; } - (void) getMeWithCompletionBlock: (GetMeCompletionBlock) completion { // GET /me NSString* meUrlString = [NSString stringWithFormat:@"%@/me?%@", MSGraphBaseURL, @"$select=displayName,mail,mailboxSettings,userPrincipalName"]; NSURL* meUrl = [[NSURL alloc] initWithString:meUrlString]; NSMutableURLRequest* meRequest = [[NSMutableURLRequest alloc] initWithURL:meUrl]; MSURLSessionDataTask* meDataTask = [[MSURLSessionDataTask alloc] initWithRequest:meRequest client:self.graphClient completion:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { completion(nil, error); return; } // Deserialize the response as a user NSError* graphError; MSGraphUser* user = [[MSGraphUser alloc] initWithData:data error:&graphError]; if (graphError) { completion(nil, graphError); } else { completion(user, nil); } }]; // Execute the request [meDataTask execute]; } @end
Откройте WelcomeViewController.m и добавьте следующие утверждения
#import
в верхней части файла.#import "SpinnerViewController.h" #import "GraphManager.h" #import <MSGraphClientModels/MSGraphClientModels.h>
Добавьте следующее свойство в
WelcomeViewController
объявление интерфейса.@property SpinnerViewController* spinner;
Замените
viewDidLoad
существующий следующим кодом.- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.spinner = [SpinnerViewController alloc]; [self.spinner startWithContainer:self]; self.userProfilePhoto.image = [UIImage imageNamed:@"DefaultUserPhoto"]; [GraphManager.instance getMeWithCompletionBlock:^(MSGraphUser * _Nullable user, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ [self.spinner stop]; if (error) { // Show the error UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Error getting user profile" message:error.debugDescription preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* okButton = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; [alert addAction:okButton]; [self presentViewController:alert animated:true completion:nil]; return; } // Set display name self.userDisplayName.text = user.displayName ? : @"Mysterious Stranger"; [self.userDisplayName sizeToFit]; // AAD users have email in the mail attribute // Personal accounts have email in the userPrincipalName attribute self.userEmail.text = user.mail ? : user.userPrincipalName; [self.userEmail sizeToFit]; // Save user time zone [GraphManager.instance setGraphTimeZone:(user.mailboxSettings.timeZone ? : @"UTC")]; }); }]; }
Если вы сохраните изменения и перезапустите приложение сейчас, после регистрации пользовательский интерфейс обновляется с отображаемого имени пользователя и адресом электронной почты.
Просмотр календаря
В этом упражнении вы будете включать Graph Microsoft в приложение. Для этого приложения для звонков в Корпорацию Майкрософт Graph SDK для objective C для звонков в Microsoft Graph.
Получение событий календаря из Outlook
В этом разделе вы расширим класс, чтобы добавить функцию для получения событий пользователя на текущую неделю и обновить для GraphManager
CalendarViewController
использования этой новой функции.
Откройте GraphManager.h и добавьте следующий код выше
@interface
объявления.typedef void (^GetCalendarViewCompletionBlock)(NSData* _Nullable data, NSError* _Nullable error);
Добавьте в декларацию следующий
@interface
код.- (void) getCalendarViewStartingAt: (NSString*) viewStart endingAt: (NSString*) viewEnd withCompletionBlock: (GetCalendarViewCompletionBlock) completion;
Откройте GraphManager.m и добавьте в класс следующую
GraphManager
функцию.- (void) getCalendarViewStartingAt: (NSString *) viewStart endingAt: (NSString *) viewEnd withCompletionBlock: (GetCalendarViewCompletionBlock) completion { // Set calendar view start and end parameters NSString* viewStartEndString = [NSString stringWithFormat:@"startDateTime=%@&endDateTime=%@", viewStart, viewEnd]; // GET /me/calendarview NSString* eventsUrlString = [NSString stringWithFormat:@"%@/me/calendarview?%@&%@&%@&%@", MSGraphBaseURL, viewStartEndString, // Only return these fields in results @"$select=subject,organizer,start,end", // Sort results by start time @"$orderby=start/dateTime", // Request at most 25 results @"$top=25"]; NSURL* eventsUrl = [[NSURL alloc] initWithString:eventsUrlString]; NSMutableURLRequest* eventsRequest = [[NSMutableURLRequest alloc] initWithURL:eventsUrl]; // Add the Prefer: outlook.timezone header to get start and end times // in user's time zone NSString* preferHeader = [NSString stringWithFormat:@"outlook.timezone=\"%@\"", self.graphTimeZone]; [eventsRequest addValue:preferHeader forHTTPHeaderField:@"Prefer"]; MSURLSessionDataTask* eventsDataTask = [[MSURLSessionDataTask alloc] initWithRequest:eventsRequest client:self.graphClient completion:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { completion(nil, error); return; } // TEMPORARY completion(data, nil); }]; // Execute the request [eventsDataTask execute]; }
Примечание
Рассмотрим, что делает
getCalendarViewStartingAt
код.- Вызывается URL-адрес
/v1.0/me/calendarview
.- Параметры
startDateTime
endDateTime
и параметры запроса определяют начало и конец представления календаря. - Параметр запроса ограничивает поля, возвращенные для каждого события, только теми, которые будут
select
фактически использовать представление. - Параметр
orderby
запроса сортировать результаты по времени начала. - Параметр
top
запроса запрашивает 25 результатов на страницу. - в загонах Graph майкрософт возвращает время начала и окончания каждого события в часовом
Prefer: outlook.timezone
поясе пользователя.
- Параметры
- Вызывается URL-адрес
Создайте новый класс касания какао в проекте GraphTutorial с именем GraphToIana. Выберите NSObject в подклассе поля.
Откройте GraphToIana.h и замените содержимое следующим кодом.
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface GraphToIana : NSObject + (NSString*) getIanaIdentifierFromGraphIdentifier: (NSString *) graphIdentifier; @end NS_ASSUME_NONNULL_END
Откройте GraphToIana.m и замените содержимое следующим кодом.
#import "GraphToIana.h" @implementation GraphToIana // Basic lookup for mapping Windows time zone identifiers to // IANA identifiers // Mappings taken from // https://github.com/unicode-org/cldr/blob/master/common/supplemental/windowsZones.xml + (NSString*) getIanaIdentifierFromGraphIdentifier: (NSString *) graphIdentifier { static NSDictionary* timeZoneMap = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ timeZoneMap = @{ @"Dateline Standard Time" : @"Etc/GMT+12", @"UTC-11" : @"Etc/GMT+11", @"Aleutian Standard Time" : @"America/Adak", @"Hawaiian Standard Time" : @"Pacific/Honolulu", @"Marquesas Standard Time" : @"Pacific/Marquesas", @"Alaskan Standard Time" : @"America/Anchorage", @"UTC-09" : @"Etc/GMT+9", @"Pacific Standard Time (Mexico)" : @"America/Tijuana", @"UTC-08" : @"Etc/GMT+8", @"Pacific Standard Time" : @"America/Los_Angeles", @"US Mountain Standard Time" : @"America/Phoenix", @"Mountain Standard Time (Mexico)" : @"America/Chihuahua", @"Mountain Standard Time" : @"America/Denver", @"Central America Standard Time" : @"America/Guatemala", @"Central Standard Time" : @"America/Chicago", @"Easter Island Standard Time" : @"Pacific/Easter", @"Central Standard Time (Mexico)" : @"America/Mexico_City", @"Canada Central Standard Time" : @"America/Regina", @"SA Pacific Standard Time" : @"America/Bogota", @"Eastern Standard Time (Mexico)" : @"America/Cancun", @"Eastern Standard Time" : @"America/New_York", @"Haiti Standard Time" : @"America/Port-au-Prince", @"Cuba Standard Time" : @"America/Havana", @"US Eastern Standard Time" : @"America/Indianapolis", @"Turks And Caicos Standard Time" : @"America/Grand_Turk", @"Paraguay Standard Time" : @"America/Asuncion", @"Atlantic Standard Time" : @"America/Halifax", @"Venezuela Standard Time" : @"America/Caracas", @"Central Brazilian Standard Time" : @"America/Cuiaba", @"SA Western Standard Time" : @"America/La_Paz", @"Pacific SA Standard Time" : @"America/Santiago", @"Newfoundland Standard Time" : @"America/St_Johns", @"Tocantins Standard Time" : @"America/Araguaina", @"E. South America Standard Time" : @"America/Sao_Paulo", @"SA Eastern Standard Time" : @"America/Cayenne", @"Argentina Standard Time" : @"America/Buenos_Aires", @"Greenland Standard Time" : @"America/Godthab", @"Montevideo Standard Time" : @"America/Montevideo", @"Magallanes Standard Time" : @"America/Punta_Arenas", @"Saint Pierre Standard Time" : @"America/Miquelon", @"Bahia Standard Time" : @"America/Bahia", @"UTC-02" : @"Etc/GMT+2", @"Azores Standard Time" : @"Atlantic/Azores", @"Cape Verde Standard Time" : @"Atlantic/Cape_Verde", @"UTC" : @"Etc/GMT", @"GMT Standard Time" : @"Europe/London", @"Greenwich Standard Time" : @"Atlantic/Reykjavik", @"Sao Tome Standard Time" : @"Africa/Sao_Tome", @"Morocco Standard Time" : @"Africa/Casablanca", @"W. Europe Standard Time" : @"Europe/Berlin", @"Central Europe Standard Time" : @"Europe/Budapest", @"Romance Standard Time" : @"Europe/Paris", @"Central European Standard Time" : @"Europe/Warsaw", @"W. Central Africa Standard Time" : @"Africa/Lagos", @"Jordan Standard Time" : @"Asia/Amman", @"GTB Standard Time" : @"Europe/Bucharest", @"Middle East Standard Time" : @"Asia/Beirut", @"Egypt Standard Time" : @"Africa/Cairo", @"E. Europe Standard Time" : @"Europe/Chisinau", @"Syria Standard Time" : @"Asia/Damascus", @"West Bank Standard Time" : @"Asia/Hebron", @"South Africa Standard Time" : @"Africa/Johannesburg", @"FLE Standard Time" : @"Europe/Kiev", @"Israel Standard Time" : @"Asia/Jerusalem", @"Kaliningrad Standard Time" : @"Europe/Kaliningrad", @"Sudan Standard Time" : @"Africa/Khartoum", @"Libya Standard Time" : @"Africa/Tripoli", @"Namibia Standard Time" : @"Africa/Windhoek", @"Arabic Standard Time" : @"Asia/Baghdad", @"Turkey Standard Time" : @"Europe/Istanbul", @"Arab Standard Time" : @"Asia/Riyadh", @"Belarus Standard Time" : @"Europe/Minsk", @"Russian Standard Time" : @"Europe/Moscow", @"E. Africa Standard Time" : @"Africa/Nairobi", @"Iran Standard Time" : @"Asia/Tehran", @"Arabian Standard Time" : @"Asia/Dubai", @"Astrakhan Standard Time" : @"Europe/Astrakhan", @"Azerbaijan Standard Time" : @"Asia/Baku", @"Russia Time Zone 3" : @"Europe/Samara", @"Mauritius Standard Time" : @"Indian/Mauritius", @"Saratov Standard Time" : @"Europe/Saratov", @"Georgian Standard Time" : @"Asia/Tbilisi", @"Volgograd Standard Time" : @"Europe/Volgograd", @"Caucasus Standard Time" : @"Asia/Yerevan", @"Afghanistan Standard Time" : @"Asia/Kabul", @"West Asia Standard Time" : @"Asia/Tashkent", @"Ekaterinburg Standard Time" : @"Asia/Yekaterinburg", @"Pakistan Standard Time" : @"Asia/Karachi", @"Qyzylorda Standard Time" : @"Asia/Qyzylorda", @"India Standard Time" : @"Asia/Calcutta", @"Sri Lanka Standard Time" : @"Asia/Colombo", @"Nepal Standard Time" : @"Asia/Katmandu", @"Central Asia Standard Time" : @"Asia/Almaty", @"Bangladesh Standard Time" : @"Asia/Dhaka", @"Omsk Standard Time" : @"Asia/Omsk", @"Myanmar Standard Time" : @"Asia/Rangoon", @"SE Asia Standard Time" : @"Asia/Bangkok", @"Altai Standard Time" : @"Asia/Barnaul", @"W. Mongolia Standard Time" : @"Asia/Hovd", @"North Asia Standard Time" : @"Asia/Krasnoyarsk", @"N. Central Asia Standard Time" : @"Asia/Novosibirsk", @"Tomsk Standard Time" : @"Asia/Tomsk", @"China Standard Time" : @"Asia/Shanghai", @"North Asia East Standard Time" : @"Asia/Irkutsk", @"Singapore Standard Time" : @"Asia/Singapore", @"W. Australia Standard Time" : @"Australia/Perth", @"Taipei Standard Time" : @"Asia/Taipei", @"Ulaanbaatar Standard Time" : @"Asia/Ulaanbaatar", @"Aus Central W. Standard Time" : @"Australia/Eucla", @"Transbaikal Standard Time" : @"Asia/Chita", @"Tokyo Standard Time" : @"Asia/Tokyo", @"North Korea Standard Time" : @"Asia/Pyongyang", @"Korea Standard Time" : @"Asia/Seoul", @"Yakutsk Standard Time" : @"Asia/Yakutsk", @"Cen. Australia Standard Time" : @"Australia/Adelaide", @"AUS Central Standard Time" : @"Australia/Darwin", @"E. Australia Standard Time" : @"Australia/Brisbane", @"AUS Eastern Standard Time" : @"Australia/Sydney", @"West Pacific Standard Time" : @"Pacific/Port_Moresby", @"Tasmania Standard Time" : @"Australia/Hobart", @"Vladivostok Standard Time" : @"Asia/Vladivostok", @"Lord Howe Standard Time" : @"Australia/Lord_Howe", @"Bougainville Standard Time" : @"Pacific/Bougainville", @"Russia Time Zone 10" : @"Asia/Srednekolymsk", @"Magadan Standard Time" : @"Asia/Magadan", @"Norfolk Standard Time" : @"Pacific/Norfolk", @"Sakhalin Standard Time" : @"Asia/Sakhalin", @"Central Pacific Standard Time" : @"Pacific/Guadalcanal", @"Russia Time Zone 11" : @"Asia/Kamchatka", @"New Zealand Standard Time" : @"Pacific/Auckland", @"UTC+12" : @"Etc/GMT-12", @"Fiji Standard Time" : @"Pacific/Fiji", @"Chatham Islands Standard Time" : @"Pacific/Chatham", @"UTC+13" : @"Etc/GMT-13", @"Tonga Standard Time" : @"Pacific/Tongatapu", @"Samoa Standard Time" : @"Pacific/Apia", @"Line Islands Standard Time" : @"Pacific/Kiritimati" }; }); // If a mapping was not found, assume the value passed // was already an IANA identifier return timeZoneMap[graphIdentifier] ? : graphIdentifier; } @end
Это простой поиск, чтобы найти идентификатор часовой зоны IANA на основе имени часовой зоны, возвращенного корпорацией Майкрософт Graph.
Откройте CalendarViewController.m и замените все содержимое следующим кодом.
#import "CalendarViewController.h" #import "SpinnerViewController.h" #import "GraphManager.h" #import "GraphToIana.h" #import <MSGraphClientModels/MSGraphClientModels.h> @interface CalendarViewController () @property SpinnerViewController* spinner; @end @implementation CalendarViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.spinner = [SpinnerViewController alloc]; [self.spinner startWithContainer:self]; // Calculate the start and end of the current week NSString* timeZoneId = [GraphToIana getIanaIdentifierFromGraphIdentifier: [GraphManager.instance graphTimeZone]]; NSDate* now = [NSDate date]; NSCalendar* calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:timeZoneId]; [calendar setTimeZone:timeZone]; NSDateComponents* startOfWeekComponents = [calendar components:NSCalendarUnitCalendar | NSCalendarUnitYearForWeekOfYear | NSCalendarUnitWeekOfYear fromDate:now]; NSDate* startOfWeek = [startOfWeekComponents date]; NSDate* endOfWeek = [calendar dateByAddingUnit:NSCalendarUnitDay value:7 toDate:startOfWeek options:0]; // Convert start and end to ISO 8601 strings NSISO8601DateFormatter* isoFormatter = [[NSISO8601DateFormatter alloc] init]; NSString* viewStart = [isoFormatter stringFromDate:startOfWeek]; NSString* viewEnd = [isoFormatter stringFromDate:endOfWeek]; [GraphManager.instance getCalendarViewStartingAt:viewStart endingAt:viewEnd withCompletionBlock:^(NSData * _Nullable data, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ [self.spinner stop]; if (error) { // Show the error UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Error getting events" message:error.debugDescription preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* okButton = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; [alert addAction:okButton]; [self presentViewController:alert animated:true completion:nil]; return; } // TEMPORARY self.calendarJSON.text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; [self.calendarJSON sizeToFit]; }); }]; } @end
Запустите приложение, вопишите и нажмите элемент навигации Calendar в меню. Вы должны увидеть сброс JSON событий в приложении.
Отображение результатов
Теперь вы можете заменить свалку JSON чем-то, чтобы отобразить результаты в удобной для пользователя манере. В этом разделе вы измените функцию для возврата объектов с сильно типом и измените для отображения событий представление getCalendarViewStartingAt
CalendarViewController
таблицы.
Обновление getCalendarViewStartingAt
Откройте GraphManager.h. Измените
GetCalendarViewCompletionBlock
определение типа на следующее.typedef void (^GetCalendarViewCompletionBlock)(NSArray<MSGraphEvent*>* _Nullable events, NSError* _Nullable error);
Откройте GraphManager.m. Замените имеющуюся функцию
getCalendarViewStartingAt
указанным ниже кодом.- (void) getCalendarViewStartingAt: (NSString *) viewStart endingAt: (NSString *) viewEnd withCompletionBlock: (GetCalendarViewCompletionBlock) completion { // Set calendar view start and end parameters NSString* viewStartEndString = [NSString stringWithFormat:@"startDateTime=%@&endDateTime=%@", viewStart, viewEnd]; // GET /me/calendarview NSString* eventsUrlString = [NSString stringWithFormat:@"%@/me/calendarview?%@&%@&%@&%@", MSGraphBaseURL, viewStartEndString, // Only return these fields in results @"$select=subject,organizer,start,end", // Sort results by start time @"$orderby=start/dateTime", // Request at most 25 results @"$top=25"]; NSURL* eventsUrl = [[NSURL alloc] initWithString:eventsUrlString]; NSMutableURLRequest* eventsRequest = [[NSMutableURLRequest alloc] initWithURL:eventsUrl]; // Add the Prefer: outlook.timezone header to get start and end times // in user's time zone NSString* preferHeader = [NSString stringWithFormat:@"outlook.timezone=\"%@\"", self.graphTimeZone]; [eventsRequest addValue:preferHeader forHTTPHeaderField:@"Prefer"]; MSURLSessionDataTask* eventsDataTask = [[MSURLSessionDataTask alloc] initWithRequest:eventsRequest client:self.graphClient completion:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { completion(nil, error); return; } NSError* graphError; // Deserialize to an events collection MSCollection* eventsCollection = [[MSCollection alloc] initWithData:data error:&graphError]; if (graphError) { completion(nil, graphError); return; } // Create an array to return NSMutableArray* eventsArray = [[NSMutableArray alloc] initWithCapacity:eventsCollection.value.count]; for (id event in eventsCollection.value) { // Deserialize the event and add to the array MSGraphEvent* graphEvent = [[MSGraphEvent alloc] initWithDictionary:event]; [eventsArray addObject:graphEvent]; } completion(eventsArray, nil); }]; // Execute the request [eventsDataTask execute]; }
Обновление CalendarViewController
Создайте новый файл класса касания какао в проекте GraphTutorial с именем
CalendarTableViewCell
. Выберите UITableViewCell в подклассе поля.Откройте CalendarTableViewCell.h и замените его содержимое следующим кодом.
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface CalendarTableViewCell : UITableViewCell @property (nonatomic) NSString* subject; @property (nonatomic) NSString* organizer; @property (nonatomic) NSString* duration; @end NS_ASSUME_NONNULL_END
Откройте CalendarTableViewCell.m и замените его содержимое следующим кодом.
#import "CalendarTableViewCell.h" @interface CalendarTableViewCell() @property (nonatomic) IBOutlet UILabel *subjectLabel; @property (nonatomic) IBOutlet UILabel *organizerLabel; @property (nonatomic) IBOutlet UILabel *durationLabel; @end @implementation CalendarTableViewCell - (void)awakeFromNib { [super awakeFromNib]; // Initialization code } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; // Configure the view for the selected state } - (void) setSubject:(NSString *)subject { _subject = subject; self.subjectLabel.text = subject; [self.subjectLabel sizeToFit]; } - (void) setOrganizer:(NSString *)organizer { _organizer = organizer; self.organizerLabel.text = organizer; [self.organizerLabel sizeToFit]; } - (void) setDuration:(NSString *)duration { _duration = duration; self.durationLabel.text = duration; [self.durationLabel sizeToFit]; } @end
Создайте новый файл класса касания какао в проекте GraphTutorial с именем
CalendarTableViewController
. Выберите UITableViewController в подклассе поля.Откройте CalendarTableViewController.h и замените его содержимое следующим кодом.
#import <UIKit/UIKit.h> #import <MSGraphClientModels/MSGraphClientModels.h> NS_ASSUME_NONNULL_BEGIN @interface CalendarTableViewController : UITableViewController @property (nonatomic) NSArray<MSGraphEvent*>* events; @end NS_ASSUME_NONNULL_END
Откройте CalendarTableViewController.m и замените его содержимое следующим кодом.
#import "CalendarTableViewController.h" #import "CalendarTableViewCell.h" #import <MSGraphClientModels/MSGraphClientModels.h> @interface CalendarTableViewController () @end @implementation CalendarTableViewController - (void)viewDidLoad { [super viewDidLoad]; self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.estimatedRowHeight = 100; } - (NSInteger) numberOfSections:(UITableView*) tableView { return 1; } - (NSInteger) tableView:(UITableView*) tableView numberOfRowsInSection:(NSInteger) section { return self.events ? self.events.count : 0; } - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { CalendarTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"EventCell"]; // Get the event that corresponds to the row MSGraphEvent* event = self.events[indexPath.row]; // Configure the cell cell.subject = event.subject; cell.organizer = event.organizer.emailAddress.name; cell.duration = [NSString stringWithFormat:@"%@ to %@", [self formatGraphDateTime:event.start], [self formatGraphDateTime:event.end]]; return cell; } - (NSString*) formatGraphDateTime:(MSGraphDateTimeTimeZone*) dateTime { // Create a formatter to parse Graph's date format NSDateFormatter* isoFormatter = [[NSDateFormatter alloc] init]; isoFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSSSSS"; // Specify the time zone isoFormatter.timeZone = [[NSTimeZone alloc] initWithName:dateTime.timeZone]; NSDate* date = [isoFormatter dateFromString:dateTime.dateTime]; // Output like 5/5/2019, 2:00 PM NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterShortStyle; dateFormatter.timeStyle = NSDateFormatterShortStyle; NSString* dateString = [dateFormatter stringFromDate:date]; return dateString; } - (void) setEvents:(NSArray<MSGraphEvent *> *)events { _events = events; [self.tableView reloadData]; } @end
Откройте main.storyboard и найдите сцену календаря. Удаление представления прокрутки из корневого представления.
С помощью библиотеки добавьте в верхней части представления планку навигации.
Дважды щелкните Название в панели навигации и обнови его до
Calendar
.С помощью библиотеки добавьте элемент кнопки Bar в правую сторону панели навигации.
Выберите новую кнопку панели, а затем выберите инспектор атрибутов. Изменение изображения в плюс.
Добавьте представление контейнера из библиотеки в представление под панели навигации. Resize the container view to take all of the remaining space in the view.
Установите ограничения для панели навигации и представления контейнера следующим образом.
- Панель навигации
- Добавление ограничения: высота, значение: 44
- Добавление ограничения. Ведущее пространство Сейф области, значение: 0
- Добавление ограничения: пространство для Сейф области, значение: 0
- Добавление ограничения: верхнее Сейф области, значение: 0
- Представление контейнера
- Добавление ограничения. Ведущее пространство Сейф области, значение: 0
- Добавление ограничения: пространство для Сейф области, значение: 0
- Добавление ограничения: Верхнее пространство для панели навигации, значение: 0
- Добавление ограничения: нижнее Сейф области, значение: 0
- Панель навигации
Найдите второй контроллер представления, добавленный в сюжетную доску при добавлении представления контейнера. Он подключен к сцене календаря с помощью встраиваемой segue. Выберите этот контроллер и с помощью инспектора удостоверений измените класс на CalendarTableViewController.
Удаление представления из контроллера представления таблицы календаря.
Добавьте представление таблицы из библиотеки в контроллер представления таблицы календаря.
Выберите представление таблицы, а затем выберите инспектор атрибутов. Установите ячейки прототипа до 1.
Перетащите нижний край ячейки прототипа, чтобы предоставить вам большую область для работы.
Используйте библиотеку, чтобы добавить три метки в ячейку прототипа.
Выберите ячейку прототипа, а затем выберите инспектор удостоверений. Изменение класса на CalendarTableViewCell.
Выберите инспектор атрибутов и установите идентификатор
EventCell
.Выбрав EventCell, выберите инспектор подключений и соедините его, а также метки, добавленные в ячейку
durationLabel
наorganizerLabel
subjectLabel
сюжетной доске.Установите свойства и ограничения на трех меток следующим образом.
- Метка subject
- Добавление ограничения: ведущее пространство для просмотра контента, значение: 0
- Добавление ограничения: отставающий пробел для поля для просмотра контента, значение: 0
- Добавление ограничения: верхнее пространство для верхнего поля представления контента, значение: 0
- Метка организатора
- Шрифт: System 12.0
- Добавление ограничения: высота, значение: 15
- Добавление ограничения: ведущее пространство для просмотра контента, значение: 0
- Добавление ограничения: отставающий пробел для поля для просмотра контента, значение: 0
- Добавление ограничения: Верхнее пространство для нижней метки субъекта, значение: Стандартный
- Метка продолжительности
- Шрифт: System 12.0
- Цвет: темно-серый цвет
- Добавление ограничения: высота, значение: 15
- Добавление ограничения: ведущее пространство для просмотра контента, значение: 0
- Добавление ограничения: отставающий пробел для поля для просмотра контента, значение: 0
- Добавление ограничения: Верхнее пространство для основания метки организатора, значение: Стандартный
- Добавление ограничения: нижнее пространство для нижней маржи представления контента, значение: 0
- Метка subject
Выберите EventCell, а затем выберите инспектор размера. Включить автоматическую для высоты строки.
Откройте CalendarViewController.h и удалите
calendarJSON
свойство.Откройте CalendarViewController.m и замените его содержимое следующим кодом.
#import "CalendarViewController.h" #import "CalendarTableViewController.h" #import "SpinnerViewController.h" #import "GraphManager.h" #import "GraphToIana.h" #import <MSGraphClientModels/MSGraphClientModels.h> @interface CalendarViewController () @property SpinnerViewController* spinner; @property CalendarTableViewController* tableView; @end @implementation CalendarViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.spinner = [SpinnerViewController alloc]; [self.spinner startWithContainer:self]; // Calculate the start and end of the current week NSString* timeZoneId = [GraphToIana getIanaIdentifierFromGraphIdentifier: [GraphManager.instance graphTimeZone]]; NSDate* now = [NSDate date]; NSCalendar* calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:timeZoneId]; [calendar setTimeZone:timeZone]; NSDateComponents* startOfWeekComponents = [calendar components:NSCalendarUnitCalendar | NSCalendarUnitYearForWeekOfYear | NSCalendarUnitWeekOfYear fromDate:now]; NSDate* startOfWeek = [startOfWeekComponents date]; NSDate* endOfWeek = [calendar dateByAddingUnit:NSCalendarUnitDay value:7 toDate:startOfWeek options:0]; // Convert start and end to ISO 8601 strings NSISO8601DateFormatter* isoFormatter = [[NSISO8601DateFormatter alloc] init]; NSString* viewStart = [isoFormatter stringFromDate:startOfWeek]; NSString* viewEnd = [isoFormatter stringFromDate:endOfWeek]; [GraphManager.instance getCalendarViewStartingAt:viewStart endingAt:viewEnd withCompletionBlock:^(NSArray<MSGraphEvent*>* _Nullable events, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ [self.spinner stop]; if (error) { // Show the error UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Error getting events" message:error.debugDescription preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* okButton = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; [alert addAction:okButton]; [self presentViewController:alert animated:true completion:nil]; return; } [self.tableView setEvents:events]; }); }]; } - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Save a reference to the contained table view so // we can pass the results of the Graph call to it if ([segue.destinationViewController isKindOfClass:[CalendarTableViewController class]]) { self.tableView = segue.destinationViewController; } } - (IBAction) showNewEventForm { [self performSegueWithIdentifier:@"showEventForm" sender:nil]; } @end
Запустите приложение, войди и нажмите вкладку Календарь. Список событий.
Создание нового события
В этом разделе вы добавим возможность создания событий в календаре пользователя.
Откройте GraphManager.h и добавьте следующий код выше
@interface
объявления.typedef void (^CreateEventCompletionBlock)(MSGraphEvent* _Nullable event, NSError* _Nullable error);
Добавьте в декларацию следующий
@interface
код.- (void) createEventWithSubject: (NSString*) subject andStart: (NSDate*) start andEnd: (NSDate*) end andAttendees: (NSArray<NSString*>* _Nullable) attendees andBody: (NSString* _Nullable) body andCompletionBlock: (CreateEventCompletionBlock) completion;
Откройте GraphManager.m и добавьте следующую функцию для создания нового события в календаре пользователя.
- (void) createEventWithSubject: (NSString*) subject andStart: (NSDate*) start andEnd: (NSDate*) end andAttendees: (NSArray<NSString*>* _Nullable) attendees andBody: (NSString* _Nullable) body andCompletionBlock: (CreateEventCompletionBlock) completion { NSDateFormatter* isoFormatter = [[NSDateFormatter alloc] init]; isoFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm"; // Create a dictionary to represent the event // Current version of the Graph SDK models don't serialize properly // see https://github.com/microsoftgraph/msgraph-sdk-objc-models/issues/27 NSMutableDictionary* eventDict = [NSMutableDictionary dictionary]; [eventDict setObject:subject forKey:@"subject"]; NSDictionary* startDict = @{ @"dateTime": [isoFormatter stringFromDate:start], @"timeZone": self.graphTimeZone }; [eventDict setObject:startDict forKey:@"start"]; NSDictionary* endDict = @{ @"dateTime": [isoFormatter stringFromDate:end], @"timeZone": self.graphTimeZone }; [eventDict setObject:endDict forKey:@"end"]; if (attendees != nil && attendees.count > 0) { NSMutableArray* attendeeArray = [NSMutableArray array]; for (id email in attendees) { NSDictionary* attendeeDict = @{ @"type": @"required", @"emailAddress": @{ @"address": email } }; [attendeeArray addObject:attendeeDict]; } [eventDict setObject:attendeeArray forKey:@"attendees"]; } if (body != nil) { NSDictionary* bodyDict = @{ @"content": body, @"contentType": @"text" }; [eventDict setObject:bodyDict forKey:@"body"]; } NSError* error = nil; NSData* eventData = [NSJSONSerialization dataWithJSONObject:eventDict options:kNilOptions error:&error]; // Prepare Graph request NSString* eventsUrlString = [NSString stringWithFormat:@"%@/me/events", MSGraphBaseURL]; NSURL* eventsUrl = [[NSURL alloc] initWithString:eventsUrlString]; NSMutableURLRequest* eventsRequest = [[NSMutableURLRequest alloc] initWithURL:eventsUrl]; eventsRequest.HTTPMethod = @"POST"; eventsRequest.HTTPBody = eventData; [eventsRequest addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; MSURLSessionDataTask* createEventDataTask = [[MSURLSessionDataTask alloc] initWithRequest:eventsRequest client:self.graphClient completion:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { completion(nil, error); return; } NSError* graphError; // Deserialize to an event MSGraphEvent* event = [[MSGraphEvent alloc] initWithData:data error:&graphError]; if (graphError) { completion(nil, graphError); return; } completion(event, nil); }]; // Execute the request [createEventDataTask execute]; }
Создайте новый файл класса касания какао в папке GraphTutorial с именем
NewEventViewController
. Выберите UIViewController в подклассе поля.Откройте NewEventViewController.h и добавьте в декларацию следующий
@interface
код.@property (nonatomic) IBOutlet UITextField* subject; @property (nonatomic) IBOutlet UITextField* attendees; @property (nonatomic) IBOutlet UIDatePicker* start; @property (nonatomic) IBOutlet UIDatePicker* end; @property (nonatomic) IBOutlet UITextView* body;
Откройте NewEventController.m и замените содержимое следующим кодом.
#import "NewEventViewController.h" #import "SpinnerViewController.h" #import "GraphManager.h" @interface NewEventViewController () @property SpinnerViewController* spinner; @end @implementation NewEventViewController - (void)viewDidLoad { [super viewDidLoad]; self.spinner = [SpinnerViewController alloc]; // Add border around text view UIColor* borderColor = [UIColor colorWithRed:0.85 green:0.85 blue:0.85 alpha:1.0]; self.body.layer.borderWidth = 0.5; self.body.layer.borderColor = [borderColor CGColor]; self.body.layer.cornerRadius = 5.0; // Set start picker to the next closest half-hour NSDate* now = [NSDate date]; NSCalendar* calendar = [NSCalendar currentCalendar]; NSInteger minutes = [calendar component:NSCalendarUnitMinute fromDate:now]; NSInteger offset = 30 - (minutes % 30); NSDate* start = [calendar dateByAddingUnit:NSCalendarUnitMinute value:offset toDate:now options:kNilOptions]; self.start.date = start; // Set end picker to start + 30 min NSDate* end = [calendar dateByAddingUnit:NSCalendarUnitMinute value:30 toDate:start options:kNilOptions]; self.end.date = end; } - (IBAction) createEvent { [self.spinner startWithContainer:self]; NSString* subject = self.subject.text; NSString* attendeeString = self.attendees.text; NSArray* attendees = nil; if (attendeeString != nil && attendeeString.length > 0) { attendees = [attendeeString componentsSeparatedByString:@";"]; } NSDate* start = self.start.date; NSDate* end = self.end.date; NSString* body = self.body.text; [GraphManager.instance createEventWithSubject:subject andStart:start andEnd:end andAttendees:attendees andBody:body andCompletionBlock:^(MSGraphEvent * _Nullable event, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ [self.spinner stop]; NSString* alertTitle = nil; NSString* alertMessage = nil; UIAlertAction* okButton = nil; if (error) { // Show the error alertTitle = @"Error creating event"; alertMessage = error.debugDescription; okButton = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; } else { alertTitle = @"Success"; alertMessage = @"Event created"; okButton = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self dismissViewControllerAnimated:true completion:nil]; }]; } UIAlertController* alert = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:okButton]; [self presentViewController:alert animated:true completion:nil]; }); }]; } - (IBAction) cancel { [self dismissViewControllerAnimated:true completion:nil]; } @end
Откройте main.storyboard. Используйте библиотеку для перетаскивания контроллера представления на подмок.
С помощью библиотеки добавьте панели навигации в контроллер представления.
Дважды щелкните Название в панели навигации и обнови его до
New Event
.С помощью библиотеки добавьте элемент кнопки Bar в левую сторону панели навигации.
Выберите новую кнопку панели, а затем выберите инспектор атрибутов. Измените название
Cancel
на .С помощью библиотеки добавьте элемент кнопки Bar в правую сторону панели навигации.
Выберите новую кнопку панели, а затем выберите инспектор атрибутов. Измените название
Create
на .Выберите контроллер представления, а затем выберите инспектор удостоверений. Изменение класса на NewEventViewController.
Добавьте в представление следующие элементы управления из библиотеки.
- Добавьте метку под панели навигации. Установите его
Subject
текст. - Добавьте текстовое поле под меткой. Задайте атрибуту Placeholder
Subject
. - Добавьте метку в текстовое поле. Установите его
Attendees
текст. - Добавьте текстовое поле под меткой. Задайте атрибуту Placeholder
Separate multiple entries with ;
. - Добавьте метку в текстовое поле. Установите его
Start
текст. - Добавьте выбор даты под меткой. Установите его предпочтительный стиль для компактности, его интервал до 15 минут, а его высота до 35.
- Добавьте метку под выборщиком дат. Установите его
End
текст. - Добавьте выбор даты под меткой. Установите его предпочтительный стиль для компактности, его интервал до 15 минут, а его высота до 35.
- Добавьте текстовое представление под выборщиком дат.
- Добавьте метку под панели навигации. Установите его
Выберите контроллер представления событий и используйте инспектор подключения, чтобы сделать следующие подключения.
- Подключение отменить полученное действие на кнопку Отмена панели.
- Подключение createEvent получил действие на кнопку Создать планку.
- Подключение субъекта в первое текстовое поле.
- Подключение выход участников на второе текстовое поле.
- Подключение выход на первый выбор даты.
- Подключение конечный выход на второй выборщик даты.
- Подключение на текстовое представление.
Добавьте следующие ограничения.
- Панель навигации
- Leading space to Сейф Area, value: 0
- Отставное пространство Сейф области, значение: 0
- Верхнее Сейф области, значение: 0
- Высота, значение: 44
- Метка subject
- Ведущее пространство для просмотра маржи, значение: 0
- Отставное пространство для просмотра поля, значение: 0
- Верхнее пространство для панели навигации, значение: 20
- Поле Subject Text
- Ведущее пространство для просмотра маржи, значение: 0
- Отставное пространство для просмотра поля, значение: 0
- Верхнее пространство для метки субъекта, значение: Стандартный
- Метка участников
- Ведущее пространство для просмотра маржи, значение: 0
- Отставное пространство для просмотра поля, значение: 0
- Верхнее пространство для темы текстового поля, значение: Стандартный
- Текстовое поле участников
- Ведущее пространство для просмотра маржи, значение: 0
- Отставное пространство для просмотра поля, значение: 0
- Верхнее пространство для метки участников, значение: Стандартный
- Начните метку
- Ведущее пространство для просмотра маржи, значение: 0
- Отставное пространство для просмотра поля, значение: 0
- Верхнее пространство для темы текстового поля, значение: Стандартный
- Выбор даты начала
- Ведущее пространство для просмотра маржи, значение: 0
- Отставное пространство для просмотра поля, значение: 0
- Верхнее пространство для метки участников, значение: Стандартный
- Высота, значение: 35
- End Label
- Ведущее пространство для просмотра маржи, значение: 0
- Отставное пространство для просмотра поля, значение: 0
- Верхнее пространство для даты начала выборки, значение: Стандартный
- Выбор конечных дат
- Ведущее пространство для просмотра маржи, значение: 0
- Отставное пространство для просмотра поля, значение: 0
- Верхнее пространство для конечных меток, значение: Стандартный
- Высота: 35
- Просмотр текста тела
- Ведущее пространство для просмотра маржи, значение: 0
- Отставное пространство для просмотра поля, значение: 0
- Верхнее пространство для выборки конечных дат, значение: Стандартный
- Нижнее пространство для просмотра поля, значение: 0
- Панель навигации
Выберите сцену календаря, а затем выберите инспектор подключений.
В статье Triggered Segues перетащите неисполненный круг рядом с вручную на контроллер представления событий на доске. Выберите Present Modally в всплывающее меню.
Выберите только что добавленную сегу, а затем инспектор атрибутов. Установите поле Идентификатор
showEventForm
.Подключение showNewEventForm получил действие на кнопку + панели навигации.
Сохраните изменения и перезапустите приложение. Перейдите на страницу календаря и нажмите + кнопку. Заполните форму и нажмите кнопку Создать для создания нового события.
Поздравляем!
Вы завершили руководство по Graph Microsoft objective-C. Теперь, когда у вас есть рабочее приложение, которое вызывает Microsoft Graph, вы можете экспериментировать и добавлять новые функции. В обзоре microsoft Graph, чтобы увидеть все данные, к ним можно получить доступ с помощью microsoft Graph.
Отзывы
Возникла проблема с этим разделом? Если это так, отправьте нам отзыв, чтобы мы исправили этот раздел.