Добавляем возможность входа через ВКонтакте в iOS приложение

в 8:57, , рубрики: apple, ios development, objective-c, SDK, Вконтакте API, разработка под iOS, метки: , , ,
Вступление

Добрый день!
Иметь «выходы» на социальные сети в своем приложении — крайне полезная штука. И сегодня мне хотелось бы поделиться своим небольшим опытом в «прикручивании» всем известной социальной сети к своему приложению. Что в этом интересного? А то, что в некоторых случаях использовать полноценные SDK просто нет смысла (учтем еще то, что официального ВКонтакте не имеет). Мне требовалось всего лишь узнать user_id и добавить возможность «Рассказать друзьям». В общем, всех, кто заинтересовался, прошу под кат! На сладкое — немного кодинга.

Кому будет полезно?

Новичкам в iOS разработке, тем, кто хочет расширить возможности своего приложения, но не хочет использовать сторонние SDK, и, конечно, тем, кто просто увлекается этим делом.

Начнем

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

http://oauth.vk.com/authorize? 
 client_id=APP_ID& 
 scope=SETTINGS& 
 redirect_uri=REDIRECT_URI& 
 display=DISPLAY& 
 response_type=token

Не буду повторять, что означают все эти параметры, они как минимум расписаны в приведенной выше странице, как максимум — интуитивно понятны по названию.

А теперь кодим!

Ура-ура, все этого ждали! Допустим, в вашем приложении есть некий ViewController, в котором имеется кнопочка «Зайти через ВКонтакте». Хорошо.

Получаем access_token

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

-(IBAction)vkontakteButton:(id)sender {
//создаем webView
	authWebView = [[UIWebView alloc] initWithFrame:CGRectMake(10, 20, 300, 400)];
	authWebView.tag = 1024;
	authWebView.delegate = self;
	UIButton* closeButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
	[self.view addSubview:authWebView];
	[authWebView loadRequest: [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://oauth.vk.com/authorize?client_id=3038779&scope=wall,offline&redirect_uri=oauth.vk.com/blank.html&display=touch&response_type=token"]]];
//обеспечиваем появление клавиатуры для авторизации
	[self.view.window makeKeyAndVisible];
//создаем кнопочку для закрытия окна авторизации
	closeButton.frame = CGRectMake(5, 15, 20, 20);
	closeButton.tag = 1025;
	[closeButton addTarget:self action:@selector(closeWebView:) forControlEvents:UIControlEventTouchUpInside];
	[closeButton setTitle:@"x" forState:UIControlStateNormal];
	[self.view addSubview:closeButton];
}
-(void) closeWebView {
	[[self.view viewWithTag:1024] removeFromSuperview];
	[[self.view viewWithTag:1025] removeFromSuperview];
}
-(IBAction)closeWebViewButton:(id)sender {
	[self closeWebView];
}

Собственно, здесь мы сделали все необходимые приготовления. Остановлюсь подробнее на самом главном — запросе.

http://oauth.vk.com/authorize?client_id=APP_ID&scope=wall,offline&redirect_uri=oauth.vk.com/blank.html&display=touch&response_type=token

client_id=APP_ID — вместо APP_ID подставляем то, что получим после регистрации приложения на сайте;
scope=wall,offline — попросим доступ на работу со стеной и на работу в оффлайне (чтоб токен долго не истекал);
redirect_uri=oauth.vk.com/blank.html — тут мы найдем запрашиваемый токен, главная задача — отследить перенаправление на эту страницу и тут же закрыть окно авторизации (страница совсем некрасивая и пользователю совсем не надо ее видеть);
display=touch — на iPhone смотрится как родная, все оптимизировано для работы с тач устройствами;
response_type=token — ну и, собственно, то что хотим получить.
Далее, нам надо отследить момент перехода на oauth.vk.com/blank.html. Что ж, вспоминаем, у UIWebView есть замечательный метод webViewDidFinishLoad, который вызывается после загрузки очередной страницы (не забудьте добавить его в хедер!).

-(void) webViewDidFinishLoad:(UIWebView *)webView {
//создадим хеш-таблицу для хранения данных
        NSMutableDictionary* user = [[NSMutableDictionary alloc] init];
//смотрим на адрес открытой станицы
	NSString *currentURL = webView.request.URL.absoluteString;
	NSRange textRange =[[currentURL lowercaseString] rangeOfString:[@"access_token" lowercaseString]];
//смотрим, содержится ли в адресе информация о токене
	if(textRange.location != NSNotFound){
//Ура, содержится, вытягиваем ее
		NSArray* data = [currentURL componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"=&"]];		
		[user setObject:[data objectAtIndex:1] forKey:@"access_token"];
		[user setObject:[data objectAtIndex:3] forKey:@"expires_in"];
		[user setObject:[data objectAtIndex:5] forKey:@"user_id"];
		[self closeWebView];
//передаем всю информацию специально обученному классу
		[[VkontakteDelegate sharedInstance] loginWithParams:user];
	}
	else {
//Ну иначе сообщаем об ошибке...
		textRange =[[currentURL lowercaseString] rangeOfString:[@"access_denied" lowercaseString]];
		if (textRange.location != NSNotFound) {
			UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Ooops! something gonna wrong..." message:@"Check your internet connection and try again!" delegate:self cancelButtonTitle:@"Ok" otherButtonTitles: nil];
			[alert show];
			[self closeWebView];
		}
	}
}

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

Токены получили, что дальше?

Дальше создадим синглтон для работы с API (возможности, конечно же, можно расширить, тут они невелики)
Хедер:

#import <Foundation/Foundation.h>

@interface VkontakteDelegate : NSObject

@property NSString *username, *realName, *ID, *link, *email, *access_token;
@property UIImage* photo;

+ (id)sharedInstance;
-(void) loginWithParams: (NSMutableDictionary*) params;
-(void) postToWall;

@end

Реализация:

#import "VkontakteDelegate.h"

@implementation VkontakteDelegate

@synthesize username, realName, ID, photo, access_token, email, link;

+ (id)sharedInstance {
	static VkontakteDelegate *__sharedInstance;
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		__sharedInstance = [[VkontakteDelegate alloc]init];
	});
	return __sharedInstance;
}
- (id) init {
	access_token = [[NSUserDefaults standardUserDefaults] objectForKey:@"vk_token"];
	ID = [[NSUserDefaults standardUserDefaults] objectForKey:@"vk_id"];
	return  self;
}
-(void) loginWithParams:(NSMutableDictionary *)params {
	ID = [params objectForKey:@"user_id"];
	access_token = [params objectForKey:@"access_token"];
//Сохраняемся, ребят!
	[[NSUserDefaults standardUserDefaults] setValue:access_token forKey:@"vk_token"];
	[[NSUserDefaults standardUserDefaults] setValue:ID forKey:@"vk_id"];
	[[NSUserDefaults standardUserDefaults] synchronize];
//Теперь попробуем вытяныть некую информацию
	NSString *urlString = [NSString stringWithFormat:@"https://api.vk.com/method/users.get?uid=%@&access_token=%@", ID, access_token] ;
	NSURL *url = [NSURL URLWithString:urlString];
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];	
    NSHTTPURLResponse *response = nil;
    NSError *error = nil;
    NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
	NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
