Tuesday, June 25, 2013

Create, Rename, Delete, Read and Write File on iOS

On iOS Programming, one of the most frequent tasks is Manipulation with Files. Using Local Files is one of the most efficient way to store your local data, other ways may include NSUserDefault, Core Data, but local files is very easy to be implemented and organized. So in this tutorial, we will focus on 5 main manipulation related to Local File on iOS.

Before we get started, we first should know what files have been stored in your app's sandbox. As you might know, every app on iPhone is like an island, usually we name it SandBox. It contains AppName.app, Library folder, tmp folder and Document folder. User usually have their data stored in Document folder if they choose to use local file to store their data because other folders are not designed to store user's data. So here we use Document Folder to give our test.

So, let's list all the files exists in Document Folder in our Sandbox.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
- (void)listAllLocalFiles
{
    // Fetch directory path of document for local application.
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    
    // NSFileManager is the manager organize all the files on device.
    NSFileManager *manager = [NSFileManager defaultManager];
    // This function will return all of the files' Name as an array of NSString.
    NSArray *files = [manager contentsOfDirectoryAtPath:documentsDirectory error:nil];
    // Log the Path of document directory.
    NSLog(@"Directory: %@", documentsDirectory);
    // For each file, log the name of it.
    for (NSString *file in files) {
        NSLog(@"File at: %@", file);
    }
}

1, Create a File in the Document Folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
- (void)createFileWithName:(NSString *)fileName
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];

    NSFileManager *manager = [NSFileManager defaultManager];
    // 1st, This funcion could allow you to create a file with initial contents.
    // 2nd, You could specify the attributes of values for the owner, group, and permissions.
    // Here we use nil, which means we use default values for these attibutes.
    // 3rd, it will return YES if NSFileManager create it successfully or it exists already.
    if ([manager createFileAtPath:filePath contents:nil attributes:nil]) {
        NSLog(@"Created the File Successfully.");
    } else {
        NSLog(@"Failed to Create the File");
    }
}

2, Delete a File in the Document Folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)deleteFileWithName:(NSString *)fileName
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    // Have the absolute path of file named fileName by joining the document path with fileName, separated by path separator.
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];

    NSFileManager *manager = [NSFileManager defaultManager];
    // Need to check if the to be deleted file exists.
    if ([manager fileExistsAtPath:filePath]) {
        NSError *error = nil;
        // This function also returnsYES if the item was removed successfully or if path was nil.
        // Returns NO if an error occurred.
        [manager removeItemAtPath:filePath error:&error];
        if (error) {
            NSLog(@"There is an Error: %@", error);
        }
    } else {
        NSLog(@"File %@ doesn't exists", fileName);
    }
}

3, Rename a File in the Document Folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
- (void)renameFileWithName:(NSString *)srcName toName:(NSString *)dstName
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *filePathSrc = [documentsDirectory stringByAppendingPathComponent:srcName];
    NSString *filePathDst = [documentsDirectory stringByAppendingPathComponent:dstName];
    NSFileManager *manager = [NSFileManager defaultManager];
    if ([manager fileExistsAtPath:filePathSrc]) {
        NSError *error = nil;
        [manager moveItemAtPath:filePathSrc toPath:filePathDst error:&error];
        if (error) {
            NSLog(@"There is an Error: %@", error);
        }
    } else {
        NSLog(@"File %@ doesn't exists", srcName);
    }
}

4, Read a File in the Document Folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* This function read content from the file named fileName.
 */
- (void)readFileWithName:(NSString *)fileName
{
    // Fetch directory path of document for local application.
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    // Have the absolute path of file named fileName by joining the document path with fileName, separated by path separator.
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];

    // NSFileManager is the manager organize all the files on device.
    NSFileManager *manager = [NSFileManager defaultManager];
    if ([manager fileExistsAtPath:filePath]) {
        // Start to Read.
        NSError *error = nil;
        NSString *content = [NSString stringWithContentsOfFile:filePath encoding:NSStringEncodingConversionAllowLossy error:&error];
        NSLog(@"File Content: %@", content);
        
        if (error) {
            NSLog(@"There is an Error: %@", error);
        }
    } else {
        NSLog(@"File %@ doesn't exists", fileName);
    }
}

