web-dev-qa-db-fra.com

Prise en charge de l'élément de menu Ouvrir dans ... dans mon application pour iOS Mail et Safari

J'ai besoin que mon application ouvre les documents des applications Safari et Mail avec ce truc "Ouvrir dans ..." dans la classe UIDocumentInteractionController. Comment est-ce que j'accomplis ceci?

49
CodaFi

Je sais que c'était extrêmement frustrant pour moi en tant que programmeur débutant, ou même en tant que modérément qualifié maintenant. Les E/S de fichiers via les applications Mail et Safari impliquent des conventions nommées de manière très intéressante dans l'application elle-même. Alors mettons la main à la pâte avec un projet Xcode pour iPhone. Ouvrez Xcode (j'utiliserai 4.2 pour ce tutoriel) et sélectionnez le modèle d'application 'Vue unique' (ou créez un projet vide, puis ajoutez une vue unique avec un .xib).

Screenshot showing Xcode template selection sheet

Dans cette application nouvellement créée, renommez le contrôleur de vue (et le xib associé) en OfflineReaderViewController, puis nous passerons au code. (Nous toucherons tous les fichiers sauf l'en-tête de préfixe et main.m, alors sachez que vous aurez besoin de tout devant vous!)

Entrez l'en-tête AppDelegate et collez-y le code suivant:

#import <UIKit/UIKit.h>

@class OfflineReaderViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) OfflineReaderViewController *viewController;

@end

Entrez ensuite le fichier .m du délégué et collez le code suivant textuellement:

#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

Cette:

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

Est la partie la plus importante de ce didacticiel. Pour le décomposer en ses parties respectives: -(BOOL)application:(UIApplication *)application est notre exemple d'application; openURL:(NSURL *)url est l'URL qui est envoyée pour nous dire quoi ouvrir; sourceApplication:(NSString *)sourceApplication est l'application qui a envoyé le lien; et annotation:(id)annotation est une fonctionnalité supplémentaire dans laquelle nous n'entrerons pas.

Maintenant, nous devons mettre en page notre xib. Entrez le xib (qui devrait être intitulé "OfflineReaderViewController", mais cela n'a pas d'importance avec un xib, sauf si nous appelons initWithNibName: (Ce que nous ne ferons pas)), et le faire ressembler à l'image ci-dessous:

Screenshot of IB layout

Il est TRÈS important que vous alliez dans les attributs de UIWebView et que vous cochez "Scales Pages To Fit", car cela nous permet de zoomer et dézoomer sur les pages Web avec des pincements. Ne vous inquiétez pas des connexions pour l'instant, nous les créerons sous peu.

Entrez l'en-tête OfflineReaderViewController et collez ce qui suit:

#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

Maintenant, le .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

Ceux d'entre vous qui regardent activement et ne copient pas seulement tout ce que je vous dis (plaisantant) savent que cette ligne: [[NSBundle mainBundle] pathForResource:@"Minore" ofType:@"pdf"]; Nous donnera un SIGABRT car, eh bien, le fichier n'existe pas! Donc, faites glisser n'importe quel PDF PDF que vous avez tiré de partout (je recommande ici car qui ne passe pas son temps libre à lire d'énormes quantités de documentation?), Puis copiez son titre et collez-le avec le suffixe (.pdf) supprimé; la partie ofType:@"pdf" s'en charge pour nous. La ligne devrait ressembler à ceci quand vous en aurez fini: [[NSBundle mainBundle] pathForResource:@"//file name//" ofType:@"pdf"];

Maintenant, retournez dans le xib et connectez ces IBOutlets! Tout compte fait, voici à quoi devrait ressembler votre onglet "Propriétaire du fichier":

Screenshot showing established connections

Il semble que nous ayons fini ... mais attendez! Nous n'avons rien fait pour que le menu "Ouvrir dans ..." soit opérationnel! Eh bien, il s'avère que le fichier .plist est nécessaire. Ouvrez l'application .plist (avec un clic droit rapide, puis sélectionnez Ouvrir en tant que> Code source) et collez ce qui suit:

<?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>

[Note latérale: soyez prudent dans le code source de n'importe quel plist, si vous ne savez pas ce que vous faites, vous pourriez obtenir l'erreur redoutée "Ce fichier a été corrompu" de Xcode]

Si l'on faisait un clic droit et sélectionnez Ouvrir en tant que> Liste des propriétés, cela ressemblerait à ceci:

Shot of Xcode plist editor window

Il y a un autre champ TRÈS important appelé "L'application prend en charge le partage de fichiers iTunes". Cela doit être réglé sur "OUI", sinon votre application n'apparaîtra pas dans iTunes comme prenant en charge le partage de fichiers.

Le champ 'Types de documents' spécifie les types de documents que notre exemple peut ouvrir. Développez la flèche pour trouver son rôle et les UTI. Ce sont des identifiants uniques (Unique Type Identifiers; semble évident ce que cet acronyme signifie maintenant, n'est-ce pas?) Que chaque type de fichier possède. Les UTI permettent au Finder de remplacer une image de document générique par cette belle image localisée du type de fichier (ne me croyez pas, renommez une extension de fichier sans importance en .ouhbasdvluhb et essayez d'obtenir une belle image!) Si je voulais ouvrir mon propre format personnalisé (disons un fichier .code) alors je mettrais quelque chose comme com.CodaFi.code (notation DNS inversée pour ceux sans indice) dans le champ UTI et le nom du type de document serait 'Document CodaFi'. Le rang et le rôle du gestionnaire doivent être simples, car notre rang de gestionnaire est alternatif (parce que nous ne possédons pas le fichier) et notre rôle est le visualiseur (parce que nous n'avons besoin de rien de plus important. Notre exemple est juste un visualiseur et non un éditeur, nous allons donc le laisser tel quel.

Pour référence future, les UTI ont des schémas de dénomination officiels déclarés par le système lorsqu'ils proviennent de sources respectées (Oracle, Microsoft, même Apple lui-même) qui peuvent être trouvés dans le niform Type Identifier Reference Guide , mais sont répertoriés ici pour la pédanterie.

Maintenant, courons! Le code devrait être construit sans erreur, en supposant que vous ayez copié mot pour mot et obtenu ces bons raccords xib. Maintenant, lorsque vous lancez votre application pour la première fois, vous devriez avoir la possibilité d'ouvrir un document dans iBooks. Désélectionnez-le, la véritable viande du code ouvre d'autres documents! Lancez Safari et recherchez n'importe quel PDF que Safari peut QuickLook ou ouvrir. Puis dans le menu "Ouvrir dans ...", notre application apparaît! Cliquez dessus. Vous obtiendrez le petit switcheroo une animation et une alerte s'afficheront avec l'emplacement du fichier. Lorsque vous le supprimez, le UIWebView aura chargé le PDF. L'application Mail a des fonctionnalités similaires avec des pièces jointes. Vous pouvez également appeler ces PDF jusqu'à votre app.

Voilà, tout est fait. Profitez-en et codage heureux!

97
CodaFi

Il y a une superbe réponse à cette question ici . J'ai copié une partie de la réponse ci-dessous pour plus de clarté, mais vous devriez vous référer à cette question pour la réponse complète.

La gestion des types de fichiers est nouvelle avec iPhone OS 3.2 et diffère des schémas d'URL personnalisés déjà existants. Vous pouvez enregistrer votre application pour gérer des types de documents particuliers, et toute application utilisant un contrôleur de documents peut transférer le traitement de ces documents à votre propre application.

Pour enregistrer le support, vous aurez besoin d'avoir quelque chose comme ce qui suit dans votre 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>

L'un des UTI utilisé dans l'exemple ci-dessus était défini par le système, mais l'autre était un UTI spécifique à l'application. L'UTI spécifique à l'application devra être exportée pour que les autres applications du système puissent en être informées. Pour ce faire, vous devez ajouter une section à votre Info.plist comme suit:

<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>

Cet exemple particulier exporte le com.sunsetlakesoftware.molecules.pdb UTI avec l'extension de fichier .pdb, correspondant au type MIME chemical/x-pdb.

Avec cela en place, votre application sera en mesure de gérer les documents joints aux e-mails ou à partir d'autres applications sur le système. Dans Mail, vous pouvez appuyer longuement pour afficher une liste d'applications pouvant ouvrir une pièce jointe particulière.

Lorsque la pièce jointe est ouverte, votre application sera lancée et vous devrez gérer le traitement de ce fichier dans votre -application:didFinishLaunchingWithOptions: méthode de délégué d'application. Il semble que les fichiers chargés de cette manière à partir de Mail soient copiés dans le répertoire Documents de votre application sous un sous-répertoire correspondant à la boîte aux lettres dans laquelle ils sont arrivés.

6
memmons