CloudCaptions/AAPLSubmitPostViewController.m
/* |
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
*/ |
@import CloudKit; |
#import "AAPLImage.h" |
#import "AAPLSubmitPostViewController.h" |
#import "AAPLTableViewController.h" |
typedef NS_ENUM(NSInteger, AAPLSubmissionErrorResponse) { |
AAPLSubmissionErrorIgnore, |
AAPLSubmissionErrorRetry, |
AAPLSubmissionErrorSuccess, |
}; |
@interface AAPLSubmitPostViewController () <UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate> |
@property (weak, nonatomic) IBOutlet UIProgressView *progressBar; |
@property (weak, nonatomic) IBOutlet UIImageView *imageView; |
@property (weak, nonatomic) IBOutlet UIPickerView *fontPicker; |
@property (weak, nonatomic) IBOutlet UITextField *hiddenText; |
@property (weak, nonatomic) IBOutlet UITextField *tagField; |
@property (weak, nonatomic) IBOutlet UILabel *imageLabel; |
@property (weak, nonatomic) IBOutlet UIBarButtonItem *postButton; |
@property (strong, atomic) AAPLImage *imageRecord; |
@end |
#pragma mark - |
@implementation AAPLSubmitPostViewController |
- (void)viewDidLoad |
{ |
[super viewDidLoad]; |
// Sets the preview to the image record passed in |
self.imageView.image = self.imageRecord.fullImage; |
// sets up font picker and picks a random font |
self.fontPicker.delegate = self; |
self.fontPicker.dataSource = self; |
u_int randomFont = arc4random() % [UIFont familyNames].count; |
[self.fontPicker selectRow:randomFont inComponent:0 animated:NO]; |
// Sets up the label with random font |
self.imageLabel.font = [UIFont fontWithName:[UIFont familyNames][randomFont] size:24]; |
// sets delegates so enter dimsisses keyboard |
self.tagField.delegate = self; |
self.hiddenText.delegate = self; |
// typing into the hiddent text field automatically updates the label on the image |
[self.hiddenText addTarget:self action:@selector(didEditField:) forControlEvents:UIControlEventEditingChanged]; |
// start editing the text field as soon as the view is done loading |
[self.hiddenText becomeFirstResponder]; |
} |
- (IBAction)editText:(id)sender { |
// Pulls up the keyboard for the hidden text field when photo is tapped |
[self.hiddenText becomeFirstResponder]; |
} |
- (IBAction)cancelPost:(id)sender { |
// Hides the keyboards and then returns back to AAPLSubmitPostViewController |
[self.hiddenText endEditing:YES]; |
[self.tagField endEditing:YES]; |
[self dismissViewControllerAnimated:YES completion:nil]; |
} |
- (IBAction)publishPost:(id)sender { |
// Prevents multiple posting, locks as soon as a post is made |
self.postButton.action = NULL; |
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; |
[indicator startAnimating]; |
self.postButton.customView = indicator; |
// Hides the keyboards and dispatches a UI update to show the upload progress |
[self.hiddenText endEditing:YES]; |
[self.tagField endEditing:YES]; |
self.progressBar.hidden = NO; |
// Creates post record type and initizalizes all of its values |
CKRecord *newRecord = [[CKRecord alloc] initWithRecordType:AAPLPostRecordType]; |
newRecord[AAPLPostFontKey] = self.imageLabel.font.fontName; |
newRecord[AAPLPostImageRefKey] = [[CKReference alloc] initWithRecordID:self.imageRecord.record.recordID action:CKReferenceActionDeleteSelf]; |
newRecord[AAPLPostTextKey] = self.hiddenText.text; |
newRecord[AAPLPostTagsKey] = [self.tagField.text.lowercaseString componentsSeparatedByString:@" "]; |
AAPLPost *newPost = [[AAPLPost alloc] initWithRecord:newRecord]; |
newPost.imageRecord = self.imageRecord; |
// Only upload image record if it is not on server, otherwise just upload the new post record |
NSArray *recordsToSave = self.imageRecord.isOnServer ? @[newRecord] : @[newRecord, self.imageRecord.record]; |
CKModifyRecordsOperation *saveOp = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave recordIDsToDelete:nil]; |
saveOp.perRecordProgressBlock = ^(CKRecord *record, double progress) |
{ |
// Image record type is probably going to take the longest to upload. Reflect it's progress in the progress bar |
if([record.recordType isEqual:AAPLImageRecordType]) |
{ |
dispatch_async(dispatch_get_main_queue(), ^{ |
[self.progressBar setProgress:progress*0.95 animated:YES]; |
}); |
} |
}; |
// When completed it notifies the tableView to add the post we just uploaded, displays error if it didn't work |
saveOp.modifyRecordsCompletionBlock = ^(NSArray *savedRecords, NSArray *deletedRecordIDs, NSError *operationError){ |
AAPLSubmissionErrorResponse errorResponse = [self handleError:operationError]; |
if(errorResponse == AAPLSubmissionErrorSuccess) |
{ |
[self dismissViewControllerAnimated:YES completion:nil]; |
// Tells delegate to update so it can display our new post |
if([self.delegate respondsToSelector:@selector(AAPLSubmitPostViewController:postedRecord:)]) |
{ |
dispatch_async(dispatch_get_main_queue(), ^{ |
[self.delegate AAPLSubmitPostViewController:self postedRecord:newPost]; |
}); |
} |
} |
else if(errorResponse == AAPLSubmissionErrorRetry) |
{ |
NSNumber *retryAfter = operationError.userInfo[CKErrorRetryAfterKey] ?: @3; |
NSLog(@"Error: %@. Recoverable, retry after %@ seconds", [operationError description], retryAfter); |
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryAfter.intValue * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ |
[self publishPost:sender]; |
}); |
} |
else if(errorResponse == AAPLSubmissionErrorIgnore) |
{ |
NSLog(@"Error saving record: %@", [operationError description]); |
NSString *errorTitle = NSLocalizedString(@"ErrorTitle", @"Title of alert notifying of error"); |
NSString *dismissButton = NSLocalizedString(@"DismissError", @"Alert dismiss button string"); |
NSString *errorMessage; |
if([operationError code] == CKErrorNotAuthenticated) errorMessage = NSLocalizedString(@"NotAuthenticatedErrorMessage", @"Error message, not logged in"); |
else errorMessage = NSLocalizedString(@"UploadFailedErrorMessage", @"Non recoverable upload failed error"); |
UIAlertController *alert = [UIAlertController alertControllerWithTitle:errorTitle message:errorMessage preferredStyle:UIAlertControllerStyleAlert]; |
[alert addAction:[UIAlertAction actionWithTitle:dismissButton style:UIAlertActionStyleCancel handler:nil]]; |
self.postButton.action = @selector(publishPost:); |
dispatch_async(dispatch_get_main_queue(), ^{ |
[self presentViewController:alert animated:YES completion:nil]; |
self.progressBar.hidden = YES; |
self.postButton.customView = nil; |
}); |
} |
}; |
[[CKContainer defaultContainer].publicCloudDatabase addOperation:saveOp]; |
} |
- (AAPLSubmissionErrorResponse) handleError:(NSError *)error |
{ |
if (error == nil) { |
return AAPLSubmissionErrorSuccess; |
} |
switch ([error code]) |
{ |
case CKErrorUnknownItem: |
// This error occurs if it can't find the subscription named autoUpdate. (It tries to delete one that doesn't exits or it searches for one it can't find) |
// This is okay and expected behavior |
return AAPLSubmissionErrorIgnore; |
break; |
case CKErrorNetworkUnavailable: |
case CKErrorNetworkFailure: |
// A reachability check might be appropriate here so we don't just keep retrying if the user has no service |
case CKErrorServiceUnavailable: |
case CKErrorRequestRateLimited: |
return AAPLSubmissionErrorRetry; |
break; |
case CKErrorPartialFailure: |
// This shouldn't happen on a query operation |
case CKErrorNotAuthenticated: |
case CKErrorBadDatabase: |
case CKErrorIncompatibleVersion: |
case CKErrorBadContainer: |
case CKErrorPermissionFailure: |
case CKErrorMissingEntitlement: |
// This app uses the publicDB with default world readable permissions |
case CKErrorAssetFileNotFound: |
case CKErrorAssetFileModified: |
// Users don't really have an option to delete files so this shouldn't happen |
case CKErrorQuotaExceeded: |
// We should not retry if it'll exceed our quota |
case CKErrorOperationCancelled: |
// Nothing to do here, we intentionally cancelled |
case CKErrorInvalidArguments: |
case CKErrorResultsTruncated: |
case CKErrorServerRecordChanged: |
case CKErrorChangeTokenExpired: |
case CKErrorBatchRequestFailed: |
case CKErrorZoneBusy: |
case CKErrorZoneNotFound: |
case CKErrorLimitExceeded: |
case CKErrorUserDeletedZone: |
// All of these errors are irrelevant for this save operation. We're only saving new records, not modifying old ones |
case CKErrorInternalError: |
case CKErrorServerRejectedRequest: |
case CKErrorConstraintViolation: |
//Non-recoverable, should not retry |
default: |
return AAPLSubmissionErrorIgnore; |
break; |
} |
} |
#pragma mark UITextFieldDelegate |
-(BOOL)textFieldShouldReturn:(UITextField *)textField { |
// This method is called to dismiss the keyboard |
[self.view endEditing:YES]; |
return YES; |
} |
-(void)didEditField:(id)sender |
{ |
// This is called when the user types into a textField. Keeps the label up to date |
self.imageLabel.text = self.hiddenText.text; |
} |
#pragma mark UIPickerViewDataSource |
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView |
{ |
// Only one column in the font picker |
return 1; |
} |
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component |
{ |
// One row for each font in the familyNames array |
return [UIFont familyNames].count; |
} |
#pragma mark UIPickerViewDelegate |
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view |
{ |
// Sets each item in pickerview as the name of each font with its typeface in its own font |
// (e.g. Helvetica appears in Helvetica, Courier New appears in Courier New |
UILabel *fontLabel = (UILabel *) view; |
if(!fontLabel) |
{ |
fontLabel = [[UILabel alloc] init]; |
} |
fontLabel.font = [UIFont fontWithName:[UIFont familyNames][row] size:24]; |
fontLabel.text = [UIFont familyNames][row]; |
return (UIView *)fontLabel; |
} |
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component |
{ |
// This method sets the height of each row in the pickerView |
return 35; |
} |
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component |
{ |
// Sets the imageLabel font to the selected font |
NSString *fontName = [UIFont familyNames][row]; |
self.imageLabel.font = [UIFont fontWithName:fontName size:24]; |
} |
@end |
Copyright © 2014 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2014-09-17