📜 ⬆️ ⬇️

IOS development techniques used by me in the Pictograph contest

Recently passed three rounds of the Vkontakte competition for creating a photo application for the iOS platform. Link to the competition: http://vk.com/photo_contest . In the process of developing the application of the first round, I found some interesting solutions to some problems. I wanted to share these decisions with the public. For older iOS developers, I’ll hardly discover something new, I don’t think the article will work for novices either. I suppose that the article will be of interest to developers under iOS with experience of 2-5 applications.



Horizontal scrolling tape with
latest photos from the device’s memory


Firstly, it is very competent that from the very beginning not exactly 4 or 5 pictures are seen in the tape, but 4 and 1/3. This makes the user instantly aware that the list of photos is scrolling horizontally.

There are several questions:

At first I decided that I would display all the photos from the device’s memory in the tape, in case of problems with download speed I promised myself to return to this issue.
Immediately there was a problem with getting all the photos from the device’s memory in the correct order, because it was required to display the newest photos at the beginning of the tape. Immediately it turned out that the newest photos I have are not in the album with the saved photos, but in the Photostream, which my brother shared with me on that day.
It was decided to display the latest photos from the album with the saved photos at the beginning of the tape, and all the others are already behind this album. Inside each album of a photo I began to locate since the last. Here is the source code that receives the ALAsset array in the order described:
')
 @implementation ALAssetsLibrary (Extension) - (void)latestAssetsAndCall:(void (^)(NSMutableArray *))callback { __block NSMutableArray * assets = [NSMutableArray arrayWithCapacity:5000]; [self enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) { if (group == nil) { callback(assets); return; } ALAssetsGroupType groupType = [[group valueForProperty:ALAssetsGroupPropertyType] intValue]; int insertIndex = (groupType == ALAssetsGroupSavedPhotos) ? 0 : assets.count; [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { if (result != nil) [assets insertObject:result atIndex:insertIndex]; }]; } failureBlock:^(NSError *error) { if (error) NSLog(@"%@", error); }]; } @end 

As for the second question, I didn’t find anything better than using UITableView , because it’s just created to scroll through long lists, dynamically load content and reuse similar table cells. The only thing that - the table must be rotated 90 ° counterclockwise. Given that the transformation is carried out relative to the center of the object, we place the center of UITableView in the estimated location of the center of the tape and perform:

 self.tableView.transform = CGAffineTransformMakeRotation(-M_PI_2); 

When creating table cells, it is necessary to perform the reverse transformation - rotation 90 ° clockwise:

 - (UItableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BottomRollCell"]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"BottomRollCell"]; cell.contentView.transform = CGAffineTransformMakeRotation(M_PI_2); //     } //       } 

What do we have in the end? The table as it scrolls asks us for the contents of its cells. Having an array of ALAsset , we get thumbnails of the images and fill them with table cells. The table scrolls very smoothly, lags with uploading photos are not noticed. Regarding the time of receiving all the photos - getting 2500 photos takes less than 1 second of time, but it is critical to start the application. We make animation of table dropout from right to left upon receipt of ALAsset . It turns out very nice and the delay of half a second is almost not noticeable. Moreover, the request is not all assets, but through the task of a set of speed increase indices does not give, it even discouraged me a little. Thus, optimization with fast preloading of the first photos - did not roll.

Animation of opening and closing photos


It was decided to expand the photos directly from the thumbnails from the ribbon when they were opened and to turn them back when the editing was canceled. In order to get the coordinates of a rectangle of a specific table cell, I used the method of the UITableView class:

 - (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath; 

I had to spend some manna to determine the exact coordinates of the picture, taking into account its rotation, etc. After receiving the coordinates of the miniature image in the main view, on top of the miniature, I placed a smaller full-size image and animatedly resized and positioned the image. Ideally, I would like the image to jump out of a tape with a non-linear trajectory, but there is no time left for it ...

In order to track changes in device photos, you need to subscribe to the ALAssetsLibraryChangedNotification event for which you need to reload the ALAsset array. To prevent the tape from being updated when the photo is saved by the application itself, you must use the internal flag to cancel the tape redraw during the next update and manually add a new ALAsset to the beginning of the ALAsset array.
I save to the leftmost position, I shift the table to the right by the width of one image, I animate the image fly into the ribbon, move the table back without animation and manually call reloadData .

In order to animate the opening and closing of images performed smoothly and as quickly as possible, I had to do one interesting thing. If you open a photo, change its scale and press the cancel button, the photo will fly to the tape exactly in the form in which we left it after scaling. The photo will remain there in this view until it is hidden outside the screen and reloaded. To achieve this effect, I used NSMutableDictionary with NSMutableDictionary URLs as keys and an NSValue containing CGRect as values. Unfortunately, I forgot to remove this property in the video review, but it was one of the most interesting problems for me.

Smooth scaling and positioning of photos using effects


I really wanted to make the scaling and positioning of the photo with the simultaneous application of the selected effect and in the previews also move everything synchronously and apply the effect. In general, if you try to do so - to brake this joy will be godless. An interesting solution was found to apply the selected effect to the main photo and take the photo reduced five times (more precisely 320.0 / 56.0) and apply other effects to it, and in the process of scaling and positioning synchronize the thumbnail scrolls with the main UIScrollView . This method works quickly, smoothly and without jambs.



The code that synchronizes thumbnail scrolls with the main scroll (these are the UIScrollViewDelegate delegate methods):

 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { for (UITableViewCell * cell in [self.filtersTable visibleCells]) { UIScrollView * filterScrollView = (UIScrollView *)[cell.contentView viewWithTag:125]; filterScrollView.contentOffset = CGPointMake(scrollView.contentOffset.x*56/320, scrollView.contentOffset.y*56/320); } } - (void)scrollViewDidZoom:(UIScrollView *)scrollView { for (UITableViewCell * cell in [self.filtersTable visibleCells]) { UIScrollView * filterScrollView = (UIScrollView *)[cell.contentView viewWithTag:125]; filterScrollView.zoomScale = self.scrollView.zoomScale; filterScrollView.contentOffset = CGPointMake(scrollView.contentOffset.x*56/320, scrollView.contentOffset.y*56/320); } } 


Saving result


Since for us the most important thing is the speed of the application and the quality of the pictures in general, the conditions of the competition are not regulated, I decided to save the pictures of 640x640 (retina). And the easiest way to do this is through rendering the main view in the context of an image with an upward shift:

 - (UIImage *)renderImageForSaving { UIGraphicsBeginImageContextWithOptions(self.scrollView.bounds.size, YES, 0.0); CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0, -self.scrollView.frame.origin.y); [self.view.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage * image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } 

This is fast and without additional problems with the inscriptions, etc. Yes, the resolution could be saved and more - but this is a completely different problem, requiring time and patience)

Video with my naughty dog , demonstrating the work of the application:



I think you can give a link (ok?). The application is free and without advertising, respectively.
Link to the application: https://itunes.apple.com/app/pictography/id570470169
Under iOS5, several different glitches are now observed, an update with fixes has been sent for review and is expected by the end of the week ...

PS And finally, many thanks to Vkontakte for organizing and holding such contests. After all, they motivate / stimulate programmers to start developing promising platforms for them (for some reason it seems to me that there are a lot of newbies among the participants in relation to the platform). Very pleased with the input data for the contest - all the images were like a selection. Not a single extra pixel stuck anywhere ...

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


All Articles