Monday 30 March 2015

Tricks to debugging autolayout issues

Sometime back i started working on the autolayout overhaul for the app i am working for. The most important thing about autolayout is to be able to debug autolayout issues. Here i would like to share some tips to make debugging in autolayout easier also i will share some issues that i ran into and their possible solutions. This is the stuff that i do when i see an autolayout issue -

1. The first and the foremost thing i do is print the autolayout trace using :

 po [[UIWindow keyWindow] _autolayoutTrace]
 which in turn prints something like this :
*<UITableViewCellScrollView:0x147dcdd0>|   |   |   |   |   |   |   |   |   *<UITableViewCellContentView:0x147dd4c0>|   |   |   |   |   |   |   |   |   |   *<UIImageView:0x147dd6e0>|   |   |   |   |   |   |   |   |   |   *<UIView:0x147dead0>|   |   |   |   |   |   |   |   |   |   |   *<UILabel:0x147de590> - AMBIGUOUS LAYOUT|   |   |   |   |   |   |   |   |   |   |   *<UILabel:0x147deb60>|   |   |   |   |   |   |   |   |   |   |   *<UIButton:0x147df010>|   |   |   |   |   |   |   |   |   |   |   |   <UIImageView:0x147df150>|   |   |   |   |   |   |   |   |   |   |   <UIImageView:0x147df930>|   |   |   |   |   |   |   |   |   |   |   <UIImageView:0x147df9e0>|   |   |   |   |   |   |   |   |   |   |   *<UILabel:0x147ef780>|   |   |   |   |   |   |   |   |   |   *<UIView:0x147dd790>|   |   |   |   |   |   |   |   |   |   *<UIView:0x147ddb20>|   |   |   |   |   |   |   |   |   |   *<UIImageView:0x147dde80>

The memory addresses do not tell a whole lot about which view is ambiguous.

2. Now to reflect in the UI which view is ambiguous i use the exceptional xcode debugger. I get to know about the view by changing its background color.
For eg.
(lldb) e (void)[0x147de590 setBackgroundColor:(UIColor*)[UIColor greenColor]]
Then i have a brilliant library integrated in the debugger namely chisel (Chisel Github) which provides with command namely caflush and many other useful commands, caflush basically repaints your UI and hence you immediately see the green color reflected on your screen.
For eg.
(lldb) caflush
OR 

3. I try to print the superView's recursive description which gives me a fair idea which view is ambiguous by matching the address with the autolayoutTrace
For eg.
(lldb) [0x147dcdd0 recursiveDescription]

4.  I have run into multiple issues time and again when animating views using autolayout, one of the issues i ran into was when table view cells were animating unnecessarily when some other view was animating in the view controller's view. The problem was that i did not follow apple guidelines and tried to be extra smart, i.e.,
             [self.view layoutIfNeeded];     //I missed this line                     self.postStatusViewTopConstraint.constant = [self.topLayoutGuide length];            [UIView animateWithDuration:ANIMATION_HIDE_BUTTONS_TIME                             animations:^{
                                 [self.view layoutIfNeeded];  
                          } completion: NULL];

Basically what was happening  was that the table view cells were about to appear and their layout was pending and at the same time i used to animate postStatusViewTopConstraint which lead to tableView cells getting animated. Apple suggests using layoutIfNeeded before animating to wrap up any pending layout changes.

5. I run into unsatisfiable constraints repeatedly when working with various views. More often then not i can get away with reducing the priority of one of the constraints to solve the issue.

6. When using multiple table view cells having different layouts i prefer having different reuse identifiers. This helps me in adding sub-views selectively to the table view cell. (Adding sub-views and not adding their constraints usually leads to a lot of ambiguity)

7. One hard learnt lesson is that calculation table view cell's height using autolayout by calculating its content view size (using systemLayoutSizeFittingSize)  slows down the performance on lower end devices. Hence for the time begin i prefer pre-calculating the heights for different types of table view cells.

Further Readings:
a. How Autolayout Works?
b. Using debugger
c. Apple Autolayout
d. Download Chisel

Hope this article helps someone :)

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."