//Тут произошла странная вещь - ответ должен вернуться в словаре, ан нет! 
//У меня не получилось пропарсить стандартными средствами. 
//Строим костыли, простите...
	NSArray* userData = [responseString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"":{},[]"]];
	realName = [userData objectAtIndex:14];
	realName = [realName stringByAppendingString:@" "];
	realName = [realName stringByAppendingString:[userData objectAtIndex:20]];
//Кому надо, сохраняемся
	[[NSUserDefaults standardUserDefaults] setValue:@"vkontakte" forKey:@"SignedUpWith"];
	[[NSUserDefaults standardUserDefaults] setValue:realName forKey:@"RealUsername"];
	[[NSUserDefaults standardUserDefaults] synchronize];
}
-(void) postToWall {
//напишем что-нибудь на стене (вместо пробелов ставим "+")
	NSString* message = @"vkontakte+wall+posting";
	NSString *urlString = [NSString stringWithFormat:@"https://api.vk.com/method/wall.post?uid=%@&message=%@&attachments=http://google.com&access_token=%@", ID, message,access_token] ;
	NSURL *url = [NSURL URLWithString:urlString];
//Теперь, если очень хочется, можно взглянуть на ответ
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    NSHTTPURLResponse *response = nil;
    NSError *error = nil;
    NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
	NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
}

А вот так все должен был выглядеть момент парсинга ответа сервера:

NSError *jsonParsingError = nil;
	NSMutableDictionary *userInfo = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonParsingError];
	userInfo = [userInfo objectForKey:@"response"];
	realName = [userInfo objectForKey:@"first_name"];
	realName = [realName stringByAppendingFormat:[userInfo objectForKey:@"last_name"]];

Запрос на информацию о пользователе элементарен (документация):

https://api.vk.com/method/users.get?uid=%@&access_token=%@ 

Не буду его разъяснять, рассмотрим запрос посложнее. Вот и он:

https://api.vk.com/method/wall.post?uid=%@&message=%@&attachments=http://google.com&access_token=%@

Что же он из себя представляет?
uid=%@ — подставляем user_id;
message=%@ — нужное нам сообщение;
attachments=http://google.com — добавим ссылку на сайт;
access_token=%@ — подставляем, собственно, токен.
Подробнее тут.

Итог

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

На этом откланяюсь, надеюсь, начинающим будет полезно («аццким прогерам» — вряд ли). Жду ваших коментариев и замечаний. Спасибо, что прочли, как говорится, «пальцы вверх, подписывайтесь на наш канал!»

P.S. Возможно, код грязноват, но четко иллюстрирует картину взаимодействия с API.

Автор: x401om

  1. Negus:

    Здравствуйте! Спасибо за туториал, очень пригодился. Поправте ошибку в
    NSCharacterSet characterSetWithCharactersInString:@””:{},[]”]]; нужно заэкранировать кавычку. И костыль не нужен, просто вк присылает словарь в котором массив и в нем уже нужный словарь =) Пример:
    NSError *jsonError = nil;
    id jsonObject = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&jsonError];
    NSDictionary *tempVkDic = (NSDictionary*)jsonObject;
    NSArray *tempVkArry = [[NSArray alloc] initWithArray:[tempVkDic objectForKey:@”response”]];
    NSDictionary *infoFromVk = [[NSDictionary alloc] initWithDictionary:[tempVkArry objectAtIndex:0]];

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