Wednesday, October 21, 2015

Converting a synchronous API into an asynchronous API

Normally in iOS it is always good to go for an asynchronous API. Because we should not make the UI freeze due to synchronous API calls. Let's first create a synchronous method which take some time to return.
- (NSString*)methodSync {
    for (int i = 0; i < 20; i++) {
        [NSThread sleepForTimeInterval:1.0];
    }
}
If we can turn this into a block based async method, it would be nice. So this is what you should do.
  1. Call the  synchronous method in a background thread
  2. when the execution of the method finishes, we can get the result
  3. The result can be processed by a block created by the user

This is the asynchronous block based implementation.
-(void)methodAsync:(void(^)(NSString *rslt))completionHnadler {
    NSString __block *res;
   
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        res = [self methodSync];
    });
    dispatch_group_notify(group, queue, ^{
        completionHnadler(res);
    });
}
Here I am using dispatch groups to wait till the synchronous method finishes. This is how to call the new method
[self methodAsync:^(NSString *rslt) {
        NSLog(rslt);
    }]; 
For any query, feel free to contact me via my linkedin profile.

Thursday, July 30, 2015

Stratergy pattern example in java for android enviroments

If you want to switch between different implementations in different situations, strategy pattern is an ideal solutions. Recently I used this pattern for process the output in android UI tests. So depending on the situation the out put was redirected to either Logcat or a text file. This is how it was implemented.
This is the service class.
public class ReportService {
    public  void generateReport(T data, ReportStrategy strategy ){
       strategy.generateReport(data);
    }
}
This is the interface for the strategy classes.
public interface ReportStrategy {
    public void generateReport(T data);
}
Here the TestCasePoint class contains the test results.
Class for the Logcat output strategy.
public class LogCatReportStrategy implements ReportStrategy<TestCasePoint> {

    public static final String LOGNAME = "Logindicator";

    @Override
    public void generateReport(TestCasePoint data) {
        Log.d(LOGNAME,"*****************************");
        String testCaseID = "Testcase ID = "+data.getTestCaseID();
        Log.d(LOGNAME,testCaseID);
        for (ControllerPoint point : data.getControllPoints()) {
            String level = "********";
            String controllerID = level+"Controller ID = "+point.getId();
            String testStatus = level+point.getTestResult();
            Log.d(LOGNAME,controllerID);
            Log.d(LOGNAME,"Result = "+testStatus);
        }
        Log.d(LOGNAME,"Test case result "+data.getResult().toString());
        Log.d(LOGNAME,"*****************************");
    }
}
Class for the text file output strategy.
public class TextFileReportStratergy implements ReportStrategy<TestCasePoint>{
    @Override
    public void generateReport(TestCasePoint data) {
        File file = null;
        try {
            file = new File(Environment.getDataDirectory(), "Report.txt");
            BufferedWriter writer = new BufferedWriter(new FileWriter(new File(Environment.getDataDirectory(),"Report.txt")));
            writer.write("*****************************\n");
            writer.write("Testcase ID = " + data.getTestCaseID()+"\n");
            for (ControllerPoint point : data.getControllPoints()){
                String level = "********";
                String controllerID = level+"Controller ID = "+point.getId()+"\n";
                String testStatus = point.getTestResult().toString();
                writer.write(controllerID);
                writer.write(level+"Result = " + testStatus+"\n");
            }
            writer.write( "Test case result " + data.getResult().toString()+"\n");
            writer.write("*****************************\n");
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
This is how log cat strategy can be used.
ReportService service = new ReportService();
ReportStrategy strategy = new LogCatReportStrategy();
service.generateReport(testResult, strategy);
This is how text file strategy can be used.
ReportStrategy strategyTextFile = new TextFileReportStratergy();
service.generateReport(testResult, strategyTextFile);

For any query, feel free to contact me via my linkedin profile.

Wednesday, June 17, 2015

Adapter pattern for XML passing in Android

For the xml parsing in android environments, usually XMLPullParser is recommended in android docs. This example uses ExpatPullParser.
ExpatPullParser has pre-defined set of interfaces. So what we need is, adapting the interfaces of this class according to our needs. According to wikpedia and this stackoverflow question, adapter pattern is the right candidate for this situation.
This is our xml file.
<Window dynamic="true" delay="10">
 <controller automationId=\"Settings1\" monitoring=\"false\" check=\"existance\">   </controller>
 <controller automationId=\"Settings2\" monitoring=\"true\" check=\"existance\">   </controller>
</Window>
    
So lets first create models and the necessary interfaces.
public interface IBasicModel {
}
import java.util.List;

public interface IWindow extends IBasicModel {
    public boolean isDynamic();
    public String getDelay();
    public void setDynamic(boolean dynamic);
    public void setDelay(String delay);
    public void setControllerList(List controllerList);
}
import java.util.ArrayList;
import java.util.List;

public class Window implements IWindow{

    private boolean dynamic;
    private String delay;
    private List controllerList;

    public Window () {
        controllerList = new ArrayList();
    }

    public boolean isDynamic() {
        return dynamic;
    }

    public String getDelay() {
        return delay;
    }

    public void setDynamic(boolean dynamic) {
        this.dynamic = dynamic;
    }

    public void setDelay(String delay) {
        this.delay = delay;
    }

    public void setControllerList(List controllerList) {
        this.controllerList = controllerList;
    }
}
public interface IController extends IBasicModel{
    public String getAutomationId();

    public boolean isMonitoring();

    public String getCheck();

    public void setAutomationId(String automationId);

    public void setMonitoring(boolean monitoring);

    public void setCheck(String check);
}
public class Controller implements IController{
    private String automationId;
    private boolean monitoring;
    private String check;

    public String getAutomationId() {
        return automationId;
    }

    public boolean isMonitoring() {
        return monitoring;
    }

    public String getCheck() {
        return check;
    }

    public void setAutomationId(String automationId) {
        this.automationId = automationId;
    }

    public void setMonitoring(boolean monitoring) {
        this.monitoring = monitoring;
    }

    public void setCheck(String check) {
        this.check = check;
    }
}
Now what we need is adapting the interfaces of ExpatPullParser according to our requirements.
First we create an abstract class as follows.
public abstract class AbstractXMLAdapter {
    protected T1 parser;
    public abstract IWindow parseXML(T2 xmlResource);
    public abstract IWindow processWindowTag() throws Exception;
    public abstract IController processControllerTag() throws Exception;
}
Here is the concrete implementation of the adapter using ExpatFullParser
public class ExpatPullParserAdapter extends AbstractXMLAdapter<XmlPullParser, String> {

    @Override
    public IWindow parseXML(String xmlResource) {
        try {
            parser = Xml.newPullParser();
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
            parser.setInput(new StringReader(xmlResource));
            parser.nextTag();
            return processWindowTag();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public IWindow processWindowTag() throws Exception {

        List entries = new ArrayList();
        parser.require(XmlPullParser.START_TAG, null, "Window");

        IWindowBuilder windowBuilder = new WindowBuilder();
        windowBuilder.processDelayTag(parser.getAttributeValue(null, "delay"));
        windowBuilder.processDynamicTag(parser.getAttributeValue(null, "dynamic"));

        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) {
                continue;
            }
            String name = parser.getName();
            // Starts by looking for the entry tag
            if (name.equals("controller")) {
                entries.add(processControllerTag());
            } else {
                skip(parser);
            }
        }
        windowBuilder.setControllerList(entries);
        return (IWindow)windowBuilder.getInstace();
    }

    private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
        if (parser.getEventType() != XmlPullParser.START_TAG) {
            throw new IllegalStateException();
        }
        int depth = 1;
        while (depth != 0) {
            switch (parser.next()) {
                case XmlPullParser.END_TAG:
                    depth--;
                    break;
                case XmlPullParser.START_TAG:
                    depth++;
                    break;
            }
        }
    }

    @Override
    public IController processControllerTag() throws Exception {
        IControllerBuilder builder = new ControllerBuilder();
        builder.processAutomationId(parser.getAttributeValue(null, "automationId"));
        builder.processMonitoring(parser.getAttributeValue(null, "monitoring"));
        builder.processCheck(parser.getAttributeValue(null, "check"));

        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) {
                continue;
            }
        }

        return (IController)builder.getInstace();
    }
}
This is the implementation of adapter pattern. Now next question is, generating the models. For that purpose, I have used builder pattern

