Documentation : Files - iOS

This example shows how to share binary data between multiple users and devices.

Application consists of only one model, which has one file field. Main screen of application contains list of model entities and two buttons. User can add new entity and synchronize with mobeelizer cloud. There are two users in the system and there's a possibility to switch user on application life time.

Design Your App

To complete this example we need to create new application in Mobeelizer App Designer called Files.

Next we will create model. We have to go to Models section, create new one and call it FileSyncEntity.

Last thing to do to finish this step is creating new field with file type called photo. Note that you shoud use default model credentials (CRUD operations avaliable for everyone). 

For this example we leave conflict resolving setting in overwrite state. 
Next section to configure is 'Groups & Roles', by default there is one group called 'users' and one device category 'mobile' and this two together create role 'users-mobile'. This default configuration is perfect for our example and you don't have to change it. 
When everything is done in Create mode, deploy our application to test environment, and create two users with passwords:

  • a - usera
  • b - userb

Last thing to do in App Designer is download configured template for iOS Change class prefix to SF and model prefix to MSF.

Use the Mobeelizer SDK

Open project in XCode. We can see that several files has been generated and configured.

Configure AppDelegate

First of all, there is app delegate with configured creation and destruction of Mobeelizer object. Generated application does not have any view. We will add storyboard: SFStoryboad to our application and set newly created storyboard as Main Storyboard in project settings. Furthermore, we have to remove setting window from application: didFinishLaunchingWithOptions: in SFAppDelegate

SFAppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Initialize Mobeelizer SDK
    [Mobeelizer create];

    return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application
{	
    // Terminate Mobeelizer SDK
    [Mobeelizer destroy];
}

SFAppDelegate.m

Configure Mobeelizer entity

Both application.xml and Mobeelizer.plist were generated. Moreover, model has been generated for FileSyncEntity. We will add guid and owner fields together with custom costructor. We will also overwrite isEqual function to compare guid.

MSFFileSyncEntity.h
@property(strong, nonatomic) MobeelizerFile* photo;
@property(strong, nonatomic) NSString* guid;
@property(strong, nonatomic) NSString* owner;

- (MSFFileSyncEntity*) initWithFile:(MobeelizerFile*)photo;
MSFFileSyncEntity.m
- (MSFFileSyncEntity*) initWithFile:(MobeelizerFile*)photo {
    self.photo = photo;
    return self;
}

- (BOOL)isEqual:(id)object {
    if ([object isKindOfClass:[MSFFileSyncEntity class]]) {
        MSFFileSyncEntity* entity = object;
        if ([entity.photo.guid isEqualToString:self.photo.guid]) {
            return YES;
        }
    }
    return NO;
}

MSFFileSyncEntity.h MSFFileSyncEntity.m

Create user login and logout manager

Next, we will handle user functionality. We will create singleton class SFUserManager, which will hold information about currently logged user. There are two use cases to handle user functionality. Firstly, when application starts user a will be logged in. Secondly, during runtime, users can be switched.

SFUserManager.h
#define USER_A @"a"
#define USER_B @"b"
 
@property (copy, nonatomic) NSString* user;

+ (SFUserManager*)instance;
- (BOOL) performLoginAsUser:(NSString *)login;
- (void) switchUser;
SFUserManager.m
- (BOOL) performLoginAsUser:(NSString *)login{
    self.user = login;
    NSString* password = nil;
    if ([login isEqualToString:USER_A]) {
        password = @"usera";
    } else {
        password = @"userb";
    }

	//Login to Mobeelizer instance
    MobeelizerOperationError *error = [Mobeelizer loginToInstance:@"test" withUser:login andPassword:password];

    if(error != nil) {
        NSLog(@"Joining failure: %@ - %@", error.code, error.message);
    }

    return error == nil;
}

-(void) switchUser{
    if ([self.user isEqualToString:USER_A]) {
        self.user = USER_B;
    } else {
        self.user = USER_A;
    }

    [self performLogout];
    [self performLoginAsUser:self.user];
}

- (void) performLogout {
	//Logout from Mobeelizer
    [Mobeelizer logout];
}

SFUserManager.h SFUserManager.m

Next, we will move to user interface. Add a new file to project. Choose the UITableViewController subclass template and name the class SFUserContextController. We will add button to navigation bar, which will handle switching users. Controller will hold reference to button, because we will change appearance of button to show currently logged user.

