Подтвердить что ты не робот

Поддержка пункта Open In... в моем приложении для iOS Mail And Safari

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

4b9b3361

Ответ 1

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

Screenshot showing Xcode template selection sheet

В этом новом приложении переименуйте контроллер представления (и связанный с ним 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: (которого мы не будем)) и сделаем его похожим на рисунок ниже:

Screenshot of IB layout

ОЧЕНЬ важно, чтобы вы вошли в атрибуты 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! Все сказано, вот что должно выглядеть ваша вкладка "Файл владельца":

Screenshot showing established connections

Кажется, мы закончили... но подождите! Мы ничего не сделали, чтобы открыть меню "Открыть в..."! Ну, оказывается, что в файле .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>

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

Если нужно щелкнуть правой кнопкой мыши и выбрать Open As > Property List, это будет выглядеть так:

Shot of Xcode plist editor window

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

В поле "Типы документов" указаны типы документов, которые может открыть наш пример. Разверните стрелку, чтобы найти ее роль и UTI. Это уникальные идентификаторы (уникальные идентификаторы типов, кажется очевидным, что означает этот аббревиатура, не так ли?), Что каждый вид файла имеет. UTI - это то, что позволяет finder заменить общий образ документа с этим красивым локализованным изображением типа файла (не верьте мне, переименуйте несущественное расширение файла в .ouhbasdvluhb и попытайтесь получить красивое изображение!) Если бы я хотел открыть (скажем, файл .code), тогда я бы поставил что-то вроде com.CodaFi.code (обратная DNS-запись для тех, у кого нет подсказки) в поле UTI, а Document Type Name будет "Document CodaFi". Ранг и роль обработчика должны быть понятны, поскольку наш ранг обработчика является альтернативным (потому что у нас нет файла), а наша роль - зритель (потому что нам не нужно ничего более важного. Наш пример - это просто средство просмотра, а не редактор, поэтому мы оставим это как таковое.

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

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

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

Ответ 2

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

Обработка типов файлов является новой с 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, копируются в ваш каталог "Документы" под подкаталогом, соответствующим тому, в каком почтовом ящике они прибыли.