jjzjj

objective-c - 用于 Google 文档的 iOS Objective-C 包装器

coder 2023-09-23 原文

有没有人将谷歌文档集成到他们的 iOS 应用程序中?通过示例代码,Google Docs 的 API 比我预期的要复杂得多,而且示例都是 MacOS。是的,有 iOS 支持,但明显缺乏关于如何使用它的示例代码,而且文档也有些缺乏。

我确实在网上找到了一个接口(interface)类,但它是基于旧的、已弃用的 Google Docs API 版本,并且不能使用 XCode 4.2 进行编译。

我所追求的是一个相对直接的界面,它允许:

  1. 从 google 文档帐户登录/注销。
  2. 获取该帐户中的文档列表(可选的特定类型),可能具有浏览文件夹结构的能力。
  3. 能够将特定文档下载到本地存储。
  4. 能够将特定文档上传到 google 文档。

我已经开始编写这样的界面,但到目前为止,它的复杂程度超出了我的预期。如果有人有任何建议或可以指导我的示例,我将不胜感激。

我的偏好是包装器是操作系统中立的;这意味着我希望能够在 MacOS 和 iOS 中使用相同的界面。同样,这就是我开始写的内容,但我不禁觉得我必须在这里重新发明轮子。

谢谢

最佳答案

好吧,在没有其他人回答的情况下,我硬着头皮自己写了一个包装器。

我现在拥有适用于 Mac OS 和 iOS 的单一包装器,可显着简化与 Google 文档的界面。

下面是实际界面的所有代码。我应该指出这个类作为一个单例,你需要通过更新行来为每个项目稍微定制它:

#define GOOGLE_DATA_CLIENT_ID @"<client id>.apps.googleusercontent.com"
#define GOOGLE_DATA_SECRET @"<google data secret>"
#define GOOGLE_DATA_USERNAME @"googleDocsUsername"
#define GOOGLE_DATA_PASSWORD @"googleDocsPassword"

使用您从 Google 获得的适当值。

我还指出该类通过 NSUserDefaults 存储密码,并使用单独的实用程序类以加密方式执行此操作。我在 bitbucket 中创建了一个存储库,而不是用所有这些额外的代码来阻塞这个答案:

https://bitbucket.org/pkclsoft/gdatainterface

其中包含构建两个目标的整个 XCode 项目,一个用于 Mac OS,一个用于 iOS。我在应用程序商店中的一个应用程序中使用了这两个应用程序,效果很好。可能是虽然这个项目是为我构建的,但您必须根据自己的目的对其进行调整。该项目包含一整套使用我的代码构建和运行的 Google SDK。我包含它是为了尝试降低与新版本 SDK 不兼容的风险,以防任何人使用它。

这是目前的接口(interface)规范:

//
//  GDataInterface.h
//  GDataInterface
//
//  Some of the code in this class is from the original GData sample code, but it has been
//  enhanced somewhat and made to work on both iOS and MacOS transparently.
//
//  Created by Peter Easdown on 19/12/11.
//  Copyright (c) 2011 PKCLsoft. All rights reserved.
//

#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#import "GDataDocs.h"
#import <UIKit/UIKit.h>
#else
#import "GData/GData.h"
#endif

@interface GDataInterfaceTypes

// This handler is used by methods that have no explicit result.  The boolean value indicates
// the success or failure of the the methods action.
//
typedef void (^CompletionHandler)(BOOL successful);

// This handler is called to update a progress indicator as a file is uploaded.
//
typedef void (^UploadProgressHandler)(double min, double max, double value);

// This handler is called to update a progress indicator as a file is downloaded.
//
typedef void (^DownloadProgressHandler)(double min, double max, double value);

@end

@interface GDataInterface : NSObject {

#if TARGET_OS_IPHONE
    // Needed so that when authenticating under iOS, we can push the google authentication
    // view, and later pop it.
    //
    UIViewController *rootController_;
#endif

    GDataFeedDocList *mDocListFeed;
    GDataServiceTicket *mDocListFetchTicket;
    NSError *mDocListFetchError;