Use of Builder pattern in model class generation
In order to decouple the model class generation from xml parsing, I have used builder pattern. Here what we do is, generating a complex class structure.So in this situation, builder is useful than something like abstract factory. I will show you how Controller models were generated using the builder pattern.
public interface IBuilder {
    public IBasicModel getInstace();
}
public interface IControllerBuilder extends IBuilder {
    public void processAutomationId(String automationId);
    public void processMonitoring(String monitoring);
    public void processCheck(String check);
}

public class ControllerBuilder implements IControllerBuilder{
    private IController controller;

    public ControllerBuilder() {
        controller = new Controller();
    }

    @Override
    public IController getInstace() {
        return controller;
    }

    @Override
    public void processAutomationId(String automationId) {
        controller.setAutomationId(automationId);
    }

    @Override
    public void processMonitoring(String monitoring) {
        controller.setMonitoring(Boolean.parseBoolean(monitoring));
    }

    @Override
    public void processCheck(String check) {
        controller.setCheck(check);
    }
}
Complete example is now available in the github. For any query, feel free to contact me via my linkedin profile.

Sunday, May 31, 2015

Building iOS apps in comand line

Building iOS apps in command line.
1 Using xcodebuild command
Navigate to the directory of the Xcode in the terminal and issue the command xcodebuild. Then you will be able to build. In addition you can specify the build configuration using the –configuration option. For example, this is how to build with debug configuration.
xcodebuild –configuration Debug
2 Building using xctool
Also we can build using the command xctool. In fact xctool can be considered as a replacement for xcodebuild tool. Also xctool is created and maintained by the facebook. In order to install the xctool you need to clone it to some convenient location as follows
git clone git@github.com:facebook/xctool.git

In cloned project, you will see a script called xctool.sh. That is what we should be concentrating.Next you have to build the tool. Fortunately, when you call xctool for the first time, it builds itself. Invoke the the script with with option version as follows. This will give you the current version of the tool. Also it will build the tool.
./xctool.sh -version
Now you need to include xctool installation path to the installation path. So you can easily invoke the command from anywhere else. Now you have the xctool installed and path also correctly set. Now navigate to the folder where your project is residing and issue the xctool command as follows.
xctool.sh -workspace /Your workspace name/ -scheme /Your project name/
There are lot of advantages of using xctool over xcodebuild. One is, output of xctool is better readable. In addition xctool provides different reporters making it easier to integrate CI systems such as jenkins. Also it supports cofiguration file format. In order to use it, simply create a “.xctool-args” file with the following format
[
        "-workspace", "/workspace name/.xcworkspace",
        "-scheme", "/project name/",
        "-configuration", "Release"
]
Because of this file, now you can invoke xctool without other options. But since the xctool is created and maintained by a third party company, xctool may fail with newer xcode versions.
3 Building IPA files with command line
Now you know how to build a xcode project in command line. But in order to run it, you need to create an IPA file. It can be done in command line. It can be done using xcrun tool. xcrun is a xcode tool. It is impacted by the location of the developer directory and the value of $DEVELOPER_DIR environment variable. This tool does two tasks. That is finding development tools and executing them. If you want to find the location of you git installation, you can call this command as follows
xcrun --find git
As I mentioned earlier, this tool can execute tools as well. This is how to execute a git command via xcrun
xcrun git --version
Here you need to be bit carefull. Because xcrun can bypass the traditional $PATH enviroment variable to locate executable files. Now lets get back to our original topic. That is generating the IPA file. It can be achieved with PackageApplication tool. This tool can be invoked by xcrun tool. This is how to invoke it
xcrun PackageApplication
But unfortunately, you will get the following error message.
xcrun: error: unable to find utility "PackageApplication", not a developer tool or in PATH
This error can be caused by either of this issues.
  • It is simply not in your PATH
  • PackageApplication is an executable specific to the iOS SDK. Since we did not specify the SDK we are using and the SDKROOT environment variable was not set, the xcrun was unable to find the tool
