Monday, December 9, 2013

Android 4.4 KitKat Share Action List Bug & Fix

Ever since I bought my new Nexus 5 from LG, I feel the system is good, the UI design is good, while the battery sucks still.

After using it for a few weeks, I found one thing frustrated me most. Once I opened my Chrome browser on my phone, and shared the content by choosing Google Keep from a Dialog, which is exactly like the other version of Android. I don't know if I had the second chance to choose option from this list, but days later, ever I tried to choose another way to share the content I want to, the System open straightly Google Keep with the content I want to share. The choose and share steps has been directly skipped.

If you ever found this is also your problem. Here Is The SOLUTION:

Go to "System Settings" -> "Apps" - > "Download or All" Tab -> "Reset app preferences".
The first three steps are easy, I think the most of android user should be familiar with those. For the last step, to reset the preference, user need to click on the optional menu option on the Apps UI, then user could choose "Reset app preferences" under "Sort by size". To find this menu option, user could swipe left or right to the "Download" or "All" tab page, then click on the right top button to display this option.

Hope this is helpful, and the bug on the system could be soon fixed.

Tuesday, October 8, 2013

Android Tutorial: Saving Key-Value Sets

Get a Handle to a SharedPreferences


You can create a new shared preference file or access an existing one by calling one of two methods:
  • getSharedPreferences() — Use this if you need multiple shared preference files identified by name, which you specify with the first parameter. You can call this from any Context in your app.
  • getPreferences() — Use this from an Activity if you need to use only one shared preference file for the activity. Because this retrieves a default shared preference file that belongs to the activity, you don't need to supply a name.

These two methods are very close to each other. But certain differences can be found:
  1. One Activity vs Multiple Activity. getPreferences() is used for one activity only, the information stored here will not be available for other activities. While, getSharedPreferences() can be used for different activities. It's really shared information for every activity who knows the name of the shared preference file.
  2. getSharedPreferences() could have information saved separated in multiple files, while  getPreferences() only has information available for the current activity in which it's called, it's an one file.

Tuesday, October 1, 2013

Grey Out UI with Animating Indicator

One of the function which most of code needs is blocking user out of any interaction when the app needs serious being alone for a few seconds. This function could be found in a lot of published iOS Application, especially when App needs to refresh, temporary short time download/upload, server synchronization, etc. At these moments, the developer would not expect user touch anything to break the app down until it's finished. So, here comes this demo to show you how to make a screen blocker with an spinning indicator.


Several Prerequisites needs to be confirmed: 

We have two components:

  • Blocker, color: grey. (Name: overlayView, Class: UIView)
  • Indicator, color: white. (Name: indicatorView, Class: UIActivityIndicatorView)
1st, What Area Needs to Be Blocked.
        What area should block covered. For this demo, we will use the whole screen as an example, which not just it's very simple, also it's very common to be used. 


    CGRect rect = [UIScreen mainScreen].bounds;
    rect.size.height = MAX(rect.size.height, rect.size.width);
    rect.size.width = MAX(rect.size.height, rect.size.width);
    self.overlayView = [[UIView alloc] initWithFrame:rect];

2nd, What is the Transparency this Blocker Will Be
        Using Transparency could help user be aware of the status of theirs: "Being blocked out, while it won't be long". This could make user feel less confusing about what the app is doing, or what's going on with the app, the user will be more patient if appropriate transparency is used. (Some programmer use real-time updated progress information to make user less confusing and more patient) Here, I'll use 50% transparency to cover our block area.

    self.overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];

3rd, Starts the Animation of Indicator.
So, the code looks like:

- (void)startSpinning
{
    if (self.indicatorView) {
        CGRect rect = self.indicatorView.superview.bounds;
        self.indicatorView.center = CGPointMake(rect.size.width / 2, rect.size.height / 2);
        [self.indicatorView startAnimating];
    }
}

- (void)stopSpinning
{
    if (self.indicatorView) {
        [self.indicatorView stopAnimating];
    }
}

- (void)drawView
{
    CGRect rect = [UIScreen mainScreen].bounds;
    rect.size.height = MAX(rect.size.height, rect.size.width);
    rect.size.width = MAX(rect.size.height, rect.size.width);
    self.overlayView = [[UIView alloc] initWithFrame:rect];
    self.overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
    self.indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
    [self.view addSubview:self.overlayView];
    [self.view addSubview:self.indicatorView];
    [self startSpinning];
}

- (void)removeView
{
    [self stopSpinning];
    [self.overlayView removeFromSuperview];
    self.indicatorView = nil;
}

We could start blocking by calling drawView, while stop it by calling removeView.

Here, one minor thing needs your attention, the center position of indicatorView.
Usually people believe they could use self.view.center as their new center position, no matter if screen's orientation is portrait or landscape. But it's a wrong assumption I have to say. My Testing data shows on iPad 6.0 simulator:  
Portrait: self.view Center (384.000 502.000), Bounds (768.000 1004.000)
Landscape: self.view Center (394.000 512.000), Bounds (1024.000 748.000)
Which is not exactly what we presume. So we need to calculate the center of screen every time before starting animation of indicator. This will be very useful when the app allows user switch orientation by rotating the iOS device around. So, every time the user rotate the screen when the indicator is still animating, the code needs to stop animation first, then recalculate the position of new screen center, starting the animation again at last.

Here is the code.
// This function is called when rotation is finished.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    NSLog(@"To (%.3f %.3f)", self.view.center.x, self.view.center.y);
    NSLog(@"To Bounds (%.3f %.3f)", self.view.bounds.size.width, self.view.bounds.size.height);
    [self startSpinning];
}

// This function is called when rotation is not started yet.
-(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { NSLog(@"From (%.3f %.3f)", self.view.center.x, self.view.center.y); NSLog(@"From Bounds (%.3f %.3f)", self.view.bounds.size.width, self.view.bounds.size.height); [self stopSpinning]; }

Summary:

Implementing Block with animating indicator could be:

  1. Init the Block View;
  2. Add Block View to the Should-Be-Covered Area.
  3. Init the Indicator;
  4. Add Indicator to the  Should-Be-Covered Area.
  5. Start Indicator.
  6. Stop Indicator if Task finished.
Key Points:
  • Take the Right Center Position for Indicator from Covered Area.
  • Recalculate the Center Position every time the Area dimension changed.
  • Re-animating the Indicator if it's center position is changed.

The clonable Git rep can be found here.

Monday, September 30, 2013

Android Tutorial: Recreating an Activity

Probably I really should read this tutorial much earlier, while it's never too late to learn. The key knowledge in this tutorial confused me for a while.



  • By default, the system uses the Bundle instance state to save information about each View object in your activity layout (such as the text value entered into an EditText object). So, if your activity instance is destroyed and recreated, the state of the layout is restored to its previous state with no code required by you. However, your activity might have more state information that you'd like to restore, such as member variables that track the user's progress in the activity.
  • To save additional data about the activity state, you must override the onSaveInstanceState() callback method. The system calls this method when the user is leaving your activity and passes it the Bundle object that will be saved in the event that your activity is destroyed unexpectedly. If the system must recreate the activity instance later, it passes the same Bundle object to both the onRestoreInstanceState() and onCreate() methods.

To Save your Activity State, such as the text in EditText, the scroll position of ListView, etc.
  • Call onSaveInstanceState(), in this function, save state information like saving Preference for the App. The super function is call at the end of this override callback function. This function is called precisely right after onPause() being called. I have this confirmed with my customized demo code from tutorial.
To Restore your Activity State, two ways can be used:
  1. In onCreate() function, savedInstanceState has saved state information, but need to check if it's null or not before fetch any information from it.
  2. Call onRestoreInstanceState(), in this function, doesn't need to check the nullity, because this function would not be called unless there exists saved state information already. One thing to remember for using this function, this function is called between onStart() and onResume(). The super function is called at the beginning of this override callback function.

Saturday, September 28, 2013

Android Tutorial: Stopping and Restarting an Activity

When your activity is stopped, the Activity object is kept resident in memory and is recalled when the activity resumes. You don’t need to re-initialize components that were created during any of the callback methods leading up to the Resumed state. The system also keeps track of the current state for each View in the layout, so if the user entered text into an EditText widget, that content is retained so you don't need to save and restore it.

Note: Even if the system destroys your activity while it's stopped, it still retains the state of the View objects (such as text in an EditText) in a Bundle (a blob of key-value pairs) and restores them if the user navigates back to the same instance of the activity (the next lesson talks more about using a Bundle to save other state data in case your activity is destroyed and recreated).

It's uncommon that an app needs to use onRestart() to restore the activity's state, so there aren't any guidelines for this method that apply to the general population of apps. However, because your onStop()method should essentially clean up all your activity's resources, you'll need to re-instantiate them when the activity restarts. Yet, you also need to instantiate them when your activity is created for the first time (when there's no existing instance of the activity). For this reason, you should usually use the onStart() callback method as the counterpart to the onStop() method, because the system calls onStart() both when it creates your activity and when it restarts the activity from the stopped state.

Because the user might have been away from your app for a long time before coming back it, the onStart() method is a good place to verify that required system features are enabled.

The tutorial also mentioned onPause() is a good place  to release BroadcaseReceiver.

Android Tutorial: Pausing and Resuming an Activity

On Google Android's Pausing and Resuming an Activity, things I learned could greatly improve or heal the performance of my current Android application. The most important things I learned are what should be done in onPause() and what should not be done in onPause(). These things will be listed below.

SHOULD Do in onPause():

  • Stop animations or other ongoing actions that could consume CPU.
  • Commit unsaved changes, but only if users expect such changes to be permanently saved when they leave (such as a draft email).
  • Release system resources, such as broadcast receivers, handles to sensors (like GPS), or any resources that may affect battery life while your activity is paused and the user does not need them.
SHOULD NOT Do in onPause():
  • NOT Store user changes (such as personal information entered into a form) to permanent storage, unless the auto-save function is needed to be implemented here.
  • NOT Perform CPU-intensive work, such as writing to a database. (This should be done in onStop()).
For onResume(), as the tutorial said, onResume() really depends on what are stopped in onPause() function, if anything is stopped or released in onPause(), then onResume() is the place to reinitialize and restart it if it's needed.

Friday, September 27, 2013

Android Tutorial: Starting an Activity

To rebuild my solid background of Android Development, I recently went through the whole tutorial provided from Google Android. Then, here comes the first lesson of "The Activity Lifecycle Management", to start an activity.

Though, most of staff on the page is known for me, there are still things I learned today here. Here follows a very important image which explains all the piece of Activity Lifecycle.



1st, onCreate() and onStart() function goes list a swift.

2nd, Most of UI and class member initialization work are done in onCreate() function.

3rd, Implementing your activity lifecycle methods properly ensures your app behaves well in several ways, including that it:
  • Does not crash if the user receives a phone call or switches to another app while using your app.
  • Does not consume valuable system resources when the user is not actively using it.
  • Does not lose the user's progress if they leave your app and return to it at a later time.
  • Does not crash or lose the user's progress when the screen rotates between landscape and portrait orientation.
4th, While stopped, the activity instance and all its state information such as member variables is retained, but it cannot execute any code.

5th, onDestroy(), this function is barely called in the code, unless the app has some background running thread, which might cause memory leak potentially.

6th, Note: The system calls onDestroy() after it has already called onPause() and onStop() in all situations except one: when you call finish() from within the onCreate() method. In some cases, such as when your activity operates as a temporary decision maker to launch another activity, you might call finish() from within onCreate() to destroy the activity. In this case, the system immediately calls onDestroy() without calling any of the other lifecycle methods.

Tuesday, September 3, 2013

Android Tutorial: Start Another Activity

For this section, I know most of the knowledge the author mentioned, but there are still 3 things I can learn.

1, Using Build Class to check the current version of SDK is very helpful.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
2, These is a better implementation of action code when an activity's Action Bar go up button clicked.
case android.R.id.home:
     NavUtils.navigateUpFromSameTask(this);
     return true;
3, In Android Manifest.xml file, developer could easily set the parent activity (android:parentActivityName) of current activity class. The <meta-data> code is used for
android:parentActivityName="com.example.myfirstapp.MainActivity" >
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.example.myfirstapp.MainActivity" />

Monday, September 2, 2013

Android Tutorial: Building a Simple User Interface

Today is the first day of my Android Tutorial Journey, I do believe the more I learn through Android Tutorial, the quicker I will leave my current company.

After Reading through this article, One thing caught my eyes, layout_weight.

I used this attributes a lot in my application, but I have never understood it well like I do today, now.



  1. The default weight for all views is 0.
  2. If you specify any weight value greater than 0 to only one view, then that view fills whatever space remains after all views are given the space they require.
So this could be the easiest way to have a button at the rightmost place of the screen, or have the button at the bottom of the view, while the ListView above it could have as much of space as it could take. In this case, developer do not need a RelativeLayout to implement this purpose.


Friday, August 16, 2013

How to use TextSwitcher with Animation

For a recent project, I need to update display data constantly. However, this could be quite annoying when the update value doesn't change (or vibrant) while my manager asks to reveal the change of that display data, which means I have to make that TextView change with Animation even the Text in TextView doesn't change. I just need to make them looks like changed.

After a few searches on Google, I found two options we can use.

Screenshot of Demo


One is to use ViewFliper, where we could put several child views in that ViewFlipper, then each time the child views will be used in circularly order. One good example is Android ApiDemo's com.example.android.apis.view.Animation2.java.

Another one is to use TextSwitcher. The animation will be performed each time you set the new text to it.

Since I don't need multiple views support for my purpose, I chose the TextSwitcher.

The following part will demonstrate how to use TextSwitcher

Key Parts of using TextSwitcher are "What Animation You Use?", and "How Do You Display It?"

First Problem,  "What Animation You Use?"
This problem can be solved in TWO ways, in Java code or XML specification.
In XML,

1
2
3
4
5
6
7
8
9
<TextSwitcher
        android:id="@+id/main_textswitcher"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:inAnimation="@anim/push_up_in"
        android:outAnimation="@anim/push_up_out" >
</TextSwitcher>

In Java,


1
2
mTextSwitcher.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_in));
mTextSwitcher.setOutAnimation(this, R.anim.push_up_out);

As you can see, you could use setInAnimation(Animation), or setInAnimation(Context, int) to specify the animation for your TextSwitcher.

Second Problem, "How Do You Display It?"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
mTextSwitcher = (TextSwitcher) findViewById(R.id.main_textswitcher);
mTextSwitcher.setFactory(new ViewFactory() {

 public View makeView() {
  TextView myText = new TextView(MainActivity.this);
  myText.setGravity(Gravity.CENTER);

  FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
    LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
    Gravity.CENTER);
  myText.setLayoutParams(params);

  myText.setTextSize(36);
  myText.setTextColor(Color.BLACK);
  return myText;
 }
});
The TextView will display the content you need, and every time new text is set to TextSwitcher, TextView will be updated with Animation.

Full Implementation:
MainActivity.java
 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.antonio08104.example.textswitchersample;

import java.util.Random;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory;

public class MainActivity extends Activity {

 private TextSwitcher mTextSwitcher;
 private Button mButton;
 private Random mRandom;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mRandom = new Random();

  mTextSwitcher = (TextSwitcher) findViewById(R.id.main_textswitcher);
  mTextSwitcher.setFactory(new ViewFactory() {

   public View makeView() {
    TextView myText = new TextView(MainActivity.this);
    myText.setGravity(Gravity.CENTER);

    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
      LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
      Gravity.CENTER);
    myText.setLayoutParams(params);

    myText.setTextSize(36);
    myText.setTextColor(Color.BLACK);
    return myText;
   }
  });
  mTextSwitcher.setInAnimation(AnimationUtils.loadAnimation(this,
    R.anim.push_up_in));
  mTextSwitcher.setOutAnimation(this, R.anim.push_up_out);

  mButton = (Button) findViewById(R.id.main_button);
  mButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    mTextSwitcher.setText(Integer.toString(mRandom.nextInt(10000)));
   }
  });
 }

}

XML:
activity_main.xml

 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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#EEF2F4"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextSwitcher
        android:id="@+id/main_textswitcher"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:inAnimation="@anim/push_up_in"
        android:outAnimation="@anim/push_up_out" >
    </TextSwitcher>

    <Button
        android:id="@+id/main_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/next_random_number" />

</LinearLayout>

Here is the piece of code I took from Google Android for animation:
push_up_in.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
  
          http://www.apache.org/licenses/LICENSE-2.0
  
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromYDelta="100%p" android:toYDelta="0" android:duration="300"/>
 <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" />
</set>
push_up_out.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
  
          http://www.apache.org/licenses/LICENSE-2.0
  
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromYDelta="0" android:toYDelta="-100%p" android:duration="300"/>
 <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="300" />
</set>

Tuesday, July 9, 2013

How to revert a single file to a specific previous commit state using git?

For most of developers, I guess we share the same experience
Mon, PM told you to add X feature to your project. Tue, You worked hard to implement it. Wed, You worked hard and finished it. Thu, PM told you he didn't need it any more, so asked you to remove it, restore to your original code.
I don't you, but this sounds my daily experience, especially by the end of our project deadline.

So, as a git user, I found this command to quickly restore your code within a second.
git checkout [commit-ref] -- [filename]
This will revert your file:filename to your specified one im commit:commit-ref.

Let's make an example:
If my file name is MainStoryboard.storyboard, which is located under: projectname/en.lproj/,
then, I'll use
git checkout [commit-ref] projectname/en.lproj/MainStoryboard.storyboard
The commit-ref is easy to find by yourself, while I here will introduce one trick: using master~2, which assumes that you're on the master branch, and the version you want is 2 commits back. You are free to use other branch and another number of commits back to replace [commit-ref], to clarify my word, I'll make an example:
git checkout master~2 projectname/en.lproj/MainStoryboard.storyboard 

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.