    GDataFeedDocRevision *mRevisionFeed;
    GDataServiceTicket *mRevisionFetchTicket;
    NSError *mRevisionFetchError;

    GDataEntryDocListMetadata *mMetadataEntry;

    GDataServiceTicket *mUploadTicket;

    id uploadWindow;
    CompletionHandler uploadCompletionHandler;

    NSString *username_;
    NSString *password_;

}

// This handler is used when a list of documents has been requested.  The results parameter
// will be nil if the request failed.  If successful, then it will contain an array of 
// GDataEntryDocBase objects.
//
typedef void (^RetrievalCompletionHandler)(GDataFeedDocList* results, BOOL successful);

// This handler is used when a document has been downloaded.  If something prevented the 
// download from succeeding, then error parameter will be non-nil.
//
typedef void (^DocumentDownloadCompletionHandler)(NSData* fileContents, BOOL successful);

// Initializer that provides the username and password.
//
- (id) initWithUsername:(NSString*)username andPassword:(NSString*)password;

// Returns the shared instance of the class.  There will only ever be a single instance
// of this class.
//
+ (GDataInterface*) sharedInstance;

// Returns YES if currently signed in.
//
- (BOOL) isSignedIn;

// Signs in or out depending on current state, and executes the options completion handler
// block.  The window parameter is used to specify the root viewController object used when
// displaying login windows via GData, or error dialogs.
//
- (void) signInOrOutWithCompletionHandler:(CompletionHandler)handler forWindow:(id)window;

// Will retrieve a list of documents using the cached connection, and call the specified
// handler block, providing the list of documents, and a success/fail indication.
//
- (void) retrieveDocumentListWithCompletionHandler:(RetrievalCompletionHandler)handler;

// Will download the file at the specified URL.  This is not Google Docs specific and will work
// for any URL.  Be careful not to try and retrieve large files and the result is stored
// in memory.
//
- (void) downloadURL:(NSURL*)url withProgressHandler:(DownloadProgressHandler)progressHandler andCompletionHandler:(DocumentDownloadCompletionHandler)handler;

// Will download the specified google docs document.
//
- (void) downloadDocument:(GDataEntryDocBase*)document withProgressHandler:(DownloadProgressHandler)progressHandler andCompletionHandler:(DocumentDownloadCompletionHandler)handler;

// Uploads the document entry, optionally updating it with a new revision.
//
- (void) uploadEntry:(GDataEntryDocBase*)docEntry asNewRevision:(BOOL)newRevision forWindow:(id)window withProgressHandler:(UploadProgressHandler)progressHandler andCompletionHandler:(CompletionHandler)handler;

// Uploads the specified file to the authenticated google docs account.
//
- (void)uploadFileAtPath:(NSString *)path forWindow:(id)window withProgressHandler:(UploadProgressHandler)progressHandler andCompletionHandler:(CompletionHandler)handler;

// More for internal use than anything else.  Used to determine the mime type based on the google docs class
// and/or file extension.
//
- (void)getMIMEType:(NSString **)mimeType andEntryClass:(Class *)class forExtension:(NSString *)extension;

// Getter and Setter for username,
//
- (void) setUsername:(NSString*)newUsername;
- (NSString*) username;

// Getter and Setter for password.  The password will be encrypted before storing it in user preferances.
//
- (void) setPassword:(NSString*)newPassword;
- (NSString*) password;

// Returns the username that google is given for signing in.
//
- (NSString *)signedInUsername;

// Returns a static instance of the docs service.
//
+ (GDataServiceGoogleDocs *)docsService;

@end

下面是实现:

//
//  GDataInterface.m
//  GDataInterface
//
//  Created by Peter Easdown on 19/12/11.
//  Copyright (c) 2011 PKCLsoft. All rights reserved.
//

#import "GDataInterface.h"
#import "Util.h"
#if TARGET_OS_IPHONE
#import "GTMOAuth2ViewControllerTouch.h"
#import "GData.h"
#else
#import "GData/GTMOAuth2WindowController.h"
//#import "GDataServiceGoogleSpreadsheet.h"
#endif

