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

Зачем это нужно?

URL-схемы являются довольно удобным способом передачи данных в iOS-приложение из, например, другого iOS-приложения, зачастую в весьма специфических ситуациях. Например автору этого блога в определенный момент времени пришлось искать способ передачи параметров одного сложного объекта разрабатываемой системы от одного пользователя мобильного приложения другому пользователю.

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

Одним из способов решить данную задачу будет реализовать в мобильном приложении поддержку специальной URL-схемы, и дать пользователям возможность посылать из приложения электронные письма со специально сформированной строкой вида appurlscheme://id, где appurlscheme:// представляет собой URL-схему, а id - идентификатор объекта, которым пользователь хочет поделиться с товарищем.

Получатель письма может перейти по этой ссылке (а все это безобразие будет выглядеть как обычная ссылка), только вместо мобильного Safari откроется наше приложение, которое при открытии получит параметр id и запросит у сервера копию объекта (чтобы не попортить объект отправителя). И дело в шляпе! :)

Как это сделать?

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

Для реализации всего нижеперечисленного необходимо устройство с iOS для запуска приложений.

Приложение Sender

Данное приложение будет вызывать другое приложение (которое мы назовем Receiver) и передавать в него данные. Создайте новый проект с названием Sender на базе шаблона Single View Application.

URL Schemes, рис. 1

Рис. 1. Создаем приложение Sender


Далее приведите h-файл view controller-а к следующему виду:

#import <UIKit/UIKit.h>

@interface SNDViewController : UIViewController

- (IBAction)openYoutube:(id)sender;
- (IBAction)openMaps:(id)sender;
- (IBAction)openReceiverApp:(id)sender;

@property (nonatomic, weak) IBOutlet UITextField *textField;

@end

Далее идем в Interface Builder и создаем на нашем View 3 кнопки и 1 TextField, располагая их примерно так, как на рисунке ниже.

URL Schemes, рис. 2

Рис. 2. Внешний вид приложения Sender


Соединяем необходимые IBAction-ы и IBOutlet (надеюсь, это не составит серьезных затруднений для моих читателей).

После этого необходимо добавить следующий код в m-файл view controller-а:

- (IBAction)openYoutube:(id)sender {
    NSString *path = @"http://www.youtube.com/watch?v=Xp697DqsbUU";
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:path]];
}

- (IBAction)openMaps:(id)sender {
    NSString *path = @"http://maps.apple.com/maps?ll=37.3316979,-122.0301982&z=18";
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:path]];
    
}

- (IBAction)openReceiverApp:(id)sender {
    NSString *text = [self.textField.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSString *path = [@"receiverapp://" stringByAppendingString:text];
    NSURL *url = [NSURL URLWithString:path];
    if ([[UIApplication sharedApplication] canOpenURL:url]) {
        [[UIApplication sharedApplication] openURL:url];
    } else {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Приложение Receiver не найдено" message:@"Приложение Receiver не установлено на данном устройстве!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];
    }
}

Ключевыми здесь являются два метода - openURL и canOpenURL.

Метод openURL осуществляет переход непосредственно к контенту, расположенному по передаваемой ссылке.

