Tuesday, April 19, 2011

Beware of the self keyword within setter functions

If you have read my previous post Self keyword and setter functions, you will find it easier to follow along with this post.

In the last post, I asked you to use the self.member syntax when setting a member variable.

In this post, I am going to ask you not to use the self.member syntax within your setter function.

This is applicable only if you are writing your own custom setter function. The following example summarizes it all:

- (void)setMyArray:(NSArray*)tmpArray{

  // First, retain the value
  [tmpArray retain];

  // Next, release the previous value
  [myArray release];


  // Now here is where you need to be careful
  // Set the value
 
// WRONG: Don't use self.member within a setter function 
  // self.myArray = tmpArray


  // RIGHT: Always call the member variable directly within the   setter function
   myArray = tmpArray
}


Now for those of you who are still curious and wondering why you shouldn't use the self.member syntax within the setter function, here is the reason.

Using self.member syntax invokes the setter function of that variable, so if you use the self.member syntax within the setter function the setter function will be called recursively and your program will be in an infinite loop.

Monday, April 18, 2011

Self keyword and setter functions

Let me begin this post with an example:
Consider this variable:

NSArray * myArray;
@property(nonatomic, retain) NSArray * myArray;


The above lines of code causes the compiler to generate a default setter function for the myArray variable which retains the variable.

The compiler generated setter function would like something like:

(void)setMyArray:(NSArray *)tmpMyArray{
  [tmpMyArray retain];
  [myArray release];
  myArray = tmpMyArray;
}


Observe that the setter function releases the current myArray before assigning a new value to it. That is why, when we set a value to a variable we should always ensure that we use the self.member syntax.

// The following line invokes the setter function to set this value
self.myArray = anArray;

// The following line does not invoke the setter function and thus causes a memory leak because release is not called on the previous value of myArray.
myArray = anArray;

Another thing to be remembered is, if we are defining a custom setter function, we MUST implement all the steps that a compiler-generated setter would contain.

Monday, April 11, 2011

Positioning the UIPopoverController after rotation

When creating applications for iPad, one of the most important feature we need to take into account is device orientation change. One issue that I came across while rotating a popover controller was maintaining the position of the popover controller after rotation.

So if you are at the stage where you still have to fight your popover controller to get it to behave, continue reading to know how I did it!

Here, we will consider showing the popover controller from a bar button.

Here's a recap for creating a popover controller:

1. Instantiate your nib file.
    MyNib * myNib = [[MyNib alloc] initWithNibName: @"MyNib" bundle: nil];

2. Get the instance of the bar button item from which the popover controller should be displayed:
    UIBarButtonItem *myBarButton = (UIBarButtonItem *)self.barButton;

3. Instantiate the popover controller and set the delegate
    self.myPopoverController = [[UIPopoverController alloc] initWithContentViewController: myNib];
     self.myPopoverController.delegate = self;


4. Set the size of the popover content controller
    [self.myPopoverController setPopoverContentSize: CGSizeMake(anyWidth, anyHeight)];

5. Finally present the popover
   [self.myPopoverController presentPopoverFromBarButtonItem: myBarButton
                                    permittedArrowDirections: UIPopoverArrowDirectionUp
                                                    animated: YES];


Put the above lines of code into a function called showMyPopover.

Now we come to the interesting part, handling the popover during rotation:

Override the didRotateFromInterfaceOrientation orientation handler like follows:

- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation {
    if (self.myPopoverController) {
        [self.myPopoverController dismissPopoverAnimated: YES];
        [self.myPopoverController release];
        self.myPopoverController = nil;
    }

    [self showMyPopover];
}


And that's it, each time your orientation changes, your popover rotates appropriately.
If your popover controller contains a UITableViewController and you want to maintain state each time the UIPopoverController is rotated, have a look at my blog How to maintain the UITableView scrolled location

Thursday, April 7, 2011

How to maintain the UITableView scrolled location.

Recently, in one of my code, I discovered an issue in my UITableView's scrolled location. The issue was that, my UITableView was not maintaining its scroll location. My UITableView happens to be in a UIPopoverController and it has so many rows that it won't fit on screen. So it would take around 5 or 6 scrolls to see the entire contents of the UITableView.