#define GOOGLE_DATA_CLIENT_ID @"<client id>.apps.googleusercontent.com"
#define GOOGLE_DATA_SECRET @"<google data secret>"
#define GOOGLE_DATA_USERNAME @"googleDocsUsername"
#define GOOGLE_DATA_PASSWORD @"googleDocsPassword"

@interface GDataInterface (PrivateMethods)

- (GDataServiceTicket *) uploadTicket;
- (void) setUploadTicket:(GDataServiceTicket *)ticket;

- (GDataFeedDocList *)docListFeed;
- (void)setDocListFeed:(GDataFeedDocList *)feed;
- (NSError *)docListFetchError;
- (void)setDocListFetchError:(NSError *)error;
- (GDataServiceTicket *)docListFetchTicket;
- (void)setDocListFetchTicket:(GDataServiceTicket *)ticket;

@end

@implementation GDataInterface

static NSString *const kKeychainItemName = @"GDataInterface: Google Docs";

// Initializer that provides the username and password.
//
- (id) initWithUsername:(NSString*)username andPassword:(NSString*)password {
    self = [super init];

    if (self != nil) {
        username_ = [username retain];
        password_ = [password retain];
        [[GDataInterface docsService] setUserCredentialsWithUsername:username_ password:password_];
    }

    return self;
}

- (void) setUsername:(NSString*)newUsername {
    username_ = [newUsername retain];
    [[GDataInterface docsService] setUserCredentialsWithUsername:newUsername password:password_];
}

- (NSString*) username {
    return username_;
}

- (void) setPassword:(NSString*)newPassword {
    password_ = [newPassword retain];
    [[GDataInterface docsService] setUserCredentialsWithUsername:username_ password:newPassword];
    [Util setPassword:newPassword forKey:GOOGLE_DATA_PASSWORD];
}

- (NSString*) password {
    return password_;
}


static GDataInterface *shared_instance_;

// Returns the shared instance of the class.  There will only ever be a single instance
// of this class.
//
+ (GDataInterface*) sharedInstance {
    if (shared_instance_ == nil) {
        shared_instance_ = [[GDataInterface alloc] initWithUsername:[[NSUserDefaults standardUserDefaults] valueForKey:GOOGLE_DATA_USERNAME] andPassword:[Util getPassword:GOOGLE_DATA_PASSWORD]];

        // Load the OAuth token from the keychain, if it was previously saved
        NSString *clientID = GOOGLE_DATA_CLIENT_ID;
        NSString *clientSecret = GOOGLE_DATA_SECRET;

        GTMOAuth2Authentication *auth;

#if TARGET_OS_IPHONE
        auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName clientID:clientID clientSecret:clientSecret];
#else
        auth = [GTMOAuth2WindowController authForGoogleFromKeychainForName:kKeychainItemName
                                                                  clientID:clientID
                                                              clientSecret:clientSecret];
#endif

        [[GDataInterface docsService] setAuthorizer:auth];
    }

    return shared_instance_;
}

- (NSString *)signedInUsername {
    // Get the email address of the signed-in user
    GTMOAuth2Authentication *auth = [[GDataInterface docsService] authorizer];
    BOOL isSignedIn = auth.canAuthorize;

    if (isSignedIn) {
        return auth.userEmail;
    } else {
        return nil;
    }
}

- (BOOL) isSignedIn {
    return ([self signedInUsername] != nil);
}

