📜 ⬆️ ⬇️

The story of how Apple taught me to make a quality product

Hello everyone, the essence of my story is to tell where the idea came from, how the application was developed and how Apple influenced the development.

I will show the application code of the latest version.

The idea came on an ordinary day, I was visiting my parents in the summer with my brother. Brother - senior lieutenant. On this day, he wanted to find an application in the App Store that would contain a complete military charter with simple and simple navigation, but not a single application was found in the store by his search queries. Then he immediately offered me to implement this idea, since he believed that this is a rather necessary thing on the phone, which will always be with you and should be a good demand.
You probably say, “What is the problem? Go to Google, download your pdf-nickname and drop it in iBooks ”. But why so many gestures? If you now have an application, when you have everything at hand, you don’t need to download and search anything! In addition, with convenient navigation, which will be improved to great heights.

And so, you learned how the idea appeared, but now the most interesting thing is how did the development itself go, and where does Apple influence it?
')
Around the middle of August, I got down to business. I was thinking how best to implement an application: throw everything in a Label or use a TextView?
After thinking for a few days, I found a solution for myself - to use the usual Web View. In order for the charter to be displayed in the Web View, I sliced ​​the charter into separate html files into different parts.
The code already contains the functionality of the ability to change the font size. I used NSUserDefaults to save the font size value and did a small check, where I already substituted the html file I needed. The font can be set as follows - small, medium, large.