We can specify the iOS sdk we are using with the SDK argument.
xcrun --sdk iphoneos --f PackageApplication
Package application only needs the path to the .app package generated at the end of the xcodebuild command line tool. In addition it will perform tasks such as resigning the application before packaging it into the IPA file. Path to the generated package can be found at the end of the command's output. This is how to use it
/usr/bin/xcrun -sdk iphoneos PackageApplication -v "${RELEASE_BUILDDIR}/${APPLICATION_NAME}.app" -o "${BUILD_HISTORY_DIR}/${APPLICATION_NAME}.ipa" --sign "${DEVELOPER_NAME}" --embed "${PROVISONING_PROFILE}”
Putting everything in a single script
Now you have scene how to build and generate IPA files in command line. In a CI environment it is better to have them in a single script. This is how to do it.
#!/bin/bash
WORKSPACE="/Your workspace name/"
SCHEME="/Your project name/"
BUILD_DIR="$(pwd)/build"
CONFIGURATION="Release"
ARCHIVE_FILENAME="/Your project name/.app"
IPA_FILENAME="/Your project name/.ipa"
security unlock-keychain -p $PASSWORD $HOME/Library/Keychains/login.keychain

xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration
CONFIGURATION_BUILD_DIR="$BUILD_DIR" clean build | xcpretty -c
xcrun -sdk iphoneos PackageApplication "$BUILD_DIR/$ARCHIVE_FILENAME" -o "$BUILD_DIR/$IPA_FILENAME"

For any query, feel free to contact me via my linkedin profile.

Friday, May 1, 2015

Defining a service layer with AFNetworking

This example shows how to create a service layer for json based web services. This is how I developed it.
  1. Create a basic interface for calling web services
  2. Create a subclass of correct AFNetworking seralizer class(in this case AFJsonSerializer) and format the response
  3. For each web service create a singleton subclass of AFHttpSessionManager
  4. Create a Façade for service layer
For this example I am using free web service used used by raywenderlich afnetworking tutorial

1 Creating basic interface
In modern software engineering, we write code for the interface, not for the implementation. So create a protocol named Service as folows
@protocol Service <NSObject>

- (void)callService:(NSDictionary*)parameters withCompletionBlock:(void(^)(NSArray *resultArray, NSError *error))completionBlock;

@end

This has a method called callService and it is the method used to call the web services. Now we have an interface, which can be used to call web services. I will explain the importance of this when we actually call a web service.

2 Creating a custom json response serializer
Here, we get the response in json format. we need to process this json response in order to get the correct result out of it. For that purpose we can create a subclass of the AFJSONResponseSerializer class and override it's responseObjectForResponse as follows. This method is overridden according to the json response we get. This method directly create models from the response and add it to an array
@implementation WeatherServieJasonSerializer

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error {
    NSMutableArray *retArray = [[NSMutableArray alloc] init];
    NSDictionary *json = [super responseObjectForResponse:response data:data error:error];
    NSDictionary *dictionary = json[@"data"];
    WeatherInfo *infoRet = [[WeatherInfo alloc] init];
    NSArray *arrData = dictionary[@"weather"];
    
    infoRet.tempMaxC = @([((NSString*)((NSDictionary*)arrData[0])[@"tempMaxC"]) integerValue]);
    infoRet.tempMaxF = @([((NSString*)((NSDictionary*)arrData[0])[@"tempMaxF"]) integerValue]);
    infoRet.tempMinC = @([((NSString*)((NSDictionary*)arrData[0])[@"tempMinC"]) integerValue]);
    infoRet.tempMinF = @([((NSString*)((NSDictionary*)arrData[0])[@"tempMinF"]) integerValue]);
    
    [retArray addObject:infoRet];
    
    return retArray;
}
@end
 
3 Creating a singleton subclass of AFHttpSessioManager to invoke web services.
For each web service you have, you need to create a separate class like this. In addition this class conform to the Service protocol and implements its callService method. Following is the code for the class.
.h file
#import "AFHTTPSessionManager.h"
#import "Service.h"

@interface WetherWebService : AFHTTPSessionManager<Service>

+ (WetherWebService*)getSharedInstance;

@end

.m file
#import "WetherWebService.h"
#import "WeatherServieJasonSerializer.h"

static NSString * const baseURLString = @"http://www.raywenderlich.com/demos/weather_sample/";

@implementation WetherWebService

+ (WetherWebService*)getSharedInstance {
    static WetherWebService *sharedInstance = nil;
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        sharedInstance = [[WetherWebService alloc] initWithBaseURL:[NSURL URLWithString:baseURLString]];
        sharedInstance.responseSerializer = [WeatherServieJasonSerializer serializer];
    });
    
    return sharedInstance;
}