- (void)runSigninThenInvokeHandler:(CompletionHandler)handler forWindow:(id)window {
    // Applications should have client ID and client secret strings
    // hardcoded into the source, but the sample application asks the
    // developer for the strings
    NSString *clientID = GOOGLE_DATA_CLIENT_ID;
    NSString *clientSecret = GOOGLE_DATA_SECRET;

    // Show the OAuth 2 sign-in controller
    NSString *scope = [GTMOAuth2Authentication scopeWithStrings:
                       [GDataServiceGoogleDocs authorizationScope],
                       [GDataServiceGoogleSpreadsheet authorizationScope],
                       nil];

#if TARGET_OS_IPHONE
    NSAssert((window != nil), @"window must be a non-nil navigation controller");

    GTMOAuth2ViewControllerTouch *viewController;
    viewController = [GTMOAuth2ViewControllerTouch 
                      controllerWithScope:scope
                      clientID:clientID 
                      clientSecret:clientSecret 
                      keychainItemName:kKeychainItemName
                      completionHandler:^(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error) {

                          [rootController_ dismissModalViewControllerAnimated:YES];
                          [rootController_ release];
                          rootController_ = nil;

                          // callback
                          if (error == nil) {
                              [[GDataInterface docsService] setAuthorizer:auth];

                              username_ = [self signedInUsername];

                              handler(YES);
                          } else {
                              NSLog(@"Authentication error: %@", error);
                              NSData *responseData = [[error userInfo] objectForKey:@"data"]; // kGTMHTTPFetcherStatusDataKey
                              if ([responseData length] > 0) {
                                  // show the body of the server's authentication failure response
                                  NSString *str = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease];
                                  NSLog(@"%@", str);
                              }
                              handler(NO);
                          }
                      }];

    // Optional: display some html briefly before the sign-in page loads
    NSString *html = @"<html><body bgcolor=silver><div align=center>Loading sign-in page...</div></body></html>";
    viewController.initialHTMLString = html;

    // For iOS, window is a navigation controller.
    //
    rootController_ = [(UIViewController*)window retain];
    [rootController_ presentModalViewController:viewController animated:YES];

#else
    NSBundle *frameworkBundle = [NSBundle bundleForClass:[GTMOAuth2WindowController class]];
    GTMOAuth2WindowController *windowController;
    windowController = [GTMOAuth2WindowController controllerWithScope:scope
                                                             clientID:clientID
                                                         clientSecret:clientSecret
                                                     keychainItemName:kKeychainItemName
                                                       resourceBundle:frameworkBundle];

    [windowController signInSheetModalForWindow:window
                              completionHandler:^(GTMOAuth2Authentication *auth, NSError *error) {
                                  // callback
                                  if (error == nil) {
                                      [[GDataInterface docsService] setAuthorizer:auth];
                                      username_ = [auth userEmail];
                                      handler(YES);
                                  } else {
                                      handler(NO);
                                  }
                              }];
#endif
}

- (void) signInOrOutWithCompletionHandler:(CompletionHandler)handler forWindow:(id)window {
    if (![self isSignedIn]) {
        // Sign in
        [self runSigninThenInvokeHandler:handler forWindow:window];
    } else {
        // Sign out
        GDataServiceGoogleDocs *service = [GDataInterface docsService];

#if TARGET_OS_IPHONE
        [GTMOAuth2ViewControllerTouch removeAuthFromKeychainForName:kKeychainItemName];
#else
        [GTMOAuth2WindowController removeAuthFromKeychainForName:kKeychainItemName];
#endif

        [service setAuthorizer:nil];
        handler(YES);
    }
}

- (void) retrieveDocumentListWithCompletionHandler:(RetrievalCompletionHandler)handler {

    [self setDocListFeed:nil];
    [self setDocListFetchError:nil];
    [self setDocListFetchTicket:nil];

    GDataServiceGoogleDocs *service = [GDataInterface docsService];
    GDataServiceTicket *ticket;

    // Fetching a feed gives us 25 responses by default.  We need to use
    // the feed's "next" link to get any more responses.  If we want more than 25
    // at a time, instead of calling fetchDocsFeedWithURL, we can create a
    // GDataQueryDocs object, as shown here.

    NSURL *feedURL = [GDataServiceGoogleDocs docsFeedURL];

    GDataQueryDocs *query = [GDataQueryDocs documentQueryWithFeedURL:feedURL];
    [query setMaxResults:1000];
    [query setShouldShowFolders:NO];

    ticket = [service fetchFeedWithQuery:query
                       completionHandler:^(GDataServiceTicket *ticket, GDataFeedBase *feed, NSError *error) {
                           // callback
                           [self setDocListFeed:(GDataFeedDocList *)feed];
                           [self setDocListFetchError:error];
                           [self setDocListFetchTicket:nil];

                           if (handler != nil) {
                               handler((GDataFeedDocList *)feed, (error == nil));
                           }
                       }];

    [self setDocListFetchTicket:ticket];
}

