IOS Development Techniques Used by Me in Pictograph Contest

          Three rounds of the Vkontakte photo contest for the iOS platform have recently been held. Link to the contest: http://vk.com/photo_contest . In the process of developing the first round application, I found some interesting solutions to some problems. I wanted to share these decisions with the public. I’m unlikely to discover something new for iOS developers, I don’t think the article is suitable for beginners either. I suppose that the article will be interesting for iOS developers with the experience of 2-5 applications.



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


          Firstly, it’s very competent that from the very beginning in the tape you can see not exactly 4 or not exactly 5 pictures, but 4 and 1/3. This allows the user to instantly understand that the list of photos is scrolling horizontally.

    There are several questions:
    • How many photos are uploaded to this feed?
    • How to organize dynamic uploading of photos so that they all do not hang in RAM?

          At first I decided that I would display all the photos from the device’s memory in the tape, in case of problems with the 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 latest photos at the beginning of the tape. Immediately it turned out that the newest photographs were not in my album with saved photos, but in Photo Stream, which my brother shared with me that day.
          It was decided at the beginning of the tape to display the latest photos from the album with the saved photos, and already the rest behind this album. Inside each album of photography I began to arrange starting from the last. Here is the source that receives the array of ALAssets in the described order:

    @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 did not find anything better than to use UITableView, because it was simply created to scroll through long lists, dynamically load content and reuse similar table cells. The only thing is 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 UITableViewin the proposed location of the center of the tape and perform:

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

    When creating table cells, you need 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? As the table scrolls, it asks us for the contents of its cells. Having an array of ALAssets, we get thumbnail s of the images and fill them with table cells. The table scrolls very smoothly, no lags with uploading photos have been noticed. Regarding the time for taking all the photos - getting 2500 photos takes less than 1 second of time, but it is critical to launch the application. We make an animation of the table falling from right to left upon receipt of ALAssets. It turns out very nice and a half-second delay is practically not noticeable. Moreover, the query is not of all assets, but by setting multiple indexes of speed increase does not give, it even somewhat discouraged me. Thus, optimization with a quick preload 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 tape when they were opened and collapse them back when editing was canceled. In order to get the coordinates of the rectangle of a specific cell in the table, I used the class method UITableView:

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

          I had to spend a little manna to determine the exact coordinates of the picture, taking into account its rotation, etc. After receiving the coordinates of the thumbnail image in the main view, on top of the thumbnail I placed a reduced full-size image and animatedly changed the size and position of the image. Ideally, I would like the image to jump out of the tape with a non-linear path, but there wasn’t any time left ...

          In order to track changes in the device’s photos, you need to subscribe to the ALAssetsLibraryChangedNotification event , on which you need to reload the arrayALAssets. To prevent the tape from updating when the application saves the photo, you must use the internal flag to cancel the tape redraw during the next update and manually add a new one ALAssetto the beginning of the asset array.
          Saving at me is carried out in the leftmost position, I shift the table to the right by the width of one image, carry out the animation of the image flying into the tape, move the table back without animation and manually call it reloadData.

          In order for the animation of opening and closing images to be 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 off to the ribbon exactly in the form in which we left it after scaling. The photo will remain there in this form until it is hidden behind the border of the screen and reloaded again. To achieve this effect, I used NSMutableDictionaryassets with URLs as keys and NSValuecontaining CGRectas values. Unfortunately, I forgot to remove this property in the video review, but this was one of the most interesting problems for me.

    Smoothly scale and position photos using effects


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



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

    - (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 the result


          Since for us the most important thing is the speed of the application and the quality of the images as a whole, the competition conditions are not regulated, I decided to save the pictures in the size of 640x640 (on retin). And the easiest way to do this is by rendering the main view in the context of the 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;
    }

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

    video with my naughty dog demonstrating the operation of the application:



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

    PS And finally, thank you very much Vkontakte for organizing and holding such contests. After all, they motivate / stimulate programmers to start developing new, promising platforms for them (for some reason, it seems to me that there are many newcomers to the platform among the participants). The input data for the contest was very pleased - all the images were like a selection. No extra pixels stuck anywhere ...

    Also popular now: