Saturday 10 January 2015

UIScrollView + UILabel changing label size according to its text content : The power of Autolayout in iOS

The power of Autolayout 

Consider a view with 3 UILabels vertically aligned one by one, from top to bottom in a UIScrollView. These UILabels should adjust its size and layout according to their text contents. Please download the source code attached at the end of this page and open it in XCode. All the contents of the label should be visible on scrolling. Here the following autolayout features can be seen
  • Resizing UILabels according to the text contents
  • Making a scrollable UI which shows those UILabel contents aligned one by one using UIScrollView
  • UILabel Automatically adjusts frame if any UILabel on top / in-between does not have any contents to show
  • All With 0 Coding.  Yes, No coding effort is needed. Everything is done in InterfaceBuilder only !!!
A Good Autolayout design do not show any warnings in the nib file or the storyboard. If it shows, it means the layout may break at runtime causing even an App crash ! 

The big problem with Autolayout and UIScrollView is the warning "Has ambiguous scrollable content height".  example:


Let us see how to overcome this warnings and make a good design in Autolayout ( iOS 7 and above )

1.  Create an XCode project with 'single view application' template and select Universal device from the drop-down

2.  Place a UIScrollView on top of the main UIView and Make the size same as the Parent View. Set the Autolayout Constraint to Top:0, Leading:0,Bottom:0, Trailing:0 with respect to (w.r.t) superview

3. Place a UIView as content view ( please remember this term. this is used in lot of places ) on top of UIScrollView. Make the size of the content view same as the UIScrollView. Set Autolayout Constraint to Top:0, Leading:0,Bottom:0,Trailing w.r.t UIScrollView. As shown in the figure select the View and its parent UIScrollView together and apply "widths equally" constraint.

          
          This is to avoid Horizontal Scrolling and to prevent UILabels from growing towards two sides and get its contents hidden. 

In the same way as shown below set "heights equally" constraint as shown

Select this constraint and don't forget to check its "Remove at build time" option, since we want to make the content view grow in height according to the size of its children 


Now the warning  

will disappear. 

4.  Place 3 UILabels one by one vertically aligned. Set different font colours and font sizes to them. Set constraints as shown below

Top UILabel (name it as topicLabel): leading:20, top:20, trailing:20, height > 1 ( ** this is very important since it helps the autolayout to handle hiding the UILabel for empty contents and adjusts rest of the UILabel frames and alignments automatically ) w.r.t superview

Middle UILabel (name it as descriptionLabel) : leading:20, top:10 ( margin between upper label and current label), trailing:20, height > 1 all w.r.t superview

Bottom UILabel (name it as bottomLabel): leading:20, top:10, trailing:20, height >1, bottom >= 30 ( To create a margin between the bottom most UILabel and its superview )

In the sample code there are 3 outlets for those labels to push some texts programmatically as shown in the method below

-(void)setAllTexts
{
    [self.topicLabel setText:@"Autolayout iOS : Tutorial with Sample Code !!!"];
    [self.descriptionLabel setText:@"This sample code demonstrates how to use variable content UILabels can be used inside a UIScrollView that is scrollable"];
    [self.bottomLabel setText:@"This is Bottom Label. An Offset is set to the bottom of my superview !"];
}

Run the Sample App "Scroll" and feel the power of autolayout. As per the given settings a scrollable colourful label contents layout with proper margins and separations will be displayed. Please try commenting any line / lines in the given method in random and run the code to see how the labels adapts to size and margins !

Example:

 -(void)setAllTexts
{
    [self.topicLabel setText:@"Autolayout iOS : Tutorial with Sample Code !!!"];
    //[self.descriptionLabel setText:@"This sample code demonstrates how to use variable content UILabels can be used inside a UIScrollView that is scrollable"];
    [self.bottomLabel setText:@"This is Bottom Label. An Offset is set to the bottom of my superview !"];
}

iPhone Screenshots attached:

1. All the labels with text contents (scrollview scrolled to middle)



2. Only Top and Bottom labels are set with text contents

case : 

 -(void)setAllTexts
{
    [self.topicLabel setText:@"Autolayout iOS : Tutorial with Sample Code !!!"];
    //[self.descriptionLabel setText:@"This sample code demonstrates how to use variable content UILabels can be used inside a UIScrollView that is scrollable"];
    [self.bottomLabel setText:@"This is Bottom Label. An Offset is set to the bottom of my superview !"];
}

Click here to download sourcecode !

Design UI with AutoLayout and avoid/reduce UI coding !





Saturday 29 November 2014

iOS Swift Closures and Objective C Blocks

Blocks/Closures are fragment of code that can be executed along-with/in-between another section of code, by passing them to those sections through method arguments/variables/properties. There the Blocks/Closures will be executed as next line of code from where they are invoked. Blocks can return like normal methods

Example 1:

How to create a block/closure ?

1) Define Block/Closure data type

// create types globally
Objective C Syntax: typedef returnType (^blockname>)(arguments);
typedef void(^DisplayMessage)(NSString *message);

Swift Syntax: typealias blockname = (arguments) -> returnType

typealias DisplayMessage = (message:NSString) -> Void

2) Create method with block/closure as parameter and Invoke the block ( invoking inside method body itself is Optional and not mandatory. Also it can be set/hold in a property and invoked later on)