- (void) downloadDocument:(GDataEntryDocBase*)document withProgressHandler:(DownloadProgressHandler)progressHandler andCompletionHandler:(DocumentDownloadCompletionHandler)handler {

    // the content src attribute is used for downloading
    NSURL *exportURL = [[document content] sourceURL];

    if (exportURL != nil) {
        GDataQuery *query = [GDataQuery queryWithFeedURL:exportURL];
        [query addCustomParameterWithName:@"exportFormat"
                                    value:@"txt"];
        NSURL *downloadURL = [query URL];
        // Read the document's contents asynchronously from the network

        // requestForURL:ETag:httpMethod: sets the user agent header of the
        // request and, when using ClientLogin, adds the authorization header
        NSURLRequest *request = [[GDataInterface docsService] requestForURL:downloadURL
                                                                       ETag:nil
                                                                 httpMethod:nil];

        GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
        [fetcher setAuthorizer:[[GDataInterface docsService] authorizer]];

        __block double maxSize = 10240.0;

        if (progressHandler != nil) {            
            [fetcher setReceivedDataBlock:^(NSData *dataReceivedSoFar) {
                if ([[fetcher response] expectedContentLength] > 0) {
                    maxSize = [[fetcher response] expectedContentLength];
                } else if ([dataReceivedSoFar length] > maxSize) {
                    maxSize += 5120.0;
                }

                progressHandler(0.0, maxSize, (double)[dataReceivedSoFar length]);
            }];
        }

        [fetcher setCommentWithFormat:@"downloading \"%@\"", [[document title] stringValue]];
        [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
            if (progressHandler != nil) {
                // Update the progress handler with a "complete" progress.
                //
                progressHandler(0.0, (double)[data length], (double)[data length]);
            }

            // callback
            if (error == nil) {
                // Successfully downloaded the document
                //                
                if (handler != nil) {
                    handler(data, YES);
                }
            } else {
                if (handler != nil) {
                    handler(nil, NO);
                }
            }
        }];
    }
}

- (void) downloadURL:(NSURL*)url withProgressHandler:(DownloadProgressHandler)progressHandler andCompletionHandler:(DocumentDownloadCompletionHandler)handler {

    NSURL *downloadURL = [url copy];
    // Read the document's contents asynchronously from the network

    NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];

    GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];

    __block double maxSize = 10240.0;

    if (progressHandler != nil) {            
        [fetcher setReceivedDataBlock:^(NSData *dataReceivedSoFar) {
            if ([[fetcher response] expectedContentLength] > 0) {
                maxSize = [[fetcher response] expectedContentLength];
            } else if ([dataReceivedSoFar length] > maxSize) {
                maxSize += 5120.0;
            }

            progressHandler(0.0, maxSize, (double)[dataReceivedSoFar length]);
        }];
    }

    [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
        if (progressHandler != nil) {
            progressHandler(0.0, (double)[data length], (double)[data length]);
        }

        // callback
        if (error == nil) {
            // Successfully downloaded the document
            //                
            if (handler != nil) {
                handler(data, YES);
            }
        } else {
            if (handler != nil) {
                handler(nil, NO);
            }
        }
    }];

    // Block, waiting for 60 seconds for the download.
    //
    [fetcher waitForCompletionWithTimeout:60.0];

    if ([fetcher isFetching] == YES) {
        // OK, so this looks like we've timed out waiting for the download to complete.  Cancel the 
        // fetch.
        //
        [fetcher stopFetching];

        if (handler != nil) {
            handler(nil, NO);
        }
    }
}

#pragma mark Upload

