Поддержка пункта меню "Открыть в..." в моем приложении для iOS Mail и Safari

Мне нужно, чтобы мое приложение открывало документы из приложений Safari и Mail с помощью этой кнопки "Открыть в..." в UIDocumentInteractionController учебный класс. Как мне это сделать?

2 ответа

Решение

Я знаю, что это было крайне неприятно для меня, как для начинающего программиста, или даже для человека с умеренными навыками. Файловый ввод / вывод через приложения Mail и Safari включает в себя очень... интересно названные соглашения внутри самого приложения. Итак, давайте запачкаем руки проектом Xcode для iPhone. Откройте XCode (я буду использовать 4.2 для этого урока) и выберите шаблон приложения "Единый вид" (или создайте пустой проект, затем добавьте один вид с расширением.xib).

Снимок экрана, показывающий лист выбора шаблона Xcode

В этом недавно созданном приложении переименуйте контроллер представления (и связанный xib) в OfflineReaderViewController и тогда мы перейдем к коду. (Мы коснемся каждого файла, кроме заголовка префикса и main.m, так что имейте в виду, что вам нужно все перед вами!)

Введите заголовок AppDelegate и вставьте в него следующий код:

#import <UIKit/UIKit.h>

@class OfflineReaderViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) OfflineReaderViewController *viewController;

@end

Затем введите файл делегата.m и дословно вставьте следующий код:

#import "AppDelegate.h"
#import "OfflineReaderViewController.h"

@implementation AppDelegate

@synthesize window;
@synthesize viewController;