Метод canOpenURL проверяет, может ли iOS открыть передаваемую ссылку. Если в качестве URL-схемы будет передана схема, не зарегистрированная в iOS (и отличная, например, от http://, https://, ftp:// и т.п.), этот метод вернет NO.

В случае методов openYoutube: и openMaps: в результате выполнения метода openURL произойдет запуск соответствующих мобильных приложений iOS - YouTube и Maps. Я не буду подробно останавливаться на этих двух методах, как формировать адресную строку для них прекрасно описано в документации Apple.

Про метод openReceiverApp: поговорим чуть подробнее. В этом методы мы берем введенную пользователем в TextField строку, преобразуем ее к виду валидной строки URL при помощи метода stringByAddingPercentEscapesUsingEncoding: (в результате этого, например, пробелы заменяются на строку вида %20), добавляем перед результатом строку receiverapp://, которая и будет нашей кастомной URL-схемой в данном примере.

После этого мы с помощью метода canOpenURL: проверяем, может ли iOS запустить приложение Receiver с помощью нашей ссылки. Если может, просто запускаем Receiver. Если не может - выдаем соответствующее сообщение пользователю.

Если запустить полученное приложение на мобильном устройстве и попробовать понажимать на кнопки, мы убедимся, что приложение успешно запускает проигрывание видео в YouTube и отображение объекта по указанным координатам в Maps (см. рис. ниже).

URL Schemes, рис. 3

Рис. 3. Результат нажатия на кнопку вызова Maps


URL Schemes, рис. 4

Рис. 4. Результат нажатия на кнопку вызова YouTube


При этом, если прямо сейчас нажать на кнопку запуска приложения Receiver, то мы получим сообщение об ошибке, как на рис. ниже.

URL Schemes, рис. 5

Рис. 5. Результат нажатия на кнопку вызова приложения Receiver,
когда оно еще не установлено на устройстве


Самое время создать приложение Receiver.

Приложение Receiver

Создайте новый проект с названием Receiver на базе шаблона Single View Application.

URL Schemes, рис. 6

Рис. 6. Создаем приложение Receiver


Далее нам необходимо зарегистрировать упомянутую нами ранее кастомную url-схему receiverapp://. Для этого идем в plist-файл проекта и добавляем в нем ключ URL types, а в нем добавляем ключ URL Schemes на одном уровне с ключом URL identifier (см. рис. ниже).

URL Schemes, рис. 7

Рис. 7. Добавляем в Receiver поддержку URL-схемы


В URL Schemes добавляем элемент с именем receiverapp.

URL Schemes, рис. 8

Рис. 8. Наша URL-схема будет называться receiverapp


Особых изменений в отображаемом view данного приложения я не делал, можно просто добавить на него Label с текстом Receiver.

URL Schemes, рис. 9

Рис. 9. Внешний вид приложения Receiver


Далее идем в файл AppDelegate.m и приводим метод application:application didFinishLaunchingWithOptions: к виду:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    if (![launchOptions objectForKey:UIApplicationLaunchOptionsURLKey]) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Ошибка" message:@"Приложение запущено не из приложения Sender!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];
    }
    
    return YES;
}

При обычном запуске этого приложения (не из приложения Sender!) приложение будет выдавать alert с текстом ошибки. Это сделано исключительно в целях тестирования и в реальном приложении такая функциональность совершенно ни к чему.

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

Для запуска приложения по ссылке с кастомной URL-схемой нам необходимо добавить в AppDelegate.m метод application:handleOpenURL::

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    NSString *text = [[url host] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Получен текст" message:text delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alert show];
    
    return YES;
}

Здесь мы извлекаем передаваемый текст из объекта NSURL и отображаем его пользователю.

Теперь можно запустить приложение Sender и из него запустить приложение Receiver.

URL Schemes, рис. 10

Рис. 10. Отправляем сообщение из Sender в Receiver


URL Schemes, рис. 11

Рис. 11. Receiver получил сообщение


Заключение

Несколько мыслей по поводу описанного механизма.

  1. URL-схему можно использовать не только в нативном приложении, но и в виде обыкновенной html-ссылки вида <a href="receiverapp://">Открыть объект в приложении Receiver</a>.

  2. Не стоит передавать подобным образом между приложениями логины и пароли (да и вообще любые “чувствительные” для пользователя данные). Для этого существует Keychain API.

  3. Не нужно использовать URL-схему, если вместо этого можно встроить требуемую функциональность в само приложение. Например вместо того, чтобы открыть координаты в Apple Maps, рассмотрите возможность встроить карты прямо в Ваше приложение. То же самое касается и отображения веб-страниц: вместо вызова Mobile Safari, в некоторых случаях, будет лучше отображать их прямо в самом приложении.

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