- (void)callService:(NSDictionary*)parameters withCompletionBlock:(void(^)(NSArray *resultArray, NSError *error))completionBlock {
    [self GET:@"weather.php" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
        completionBlock(responseObject, nil);
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        completionBlock(nil, error);
    }];
}

@end
 
4 Creating the service layer façade.
This is an implementation of façade design pattern. This creates an abstraction layer between the web service layer and the rest of the application. This is the source of ServiceLayerFacade.h file
@interface ServiceLayerFacade : NSObject

+ (ServiceLayerFacade*)getSharedInstance;
- (id<Service>)getWeatherService;

@end
#import "ServiceLayerFacade.h"
#import "WetherWebService.h"

@implementation ServiceLayerFacade

+ (ServiceLayerFacade*)getSharedInstance {
    static ServiceLayerFacade *sharedInstance = nil;
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        sharedInstance = [[ServiceLayerFacade alloc] init];
    });
    
    return sharedInstance;
}
- (id<service>)getWeatherService {
    return [WetherWebService getSharedInstance];
}
This is how these web services should be called.
NSDictionary *parameters = @{@"format": @"json"};
    ServiceLayerFacade *serviceLayer = [ServiceLayerFacade getSharedInstance];
    id<Service> weatherWebService = [serviceLayer getWeatherService];
    [weatherWebService callService:parameters withCompletionBlock:^(NSArray *resultArray, NSError *error) {
        if (resultArray[0] != nil) {
            id<Tablelayout> tableModel = resultArray[0];
            _tableArray = [tableModel getTableRepresentation];
            [_weatherTable reloadData];
        }
    }];
Here, since we are using interfaces and a facade, we can completely hide the implementation details of the web service layer. Web service caller only knows of the uniform interface method to call the web service. He does not know the classes used to call the web service, the way response is processed etc. What he gets is model classes that can be directly used in his code. Now I will explain the beauty of this approach. We use Service interface to access the web service. And we do not know how the web service is called behind the scene and how the response is processed. Because of this we can change everything related to web service calling and processing without affecting rest of the layers.

Presenting the data
This is our model class
#import 

@interface WeatherInfo : NSObject

@property NSNumber *tempMaxC;
@property NSNumber *tempMaxF;
@property NSNumber *tempMinC;
@property NSNumber *tempMinF;

@end
Now we have the data retrieved from the web service stored in our model classes. Now how to present it? You can add a method to the model class to get some sort of array and show it in a table. But this is an extremely bad idea. Because a model class should not know how the data is presented. So here our basic problem is adding behavior to the model object, without affecting behavior of other objects from the same class. What is the design pattern we can use to achieve this? The answer is decorator pattern
 
Implementation of decorator pattern
I normally use categories to implement decorator pattern in objective c. In addition I declare category methods in a protocol as well. This protocol acts as an interface
#import 

@protocol TableLayout <NSObject>

-(NSArray*)getTableRepresentation;

@end

This is the category of the model class.
.h file
#import "WeatherInfo.h"
#import "TableLayout.h"

@interface WeatherInfo (TableRepresentation)<TableLayout>

@end

This is the .m file
#import "WeatherInfo+TableRepresentation.h"

@implementation WeatherInfo (TableRepresentation)

-(NSArray*)getTableRepresentation {
    return [[NSArray alloc] initWithObjects:self.tempMaxC,self.tempMaxF,self.tempMinC,self.tempMinF, nil];
}

@end
Then in code via the TableLayout interface I am getting the table representation as follows.

id<Tablelayout> tableModel = resultArray[0];
            _tableArray = [tableModel getTableRepresentation];
            [_weatherTable reloadData];
This is how I implemented the service layer. The complete example of this tutorial is available in GitHub.

Alternative approaches
  • This stackoverflow question presents few ways of developing a service layer using AFNetworking and ReactiveCocoa
  • You can use RestKit to get Core data models directly after web service calls

For any query, feel free to contact me via my linkedin profile. Happy coding!!!!

Tuesday, April 7, 2015

Usnig dispatch_apply with asyncronuos functions

Usually dispatch_apply is a synchronous function, which will pause the current thread, then execute the block and return to the paused thread. See the following example
Dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
dispatch_apply(10, queue, ^(size_t index) { 
NSLog(@"%zu", index); }); 
NSLog(@"done");
The results is as follows