Objective C:

-(void)displayMessageWithHandler:(DisplayMessage)handler
{
    handler(@" The execution of block of code started "); // block of code invoked
}

Swift :

func displayMessageWithHandler(handler:DisplayMessage) -> Void
{
    handler(message: " The execution of block of code started "// block of code invoked
}

3) Invoke the method

Objective C:

[self displayMessageWithHandler:^(NSString *message) 

// block starts 

        NSLog(@"%@",message); // displays " The execution of block of code started "
        
        // fragments of code written here will be executed ...

        // statement n 
        // statement n+1 
        // ......

// block ends
}];

Swift :

self.displayMessageWithHandler 
{ (message) -> Void in
          
// closure starts

        println(message)

        // fragments of code written here will be executed ...

        // statement n 
        // statement n+1 
        // ......

// closure ends

}

Features:

Some of the features of Blocks/Closure are similar to the function pointer in C++. In iOS Blocks/Closures are treated as Objects. Hence they can be retained, released and even added to NSArray and NSDictionary

In Swift Closures are alternatives of Objective C Blocks. Blocks are known by name Closures in other programming languages too

The Advantage of using Blocks OR Closures are
  1. Alternative technique to delegate/callback
  2. Executable piece of code can be passed to a method/other sections and executed there
  3. Blocks along with Grand Central Dispatch (GCD) are one of the best and faster solutions for Multithreaded Web Service Access/ API integration designing [ (3) this will be explained with the help of an Example code in a separate post ]
Example 2:

This is a Simple sample code that demonstrates how blocks/closures can be passed and executed. Here blocks/closures acts as a sole decision maker of Bubble Sort implemented in a method body. Here blocks returns a BOOL type

1) Create block/closure types

Objective C:

typedef BOOL(^CompareBlock)(NSNumber *number1,NSNumber *number2);

Swift :

typealias CompareBlock = (number1:NSNumber, number2:NSNumber) -> Bool

2) Creating Category/Swift-Extension for NSMutableArray to implement sorting

Objective C:


@implementation NSMutableArray (sortArrayCategory)

-(NSMutableArray*)sortListWithSortHandler:(CompareBlock)compare
{
    /* Bubble Sorting */
    for(int j = 0; j < (self.count - 1); j++)
    {
        for(int i = 0; i < (self.count - 1); i++)
        {
            if(compare(self[i],self[i+1])) // Calling the block = Executing the piece/block of code
            {
                [self exchangeObjectAtIndex:i withObjectAtIndex:i+1];
            }
        }
    }
    
    return self;
}

@end

Swift :


extension NSMutableArray /* Category in Objective C */
{
    func sortListWithSortHandler(compare:CompareBlock) -> NSMutableArray
    {
        /* Bubble Sorting */
        for(var j = 0; j < (self.count - 1); j++)
        {
            for(var i = 0; i < (self.count - 1); i++)
            {
                if compare(number1: self[i] as NSNumber,number2: self[i+1as NSNumber/* Calling the Closure = Executing the piece/block of code /*
                {
                    self.exchangeObjectAtIndex(i, withObjectAtIndex: i+1)
                }
            }
        }
        
        return self
    }
}

3) Create Array with list of numbers and Call the Category method

Objective C:

NSMutableArray *numberList =       [NSMutableArray arrayWithObjects:@300,@100,@1000,@500,@200,@100,@2000,@4000,@2300nil];
    
[numberList sortListWithSortHandler:^BOOL(NSNumber *number1, NSNumber *number2) 
{
        
        /* This block of code decides the type of sort Ascending OR Descending. It plays the role in making comparison for Bubble sort. Ascending if '>' and Descending if '<' is used */
        
        return (number1.integerValue < number2.integerValue);
        
}];

Swift :


var arrayList:NSMutableArray = [ 1200200340050008001009900]

arrayList .sortListWithSortHandler { (number1, number2) -> Bool in
            
            /* This enclosed Closure decides the type of sort Ascending OR Descending. It plays the role in making comparison for Bubble sort. Ascending if '>' and Descending if '<' is used */

        return (number1.integerValue < number2.integerValue)
}

Variable and Method accessibility in Blocks/Closures:

Inside blocks : Global variables , Global methods, Local variables inside method scope and imported constants can be referred. Among the variables only global variables are mutable by default. We need to make local variable mutable using keyword __block before the variable. It is recommended not to use the Mutable Arrays of __block type

Example 3:

__block int count = 100


[self sampleBlock:^(......) 
     count = count++; // No error. Hence variable can be mutated
}];

Blocks/Closures and Memory management
  • Copying blocks creates strong reference to object variables used within the blocks
  • If Instance variables that are out of block's scope are used inside the block, those variables are strongly referenced by default
Blocks/Closures Uses and Possibilities
  • Simultaneous enumeration and iteration through Collections
  • Web Service / API access : Success and Failure Completion handlers - ( best if used along with GCD )
  • As Callbacks
  • Blocks can return another block
  • Blocks can have another block as parameter
  • Nested blocks is possible. But has to be implemented with great care ! developer needs to avoid memory leaks / retain cycles / reference related problems
  • Blocks can be added to Array. Here also developer needs to avoid memory leaks / retain cycles / reference related problems