So here is the exact problem:
On the main page, I have a name value. It is a superset of these name values that need to be displayed in the  UITableView when I click on a button to display the UITableView as a popover. What I need to do when the popover is displayed, is I need the name displayed on the main page to be shown as selected in the UITableView. Now if the name to be selected is within the first 20 rows in the UITableView then I have no issue, whenever the user clicks on the popover button, the tableview is shown with the required name selected.

However, if the name on my main page is a value that is not within the first 20 rows (maybe it is visible only after the 3rd or 4th scroll), so when I display the UITableView the selected value will not be seen because it is not in the current page. From a user point of view, this is a defect so I needed to correct it to improve the user experience.

This is how I fixed it.
 1. Calculate the saved scroll position. You can do it either by taking the content offset as follows:

CGPoint savedScrollPosition = [tableviewObject contentOffset];

or if this is not possible in some places, you can calculate the offset manually as follows:

int index = [namesArray indexOfObject:category.categoryName];
self.savedScrollPosition = CGPointMake(0, HEIGHT_OF_CELL * index);

2. Set the savedScrollPosition into the UITableViewController class.

3. In the viewDidAppear function of the UITableViewController class, write the following line:

[categories setContentOffset:savedScrollPosition animated:NO];

or you can set animation to yes if you want the animation.

So, eventhough I make a new instance of my UITableViewController each time the popover button is clicked, my users never lose their continuity.

Good trick!

Monday, April 4, 2011

How to add existing frameworks in XCode 4?

In the older version of XCode this was easily done by right-clicking the Frameworks group and selecting "Add existing".

In XCode 4 however, they decided to put it in a less easily accessible location.
So here's how to get there!

1. In Project Navigator, select the project Name.
2. Next, select your project's target.
3. Select the Build Phases tab.
4. Expand the "Link Binaries with Library" tab.
5. Select the "+ button to add an existing framework.

The following image sums it all up.

Adding existing frameworks in xcode 4

Friday, April 1, 2011

Where is the XCode 4 build directory?

In all previous versions, XCode used to create a build directory inside the project folder and the executables were saved in that location.

However, XCode 4 changed all that.
Infact, I spent quite sometime searching for my executables.
XCode 4 uses the derived data location for storing your builds by default.

Just click on the XCode menu on the top bar, and select Preferences -> Locations tab.
Here under build location, you can see the default setting selected as "place build products in derived data location (recommended)".

And if you check the "Derived Data Location" section of this tab you will see the default location is /Users/..../Library/Developer/XCode/DerivedData.
You will see a small arrow next to this path and clicking on it takes you directly to this location and voila there is your build directory!

How to use enums in objective c?

We can use enums to define data types which will take only some specific values.
Often we come across code that has magic numbers in it.

See the code snippet below:
if (choice == 1)

What choice is intended by 1 is unclear over here.
So for this purpose we can use constants with meaningful names.

For instance, suppose the choice equals 1 means "edit Data"
so we can define a constant as follows:
const int kSelectedEditData = 1;

and we could rewrite the code above as follows:
if (choice == kSelectedEditData)

This makes it more readable. Suppose we want to do some processing in a switch statement, this approach would not work as the switch statement does not allow the use of constant values.
So inorder to get over this limitation we define an enum for all the choices and use that enum in the switch.

enum {
      kSelectedEditData = 1,
      kSelectedDeleteData,
      kSelectedDisplayData
    } Choices;


You can now create a variable of this enum type as follows:
enum Choices myChoices;

Notice that we have to use the enum keyword during declaration which is not a very pretty sight. Just create a typedef to get rid of that.
typedef enum Choices Choices;

So now you can declare your enum variable as follows:
Choices myChoices;

Ok, now lets see how we can use this in a switch statement:
switch (myChoices){
   case kSelectedEditData:
      // do something
   break;

   case kSelectedDeleteData:
      // do something
   break;

   case kSelectedDisplayData:
        // do something
   break;
}


There, now everything is much more readable.

The only problem with the enum type is even if you declare a variable of the enum type, the compiler will not give you an error if you set a value other than the enum values to this type.
eg:- myChoices = 10; // no compiler errors

So enums are not type safe in objective c, but you can use them to make meaningful data types.