Controller with font settings:
- (IBAction)selectFont:(id)sender { [self saveSetting]; } - (void) saveSetting { NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; //    [userDefaults setInteger:self.fontSegmentedControl.selectedSegmentIndex forKey:kSettingsFont]; [userDefaults synchronize]; } - (void) loadSetting { NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; //   self.fontSegmentedControl.selectedSegmentIndex = [userDefaults integerForKey:kSettingsFont]; } 


Opening the webView itself:
 - (void) loadPage { NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; NSInteger indexFont = [userDefaults integerForKey:@"font"]; if (indexFont == 0) { //     NSString* filePath = [[NSBundle mainBundle] pathForResource:@"preOnePart" ofType:@"html"]; //   NSString NSString* html = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; //   UIWebView  [_webView loadHTMLString:html baseURL:nil]; } else if (indexFont == 1) { //     NSString* filePath = [[NSBundle mainBundle] pathForResource:@"preOnePartMediumFont" ofType:@"html"]; //   NSString NSString* html = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; //   UIWebView  [_webView loadHTMLString:html baseURL:nil]; } else if (indexFont == 2) { //     NSString* filePath = [[NSBundle mainBundle] pathForResource:@"preOnePartLargeFont" ofType:@"html"]; //   NSString NSString* html = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; //   UIWebView  [_webView loadHTMLString:html baseURL:nil]; } 


For several days I created an application framework, a menu, and other trifles. I made the simple navigation that I needed in parts and chapters of the charter, and in principle, the application was ready. Used the open-source SWRevealController library for the menu.

Added a section about the application to the menu.
Here the user can tell about the application on Twitter and Facebook:

 #pragma mark - share actions - (IBAction)twitterShare:(UIButton *)sender { if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) { SLComposeViewController* controller = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter]; [controller setInitialText:@" #AppStore    «Military! -  &      »  App Store.    ;)"]; [controller addURL:[NSURL URLWithString:@"https://itunes.apple.com/ru/app/military!-novosti-ustav-vnutrennej/id914651209?l=en&mt=8"]]; controller.completionHandler = ^(SLComposeViewControllerResult result) { NSLog(@"Completed"); }; [self presentViewController:controller animated:YES completion:nil]; } else { UIAlertView* error = [[UIAlertView alloc] initWithTitle:@"" message:@"   Twitter-  . \n , !" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil,nil]; [error show]; NSLog(@"The twitter sevice is not evailalble"); } } - (IBAction)facebookShare:(UIButton *)sender { // Put together the dialog parameters NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Military!", @"name", @" &      ", @"caption", @"              ,          ,     !", @"description", @"https://itunes.apple.com/ru/app/military!-novosti-ustav-vnutrennej/id914651209?l=en&mt=8", @"link", @"http://cs619627.vk.me/v619627853/19d6e/xdHr-XTVs8k.jpg", @"picture", nil]; // Show the feed dialog [FBWebDialogs presentFeedDialogModallyWithSession:nil parameters:params handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error) { if (error) { // An error occurred, we need to handle the error // See: https://developers.facebook.com/docs/ios/errors NSLog(@"Error publishing story: %@", error.description); } else { if (result == FBWebDialogResultDialogNotCompleted) { // User cancelled. NSLog(@"User cancelled."); } else { // Handle the publish feed callback NSDictionary *urlParams = [self parseURLParams:[resultURL query]]; if (![urlParams valueForKey:@"post_id"]) { // User cancelled. NSLog(@"User cancelled."); } else { // User clicked the Share button NSString *result = [NSString stringWithFormat: @"Posted story, id: %@", [urlParams valueForKey:@"post_id"]]; NSLog(@"result %@", result); } } } }]; } //------------------Login implementation starts here------------------ - (void)loginView:(FBLoginView *)loginView handleError:(NSError *)error { NSString *alertMessage, *alertTitle; // If the user should perform an action outside of you app to recover, // the SDK will provide a message for the user, you just need to surface it. // This conveniently handles cases like Facebook password change or unverified Facebook accounts. if ([FBErrorUtility shouldNotifyUserForError:error]) { alertTitle = @"Facebook error"; alertMessage = [FBErrorUtility userMessageForError:error]; // This code will handle session closures since that happen outside of the app. // You can take a look at our error handling guide to know more about it // https://developers.facebook.com/docs/ios/errors } else if ([FBErrorUtility errorCategoryForError:error] == FBErrorCategoryAuthenticationReopenSession) { alertTitle = @"Session Error"; alertMessage = @"Your current session is no longer valid. Please log in again."; // If the user has cancelled a login, we will do nothing. // You can also choose to show the user a message if cancelling login will result in // the user not being able to complete a task they had initiated in your app // (like accessing FB-stored information or posting to Facebook) } else if ([FBErrorUtility errorCategoryForError:error] == FBErrorCategoryUserCancelled) { NSLog(@"user cancelled login"); // For simplicity, this sample handles other errors with a generic message // You can checkout our error handling guide for more detailed information // https://developers.facebook.com/docs/ios/errors } else { alertTitle = @"Something went wrong"; alertMessage = @"Please try again later."; NSLog(@"Unexpected error:%@", error); } if (alertMessage) { [[[UIAlertView alloc] initWithTitle:alertTitle message:alertMessage delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; } } - (NSDictionary*)parseURLParams:(NSString *)query { NSArray *pairs = [query componentsSeparatedByString:@"&"]; NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; for (NSString *pair in pairs) { NSArray *kv = [pair componentsSeparatedByString:@"="]; NSString *val = [kv[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; params[kv[0]] = val; } return params; } 


For two more days I suggested a little beauty in the app. I hasten to note that in the original version, which was sent to Review to Apple, the application did not have the “Latest News” section - there was just one charter and that's it.

The application was sent to Review in early September and a week later I received the first Reject from Apple. What is the matter? Apple interpreted its point of view in this way - “Applications that constitute a song or movie should be sent to the iTunes Store. Applications that are a book should be sent to the iBooks Store. ”

Yes, in principle, Apple was right, as the application was more like a book. I decided to add something else to the functionality ... For example, the flow of fresh news on the same subject as the application. I took for this the RSS-feed “RIA Novosti” and parsed it with the help of NSXMLParser:
 #pragma mark - NSXMLParser - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { element = elementName; if ([element isEqualToString:@"item"]) { item = [[NSMutableDictionary alloc] init]; title = [[NSMutableString alloc] init]; link = [[NSMutableString alloc] init]; image = [[NSMutableDictionary alloc] init]; description = [[NSMutableString alloc] init]; date = [[NSMutableString alloc] init]; } if ([element isEqualToString:@"enclosure"]) { linkImage = [attributeDict objectForKey:@"url"]; typeImage = [attributeDict objectForKey:@"type"]; } // NSLog(@"RSS Utility: didStartElement: %@", elementName); } - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { if ([elementName isEqualToString:@"item"]) { [item setObject:title forKey:@"title"]; [item setObject:link forKey:@"link"]; [item setObject:description forKey:@"description"]; [item setObject:image forKey:@"enclosure"]; [item setObject:typeImage forKey:@"imageType"]; [item setObject:linkImage forKey:@"imageUrl"]; [item setObject:date forKey:@"pubDate"]; [feeds addObject:[item copy]]; } } - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { if ([element isEqualToString:@"title"]) { [title appendString:string]; } else if ([element isEqualToString:@"link"]) { [link appendString:string]; } else if ([element isEqualToString:@"imageUrl"]) { [linkImage appendString:string]; } else if ([element isEqualToString:@"description"]) { [description appendString:string]; } else if ([element isEqualToString:@"pubDate"]) { [date appendString:string]; } } - (void)parserDidEndDocument:(NSXMLParser *)parser { [self.tableView reloadData]; [self.refreshControl endRefreshing]; } 


In order to view the news in full, by clicking on a table cell, a controller was opened from the Web View.

The method that sends a link to the news in another controller:
 #pragma mark - Passed data to another controller - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([[segue identifier] isEqualToString:@"CustomSegue"]) { NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; NSString *string = [feeds[indexPath.row] objectForKey: @"link"]; [[segue destinationViewController] setUrl:string]; NSString* string2 = [feeds [indexPath.row] objectForKey:@"title"]; [[segue destinationViewController] setTitleForShare:string2]; } } 


Our actions in the controller with Web View:
 @implementation IVDetailViewController @synthesize titleForShare; - (void)viewDidLoad { [super viewDidLoad]; NSURL *myURL = [NSURL URLWithString: [self.url stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]; NSURLRequest *request = [NSURLRequest requestWithURL:myURL]; [self.webView loadRequest:request]; [self.navigationController.navigationBar addGestureRecognizer:self.revealViewController.panGestureRecognizer]; // Do any additional setup after loading the view. } 


Having implemented this functionality, I sent it to Apple again. After a week and a half, I get a second refusal. What is wrong this time ?! I read the comments checking application - “Apple and our customers appreciate the simple, elegant, creative, well-designed interface. They require more effort, but worth it. Apple sets the bar high. If the user interface is not good enough, the application may be rejected. ”

Thinking with my head, I rushed to finish my offspring again. Photos were added to the news feed and sharing, here I used Custom Table Cells.

After that, I added another navigation through the Web View in the controller, where you can watch the news in full. That is - on the page forward, on the page back, stop the download, update. And also the opening UIActivityViewController:

 - (IBAction)activityAction:(id)sender { NSString *textToShare = titleForShare; NSURL *myWebsite = [NSURL URLWithString:self.url]; NSArray *objectsToShare = @[textToShare, myWebsite]; UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil]; NSArray *excludeActivities = @[UIActivityTypeAirDrop, UIActivityTypePrint, UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll, UIActivityTypeAddToReadingList, UIActivityTypePostToFlickr, UIActivityTypePostToVimeo]; activityVC.excludedActivityTypes = excludeActivities; [self presentViewController:activityVC animated:YES completion:nil]; } 


I improved the interface, updated the application icon and added splash view (this is what appears before launching the application).
The application was sent for the third time. And this time, the application has finally sunburnt me happy status “Ready For Sale”.

Why do I think that Apple strongly influenced the look and functionality of the application? I think from the story everything became clear. And for myself, I understood why Apple is so sensitive to checking applications sent to them for review. No need to hurry anywhere, you need to make a quality product right away! Apple loves quality and we made sure of that again! Conclusions for themselves, of course, made.

PS I hasten to note for you, dear readers: my experience in developing for iOS is nothing — literally since May 2014.

PSPS Almost immediately on the second day after the day of the application's release, I received the first bad review of the application. The main claims were as follows: the old charter for 2007, without updates for 2014 and in the application it was not possible to change the font size for ease of reading the charter. The fault lay completely on me and I understood that. On the same day, at the price of my dream, I realized all this and corrected other trifles. Update already sent for review.
After that, a special support group was created for this application, where the user will be able to see for himself that I heard them. The group can be found on the App Store page in the “Developer’s Site” section.

UPDATE!

Now the application is in first place in the top paid applications category "News".
image

Source: https://habr.com/ru/post/240881/


All Articles