Tuesday 30 December 2014

Downscale Multiple Huge ALAssets without fear of SIGKILL

This post is inspired by http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill/
The post basically focuses on downscaling huge images which are in the form of ALAssets. ALAssets are used when you want to use Asset Library for mirroring or extending the functionality of UIImagePickerController.

I came across the issue where my app was crashing if very large images (>50 MB) were being sent. This was because the dirty memory of the image (uncompressed image loaded in memory) shot up memory usage of the app and hence leading to the app being killed.

The above link in the first line suggests the correct solution for downscaling huge images. This helped when sending one image only, but not multiple images. My app was still crashing if i tried to send multiple large images.
Then i finally figured out that i was dispatching the block on the Global Queue which introduced concurrency and hence multiple image's dirty memory lead to app getting killed.

The solution i implemented is by creating a serial queue when sending multiple images.

dispatch_queue_t serialImageProcessingQueue;
        serialImageProcessingQueue = dispatch_queue_create(kImageProcessingQueueName, NULL);
        
        for(ALAsset *asset in assets) {
            @autoreleasepool {
                dispatch_async(serialImageProcessingQueue, ^{
          //Use the method provided in the link above for creating desired resolution image out of the large image
                    UIImage *image = [ALAsset thumbnailForAsset:asset maxPixelSize:kThread_MaxMediaImageSize];
                    
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self sendImage:image saveToLibrary:NO];
                    });
                });
            }
        }
        

        dispatch_release(serialImageProcessingQueue);


If you want to dig deep on what is happening when you try to retrieve a lower resolution image. Refer to 

This link explains in detail how exactly the downsizing happens. The sample code makes for an interesting read. :)
Hope this helps someone...

Monday 8 September 2014

Relationship between contentSize, contentOffset and animation

UIScrollView's core has 2 properties which manage the visible portions of the scroll view on the screen.

- When we set contentSize
  • if the contentOffset is beyond the new contentSize the contentOffset is reset.
  • else the contentOffset is not changed. 
If you want to animate the contentOffset in the case when your contentOffset goes beyond contentSize then:

[[UIView animateWithDuration:0.2
                animations:^{
                                   self.contentSize = CGSizeMake(self.scrollViews.count *                      self.bounds.size.width, self.bounds.size.height);
                                     }
                 completion:NULL];


Thursday 4 September 2014

You are probably confused about Frames and Bounds!

Frames and Bounds have a learning curve of their own, if you do not have anyone to guide you about them. I learnt them the harder way. So that's why i want to make it simpler for people to understand them. Let us first have a look at the formal definitions of theirs. 

  • Frame: The frame property contains the frame rectangle, which specifies the size and location of the view in its superview’s coordinate system.
  • Bounds: The bounds property contains the bounds rectangle, which specifies the size of the view (and its content origin) in the view’s own local coordinate system. 
These are definitions according to the Apple's View Programming Guide. But what does these mean???

Well to begin with let's see what this magical statement does:
      [myView addSubview:anotherView];
This adds anotherView as myView's subview. Does this mean that we have told our system where to place this anotherView in my myView's coordinate system? NO!!!!
To set the position of your subview you need to use Bounds! Bounds will tell the subview to set its coordinates within its superview.

Let's say your myView's frame is (x=20,y=20,width=200,height=200).
And you do this:
anotherView.frame = myView.frame;   //This is completely wrong
This would place anotherView at origin (20,20) with respect to its superview.

What you probably wanted to do was 
anotherView.frame = myView.bounds; //Purrfect!

So when adding subview's to your view always use bounds (read as Boundary) to draw your subview in. Frame is basically for setting the position w.r.t. superview. 

At last 2 images to help you understand stuff better:



Hope this helps someone!

Monday 25 August 2014

masksToBounds and cornerRadius : An interesting catch

Every view has a layer as its backing property. The relevance of masksToBounds is that it tells the layer to clip the View to its bounds.
What i read on multiple websites was a little misleading, such as:
"It depends on masksToBounds which determines if the sublayers are clipped to the receiver’s bounds. So If YES, an implicit mask matching the layer bounds is applied to the layer, including the effects of the cornerRadius property." in a StackOverflow Post.

In this answer by sublayers this guy also means the VIEW that has the layer as its property. So when you set the cornerRadius of the layer, the layer gets rounded but the view overshadows  the layer. But when you set masksToBounds to YES, then the view gets clipped according to the layer and hence its corners also get rounded. :)

Hope this helps someone.

Wednesday 20 August 2014

UICollectionViewCell Selection and Deselection

Problem:

Today, i ran into a problem where i wanted to do some stuff on selection and deselection of a collectionView cell.
I inserted two delegate methods in my controller namely:

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath


- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath

When i tapped on my collectionView cell the method didSelectItemAtIndexPath was getting called. But when i tapped on the same cell again the method didDeselectItemAtIndexPath did not get called instead didSelectRowAtIndexPath was getting called again, hence, the actions that i specified in didDeselectItemAtIndexPath were not running.


Solution:

Add    
 [collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:nil]
to your didSelectItemAtIndexPath and also 

Add
 [collectionView deselectItemAtIndexPath:indexPath animated:NO]
to your  didDeselectItemAtIndexPath method to solve the problem.

After banging my head around for a while i realised that collectionView can't make out whether the cell is selected or deselected until we tell it so. Hence we tell the collectionView that we want this cell to be selected. What this does is that it makes the collectionView call the didDeselectItemAtIndexPath method the when we tap on the cell for the second time.

Additional Notes:

- Setting cell.selected = YES or NO does not solve the problem. Please try avoiding it as the Apple Documentation also says that "The preferred way to select the cell and highlight it is to use the selection methods of the collection view object."