C# to Objective-C - Part 10 : Events

Stephen Zaharuk / Monday, December 10, 2012

I'm not sure how I missed covering this topic thus far. Events are so basic that they generally are part of anyone's "Hello World" application. Throw a button and a label in an app, click the button, and change the label's text to "Hello World"... So how would we handle a button click?

Button Click:

C#

public MainWindow()
{
   InitializeComponent();
   
   Button button = new Button();
   button.Content = "Click Me";
   button.Click += new RoutedEventHandler(ClickMe);
   this.RootPanel.Children.Add(buton);
}
 
void ClickMe(object sender, RoutedEventArgs e)
{
   
}

Objective-C

- (void)viewDidLoad
{
   [super viewDidLoad];

   UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
   button.frame = CGRectMake(10, 10, 100, 35);
   [button setTitle:@"Click Me" forState:UIControlStateNormal];
   [button addTarget:self action:@selector(clickMe:) forControlEvents:UIControlEventTouchUpInside];
   [self.view addSubview:button];
}

-(void)clickMe:(id)sender
{

}

In C# any property of type "event" has the same basic syntax whether your on a control or just a basic class. If you're adding the event you += with the type of delegate.  And to remove the event you would -=. 

Objective-C is a bit different. First, the language itself doesn't really have a built in eventing model like C#. What you're seeing above, is a built in method to the UIControl class in iOS. Also, there are only some predefined options for control events, such as UIControlEventTouchUpInside that you see used above. In this case, UIButton derives from UIControl, so it gets to participate in this model. 

So what happens when you're working with a class that doesn't derive from UIControl, such as UITableView? Also, what happens if you want to be notified of something that doesn't fall in the predefined UIControlEvents?

For both of those cases, we fall back on to protocols, just like we did with connecting data. Most controls that you will use expose a property called "delegate". Generally each class has their own protocol for that property. For example: UITableView has UITableViewDelegate. UIScrollView has UIScrollViewDelegate. UIPickerView has UIPickerViewDelegate etc... etc...

All methods on a Delegate protocol are always optional. Meaning you as the consumer of the delegate, have the choice of picking and choosing what you want to be notified of. Lets take a look at how you would listen for a row being selected on a UITableView. 

@interface ViewController ()
{
   UITableView* _tableView;
   NSMutableArray* _data;
}

@end

@implementation ViewController

- (void)viewDidLoad
{
   [super viewDidLoad];

   _data = [[NSMutableArray alloc]initWithObjects:@"One", @"Two", @"Three", @"Four", nil];

   _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
   _tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
   [self.view addSubview:_tableView];

   _tableView.dataSource = self;
   _tableView.delegate = self;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
   // Do Something
}

/// ... data source implementation

 

@end

Here we've implemented the UITableViewDelegate on our ViewController. So we'll start by setting the delegate property of the tableView to our "self".  Then we'll choose to only implement one method from this particular delegate: "tableView:didSelectRowAtIndexPath:" And thats it. 

When you look at it from a bigger picture, although the syntax is quite different, the results are actually quite similar between the 2 languages. Essentially, all you're doing is notifying a class of a method that should be invoked when something happens.

Of course there are pros and cons to this approach compared to the .NET version. For example, if you had more than one tableView in a single viewController and you wanted to implement the delegate for both, you'd have 2 choices. First, you could implement the delegate on your ViewController like we diid above. But in that case you'd have to check to see which tableView the delegate method was being invoked for using the tableView parameter. The other option is to implement the delegate on 2 separate classes. However on the upside, if you have common eventing logic that you want to share in your application you can implement the delegate once on a separate class and reuse it wherever you need it. 

Its also worth noting, that not only does this delegate model provide your with information about the control, in some cases it can also ask you for information, such as in controlling how a control will look. 

For example, the UITableViewDelegate has quite a few methods that ask for information ranging from the height of a particular row, to the view that should be displayed for a section header or footer:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
   if(indexPath.row %2 == 0)
      return 50;
   else
      return 100;
}

In the code above, we're simply telling the UITableView to alternate the height of every other row. 

And that about does it for this post. As always I hope this was helpful!

By Stephen Zaharuk (SteveZ)