5, Write a File in the Document Folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* This function Write "content" to the file named fileName.
 */
- (void)writeString:(NSString *)content toFile:(NSString *)fileName
{
    // Fetch directory path of document for local application.
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    // Have the absolute path of file named fileName by joining the document path with fileName, separated by path separator.
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
    
    // NSFileManager is the manager organize all the files on device.
    NSFileManager *manager = [NSFileManager defaultManager];
    // Check if the file named fileName exists.
    if ([manager fileExistsAtPath:filePath]) {
        NSError *error = nil;
        // Since [writeToFile: atomically: encoding: error:] will overwrite all the existing contents in the file, you could keep the content temperatorily, then append content to it, and assign it back to content.
        // To use it, simply uncomment it.
//        NSString *tmp = [[NSString alloc] initWithContentsOfFile:fileName usedEncoding:NSStringEncodingConversionAllowLossy error:nil];
//        if (tmp) {
//            content = [tmp stringByAppendingString:content];
//        }
        // Write NSString content to the file.
        [content writeToFile:filePath atomically:YES encoding:NSStringEncodingConversionAllowLossy error:&error];
        // If error happens, log it.
        if (error) {
            NSLog(@"There is an Error: %@", error);
        }
    } else {
        // If the file doesn't exists, log it.
        NSLog(@"File %@ doesn't exists", fileName);
    }
    
    // This function could also be written without NSFileManager checking on the existence of file,
    // since the system will atomatically create it for you if it doesn't exist.
}

Also, similar API applies to URL and Directory. Check Apple's Doc for more details.

Tuesday, June 4, 2013

How to Implement iAd in your App

Apple is really trying so hard to help iOS developers make more profit through many ways. From the web page(you need an Apple Developer account to view this page, the account is free unless you need to enroll the membership),  we can see "In-App Purchase", "Earn with Affiliate Program" and "iAd Program".

iAd was introduced to developers in WWDC 2011. iAd is basically a program for iOS developer to display the obtained ads from Apple iAd Network on their iOS app, so either the users see the ads or click on the ads would help developer generate profit. The developer will get 60% (now 70%) of the total revenue.

I am here going to demonstrate how to implement iAd in iOS6+ app with Storyboard. If you are more comfortable with XiB, the iOS engineer David Duncan showed a very easy but pretty acquainted demo during the introduction of iAd in a very traditional way. The video is available from iTune U WWDC 2011 Session Videos. It's free for any registered Apple developers.

Start...
1, Create a "Single View Application" with the product name and app identifier you are comfortable with.  (If you don't know how to implement this step, you'd better get familiar with that first.)
2. Once the project is created, go to storyboard,

  • Drag and drop a UIView (call it contentView) onto the existing UIView, which is totally covered by the new UIView.
  • Drag and drop a UITextView onto the the added UIView with pre filled text.

3, Ctrl + Drag the contentView to your ViewController Implementation File. Then, you will see,
The outlets is perfectly connected. Also, there is another property called bannerView, which is the container for the ads.

4.  From the Apple Guideline, our program needs to show the AdBannerView as well as hide it. To make this happen and make the code pretty, we have:

- (void)layoutAnimated:(BOOL)animated
{
    CGRect contentFrame = self.view.bounds;
    
    CGRect bannerFrame = self.bannerView.frame;
    if (self.bannerView.bannerLoaded) {
        contentFrame.size.height -= self.bannerView.frame.size.height;
        bannerFrame.origin.y = contentFrame.size.height;
    } else {
        bannerFrame.origin.y = contentFrame.size.height;
    }
    
    [UIView animateWithDuration:animated ? 0.25 : 0.0 animations:^{
        self.contentView.frame = contentFrame;
        [self.contentView layoutIfNeeded];
        self.bannerView.frame = bannerFrame;
    }];
}