- (void)getMIMEType:(NSString **)mimeType andEntryClass:(Class *)class forExtension:(NSString *)extension {

    // Mac OS X's UTI database doesn't know MIME types for .doc and .xls
    // so GDataEntryBase's MIMETypeForFileAtPath method isn't helpful here

    struct MapEntry {
        NSString *extension;
        NSString *mimeType;
        NSString *className;
    };

    static struct MapEntry sMap[] = {
        { @"csv", @"text/csv", @"GDataEntryStandardDoc" },
        { @"doc", @"application/msword", @"GDataEntryStandardDoc" },
        { @"docx", @"application/vnd.openxmlformats-officedocument.wordprocessingml.document", @"GDataEntryStandardDoc" },
        { @"ods", @"application/vnd.oasis.opendocument.spreadsheet", @"GDataEntrySpreadsheetDoc" },
        { @"odt", @"application/vnd.oasis.opendocument.text", @"GDataEntryStandardDoc" },
        { @"pps", @"application/vnd.ms-powerpoint", @"GDataEntryPresentationDoc" },
        { @"ppt", @"application/vnd.ms-powerpoint", @"GDataEntryPresentationDoc" },
        { @"rtf", @"application/rtf", @"GDataEntryStandardDoc" },
        { @"sxw", @"application/vnd.sun.xml.writer", @"GDataEntryStandardDoc" },
        { @"txt", @"text/plain", @"GDataEntryStandardDoc" },
        { @"xls", @"application/vnd.ms-excel", @"GDataEntrySpreadsheetDoc" },
        { @"xlsx", @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", @"GDataEntrySpreadsheetDoc" },
        { @"jpg", @"image/jpeg", @"GDataEntryStandardDoc" },
        { @"jpeg", @"image/jpeg", @"GDataEntryStandardDoc" },
        { @"png", @"image/png", @"GDataEntryStandardDoc" },
        { @"bmp", @"image/bmp", @"GDataEntryStandardDoc" },
        { @"gif", @"image/gif", @"GDataEntryStandardDoc" },
        { @"html", @"text/html", @"GDataEntryStandardDoc" },
        { @"htm", @"text/html", @"GDataEntryStandardDoc" },
        { @"tsv", @"text/tab-separated-values", @"GDataEntryStandardDoc" },
        { @"tab", @"text/tab-separated-values", @"GDataEntryStandardDoc" },
        { @"pdf", @"application/pdf", @"GDataEntryPDFDoc" },
        { nil, nil, nil }
    };

    NSString *lowerExtn = [extension lowercaseString];

    for (int idx = 0; sMap[idx].extension != nil; idx++) {
        if ([lowerExtn isEqual:sMap[idx].extension]) {
            *mimeType = sMap[idx].mimeType;
            *class = NSClassFromString(sMap[idx].className);
            return;
        }
    }

    *mimeType = nil;
    *class = nil;
    return;
}

- (void) uploadEntry:(GDataEntryDocBase*)docEntry asNewRevision:(BOOL)newRevision forWindow:(id)window withProgressHandler:(UploadProgressHandler)progressHandler andCompletionHandler:(CompletionHandler)handler {

    uploadWindow = [window retain];
    uploadCompletionHandler = [handler copy];

    NSURL *uploadURL;

    if (newRevision == YES) {
        GDataQueryDocs *query = [GDataQueryDocs queryWithFeedURL:[[docEntry 
                                                                   uploadEditLink] URL]]; 
        [query setShouldCreateNewRevision:YES]; 
        uploadURL = [query URL];
    } else {
        uploadURL = [GDataServiceGoogleDocs docsUploadURL];
    }

    // make service tickets call back into our upload progress selector
    GDataServiceGoogleDocs *service = [GDataInterface docsService];
    [service setServiceUploadProgressHandler:^(GDataServiceTicketBase *ticket, unsigned long long numberOfBytesRead, unsigned long long dataLength) {
        if (progressHandler != nil) {
            progressHandler(0.0, (double)dataLength, (double)numberOfBytesRead);
        }
    }];

    // insert the entry into the docList feed
    //
    // to update (replace) an existing entry by uploading a new file,
    // use the fetchEntryByUpdatingEntry:forEntryURL: with the URL from
    // the entry's uploadEditLink
    GDataServiceTicket *ticket;

    if (newRevision == YES) {        
        ticket = [service fetchEntryByUpdatingEntry:docEntry 
                                        forEntryURL:uploadURL 
                                           delegate:self 
                                  didFinishSelector:@selector(uploadFileTicket:finishedWithEntry:error:)];
    } else {
        ticket = [service fetchEntryByInsertingEntry:docEntry
                                          forFeedURL:uploadURL
                                            delegate:self
                                   didFinishSelector:@selector(uploadFileTicket:finishedWithEntry:error:)];
    }

    [ticket setUploadProgressHandler:^(GDataServiceTicketBase *ticket, unsigned long long numberOfBytesRead, unsigned long long dataLength) {
        // progress callback
        if (progressHandler != nil) {
            progressHandler(0.0, (double)dataLength, (double)numberOfBytesRead);
        }
    }];

    // we turned automatic retry on when we allocated the service, but we
    // could also turn it on just for this ticket

    [self setUploadTicket:ticket];
    [service setServiceUploadProgressHandler:nil];
}

- (void)uploadFileAtPath:(NSString *)path forWindow:(id)window withProgressHandler:(UploadProgressHandler)progressHandler andCompletionHandler:(CompletionHandler)handler {

    NSString *errorMsg = nil;

    // make a new entry for the file

    NSString *mimeType = nil;
    Class entryClass = nil;

    NSString *extn = [path pathExtension];
    [self getMIMEType:&mimeType andEntryClass:&entryClass forExtension:extn];

    if (!mimeType) {
        // for other file types, see if we can get the type from the Mac OS
        // and use a generic file document entry class
        mimeType = [GDataUtilities MIMETypeForFileAtPath:path
                                         defaultMIMEType:nil];
        entryClass = [GDataEntryFileDoc class];
    }

    if (mimeType && entryClass) {

        GDataEntryDocBase *newEntry = [entryClass documentEnt

关于objective-c - 用于 Google 文档的 iOS Objective-C 包装器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8675662/

有关objective-c - 用于 Google 文档的 iOS Objective-C 包装器的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  3. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  4. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  5. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  6. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  7. ruby - inverse_of 是否适用于 has_many? - 2

    当我使用has_one时,它​​工作得很好,但在has_many上却不行。在这里您可以看到object_id不同,因为它运行了另一个SQL来再次获取它。ruby-1.9.2-p290:001>e=Employee.create(name:'rafael',active:false)ruby-1.9.2-p290:002>b=Badge.create(number:1,employee:e)ruby-1.9.2-p290:003>a=Address.create(street:"123MarketSt",city:"SanDiego",employee:e)ruby-1.9.2-p290

  8. Matlab imread()读到了什么 (浅显 当复习文档了) - 2

    matlab打开matlab,用最简单的imread方法读取一个图像clcclearimg_h=imread('hua.jpg');返回一个数组(矩阵),往往是a*b*cunit8类型解释一下这个三维数组的意思,行数、数和层数,unit8:指数据类型,无符号八位整形,可理解为0~2^8的数三个层数分别代表RGB三个通道图像rgb最常用的是24-位实现方法,即RGB每个通道有256色阶(2^8)。基于这样的24-位RGB模型的色彩空间可以表现256×256×256≈1670万色当imshow传入了一个二维数组,它将以灰度方式绘制;可以把图像拆分为rgb三层,可以以灰度的方式观察它figure(1

  9. objective-c - 在设置 Cocoa Pods 和安装 Ruby 更新时出错 - 2

    我正在尝试为我的iOS应用程序设置cocoapods但是当我执行命令时:sudogemupdate--system我收到错误消息:当前已安装最新版本。中止。当我进入cocoapods的下一步时:sudogeminstallcocoapods我在MacOS10.8.5上遇到错误:ERROR:Errorinstallingcocoapods:cocoapods-trunkrequiresRubyversion>=2.0.0.我在MacOS10.9.4上尝试了同样的操作,但出现错误:ERROR:Couldnotfindavalidgem'cocoapods'(>=0),hereiswhy:U

  10. ruby - "undefined method"用于 rails 模型 - 2

    我正在使用带有Rails的Devise,我想添加一个方法“getAllComments”,所以我这样写:classUser在我的Controller中:defdashboard@user=current_user@comments=@user.getAllComments();end当我访问我的url时,我得到了undefinedmethod`getAllComments'for#我做错了什么?谢谢 最佳答案 因为getAllComments是一个类方法,而您正试图将其作为实例方法访问。您要么需要访问它:User.getAllCom

随机推荐