Оболочка Objective-C для iOS для Документов Google

Кто-нибудь интегрировал google docs в свое приложение iOS? Пройдя примерный код, API для Google Docs намного сложнее, чем я ожидал, а примерами являются все MacOS. Да, есть поддержка iOS, но есть четкий код примера о том, как его использовать, и документации несколько не хватает.

Я нашел класс интерфейса в Интернете, но он был основан на более старой, устаревшей версии API Документов Google, и он не компилируется с XCode 4.2.

То, что мне нужно, – это относительно прямой интерфейс, который позволяет:

  1. Вход / выход из учетной записи google docs.
  2. Получение списка документов внутри этой учетной записи (необязательно определенного типа), возможно, с возможностью навигации по структуре папок.
  3. Возможность загрузки определенного документа в локальное хранилище.
  4. Возможность загрузить определенный документ в документы Google.

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

Я предпочитаю, чтобы обертка была нейтральной ОС; что я хочу иметь возможность использовать один и тот же интерфейс как в MacOS, так и в iOS. Опять же, это то, что я начал писать, но я не могу не чувствовать, что я должен изобретать колесо здесь.

благодаря

One Solution collect form web for “Оболочка Objective-C для iOS для Документов Google”

Хорошо, поэтому, в отсутствие ответов от других, я укусил пулю и сам написал обертку.

Теперь у меня есть единственная оболочка, которая работает как для Mac OS, так и для iOS, что значительно упрощает интерфейс с Google Docs.

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

#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 и использует отдельный класс утилиты для этого в зашифрованном виде. Вместо того, чтобы забивать этот ответ здесь со всем этим дополнительным кодом, я создал репозиторий в битбакете по адресу:

https://bitbucket.org/pkclsoft/gdatainterface

который содержит весь проект XCode, который создает две цели: одну для Mac OS и одну для iOS. Ive использовал их как в приложении, находящемся в магазине приложений, так и с отличными результатами. Возможно, что пока этот проект будет создан для меня, вы должны настроить его для своих целей. Проект содержит полный набор SDK Google, который строит и запускает мой код. Я включил его, чтобы попытаться снизить риск несовместимости с новыми версиями SDK для тех, кто его схватил.

Вот спецификация интерфейса, как она сейчас стоит:

 // // 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 
Interesting Posts

Как выставить панорамы в качестве альбома при выборе фотоальбомов

Как повернуть UICollectionView аналогично приложению фотографий и сохранить текущее изображение в центре?

Кнопка Addimg mutiple на панели навигации в Iphone SDK

Как создать несколько локальных уведомлений

Ad-Hoc для статических библиотек (Testflight)

Игровой центр iOS 7 не работает

Использование Urban Airship & Phonegap не может регистрировать устройство: «Маркер устройства равен нулю»,

Насколько важно выравнивание в данных вершинной вершины в iOS

Как включить перенос в UITextView с текстом без текста?

кордова, Firebase, плагин FCM – Не показывать уведомления в панели уведомлений на iOS

Xcode: обнаружение, когда нажат навигационный контроллер с кнопкой «назад»

Как обмениваться данными с содержимым приложения с помощью NSUserDefaults? – Сегодня виджет расширения

Место UITableViewCell ниже братьев и сестер

Исходное аудио iOS в веб-приложении

Невозможно подстроить значение типа '' с типом Int

PhoneC: Разработка iOS проста с помощью XCode, Swift3, UITableView, cocatouch, давайте создадим приложения для iPhone, iPad и Macbook.