In this section of code, the height of contentView is changed when the bannerView shows or not based on very simple calculation here. The code is short and clean, also very easy to understand. The "origin" of a CGRect is the start point where the rectangle starts. On your phone, the default UIView's origin is always the top-left corner, which is (0, 0).

5. So we have this pretty function, then where do we use it?
    The answer is easy, take a look at the following code:



//
//  ViewController.m
//  iAdTeset
//
//  Created by Antonio08104 on 6/4/13.
//  Copyright (c) 2013 286s. All rights reserved.
//

#import "ViewController.h"
#import <iAd/iAd.h>

@interface ViewController () <ADBannerViewDelegate>
@property (strong, nonatomic) ADBannerView *bannerView;
@property (strong, nonatomic) IBOutlet UIView *contentView;
@end

@implementation ViewController

- (ADBannerView *)bannerView
{
    if (!_bannerView) {
        _bannerView = [[ADBannerView alloc] init];
    }
    return _bannerView;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.view addSubview:self.bannerView];
    self.bannerView.delegate = self;
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self layoutAnimated:NO];
}

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

- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
    [self layoutAnimated:YES];
}

- (void)bannerViewWillLoadAd:(ADBannerView *)banner
{
}

- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
    [self layoutAnimated:YES];
}

- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
{
    return YES;
}

- (void)bannerViewActionDidFinish:(ADBannerView *)banner
{
    NSLog(@"banner finished.");
}

- (void)layoutAnimated:(BOOL)animated
{
    CGRect contentFrame = self.view.bounds;
    
    CGRect bannerFrame = self.bannerView.frame;
    if (self.bannerView.bannerLoaded) {
        contentFrame.size.height -= self.bannerView.frame.size.height;
        bannerFrame.origin.y = contentFrame.size.height;
    } else {
        bannerFrame.origin.y = contentFrame.size.height;
    }
    
    [UIView animateWithDuration:animated ? 0.25 : 0.0 animations:^{
        self.contentView.frame = contentFrame;
        [self.contentView layoutIfNeeded];
        self.bannerView.frame = bannerFrame;
    }];
}

@end

you'll see when:

  1. When the View first appeared, the program could started to display the bannerView. But here you could not use any animation, since there is no View displayed yet, you can't just have animation from nothing.
  2. Once the device received the ad from Apple iAd Network Server, this function will be called to display it.
  3. When the iAd network failed to send you an ad to display, then your program needs the function to hide the bannerView. So there is no big white space there displayed, which is kind of ugly or not user-friendly designed.
  4. You may think there is also a good place to use the function when user rotate the device. This is also very important, but I didn't implement it here.
6. In the code, you might find There is an "AdBannerViewDelegate". This is delegate we could communicate with the bannerView. Now, let's take a look at each delegate function whatever I used or not.

  • - (void)bannerViewWillLoadAd:(ADBannerView *)banner,  This function is called before the banner ad loaded.

  • - (void)bannerViewDidLoadAd:(ADBannerView *)banner,  This function is called when the new banner ad is loaded. At this point, it's the last chance for you to save data, pause whatever process that might get lost of your control before user could click on the ads. Many of other tutorial uses a timer to demo this.

  • - (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave,  This function is called before the banner view executes an action, like user click on the banner. Here you could decide if user could be taken away from your app or not. The Apple Guideline suggests you keep this return YES, that's where the profit you could make. For me, I think good to follow it, since the Ads never leave your app, once it's finished, it will take the user back to your app.

  • - (void)bannerViewActionDidFinish:(ADBannerView *)banner,  This function is called when the banner view finished its executed action, the user has been taken back to your application. This is a good place to reopen the staff you closed before, also it's good to resume the process you paused before.


  • - (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error,   This function is called when there is error occurred. Errors could be failed to fetch ads from server, or even your program tried too frequently to fetch the ads from server. But if there is any error happened here, that means you could not display the ad appropriately. So  your program needs to hide the unnecessary space for the others to use.
Basically, that's all about the iAd, AdbannerView, AdbannerViewDelegate.
For Source Code, Follow the Link.