SFUserContextController
#define RGB(r, g, b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1]
#define USER_A_COLOR RGB(255, 128, 0)
#define USER_B_COLOR RGB(0, 128, 0)

@property (strong, nonatomic) UIBarButtonItem *userButton;

We add button to navigation bar and log user a on viewDidLoad:

SFUserContextController.m
- (void)viewDidLoad
{
    [super viewDidLoad];
    [[self navigationController] setNavigationBarHidden:NO animated:NO];
    userButton = [[UIBarButtonItem alloc] initWithTitle:@"User" style:UIBarButtonItemStyleBordered target:self action:@selector(userButtonClicked:)];
    self.navigationItem.rightBarButtonItem = userButton;

    [[SFUserManager instance] performLoginAsUser:USER_A];
}

Now add function to handle button click:

SFUserContextController.m
- (IBAction)userButtonClicked:(id)sender {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [[SFUserManager instance] switchUser];

        dispatch_async(dispatch_get_main_queue(), ^{
            [self refreshUserButton];
            [UIView beginAnimations:@"View Flip" context:nil];
            [UIView setAnimationDuration:0.75];
            [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
            [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
            [self reloadData];
            [UIView commitAnimations];
        });
    });
}

- (void)refreshUserButton {
    if ([[SFUserManager instance].user isEqualToString:USER_B]) {
        [userButton setTitle:@"B"];
        [userButton setTintColor:USER_B_COLOR];
    } else {
        [userButton setTitle:@"A"];
        [userButton setTintColor:USER_A_COLOR];
    }
}

SFUserContextController.h SFUserContextController.m

Create entity table view controller

Now, we will create view. Add navigation controller with table view controller to storyboard. Set prototype cell identifier to FileSyncCell and set style to custom. Add Image View to prototype cell and in it's Attribute Inspector change tag to number 1. Next, add a new file to the project. Choose the SFUserContextController subclass template and name the class SFFileController. Connect newly created controller to its design in storyboard. SFFileController will hold an array of table records. Moreover, we will mark added records with user image.

SFFileController.h
@property(strong, nonatomic) NSMutableArray *currentItems;
@property(strong, nonatomic) UIImage *userAImage;
@property(strong, nonatomic) UIImage *userBImage;

First of all, we will add two buttons to toolbar. First one will add items and another will synchronize with Mobeelizer cloud.

SFFileController.m
- (void)viewDidLoad{
    [super viewDidLoad];
    [self addToolbarButtons];

    self.currentItems = [[NSMutableArray alloc] initWithArray:[self getItemsList]];
    userAImage = [UIImage imageNamed:@"userA.png"];
    userBImage = [UIImage imageNamed:@"userB.png"];
}
 
-(void)addToolbarButtons{
    UIBarButtonItem* newButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(newClicked:)];
    [newButton setStyle:UIBarButtonItemStyleBordered];
    UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    [space setWidth:10];
    UIBarButtonItem* syncButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(syncClicked:)];

    [syncButton setStyle:UIBarButtonItemStyleBordered];
    self.toolbarItems = @[newButton, space, syncButton];
    self.navigationController.toolbarHidden = NO;
}

Current items array is intialized with MSFFileSyncEntities from Mobeelizer database.

SFFileController.m
- (NSArray*)getItemsList {
	//Get MSFFileSyncEntity from Mobeelizer database
    return [[Mobeelizer database] list:[MSFFileSyncEntity class]];
}

We will be synchronizing photos. When we want to add new item, we will take a photo. In our example we will use UIImagePickerController. First of all, we have to adopt two interfaces: UIImagePickerControllerDelegate and UINavigationControllerDelegate in SFFileController. Then we should create action for ADD button.

SFFileController.m
-(void)newClicked:(id)sender {
    UIImagePickerController *imagePickController=[[UIImagePickerController alloc]init];
    imagePickController.sourceType=UIImagePickerControllerSourceTypeCamera;
    imagePickController.delegate=self;
    imagePickController.allowsEditing=NO;
    imagePickController.showsCameraControls=YES;
    imagePickController.mediaTypes = @[(NSString *) kUTTypeImage];

    [self presentModalViewController:imagePickController animated:YES];
}