Thursday, May 30, 2013

About NSManagedObjectContext PerformBlock Method.

Recently working on my to be announced project "***", I need to create core data file and import the data into my database for the user when and only when it's the first time to run the app. The whole process of importing data to my database is
1, read data from variety property list.
2, convert the data to the usable data my program can use.
3, store these data in my database via Core Data.
To implement this, I borrowed a piece of code from Stanford's CS193p Online Course Core Data Demo. The key part I borrowed is followed.

Code:

// Whenever the table is about to appear, if we have not yet opened/created or demo document, do so.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    if (!self.managedObjectContext) [self useDemoDocument];
}

- (void)useDemoDocument
{
    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    url = [url URLByAppendingPathComponent:@"Demo Document"];
    UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
    
    if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
        [document saveToURL:url
           forSaveOperation:UIDocumentSaveForCreating
          completionHandler:^(BOOL success) {
              if (success) {
                  self.managedObjectContext = document.managedObjectContext;
                  [self refresh];
              }
          }];
    } else if (document.documentState == UIDocumentStateClosed) {
        [document openWithCompletionHandler:^(BOOL success) {
            if (success) {
                self.managedObjectContext = document.managedObjectContext;
            }
        }];
    }
}

#pragma mark - Refreshing

// Fires off a block on a queue to fetch data from Flickr.
// When the data comes back, it is loaded into Core Data by posting a block to do so on
//   self.managedObjectContext's proper queue (using performBlock:).
// Data is loaded into Core Data by calling photoWithFlickrInfo:inManagedObjectContext: category method.

- (IBAction)refresh
{
    [self.refreshControl beginRefreshing];
    dispatch_queue_t fetchQ = dispatch_queue_create("Flickr Fetch", NULL);
    dispatch_async(fetchQ, ^{
        NSArray *photos = [FlickrFetcher latestGeoreferencedPhotos];
        // put the photos in Core Data
        [self.managedObjectContext performBlock:^{
            for (NSDictionary *photo in photos) {
                [Photo photoWithFlickrInfo:photo inManagedObjectContext:self.managedObjectContext];
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.refreshControl endRefreshing];
            });
        }];
    });
}

This code shows, every time the view is opened, NSManagedObjectContext State gets examined. If it's not assigned (is nil), then we go deep to see if we can do anything includes: "Create", "Open"... maybe more if we have more "else if" statement. Mostly the code did in "useDemoDocument" is assign one valid value to NSManagedObjectContext object, except it will initiate the database through refresh function if the database is not existed.

In refresh function. (KEY)

It starts the refreshControl first, refreshControl is added since iOS6.0, only available for UITableViewController. Developer could enable it through Xcode Inspection for the UITableViewController. (BTW, you can add action to it by just drag and drop through Xcode as many developers do for segue things, you can only make it functional through code.)

Then, it creates a queue, in which we could do some tasks that would not block our UIView. This is used when you have some time consuming task needs to be done without much attention from the user, like download files, core data operation (insert, delete, update) and post content to internet without user's notice.

Then, assign tasks to this queue with dispatch_async. In the queue, the code fetches data from the internet first. When it's done, we call
performBlock:
- (void)performBlock:(void (^)())blockParametersblock
The block to perform.
Discussion
Availability
  • Available in iOS 5.0 and later.
Declared InNSManagedObjectContext.h

This function was added with his brother performBlockAndWait: coming with iOS5.0. The best part of this is the developer knows who is associated with making the most operations. Because NSManagedObjectContext is the handler here to insert all of the data into our database, no one can ask it to do any conflict job until it's all done with the job I am asking it to be doing it now. Which means this NSManagedObjectContext is blocked until this task is done. Otherwise, your data can't be successfully added to your database here. The data will be lost every time.

At last, the function asks the main queue (which is also queue responsible for main UI related operations) to dismiss the refresh control it started at the start this function is called.

So for this function, three key parts.
1st, use dispatch_queue to do any time consuming task.
2nd, use performBlock to import the large data at the background for NSManagedObjectContext, or conflict will be showed up.
3rd, any NSManagedObjectContext related operations must be placed inside performBlock's block. Otherwise, it's equally like not using performBlock. So I can't make a loop out to call performBlock for every property list, since one will block the other when it's called. That's what's wrong I initially did, after the first time I opened the app, it seems app could be saved and loaded in my UITableView, the second time I opened my app, nothing shows up.

More detail, please check this.