But this is a synchronous execution. Assume you have a NSMutableArray and you need to perform some action for each of the object in array asynchronously in a separate thread, this is how to do it. Also since this is executed in a global dispatch queue, you cannot guarantee which block runs first.
//first get a  global dispatch queue
dispatch_queue_t queue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
//Then add an asynchronous block
dispatch_async(queue, ^{ 
 //inside this block, you can use dispatch_apply
dispatch_apply([array count], queue, ^(size_t index) { 
//your block code goes here.
}); 
//Now you block has finished. You can execute some other task here.
});

  For any query, feel free to contact me via my linkedin profile.

Wednesday, March 25, 2015

Thread safe Queue in objective C with GCD

There are several methods we can use to make a class thread safe in objective c. They are
  1. Using NSLock
  2. Using @synchronized block
  3. GCD

Among them GCD is the easiest and safest way. Because unlike other two approaches GCD manages many of exception for us and it is easy to use. Also GCD is recommended by apple over other two options.
#import "ThreadSafeQueue.h"

@interface ThreadSafeQueue()

@property (strong, nonatomic) NSMutableArray *queueData;
@property (strong, nonatomic) dispatch_queue_t dataQueue.

@end

@implementation ThreadSafeQueue

- (instancetype)init {
    if (self = [super init]) {
        _dataQueue = dispatch_queue_create("deltaaruna.ThreadSafeQueue", DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

- (id)dequeue {
    __block id result = nil;
    dispatch_sync(self.dataQueue, ^{ 
    if(self.queueData.count > 0) {
     result = [self.queueData[0]] 
    }
    });
    return result;
}

- (NSUInteger)length {
    __block NSUInteger result = 0;
    dispatch_sync(self.dataQueue, ^{ result = [self.queueData count] });
    return result;
}

- (void)enqueue:(id)obj {
    dispatch_barrier_async(self.dataQueue, ^{ [self.queueData addObject:obj] });
}

@end

For any query, feel free to contact me via my linkedin profile.

Monday, February 2, 2015

Autolayouting for dynamically changing views in iOS(with complex UITableViewCells and UIButtons)

I have used both IB based and code based approach with autolayout. If the view is static the solution is very easy. You can use the easiest way, the IB based autolayouting. But if the view is dynamic, I have used code base and IB based approach plus few tricks.
Consider following screen shots

First image is taken when device is in landscape mode and second image is taken when the device is in portrait mode. Now consider the image that contains a camera, which is located at top right corner. Actually it is a UIButton with an image. If you examine carefully you will notice that the top inset of the element in the landscape mode is much lower that the in the potrait mode. This is how I implemented it.
  • Add left insets in the interface builder
  • programmatically add top insets according to the orientation
  • When the orientation change programmatically change the top insets and call setNeedsUpdateConstraints method on that view

Add these two constants in after #import section
static int buttonTopinsetLandscape = 1;
static int buttonTopinsetPotrait = 14;


- (void) adjustViewsForOrientation {
    [self addInsets];
    [photoBtn setNeedsUpdateConstraints];
}

- (void) addInsets {
  self.constraint = [NSLayoutConstraint constraintWithItem:photoBtn
         attribute:NSLayoutAttributeTop
         relatedBy:NSLayoutRelationEqual
            toItem:self.view
         attribute:NSLayoutAttributeBottom
        multiplier:1.0
          constant:[self getButtonTopinset]];

  [self.view addConstraint:constraint];
}

- (int)getButtonTopinset {
    return [self returnCurrentInset:buttonTopinsetLandscape withPotrait:buttonTopinsetPotrait];
}

- (int)returnCurrentInset:(int)landscape withPotrait:(int)potrait {
    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
    if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
        return potrait;
    } else if(orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
        return landscape;
    } else {
        return potrait;
    }
}


In viewwillappear method add notification to listen to orientation changes as follows.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(adjustViewsForOrientation) name:UIDeviceOrientationDidChangeNotification object:nil];

That's cool right? lets move in to much complex example. Look at the following table view.
---UPDATE---- The example for this part of the tutorial is now available on GITHub as well.
Each cell has different number of UIImageViews from one to four. Number of UIImageViews to be displayed can only be determined at run time. And the each cell changes the image view dimensions according to orientation. Here I have to use code based approach. I have used VFL here.
When there are two image Views
NSDictionary *views = NSDictionaryOfVariableBindings(imgViewOne,imgViewTwo);
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-4-[imgViewOne(imgViewOne)]-5-[imgViewTwo(imgViewOne)]-3-|" options:0 metrics:nil views:views]];
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-95-[imgViewOne]-88-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-95-[imgViewTwo]-88-|" options:0 metrics:nil views:views]];

Here image views share the same width. Here they will retain there left and right insets to the superview and adjust their widths accordingly.
When there are three image Views

[cell.contentView addSubview:imgViewOne];
            [cell.contentView addSubview:imgViewTwo];
            [cell.contentView addSubview:imgViewThree];
            
            imgViewOne.translatesAutoresizingMaskIntoConstraints = NO;
            imgViewTwo.translatesAutoresizingMaskIntoConstraints = NO;
            imgViewThree.translatesAutoresizingMaskIntoConstraints = NO;
            
            NSDictionary *views = NSDictionaryOfVariableBindings(imgViewOne, imgViewTwo, imgViewThree);
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-4-[imgViewOne(imgViewOne)]-5-[imgViewTwo(imgViewOne)]-3-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-4-[imgViewOne(imgViewOne)]-5-[imgViewThree(imgViewOne)]-3-|" options:0 metrics:nil views:views]];
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-95-[imgViewOne]-88-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-95-[imgViewTwo(imgViewTwo)]-5-[imgViewThree(imgViewTwo)]-88-|" options:0 metrics:nil views:views]];
Here please note that width of image views are similar and height of imgViewThree and imgViewTwo are similar
When there are four image Views
NSDictionary *views = NSDictionaryOfVariableBindings(imgViewOne, imgViewTwo, imgViewThree, imgViewFour);
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-4-[imgViewOne(imgViewOne)]-5-[imgViewThree(89)]-5-[imgViewFour(89)]-3-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-4-[imgViewOne(imgViewOne)]-5-[imgViewTwo(182.5)]-3-|" options:0 metrics:nil views:views]];
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-95-[imgViewOne(178)]-88-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-95-[imgViewTwo(115.5)]-5-[imgViewThree(58)]-88-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-95-[imgViewTwo(115.5)]-5-[imgViewFour(58)]-88-|" options:0 metrics:nil views:views]];
            [imgViewTwo setContentHuggingPriority:200
                                        forAxis:UILayoutConstraintAxisHorizontal];
            [imgViewOne setContentHuggingPriority:252
                                        forAxis:UILayoutConstraintAxisHorizontal];
            [imgView3 setContentHuggingPriority:200
                                        forAxis:UILayoutConstraintAxisHorizontal];
            [imgView4 setContentHuggingPriority:200
                                        forAxis:UILayoutConstraintAxisHorizontal];
Looks cool right? But not good enough. Here for example width of each imageview is the same. But what if you need one imageview is three times of the second one? If you try to achieve this with VFL you will get a run time exception. The only way is hard coding the widths which is an extremely bad idea. Then you will have hard time when you deal with different screen sizes and orientations. So VFL is not a viable solution at all.
We still have one option left for autolayoiting. That is using pure objective C based approach without using VFL. Then we can change the height and width of each image more freely.
Assume when you have three images width of the first image is three times the width of the second and third images this is how you should change the code. And when there are four images, width of the first image is three times the width of the second image and six times the third and fourth images
//for one image
NSDictionary *views = NSDictionaryOfVariableBindings(imgViewOne);
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[imgViewOne(imgViewOne)]-5-|" options:0 metrics:nil views:views]];
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[imgViewOne]-5-|" options:0 metrics:nil views:views]];

//for two images
NSDictionary *views = NSDictionaryOfVariableBindings(imgViewOne,imgViewTwo);
            
             [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[imgViewOne(imgViewOne)]-5-[imgViewTwo(imgViewOne)]-5-|" options:0 metrics:nil views:views]];
            
           [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[imgViewOne]-5-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[imgViewTwo]-5-|" options:0 metrics:nil views:views]];

//for three images
NSLayoutConstraint *ratioCons =[NSLayoutConstraint
                                               constraintWithItem:imgViewOne
                                               attribute:NSLayoutAttributeWidth
                                               relatedBy:NSLayoutRelationEqual
                                               toItem:imgViewTwo
                                               attribute:NSLayoutAttributeWidth
                                               multiplier:3
                                               constant:0];
            
            [cell.contentView addConstraint: ratioCons];
            
            NSDictionary *views = NSDictionaryOfVariableBindings(imgViewOne, imgViewTwo, imgViewThree);
            
            // Horizontal layout - note the options for aligning the top and bottom of all views
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[imgViewOne(imgViewOne)]-5-[imgViewTwo]-5-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[imgViewOne(imgViewOne)]-5-[imgViewThree]-5-|" options:0 metric
s:nil views:views]];
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[imgViewOne]-5-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[imgViewTwo]-5-[imgViewThree(imgViewTwo)]-5-|" options:0 metrics:nil views:views]];

//for four images
NSLayoutConstraint *ratioConsOne =[NSLayoutConstraint
                                            constraintWithItem:imgViewOne
                                            attribute:NSLayoutAttributeWidth
                                            relatedBy:NSLayoutRelationEqual
                                            toItem:imgViewTwo
                                            attribute:NSLayoutAttributeWidth
                                            multiplier:3
                                            constant:0];
            
            NSLayoutConstraint *ratioConsTwo =[NSLayoutConstraint
                                               constraintWithItem:imgViewOne
                                               attribute:NSLayoutAttributeWidth
                                               relatedBy:NSLayoutRelationEqual
                                               toItem:imgViewThree
                                               attribute:NSLayoutAttributeWidth
                                               multiplier:6
                                               constant:0];
            
            NSLayoutConstraint *ratioConsThree =[NSLayoutConstraint
                                               constraintWithItem:imgViewOne
                                               attribute:NSLayoutAttributeWidth
                                               relatedBy:NSLayoutRelationEqual
                                               toItem:imgViewFour
                                               attribute:NSLayoutAttributeWidth
                                               multiplier:6
                                               constant:0];
            
            [cell.contentView addConstraint: ratioConsOne];
            [cell.contentView addConstraint: ratioConsTwo];
            [cell.contentView addConstraint: ratioConsThree];
            
            NSDictionary *views = NSDictionaryOfVariableBindings(imgViewOne, imgViewTwo, imgViewThree, imgViewFour);
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[imgViewOne(imgViewOne)]-5-[imgViewTwo]-5-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[imgViewOne(imgViewOne)]-5-[imgViewThree]-5-[imgViewFour]-5-|" options:0 metrics:nil views:views]];
            
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[imgViewOne]-5-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[imgViewTwo]-5-[imgViewThree(imgViewTwo)]-5-|" options:0 metrics:nil views:views]];
            [cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[imgViewTwo]-5-[imgViewFour(imgViewTwo)]-5-|" options:0 metrics:nil views:views]];

Here I am using the pure objective c based approach along with VFL. Actually pure objc approach alone could have been used. I have mixed both the approach since it clearly shows the limitations of the VFL. In some dynamic UIs we have to use pure code based appproach. This example nicely shows it. But use of either VFL or objective c code approach result in verbose code. So in order to keep the code base nice and clean, many use wrappers around autolayout API. My personal preference is keeplayout library. I have extensivly used it and I would highly recommend it.
---UPDATE---- The example for this part of the tutorial is now available on GITHub as well.
For any query, feel free to contact me via my linkedin profile.

Monday, January 5, 2015

Dynamically changing rotation in iOS

I had a requirement to dynamically enable and disable the rotation of an iOS. We had a navigation controller throughout the app. First you need to enable landscape and potrait mode using Xcode. You can specify this in the deployment info section in the XCode
When we dynamically change the rotation, we need to change the rotation not only in the viewcontroller, but also in the navigation controller. Next you need to create a category of UINavigationController which supports for dynamic orientation changes. Here you need to overide two methods

-(bool)shouldAutoRotate and -(NSUInteger)supportedInterfaceOrientation
This is how they were overridden.



We need a boolean instance variable to determine whether we should rotate or not. Here. I have used iOS run time programming to assign value for this boolean value. Because we can't synthasis instance variables in a category. Also I am using a NSNumber variable instead of a boolean primitive.



Here KNewProertyKey is declared as follows


When you need to enable or disable rotation you just need to set the status with setRotationStatus method.
After that we can dynamically enable and disable the rotation. For any query, feel free to contact me via my linkedin profile.