Question

Im writing an app where someone adds a contact to the app, giving their name, number and photo. Then this information is displayed in a table, with each individual contact on a different cell and when the user presses on the cell it will call the number that was typed in for the contact. I have put in a large button on each of the cells for the user to press. This is the code

PictureListMainTable.m

#import "PictureListMainTable.h"
#import "PictureListDetail.h"
#import "CoreDataHelper.h"
#import "Pictures.h"

@implementation PictureListMainTable

@synthesize managedObjectContext, pictureListData, callButton;

//  When the view reappears, read new data for table
- (void)viewWillAppear:(BOOL)animated
{
    //  Repopulate the array with new table data
    [self readDataForTable];
}

//  Grab data for table - this will be used whenever the list appears or reappears after an add/edit
- (void)readDataForTable
{
    //  Grab the data
    pictureListData = [CoreDataHelper getObjectsForEntity:@"Pictures" withSortKey:@"title" andSortAscending:YES andContext:managedObjectContext];

    //  Force table refresh
    [self.tableView reloadData];
}

#pragma mark - Actions

//  Button to log out of app (dismiss the modal view!)
- (IBAction)logoutButtonPressed:(id)sender
{
    [self dismissModalViewControllerAnimated:YES];
}

#pragma mark - Segue methods

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    //  Get a reference to our detail view
    PictureListDetail *pld = (PictureListDetail *)[segue destinationViewController];

    //  Pass the managed object context to the destination view controller
    pld.managedObjectContext = managedObjectContext;

    //  If we are editing a picture we need to pass some stuff, so check the segue title first
    if ([[segue identifier] isEqualToString:@"EditPicture"])
    {
        //  Get the row we selected to view
        NSInteger selectedIndex = [[self.tableView indexPathForSelectedRow] row];

        //  Pass the picture object from the table that we want to view
        pld.currentPicture = [pictureListData objectAtIndex:selectedIndex];
    }
}

#pragma mark - Table view data source

//  Return the number of sections in the table
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

//  Return the number of rows in the section (the amount of items in our array)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [pictureListData count];
}



//  Create / reuse a table cell and configure it for display
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";





    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

    }


    // Get the core data object we need to use to populate this table cell
    Pictures *currentCell = [pictureListData objectAtIndex:indexPath.row];

    //  Fill in the cell contents
    cell.textLabel.text = [currentCell title];
    cell.detailTextLabel.text = [currentCell desc];

    int number;


    number = [currentCell desc];

        -(IBAction)MakePhoneCall:(id)sender {
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel:",number]];
}






    //  If a picture exists then use it
    if ([currentCell smallPicture])
    {
        cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
        cell.imageView.image = [UIImage imageWithData:[currentCell smallPicture]];
    }
    else{


    }



    return cell;

}

//  Swipe to delete has been used.  Remove the table item
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete)
    {
        //  Get a reference to the table item in our data array
        Pictures *itemToDelete = [self.pictureListData objectAtIndex:indexPath.row];

        //  Delete the item in Core Data
        [self.managedObjectContext deleteObject:itemToDelete];

        //  Remove the item from our array
        [pictureListData removeObjectAtIndex:indexPath.row];

        //  Commit the deletion in core data
        NSError *error;
        if (![self.managedObjectContext save:&error])
            NSLog(@"Failed to delete picture item with error: %@", [error domain]);

        // Delete the row from the data source
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }

}


-(IBAction)MakePhoneCall:(id)sender {
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel:",number]];
}

@end

PictureListMainTable.h

#import <UIKit/UIKit.h>

@interface PictureListMainTable : UITableViewController

@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (strong, nonatomic) NSMutableArray *pictureListData;
@property (nonatomic, retain) IBOutlet UIButton *callButton;




-(IBAction)MakePhoneCall:(id)sender;

- (void)readDataForTable;

@end

Where should I place the IBaction and why isint it working at the moment where it is and how can I make it work?

Was it helpful?

Solution

There are a couple of approaches you could take to achieve this. But firstly, I don't understand what you are doing at the bottom of -tableview:cellForRowAtIndexPath:. It's as if you are trying to define your IBAction method inside this method. You also have it defined at the bottom of the implementation, but in that method the number variable is not in scope.

Anyway, you should subclass the UITableViewCell. In the implementation for the subclass, you should define the IBAction method and hook it up in interface builder, or otherwise.

When the button is tapped, you should hand the number for the selected cell back to the PictureListMainTable view controller, in order for that view controller to process it (i.e. call the number). You can do this in two ways:

1) the delegate method

Create a protocol, defined in the header file for your subclass of UITableViewCell. And make the main view controller conform to this protocol. Set the cell's delegate to the main view controller. In the implementation of the cell subclass, call this delegate method. For example:

the header file for the UITableViewCell subclass "PictureListMainTableCell.h"

@protocol PictureListMainTableCellDelegate;

@interface PictureListMainTableCell : UITableViewCell
@property (nonatomic, copy) NSString *telephoneNumber;
@property (nonatomic, weak) id<PictureListMainTableCellDelegate> delegate;
@end

@protocol PictureListMainTableCellDelegate
-(void)pictureListMainTableCell:(PictureListMainTableCell *)cell wantsToCallNumber:(NSString *)number;
@end

the implementation file "PictureListMainTableCell.m"

#import "PictureListMainTableCell.h"

@implementation PictureListMainTableCell

-(IBAction)MakePhoneCall:(id)sender
{
    //send the delegate the number to call.
    [self.delegate pictureListMainTableCell:self wantsToCallNumber:self.telephoneNumber];
}

@end

Above, in the MakePhoneCall method, we call -pictureListMainTableCell:wantsToCallNumber: on the delegate. In this case, the delegate is your main view controller. We will set this below.

Setting the cell's delegate: In your main view controller file (PictureListMainTable.m), in the -tableView:cellForRowAtIndexPath: method, set the delegate on the cell to self. e.g.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // get the cell...
    PictureListMainTableCell *cell = // dequeue the cell

    // do some other setting up...

    // set the delegate on the cell
    cell.delegate = self;

    // set the telephoneNumber variable on the cell, for example...
    cell.telephoneNumber = [currentCell desc];

    return cell;
}

Now you need to make sure self implements the delegate method. So still in PictureListMainTable.m, you need to define the method as follows:

#pragma mark - PictureListMainTableCellDelegate methods
-(void)pictureListMainTableCell:(PictureListMainTableCell *)cell wantsToCallNumber:(NSString *)number
{
    NSString *urlString = [NSString stringWithFormat:@"tel://%@", number];
    NSLog(@"calling telephone number [%@]", number);
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];
}

You should also specify that the PictureListMainTable class conforms to your new protocol, as well as the UITableViewDataSource protocol. Add a private category on PictureListMainTable as follows (at the top of the implementation file, after the imports, before @implementation):

@interface PictureListMainTable () <UITableViewDataSource, PictureListMainTableCellDelegate>
@end

(this extends the PictureListMainTable interface. It only extends it to specify privately that it conforms to these protocols.)

2) the NSNotification method

While I was typing out the above explanation, I decided it's my preferred way of doing things, so I would recommend doing it like that. There is the option of posting an NSNotification form your cell subclass, and observing for this notification from your main view controller. Just look into NSNotificationCenter, the following methods: –postNotificationName:object:userInfo: (send the number in userInfo dictionary). Listen for it using –addObserver:selector:name:object:.

But like I said, option 1 is better, in my opinion.

Let me know if anything is unclear, good luck :)

EDIT: I really recommend reading this blog post to understand delegation: http://alexefish.com/post/15966868557/understanding-and-creating-delegates-in-objective-c

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top