We set delegate of UIImagePickerController to self = SFFileController. That is why we have to implement two methods: imagePickerController:didFinishPickingMediaWithInfo: and imagePickerControllerDidCancel:. After we took picture we save new file in Mobeelizer database and insert new entry into table.

SFFileController.m
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    UIImage *chosenImage = info[UIImagePickerControllerOriginalImage];
    NSData *imageData = UIImageJPEGRepresentation(chosenImage, 0.5);
	//Create Mobeelizer file
    MobeelizerFile* file = [Mobeelizer createFile:@"file" withData:imageData];
    [self addFile:file];
    [picker dismissModalViewControllerAnimated:YES];
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    [picker dismissModalViewControllerAnimated:YES];
}

- (void) addFile:(MobeelizerFile*) file {
    MSFFileSyncEntity* entity = [[MSFFileSyncEntity alloc] initWithFile:file];
	//Save entity in Mobeelizer database
    [[Mobeelizer database] save:entity];
    [self insertNewCurrentItems];
}

- (void) insertNewCurrentItems {
    NSMutableDictionary* objects = [[NSMutableDictionary alloc] init];
    for (id item in currentItems) {
        objects[[item valueForKey:@"guid"]] = item;
    }

    currentItems = [[NSMutableArray alloc] initWithArray:[self getItemsList]];
    NSMutableArray* indexesToInsert = [[NSMutableArray alloc] init];
    for (NSInteger i=0; i<currentItems.count; i++) {
        id newItem = currentItems[i];
        id oldItem = objects[[newItem valueForKey:@"guid"]];
        if (oldItem == nil) {
            [indexesToInsert addObject:[NSIndexPath indexPathForRow:i inSection:0]];
        }
	}

    [self.tableView insertRowsAtIndexPaths:indexesToInsert withRowAnimation:UITableViewRowAnimationLeft];
    [self.tableView scrollToRowAtIndexPath:[indexesToInsert lastObject] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}

Second button is responsible for synchronization. After synchronization finishes we have to reload data in table.

SFFileController.m
- (void)syncClicked:(id) sender {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //Synchronize with Mobeelizer cloud
		MobeelizerOperationError *error = [Mobeelizer sync];
        if(error != nil) {
            NSLog(@"Sync failure: %@ - %@", error.code, error.message);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            [self reloadData];
        });
    });
}

Finally, we have to configure our table view controller.

SFFileController.m
- (UITableViewCell*)createCellForItem:(MSFFileSyncEntity*)item atRow:(NSInteger)row {
    static NSString *CellIdentifier = @"FileSyncCell";
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    UIImageView* imageView = (UIImageView*)[cell viewWithTag:1];
    imageView.image = [UIImage imageWithData:item.photo.data];
    return cell;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MSFFileSyncEntity* item = currentItems[[indexPath row]];
    UITableViewCell* cell = [self createCellForItem:item atRow:indexPath.row];
    if ([item respondsToSelector:@selector(owner)]) {
        UIImageView* userLabel = (UIImageView*)[cell.contentView viewWithTag:1001];
        CGFloat userLabelY = (cell.contentView.frame.size.height - 30) /2; // 7
        CGFloat userLabelX = (cell.contentView.frame.size.width - 37); // 283
        if (userLabel == nil) {
            CGRect userLabelRect = CGRectMake(userLabelX, userLabelY, 30, 30);
            userLabel = [[UIImageView alloc] initWithFrame:userLabelRect];
            [userLabel setTag:1001];
            [cell.contentView addSubview:userLabel];
        }
        if ([item.owner isEqual:@"a"]) {
            userLabel.image = userAImage;
        } else {
            userLabel.image = userBImage;
        }
    }
    return cell;
}

-(void)reloadData{
    currentItems = [[NSMutableArray alloc] initWithArray:[self getItemsList]];
    [self.tableView reloadData];
}

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

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

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 66.0;
}

SFFileController.h SFFileController.m

There are two files, which are necessary to complete this sample: userA.pnguserB.png

Conclusion

To conclude, example presents synchronization of files with Mobeelizer. Our model consists of only one field. We can see that photos can be synchronized between multiple users.

Attachments:

Files.png (image/png)
FileSyncEntity.png (image/png)
PhotoField.png (image/png)
AddUser.png (image/png)
FileTemplate.png (image/png)