Mobile C++ Tutorials

Todo App Using Djinni and SQLite

Todo App Using Djinni and SQLite Part 2, iOS

Updated: September 19, 2017
Photo Credit: Patrick Metzdorf

In this tutorial we will utilize our C++ back-end code in an Objective-C UI, and finally publish to an iOS device or simulator.

Create a New Xcode Project

Create a new workspace with a child project, much like we did in the Hello World tutorial. Again choose the ‘Single View’ application.

Your project directory structure should now look like the following:

Todo iOS Folder Structure

When we ran the make sqlite command in the previous tutorial, all of the libraries we need should have been built. If for some reason one of the libraries below is missing, you can now generate them by running the make ios command now that we have a workspace set up:

$ make ios

Now in the workspace, and add our library files to the project folder using File > ‘Add Files to “TodoApp”:

/build_ios/libtodoapp.xcodeproj
/build_ios/deps/sqlite3.xcodeproj
/build_ios/deps/djinni/support-lib/support_lib.xcodeproj

Also add the 3 files to the build phases tab under ‘Link Binary With Libraries’:

Todo iOS Add Libraries

Next, add the following in Build Settings under ‘User Header Search Paths’ (might need to select ‘Advanced’ to see this setting):

$(SRCROOT)/../../deps/djinni/support-lib/objc
$(SRCROOT)/../../generated-src/objc
$(SRCROOT)/../../deps/sqlite3

This will allow XCode to find the header files of the classes we are referencing in our C++ libraries.

Finally, change the filename of TodoApp/Supporting Files/main.m in the project browser to ‘main.mm’ so our app will be able to utilize the Objective-C++ bridge code generated by Djinni.

Go ahead and publish the app to make sure everything is set up correctly before we begin building our UI. You should see a white screen with ‘Hello World’ and no dreaded Mach-o-Linker errors.

Build the iOS UI

We are going to make a super simple interface for our database logic, which will include a UITableView, a UIButton at the top of the screen to add a new Todo, and a custom UITableViewCell to add a ‘delete’ button to each table row.

First, let’s create the custom UITableViewCell, since it will be used by the ViewController. Create a new Cocoa Touch class in the TodoApp folder along with the ViewController, and name it ‘TableViewCell’ (Right-click on the TodoApp folder where the ViewController files are, and select ‘New File…’, then select ‘IOs > Source > Cocoa Touch Class’.

Name the class and select UITableViewCell as the parent class:

Todo iOS UITableCell

Now replace the contents of the new files with the following:

TableViewCell.h:

#import <UIKit/UIKit.h>

@interface TableViewCell : UITableViewCell

@property (nonatomic, strong) UILabel *label;
@property (nonatomic, strong) UIButton *button;

@end

TableViewCell.m:

#import "TableViewCell.h"

@implementation TableViewCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        
        // add a label
        self.label = [[UILabel alloc] initWithFrame:CGRectMake(5, 10, 300, 30)];
        self.label.textColor = [UIColor blackColor];
        self.label.font = [UIFont fontWithName:@"Arial" size:12.0f];
        [self addSubview:self.label];
        
        // add the delete button
        self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        //set the position of the button
        self.button.frame = CGRectMake(250, 5, 100, 30);
        [self.button setTitle:@"Delete" forState:UIControlStateNormal];
        self.button.backgroundColor= [UIColor clearColor];
        [self addSubview:self.button];

    }
    return self;
}

- (void)awakeFromNib {
    // Initialization code
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}

@end

Add the following to our ViewController.h and ViewController.m files to implement our UI and database logic:

ViewController.h:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate>  {

}

@end

ViewController.m:

#import "ViewController.h"
#import "TDATodoList.h"
#import "TableViewCell.h"

@interface ViewController ()

@property NSMutableArray *objects;
@end

@implementation ViewController {
    TDATodoList *_todoInterface;
    NSMutableArray *_todos;
    int _newTodoCount;
    UITableView *_tableView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *addButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [addButton addTarget:self
               action:@selector(insertNewObject:)
     forControlEvents:UIControlEventTouchUpInside];
    [addButton setTitle:@"Add Todo" forState:UIControlStateNormal];
    addButton.frame = CGRectMake(20.0, 20.0, 160.0, 40.0);
    [self.view addSubview:addButton];
    
    // instantiate our table view
    CGRect fr = CGRectMake(0, 65, 320, 415);
    _tableView = [[UITableView alloc] initWithFrame:fr style:UITableViewStylePlain];
    _tableView.dataSource = self;
    _tableView.delegate = self;
    [self.view addSubview:_tableView];
    
    // instantiate our Todo Library Interface with correct path
    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    _todoInterface = [TDATodoList createWithPath: documentsPath];
    
    // populate the array with our data
    NSArray *todos = [_todoInterface getTodos];
    NSLog(@"%@", todos);
    _todos = [[NSMutableArray alloc] initWithArray:todos];
    _newTodoCount = 1;
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)insertNewObject:(id)sender {
    if (!_todos) {
        _todos = [[NSMutableArray alloc] initWithArray:[_todoInterface getTodos]];
    }
    
    NSString *newTodoLabel = [NSString stringWithFormat:@"New Todo %d", _newTodoCount];
    [_todoInterface addTodo:newTodoLabel];
    _todos = [[NSMutableArray alloc] initWithArray:[_todoInterface getTodos]];
    [_tableView reloadData];
    _newTodoCount++;
}

#pragma mark - Table View

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _todos.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    if (!cell) {
        cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: @"Cell"];
    }
    
    TDATodo *todo = _todos[indexPath.row];
    
    NSString *todoLabel = [todo label];
    
    NSString *completedString = @"  ";
    int completed = [todo completed];
    if (completed == 1) {
        completedString = @"X";
    }
    
    NSString *todoText = [NSString stringWithFormat:@"%@   %@", completedString, todoLabel];
    cell.label.text = todoText;
    
    [cell.button addTarget:self action:@selector(deleteButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
    cell.button.tag = [todo id];
    
    return cell;
    
}

- (void)deleteButtonPressed:(UIButton*)button {
    
    [_todoInterface deleteTodo:(int)button.tag];
    
    // get new data from database and update the table view
    _todos = [[NSMutableArray alloc] initWithArray:[_todoInterface getTodos]];
    [_tableView reloadData];

    
}

- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath {
    // toggle the todo in the database.
    TDATodo *todo = _todos[indexPath.row];
    int dbRow = todo.id;
    if ([todo completed] == 1) {
        [_todoInterface updateTodoCompleted:dbRow completed:0];
    } else {
        [_todoInterface updateTodoCompleted:dbRow completed:1];
    }
    // get new data from database and update the table view
    _todos = [[NSMutableArray alloc] initWithArray:[_todoInterface getTodos]];
    [_tableView reloadData];
    
}

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    // Return NO if you do not want the specified item to be editable.
    return YES;
}


- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        TDATodo *todo = _todos[indexPath.row];
        int dbRow = todo.id;
        NSLog(@"delete %d", dbRow);
        [_todoInterface deleteTodo:dbRow];
        _todos = [[NSMutableArray alloc] initWithArray:[_todoInterface getTodos]];
        [_tableView reloadData];
    } else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
    }
}


@end

Publish to the Simulator or a Device

Now publish to the simulator or device, and you should get a working Todo App interface.

Todo iOS Simulator

Next, check out the Android tutorial where we go through the same process for an Android device/simulator.

Todo App Using Djinni and SQLite Part 1, C++
Todo App Using Djinni and SQLite Part 3, Android