-(BOOL)application:(UIApplication *)application 
           openURL:(NSURL *)url 
 sourceApplication:(NSString *)sourceApplication 
        annotation:(id)annotation 
{    
    // Make sure url indicates a file (as opposed to, e.g., http://)
    if (url != nil && [url isFileURL]) {
        // Tell our OfflineReaderViewController to process the URL
        [self.viewController handleDocumentOpenURL:url];
    }
    // Indicate that we have successfully opened the URL
    return YES;
}
- (void)dealloc
{
    [window release];
    [viewController release];
    [super dealloc];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    // Override point for customization after application launch.
    self.viewController = [[[OfflineReaderViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    /*
     Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
     Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
     */
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    /*
     Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
     If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
     */
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
     */
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    /*
     Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
     */
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    /*
     Called when the application is about to terminate.
     Save data if appropriate.
     See also applicationDidEnterBackground:.
     */
}

@end

Это:

-(BOOL)application:(UIApplication *)application 
               openURL:(NSURL *)url 
     sourceApplication:(NSString *)sourceApplication 
            annotation:(id)annotation 
    {    
        if (url != nil && [url isFileURL]) {
            [self.viewController handleDocumentOpenURL:url];
        }    
        return YES;
    }

Является единственной наиболее важной частью этого урока. Чтобы разбить его на соответствующие части: -(BOOL)application:(UIApplication *)application это наш пример приложения; openURL:(NSURL *)url это отправленный URL-адрес, чтобы сказать нам, что открыть; sourceApplication:(NSString *)sourceApplication это приложение, которое отправило ссылку; а также annotation:(id)annotation это дополнительная функция, мы не будем вдаваться в.

Теперь мы должны сделать макет нашей XIB. Введите XIB (который должен называться "OfflineReaderViewController", но это не имеет значения с XIB, если мы не вызываем initWithNibName: (что мы не будем), и сделать его похожим на картинку ниже:

Скриншот макета IB

ОЧЕНЬ важно, чтобы вы пошли в UIWebView Атрибуты и отметьте "Scales Pages To Fit", так как это позволит нам увеличивать и уменьшать масштаб веб-страниц с помощью зажимов. Пока не беспокойтесь о связях, мы скоро их создадим.

Введите OfflineReaderViewController Заголовок и вставьте следующее:

#import <UIKit/UIKit.h>

@interface OfflineReaderViewController : UIViewController 
<UIDocumentInteractionControllerDelegate> {
    IBOutlet UIWebView *webView;
}

-(void)openDocumentIn;
-(void)handleDocumentOpenURL:(NSURL *)url;
-(void)displayAlert:(NSString *) str;
-(void)loadFileFromDocumentsFolder:(NSString *) filename;
-(void)listFilesFromDocumentsFolder;

- (IBAction) btnDisplayFiles;

@end

Теперь.m:

#import "OfflineReaderViewController.h"

@implementation OfflineReaderViewController

UIDocumentInteractionController *documentController;

-(void)openDocumentIn {    
    NSString * filePath = 
    [[NSBundle mainBundle] 
     pathForResource:@"Minore" ofType:@"pdf"];    
    documentController = 
    [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
    documentController.delegate = self;
    [documentController retain];
    documentController.UTI = @"com.adobe.pdf";
    [documentController presentOpenInMenuFromRect:CGRectZero 
                                           inView:self.view 
                                         animated:YES];
}

-(void)documentInteractionController:(UIDocumentInteractionController *)controller 
       willBeginSendingToApplication:(NSString *)application {

}

-(void)documentInteractionController:(UIDocumentInteractionController *)controller 
          didEndSendingToApplication:(NSString *)application {

}

-(void)documentInteractionControllerDidDismissOpenInMenu:
(UIDocumentInteractionController *)controller {

}
-(void) displayAlert:(NSString *) str {
    UIAlertView *alert = 
    [[UIAlertView alloc] initWithTitle:@"Alert" 
                               message:str 
                              delegate:self
                     cancelButtonTitle:@"OK"
                     otherButtonTitles:nil];
    [alert show];
    [alert release];    
}

- (void)handleDocumentOpenURL:(NSURL *)url {
    [self displayAlert:[url absoluteString]];
    NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];        
    [webView setUserInteractionEnabled:YES];    
    [webView loadRequest:requestObj];
}


-(void)loadFileFromDocumentsFolder:(NSString *) filename {
    //---get the path of the Documents folder---   
    NSArray *paths = NSSearchPathForDirectoriesInDomains(  
                                                         NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0];     
    NSString *filePath = [documentsDirectory 
                          stringByAppendingPathComponent:filename];    
    NSURL *fileUrl = [NSURL fileURLWithPath:filePath];        
    [self handleDocumentOpenURL:fileUrl];
}

-(void)listFilesFromDocumentsFolder {    
    //---get the path of the Documents folder---    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(     
                                                         NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0]; 

    NSFileManager *manager = [NSFileManager defaultManager];
    NSArray *fileList =   
    [manager contentsOfDirectoryAtPath:documentsDirectory error:nil];
    NSMutableString *filesStr = 
    [NSMutableString stringWithString:@"Files in Documents folder \n"];
    for (NSString *s in fileList){    
        [filesStr appendFormat:@"%@ \n", s];
    }
    [self displayAlert:filesStr];    
    [self loadFileFromDocumentsFolder:@"0470918020.pdf"];
}

- (IBAction) btnDisplayFiles {
    [self listFilesFromDocumentsFolder];    
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];
    [self openDocumentIn];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

@end

Те из вас, кто активно следит, а не просто копирует все, что я вам говорю (шучу), будет знать, что эта строка: [[NSBundle mainBundle] pathForResource:@"Minore" ofType:@"pdf"]; даст нам SIGABRT, потому что, ну, файл не существует! Итак, перетащите любой общий PDF-файл, который вы извлекли откуда угодно (я рекомендую здесь, потому что кто не тратит свое свободное время на чтение огромного количества документации?), Затем скопируйте его заголовок и вставьте его с удаленным суффиксом (.pdf); ofType:@"pdf" часть заботится об этом для нас. Строка должна выглядеть следующим образом: [[NSBundle mainBundle] pathForResource:@"//file name//" ofType:@"pdf"];

Теперь вернитесь в XIB и подключите эти IBOutlets! В общем, вот как должна выглядеть ваша вкладка "Владелец файла":

Снимок экрана, показывающий установленные соединения

Кажется, мы закончили... но подождите! Мы ничего не сделали, чтобы запустить меню "Открыть в..."! Что ж, получается, что в файле.plist нужно что-то путать. Откройте.plist приложения (с помощью быстрого щелчка правой кнопкой мыши, затем выберите "Открыть как"> "Исходный код") и вставьте следующее:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleDisplayName</key>
    <string>${PRODUCT_NAME}</string>
    <key>CFBundleExecutable</key>
    <string>${EXECUTABLE_NAME}</string>
    <key>CFBundleIconFiles</key>
    <array/>
    <key>CFBundleIdentifier</key>
    <string>CodaFi.${PRODUCT_NAME:rfc1034identifier}</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>${PRODUCT_NAME}</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UIFileSharingEnabled</key>
    <true/>
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeName</key>
            <string>PDF Document</string>
            <key>LSHandlerRank</key>
            <string>Alternate</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>com.adobe.pdf</string>
            </array>
        </dict>
    </array>
</dict>
</plist>

[Примечание: будьте осторожны, осматривая исходный код любого списка, если вы не знаете, что делаете, вы можете получить страшную ошибку "Этот файл был поврежден" из Xcode]

Если щелкнуть правой кнопкой мыши и выбрать "Открыть как"> "Список свойств", это будет выглядеть так:

Снимок окна редактора списков Xcode

Там есть еще одно ОЧЕНЬ важное поле, которое называется "Приложение поддерживает общий доступ к файлам iTunes". Для него должно быть установлено значение "ДА", иначе ваше приложение не будет отображаться в iTunes как поддерживающее общий доступ к файлам.

Поле "Типы документов" определяет типы документов, которые может открыть наш пример. Разверните стрелку, чтобы найти ее роль и ИМП. Это уникальные идентификаторы (Unique Type Identifiers; кажется очевидным, что сейчас означает эта аббревиатура, не так ли?), Которые есть у каждого типа файлов. UTI - это то, что позволяет поисковику заменить общее изображение документа этим красивым локализованным изображением типа файла (не верьте мне, переименуйте неважное расширение файла в.ouhbasdvluhb и попробуйте получить красивую картинку!) Если бы я хотел открыть свой свой собственный формат (скажем, файл.code), то я бы поставил что-то вроде com.CodaFi.code (обратная запись DNS для тех, у кого нет подсказки) в поле UTI и в поле "Имя типа документа" будет "CodaFi Document". Ранг и роль обработчика должны быть простыми, поскольку наш ранг обработчика является альтернативным (потому что мы не являемся владельцем файла), а наша роль - средство просмотра (потому что нам не нужно ничего более важного. Наш пример - просто средство просмотра, а не редактор, поэтому мы оставим это так.

Для дальнейшего использования UTI имеют официальные объявленные системой схемы именования, когда они поступают из уважаемых источников (Oracle, Microsoft, даже сама Apple), которые можно найти в Справочном руководстве по унифицированному идентификатору типа, но перечислены здесь для удобства педантизма.

Теперь давай бегать! Код должен собираться без ошибок, при условии, что вы скопировали дословно и правильно выполнили эти проклятые xib-соединения. Теперь, когда вы впервые запускаете свое приложение, вам должна быть предоставлена ​​возможность открыть документ в iBooks. Отмените выбор, настоящая суть кода - открытие других документов! Запустите Safari и найдите любой PDF-файл, который Safari может открыть QuickLook или открыть. Затем в меню "Открыть в..." наше приложение появляется! Нажмите на это. Вы получите небольшую анимацию переключения и появится предупреждение о местонахождении файла. Когда вы отклоняете это, UIWebView загрузит PDF. Почтовое приложение имеет схожую функциональность с вложениями. Вы также можете вызвать эти PDF-файлы до вашего приложения.

Вот и все, все готово. Наслаждайтесь и счастливого кодирования!

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

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

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

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeIconFiles</key>
        <array>
            <string>Document-molecules-320.png</string>
            <string>Document-molecules-64.png</string>
        </array>
        <key>CFBundleTypeName</key>
        <string>Molecules Structure File</string>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>LSHandlerRank</key>
        <string>Owner</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>com.sunsetlakesoftware.molecules.pdb</string>
            <string>org.gnu.gnu-zip-archive</string>
        </array>
    </dict>
</array>

Один из UTI, использованных в вышеприведенном примере, был системным, а другой - UTI для конкретного приложения. UTI для конкретного приложения необходимо будет экспортировать, чтобы другие приложения в системе могли знать об этом. Для этого вы должны добавить раздел в ваш Info.plist, как показано ниже:

<key>UTExportedTypeDeclarations</key>
<array>
    <dict>
        <key>UTTypeConformsTo</key>
        <array>
            <string>public.plain-text</string>
            <string>public.text</string>
        </array>
        <key>UTTypeDescription</key>
        <string>Molecules Structure File</string>
        <key>UTTypeIdentifier</key>
        <string>com.sunsetlakesoftware.molecules.pdb</string>
        <key>UTTypeTagSpecification</key>
        <dict>
            <key>public.filename-extension</key>
            <string>pdb</string>
            <key>public.mime-type</key>
            <string>chemical/x-pdb</string>
        </dict>
    </dict>
</array>

Этот конкретный пример экспортирует com.sunsetlakesoftware.molecules.pdb UTI с расширением.pdb, соответствующим типу MIME chemical/x-pdb,

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

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

Другие вопросы по тегам