Friday, December 30, 2011

Hello World - Spring 3.0 STS

This is to lay out the very bare details needed to create a "Hello Word" application using Spring 3.0 STS.

I actually had quite a bit of difficulty locating a tutorial on how to do it. Unfortunately, the flagship screencast at the site shows the source code for a project that exercises various aspects of - but doesn't show how to set up your own project. This leaves you struggling a bit for tutorials that tell you how to actually work the controls of this monster.

Almost by accident, I stumbled across a couple of tutorials which were enough to get me started. The first one, which I've unfortunately lost, basically showed how to run the server and what the default source code looked like. Unfortunately, it didn't work. But at least it got me started on the right track.

A better tutorial was this:

http://blog.springsource.org/2011/01/04/green-beans-getting-started-with-spring-mvc/

I *almost* works out of the box - you just need to do a *little* bit of debugging.

Anyway, without further ado, here are the steps to follow:

1. From Eclipse STS, type file > new Spring Template Project (then choose) SpringMVCProject

2. If you follow the tutorial, you'll give the project a name like "baremvc" and a package name of "xyz.sample.baremvc".

NOTE: Watch out for caps on the project name - it appears to be the url name to call, and is case-sensitive. I need to test this.

3. Because the blog is about a year old, there are slight variance in code he suggests and the code that is generated. In fact, you can leave the code as generated for home.jsp and HomeController.java.

If you do use the code for "HomeController" as provided, make sure to make the return line "return "home" and not

return "WEB-INF/views/home.jsp";

4. Now, run it on the server. Right click on the project and select "run on server".

5. Enter http://localhost:8080/baremvc

You *should* be ok. One thing to watch out for is if you set up multiple project - I think only one of them can have a controller set up with a "/". The rest of them must need something extra. I'll check that.

Thursday, December 29, 2011

Spring - the Container pattern

I've started reading "Spring Recipes" by Gary Mak. It covers up to 2.5, and we're up to 3. But I've read up to chapter 5 and I still think it's worth reading, because it conceptually explains the concept of spring, as well as providing a great overview of its functionality and lots of working code.

The very first pattern the book discusses is the "Container" pattern. Essentially, instead of instantiating a concrete class into an interface, which introduces a dependency on the concrete class and kinds of defeats the purpose of the interfaces, you instead create a Container which instantiates the concrete class and makes it available via a "get" method which specifies an id.

In the example shown, a "report generator" class is separated into two classes - a report generator class and a report service class. The report service class gets the report generator class from the container's "getComponent" method, specifying "reportGenerator" as the id. Then, when it needs to generate an annual report, monthly report etc., it just makes a call on the report generator object.

The upshot of all this is that the the Report Service doesn't have a dependency on the specific Report Generator (unlike before, when they were combined).

Since the container class also instantiates the Report Service and stores it in the same object map as the report generator, it can be accessed in a main method. The main method instantiates the container, gets the report service from the container, and then calls the appropriate generate method on it.

You could argue that you've made the system more complex by introducing the container, and just kicked the dependency can down the road to the container class. On the other hand, you've separated the dependency-type code (which type of report) from the reporting service code. Perhaps the real gain is that it represents a way to separate distinct functionality into separate classes, in a way that keeps a class that uses an interface from also referring to the concrete class. That's got to be a win.

Tuesday, December 27, 2011

A sprinkling of Spring

Although this blog is about mobile development, I also would like to learn a bit about Spring technologies. This is mostly because pretty much any non-Android Java job out there has some element of Spring or Spring concepts in it. So, basically it's just good to know.

The goal therefore of this post is to get a simple hello world up and running in Spring. I've downloaded the IDE about 7 times. But, now I'm determined to get the hello world done.

At this url: http://www.springsource.com/developer/sts

we find the link for download the SpringSource Tool Suite. Note you can skip filling in the form on the right if you click on the link that says "I'd rather not fill in the form".

On that download page - http://www.springsource.com/downloads/sts - we find that there the current Version is "2.8.1.RELEASE", it's eclipse 3.7.1 and there's a dmg (mac install file) listed right at the top. 385 MB.

springsource-tool-suite-2.8.1.RELEASE-e3.7.1-macosx-carbon-installer.dmg

Ok, this will take a few minutes. I'm at Panera, but it's 4:21 in the afternoon, so, no one's really around. While that's going on, let's look up how to do a hello world somewhere on the net.

Actually, if I click on the "Spring Framework" link from the main page, it looks like there's some promising links in there. "Get Started" has links for get sts (I'm doing that), tutorials, code samples, doc, forums. Let's go to tutorials.

Ok, how about this screencast:

http://www.youtube.com/watch?v=kSITVsOUvLU

It's a 15 minute webinar.

Ok, I've just finished watching part 1. It's a nice overview of core spring, what it is conceptually. Let's move on to part 2.

It's asking me to install Spring now. I'm just double-clicking the .dmg, and then the install app. It installs into /Users/myname/springsource, and springsource has 4 directories.


apache-maven-3.0.3
spring-roo-1.1.5.RELEASE
sts-2.8.1.RELEASE
vfabric-tc-server-developer-2.6.1.RELEASE


When you start sts, it creates this workspace:

/Users/myname/Documents/workspace-sts

Ok, now the IDE is up. I moved all the tabs to the left side, which is much nicer. There's a dashboard display with a lot of neat looking links, including tutorials, as well. There's an image below.





Ok, he says just do file, new, spring template project. Then choose "simple spring utility project" or "spring mvc project" - unfortunately, he doesn't tell you which one right away because he's already created his projects. Let's go with the utility for now. It's kind of an overview...

Actually, he kind of breezes over the code. it's more of a showing what you can do than a "do this" kind of presentation. And there are actually 5 videos altogether. I feel like I know a bit more, but I'd really like to get that hello world going. We'll tackle that in the next post.

Thursday, December 15, 2011

Getting files from the iPhone/iTouch to the Mac

As I'm implementing a new feature in my app which updates the database, I've come to realize that I'll be needing to copy the database to my mac for checking purposes. I'm going to follow a post in Stack Overflow that purports to do this.


http://stackoverflow.com/questions/3456613/how-to-copy-log-file-from-iphone-to-desktop-pc

If you write out your logs to a file inside the App's Documents directory, and you have access to the device, then you can download the Application Data directory. I've done this before to copy off the logs and SQLite database from our application. You can follow the instructions here:

Managing Application Data

To make a copy of your application’s device–based file system to your Mac:

In Xcode, choose Window > Organizer.

// done

In the Organizer, select your device in the Devices list.

// note - click on the devices icon on the top of the display
// done


In the Summary pane, click the disclosure triangle next to your application.
Click the download button (the down-pointing arrow to the left of the Application Data package), as shown in (the figure below:)




// Note - you need to choose "applications" on the left side in order to
// get the list of applications.


In the dialog that appears, choose a location for the file-system copy.
Note: I'm not sure it show the triangle you need to click on, so I've included my own screenshot of it.

// Actually, in XCode 4 it's apparently a bit different. Look for the download
// arrow at the bottom of the display:




Download the file to a convenient directory. Note the file downloaded is an "xcappdata" file. Right click on select "Show Package Contents" on it. This gives you access to the files.

Note that if you're dealing with a database, then it may or may not be in there, depending on if you left it in the resources directory (non-updatable) or copied it into user documents. See http://tinyurl.com/cks54ay for info on how to do that.

Wednesday, December 14, 2011

Simple constants in Objective C

At this point, we just want to implement a bit of date manipulation logic.

If the user had less than 5 seconds remaining before returning the question, then we want to use a shorter amount of days - call it "SLOW_RESPONSE_INTERVAL_DAYS". Otherwise, we'll use "FAST_RESPONSE_INTERVAL_DAYS;

In the Android, it looks like this:


static final int SLOW_RESPONSE_INTERVAL_DAYS = 3;
static final int FAST_RESPONSE_INTERVAL_DAYS = 5;

In iOS, pretty much is seems like defines are used...

#define SLOW_RESPONSE_INTERVAL_DAYS 3
#define FAST_RESPONSE_INTERVAL_DAYS 5

We'll keep it inside the class where we're processing the spaced repetition logic, so that ought to be sufficient for our needs.

iOS - incrementing a date value from a string input

One of the enhancements I'm making my iOS app is to add scheduling for a review of particular words. This review date is actually based on the amount of time it takes to answer a given question - the longer it takes, the quicker the review should be scheduled. Another consideration is the internal format in which dates are held by SQLLite, which is a string in yyyy-MM-dd format. This is helpful because now the input and output of the problem solution is limited to string manipulation.

So the input to our method is effectively the number of seconds it took to answer the question. We'll add the input date for flexibility - we could assumed it's today, but this is a trade-off for re-usability.

So, the first thing we need to do is take the string input date and convert it to an iOS date.

The key is the NSDateFormatter object, which has both the "stringFromDate" and the "dateFromString" methods. So, looking at an example from http://iphonedevelopertips.com/cocoa/date-formatters-examples-take-3.html, we adapt it a bit.

Example:

NSString *dateStr = @"20081122";

// Convert string to date object
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"yyyyMMdd"];
NSDate *date = [dateFormat dateFromString:dateStr];

// Convert date object to desired output format
[dateFormat setDateFormat:@"EEEE MMMM d, YYYY"];
dateStr = [dateFormat stringFromDate:date];
[dateFormat release];


We want to turn this into a method in our Utils class, which will also take the number of days:

+ (NSString *) addDaysToDate:(NSString *) inDate daysToAdd: (int) daysToAdd;

So, the first thing to do is take the code above and adapt it to convert the "yyyy-MM-dd" input string to a date.

// Convert string to date object
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"yyyy-MM-dd"];
NSDate *date = [dateFormat dateFromString:inDate];


Well tackle adding days to the date in a moment. For now, assume we've done that and just want to convert it back to the same format it came in as:

// Convert date object to desired output format
// The date format is already the way we want it

NSString *dateStr = [dateFormat stringFromDate:newDate];
[dateFormat release];
return dateStr;


The only remaining question is how to increment an instance of NSDate in iOS. Reviewing the docs, it looks like this might be the one that we're looking for:

dateByAddingTimeInterval:

Returns a new NSDate object that is set to a given number of seconds relative to the receiver.
- (id)dateByAddingTimeInterval:(NSTimeInterval)seconds


However, it's always worth a google check to make sure that's right. As it turns out, it isn't. It doesn't handle daylight savings time, for one thing. According to StackOverflow, the correct solution is this:


+ (NSDate *)getForDays:(int)days fromDate:(NSDate *)date
{
NSDateComponents *components= [[NSDateComponents alloc] init];
[components setDay:days];

NSCalendar *calendar = [NSCalendar currentCalendar];
return [calendar dateByAddingComponents:components toDate:date options:0];
}


So, once we add this to our Utils class, the call should look like something like this:


NSDate *newDate = [Utils getForDays: daysToAdd fromDate: date];

Here's the final (hopefully) product:

+ (NSString *) addDaysToDate:(NSString *) inDate daysToAdd: (int) daysToAdd
{

// Convert string to date object
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"yyyy-MM-dd"];
NSDate *date = [dateFormat dateFromString:inDate];


NSDate *newDate = [Utils getForDays: daysToAdd fromDate: date];

// Convert date object to desired output format
// The date format is already the way we want it

NSString *dateStr = [dateFormat stringFromDate:newDate];
[dateFormat release];
return dateStr;
}

Thursday, December 8, 2011

Android - regaining root access

Loyal readers of my blog, followed by millions (Joe Millions), may recall that I went to some lengths a few months ago to gain root access to my Android device - something that ought to be there by default, btw, since I paid a princely sum for the fun.

Now, it turns out that I've somehow lost root again. Arggh! The gods be cursed!

So, why did this happen? More importantly, how to get it back again? Let's see if we can reconstruct events from my previous posts.

Ah, good - in this blog post, I actually *summarized* what I did:

http://gettingintomobile.blogspot.com/2011/07/nexus-one-rooted-at-last.html

Long story short, it looks like I got through the tough part - unlocking the phone (had to be done from a Windows system), and now all I have to do is follow the instructions from this url:

http://chensun.net/android-development/root-nexus-one-with-android-2-3-3-gingerbread/8/

Ok, somebody noticed why they/I lost root:

Today the OTA Update to Android version 2.3.6 (Build GRK39F) was pushed to my Nexus One. And as i expected (from my experience of previous system updates), also with this android update you will loose your root access.


Ok, when I go to chensun.net, it confirms I can get root on 2.3.6:

Root Nexus One with Android 2.3.3 Gingerbread
Posted on March 26, 2011 by Chen

Update: September 24, 2011.
Also works for Android 2.3.6 on Nexus One!


There still might be a problem - I"m thinking the fastboot could have been a problem

Hmm...it looks like the fastboot needs to be flashed from XP, and not a virtual version either. It'll have to wait till I get home and can use my wife's pc.

Ok - I'm home. Once I got into her pc, it was very simple. In fact, it was just a question of following the tutorial steps, replicated below for you viewing:


1. Download su.zip, and put it on your phone’s SD card.

2. Download fastboot.zip, unzip it to C:\. Now you have a folder C:\fastboot\

3. Download recovery-RA-passion-v2.2.1.img, put it in C:\fastboot\

4. Shut down your phone. Then start your Nexus One in Fastboot mode by holding down the Trackball and press the Power button.

5. Connect your phone to your computer. Open a Command Prompt (Start -> All Programs -> Accessories -> Command Prompt), and run the following commands:

cd C:\fastboot
fastboot devices
fastboot flash recovery recovery-RA-passion-v2.2.1.img

6. Use Volume +/- keys to choose ‘Bootloader’ and hit the Power button.

7. Use Volume +/- keys to select ‘Recovery’ and hit the Power button.

8. From the menu, scroll the Trackball to select ‘Flash zip from sdcard’ and hit the Trackball.

9. Select the su.zip file you previously copied to your SD card and hit the Trackball. Hit it one more time.

10. When done, restart your Nexus One and you should be all set.

There you go. Enjoy your rooted Nexus One :)

Monday, December 5, 2011

Backdoor for testing the purchased app on your iPhone

I ran into a bit of an inconvenience when using my recently released application on my iPod Touch. It's a Japanese vocabulary quiz, and it's limited to the first 100 words until you make an in-app purchase. The problem is, I don't want to purchase my app, because I also want to be able test it as the free download as well. It might seem like a small thing, but I would prefer to work in the sort of "default' setting for my app, generally speaking. It's the first version people will see.

The problem was that, after I had submitted my app, I ran into the in-app-purchase limitation on my own device. To get around this, I commented out the check for purchase code in my app. Then I got busy with life and completely forgot about it. So, when I went to make an enhancement to the app, I was happily coding away when I quite accidentally got into the same section of code which checks for the purchase. I was completely taken aback when I saw the code completely skipping the check for the app purchase. Would Apple have caught the problem had I submitted it? I doubt it - they wouldn't go up to 100. Once released, there would be no reason to purchase the app - I would have been giving out the whole thing for free. With a 1% fill rate for the iAd advertising, I'm not going to be making money on ads, that's for sure. Who knows when I would have finally realized I was giving it away? Note to self - don't *do* stuff like that.

So, as soon as I got over my shock, I immediately set about fixing up the problem. The solution clearly lends itself to some type of checking of the unique id of the device. Surprisingly, this turned out to be very easy to code - just one line, and no permissions required. It can be retrieved and logged as follows:



NSString * uniqueId = [[UIDevice currentDevice] uniqueIdentifier];

NSLog(@"uniqueIdentifier: %@", uniqueId);



There is one important caveat - this method has been deprecated in iOS 5. However, until some future date, it's going to be my solution. Once that method is gone, my app won't compile - there's no danger of giving it away for free.

This method works on the simulators too. It turns out that they all have the same id (amongst themselves, not the iPod Touch). With that information in hand, I quickly created this code:



+ (BOOL) isFreeVersion {

NSString *productIdentifer = @"com.mycompany.myapp.myinapppurchase";

if
(
([uniqueId isEqualToString:@"xxxyyyzzz"]) || // my device's id
([uniqueId isEqualToString:@"zzzyyyxxx"]) // my simulator's id
)

{
return NO; // it's been purchased
}

BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productIdentifer];

return !productPurchased;

}



Nice and quick. Let's hope Apple doesn't get around to deprecating this one for a while.

Wednesday, November 30, 2011

Getting Phone Gap to work with JQuery Mobile

This is just a quick summary of how to get a simple kind of Hello World app that uses both PhoneGap and JQuery Mobile up and running. Note that I'm going to be doing this on a Mac, using Xcode4. You should be able to do this using, for example, Eclipse on a PC, but the details will vary slightly.

The first thing to do is go ahead and download the latest version of PhoneGap from their site, and install it. Once you do that, you can follow the instructions at the Phone Gap site on how to run their hello world app (http://phonegap.com/start).

To paraphrase, you basically, open XCode, and there's a Phone Gap project icon available. You can go ahead and create a new project. (Important - don't turn on arc for starters, that messes something up, I think it was something to to with jQuery Mobile)

Once that's done, *run the project*. This creates a www folder in the project folder you just created.

Copy the www folder from finder, without copy and as as a reference into the project folder.

When you run the project again, you might have an issue with the default device being incorrect. Change it to the one you usually use by selecting it on the scheme in the upper left. In my case, with Xcode 4.2, it's usually iPhone 4.2

The successful result looks like this:



Ok, that's phone gap. But we wanted to see a combination of jQueryMobile and Phone Gap. That's where this blog post comes in:

http://wiki.phonegap.com/w/page/36868306/UI%20Development%20using%20jQueryMobile

You can read up on it, then download the demo source from here:

http://wiki.phonegap.com/w/page/36868306/UI%20Development%20using%20jQueryMobile#DownloadDemoSource

Once you have downloaded and unzipped that app - *don't try opening the project*. It contains assumptions about the locations of javascript files that keep it from completing.

Instead, delete the "www" folder including files from your previously created PhoneGap project. Then copy the "www" file from the jQuery downloaded folder into your project from finder. I think it's better to copy and reference additional folders.

Then, run the app. You should see something like this:



But - there are a couple of "gotchas". It turns out that a security feature has been added to Phone Gap which requires that you provide a list of urls your app will be accessing in "PhoneGap.plist". For example, if accessing google, you'd want to specify "*.google.com". Any other form I tried didn't work.

In the case of the what text to enter, for the search, I think the only one that works is "firefox". But, if you enter that, you get a nice, long list of alternate browsers:




I'll leave getting the second button to say something more than "This app rocks" up to you ;)

Monday, November 21, 2011

jQuery Mobile - first steps

Having just finished chapter 3 of Jonathan's Stark's excellent tutorial on building iPhone apps without objective C,

(http://ofps.oreilly.com/titles/9780596805784/ch03_id35816678.html)

I then started in on Chapter 4. However, it works with jQTouch. jQtouch seems great, but apparently it's no longer being actively developed and seem to have been more or less supersceded by jQuery Mobile. There are also a number of other platforms out there, but I'm going to focus on jQuery mobile for a couple of reasons:

1) It seems to be quick to learn
2) I want to get more familiar with javascript in general.

There's a nice little summary that gives a clear, quick overview of its features here:
http://qpants.wordpress.com/2010/10/19/jquerymobile-review/

So, going to the jQuery mobile page, let's walk through the intro tutorial. It's at http://jquerymobile.com/demos/1.0/docs/about/getting-started.html


Getting Started with jQuery Mobile

jQuery Mobile provides a set of touch-friendly UI widgets and an AJAX-powered navigation system to support animated page transitions. Building your first jQuery Mobile page is easy, here's how:

Create a basic page template
Pop open your favorite text editor, paste in the page template below, save and open in a browser. You are now a mobile developer!

Here's what's in the template. In the head, a meta viewport tag sets the screen width to the pixel width of the device and references to jQuery, jQuery Mobile and the mobile theme stylesheet from the CDN add all the styles and scripts.

In the body, a div with a data-role of page is the wrapper used to delineate a page, and the header bar (data-role="header") and content region (data-role="content") are added inside to create a basic page (these are both optional). These data- attributes are HTML5 attributes are used throughout jQuery Mobile to transform basic markup into an enhanced and styled widget.


Here's the template:


<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css" />
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js"></script>
</head>
<body>

<div data-role="page">

<div data-role="header">
<h1>My Title</h1>
</div><!-- /header -->

<div data-role="content">
<p>Hello world</p>
</div><!-- /content -->

</div><!-- /page -->

</body>
</html>


A couple of key points - the whole app is in a single page. Html5's "data-role" is used in "div" declarations to distinguish between pages, headers, content etc.

So, let's bring this up in safari and see how it looks. Btw, I just learned a nice trick for displaying static html files in the iOS simulator safari browser - just drag and drop in into the simulator. There's no need even to start safari!

I wanted to show how it looks, but I'm having trouble pasting the image, I'll try to add it later.

Continuing:

Add your content
Inside your content container, you can add all any standard HTML elements - headings, lists, paragraphs, etc. You can write your own custom styles to create custom layouts by adding an additional stylesheet to the head after the jQuery Mobile stylesheet.


Make a listview
jQuery Mobile includes a diverse set of common listviews that are coded as lists with a data-role="listview" added. Here is a simple linked list that has a role of listview. We're going to make this look like an inset module by adding a data-inset="true" and add a dynamic search filter with the data-filter="true" attributes.


<ul data-role="listview" data-inset="true" data-filter="true">
<li><a href="#">Acura</a></li>
<li><a href="#">Audi</a></li>
<li><a href="#">BMW</a></li>
<li><a href="#">Cadillac</a></li>
<li><a href="#">Ferrari</a></li>
</ul>




Ok, so let's add this to the html just created under the div with the "content" role.

Very nice - a cool, iPhone-ish looking listview which includes a search. The search works out of box, although not quite as you'd expect - it finds any items that contain the string entered, as opposed to starting with it.

Continuing on:

Add a slider
The framework contains a full set of form elements that automatically are enhanced into touch-friendly styled widgets. Here's a slider made with the new HTML5 input type of range, no data-role needed. Be sure to wrap these in a form element and always properly associate a label to every form element.

<form>
<label for="slider-0">Input slider:</label>
<input type="range" name="slider" id="slider-0" value="25" min="0" max="100" />
</form>




Nice - you get a pretty little slider with a label whose text changes as you move the slider.


Next:

Make a button

There are a few ways to make buttons, but lets turn a link into a button so it's easy to click. Just start with a link and add a data-role="button" attribute to it. You can add an icon with the data-icon attribute and optionally set its position with the data-iconpos attribute.


<a href="#" data-role="button" data-icon="star">Star button</a>



Nice - a button that take you to the specified link. In this case, it's just the same page, but you could paste an address for anywhere.

Next - themes!

Play with theme swatches

jQuery Mobile has a robust theme framework that supports up to 26 sets of toolbar, content and button colors, called a "swatch". Just add a data-theme="e" attribute to any of the widgets on this page: page, header, list, input for the slider, or button to turn it yellow. Try different swatch letters in default theme from a-e to mix and match swatches.

Cool party trick: add the theme swatch to the page and see how all the widgets inside the content will automatically inherit the theme (headers don't inherit, they default to swatch A).

<a href="#" data-role="button" data-icon="star" data-theme="a">Button</a>



Very nice. Different colored buttons, and themes.


So, this was a nice little tutorial on some of the basics of jQuery Mobile. I'm going to try do a static page on it first. Later on, I'll check it for database access.

Saturday, November 19, 2011

Welcome to my blog! If you've stumbled across this in your travels on the internet, please take a moment click on an ad - or two! You might see something you like, *and* it helps finance the blogosphere. Think of the children!

Ok, after a couple of days hiatus, we're ready to tackle Jonathan Stark's phone-gap tutorial again. We were about 2/3 of the way through the murderous part 3 when we left off.

The next step is "Adding an Icon to the Home Screen". Let's see what this does. It's essentially just adding a 57 x 57 file named "apple-touch-icon.png" to your public_html directory. It lets the user put an app for your mobile web app onto his icons along with the regular app from the safari browser (by hitting the middle icon on the bottom of the browser and selecting "add to home screen". Pretty nifty.

Next up is "Full Screen Mode". All you need to do for this is add this line:


<meta name="apple-mobile-web-app-capable" content="yes" />


to the head section of "iphone.html". It gives you extra space on the web display on the hijacked links.

Note, you need to delete and re-add the icon you just added to your home screen to make it work. It should look something like this:



Ok, the next step is to change the color of the status bar:


<meta name="apple-mobile-web-app-status-bar-style" content="black" />

non-escaped html:



Finally, we can add a startup graphic with this:

<link rel="apple-touch-startup-image" href="myCustomStartupGraphic.png" />

non-escaped html:



And you get the loading graphic...and we're done!

Friday, November 18, 2011

Solving XCode4.2 upgrade warnings; importing flurry analytics; etc.

Welcome to my blog! If you've stumbled across this (or any blog) in your travels on the internet, place take a moment click on an ad -or two! You might see something you like, *and* it helps finance the blogosphere. Think of the children.

Ok, well, today, I've just released my first free version of my app. It give the first 100 for free, but you need to pay for the rest. But, I need to make it clearer when you run out, like an alert. As it stands now, you just have to keep repeating, trying to figure out why you're stuck. I went through 3 of the same quiz before I finally got it, and I wrote the program.

So, the goal of today is to put in an alert. I also want to put in some metrics.

Woah - thank God. A gang of like 20 high school girls just decided *not* to sit right next to me. That was a close call.

Oh, yeah, I almost forgot. Since I upgraded to Xcode 4.2, I've been getting warnings from the Reachability class:


declaration of '…' will not be visible outside of this function warning


Stackoverflow as usual has the solution:



Add #import <netinet/in.h> in Reachability.h to get away with this


Ok, there are still a couple of other warnings.

I'm getting an incompatible pointer warning on this:

UIScrollView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];

If I click on the warning, it takes me to the class itself, which returns a UIView. So casting it to a UIScrollView eliminates the problem:

UIScrollView *view = (UIScrollView *) [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];


Ok, good. Now, the last warning is

"initwithframe reuseidentifier is deprecated" from this statement:


cell = [ [[UITableViewCell alloc]
initWithFrame:CGRectZero reuseIdentifier:@"cell"] autorelease];



The fix, again from stackoverflow, is this:




cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"] autorelease];



Whew! That was pain.

Ok, where was I? Ok, first, let's throw in the alert.

Here's where I want to throw it in:


if (reachedEndOfFreeWords) {
if ([Utils isFreeVersion]) {
self.upperCongratsMessage.text = @"Free vocabualary completed!";
self.congratsMessage.text = @"Click the buy button below for the rest!";

=========> create the alert here! <=====================

}
else {
self.congratsMessage.text = @"You have completed *all* the words!";
}
}


Ok, let's get some alert code. Where do we have it? Hmm...right - there's an alert if you don't get all the question right.

Ah, here it is:



// construct a message letting the user know how many were wrong
NSString *title = [NSString stringWithFormat:@"You answered %d out %d correctly",appState.correctAnswers, [appState.shuffledQuestionDictKeyList count]];

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:@"To advance, all answers must be correct"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];

[alert show];
[alert release];



So, let's make this look like this:



// construct a message to the user
NSString *title = [NSString stringWithFormat:@"Press the
\"Buy\" button below for the rest of the questions!";

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:@"Congratulations - free questions completed."
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];

[alert show];
[alert release];



Ok, with that taken care of, let's set up the metrics.

Pretty easy - copy the libFlurryAnalytics.a into the project, copy the FlurryAnalytics.h into the app delegate implementation file, and the start session in the "application:didFinishLaunchingWithOptions method:


[FlurryAnalytics startSession:@"YOUR_KEY_GOES_HERE"];


Ok, that's a wrap. Check out the ads!

Tuesday, November 15, 2011

In-app payment - some example code

So, I'm reviewing the in-app payment code, now that it's implemented, to see more carefully what exactly it all does. This post is basically a listing of all the code needed to implement an in-app purchase.


Note that this is based on an excellent tutorial by Rich Levine at http://www.raywenderlich.com/2797/introduction-to-in-app-purchases.


Another absolute must for anyone doing not just in-app purchases, but app submission with xcode 4 in general, is this great tutorial by Troy Bryant: http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/


Let's take a look at all the code: We'll take a look at it from the client perspective first.

Here's the header file:


//
// NextLevelViewController.h
// JlptQuizApp
//
// Created by Mark Donaghue on 9/20/11.
// Copyright 2011 Kanjisoft Systems. All rights reserved.
//

#import

// in app purchase
#import "InAppMyAppIAPHelper.h"
#import "MBProgressHUD.h"


@interface NextLevelViewController : UIViewController {
UIImageView *imageView;
UILabel *upperCongratsMessage;
UILabel *congratsMessage;
UILabel *art_title;
UILabel *url;

UIButton *continueButtton;

// Button to purchase with
UIButton *buyButtton;

// Show whirly progress indicator when waiting for an app purchase
MBProgressHUD *_hud;
}


@property (nonatomic, retain) IBOutlet UIImageView *imageView;
@property (nonatomic, retain) IBOutlet UILabel *congratsMessage;
@property (nonatomic, retain) IBOutlet UILabel *upperCongratsMessage;
@property (nonatomic, retain) IBOutlet UILabel *url;
@property (nonatomic, retain) IBOutlet UILabel *art_title;
@property (nonatomic, retain) IBOutlet UIButton *continueButton;

// In app purchase
@property (nonatomic, retain) IBOutlet UIButton *buyButton;
@property (retain) MBProgressHUD *hud;

- (IBAction)dismissView:(id)sender;

// In app purchase
- (IBAction)buyButtonTapped:(id)sender;

@end


So, essentially we have a buy button, the progress indicator for the purchase, and the declaration of the method method to call when the buy button is tapped.

Now, what do we do in the implementation?

First, we need to import the network status checking class (and my own class utils):

#import "Utils.h"
#import "Reachability.h"

First, we synthesize the stuff declared above:

@synthesize imageView, upperCongratsMessage, congratsMessage, art_title, url, buyButton, continueButton;

@synthesize hud = _hud;


I'm not in love with the convention of naming properties after internally declared variables without the underscore - what's the point? I guess there's a good reason for it - it must have something to do with how synthesize works. I'll figure it out later.

Next, we need to hide the purchase button if it's already been purchased. For that we have this code:


// check whether or not to display the purchase button
- (void) checkPurchaseButton {

if ([Utils isFreeVersion]) {
[buyButton setHidden:NO];

}
else {
[buyButton setHidden:YES];
}

}


This is called from "viewWillAppear". We'll drill into how it checks what's been purchased later on.

In the viewDidLoad, we identify the methods to be called when the app purchase is done, be it success or failure:



// in app purchase - specify what to do when app is purchased
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];




All you really have to know is this causes the "productPurchased" and the "ProductPurchaseFailed" methods to be called in on a purchase and purchase failure, respectively.

The next method is this:



// Dismiss the swirly progress indicator
- (void)dismissHUD:(id)arg {

[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
self.hud = nil;

}


As you might imagine, this is called when the app purchase is completed and dismisses the circly-swirly doodle progress indicator.

This is the purchase button pressed method:


-
- (IBAction)buyButtonTapped:(id)sender {


NSLog(@"NextLevelController, buyButtonTapped");

// needs to be called to set up the products array
[InAppMyAppIAPHelper listProducts];


// This is (currently) a one-product app. Retrieve the only product.
SKProduct *product = [[InAppMyAppIAPHelper sharedHelper].products objectAtIndex:0];


// create object to check for net status
Reachability *reach = [Reachability reachabilityForInternetConnection];

// check for nets status
NetworkStatus netStatus = [reach currentReachabilityStatus];

if (netStatus == NotReachable) {
NSLog(@"Arggh! - no internet connection!");
}

else {

// Display the swirly progress indicator
// this takes care of adding to the view - no need to add to the xlb
self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];

// Text for the progress indicator
_hud.labelText = @"Acessing new vocab...";


NSLog(@"Buying %@...", product.productIdentifier);

// Get the singleton shared helper and purchase. Use the product identifier
// from the product which you got from the products array a few lines ago
[[InAppMyAppIAPHelper sharedHelper] buyProductIdentifier:product.productIdentifier];

// call method named "timeout" ofter delay specified
[self performSelector:@selector(timeout:) withObject:nil afterDelay:60*5];

}
}



We'll delve back into this after we finish listing the client code. The next method is the callback for the product purchased:


// Handle product purchase
- (void)productPurchased:(NSNotification *)notification {

// Drop any previous requests
[NSObject cancelPreviousPerformRequestsWithTarget:self];

// Get rid of the swirly-doodle progress indicator
[MBProgressHUD hideHUDForView:self.view animated:YES];

// Get the product identified
NSString *productIdentifier = (NSString *) notification.object;


NSLog(@"Purchased: %@", productIdentifier);


// hide the purchase button
[self checkPurchaseButton];


}


And the purchase fail:

// Handle product purchase fail
- (void)productPurchaseFailed:(NSNotification *)notification {

NSLog(@"NextLevelViewContoller - productPurchase failed");

// Drop any previous requests
[NSObject cancelPreviousPerformRequestsWithTarget:self];

// Get rid of the swirly-doodle progress indicator
[MBProgressHUD hideHUDForView:self.view animated:YES];


// Grab the transaction from the notification
SKPaymentTransaction * transaction = (SKPaymentTransaction *) notification.object;

// Show an alert with info about the reason for failure
// as long as it's other than the (user-ititiated cancel?)

if (transaction.error.code != SKErrorPaymentCancelled) {

UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Error!"
message:transaction.error.localizedDescription
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:@"OK", nil] autorelease];

// show the alert
[alert show];
}

}


Finally, we have the code to display the timeout:

// Process a purchase timeout
- (void)timeout:(id)arg {

// set the swirly-gig-doodle progress indicator text
_hud.labelText = @"Timeout!";

// Give some hopeful advice
_hud.detailsLabelText = @"Please try again later.";

// set an image on the the swirly-gig-doodle progress indicator
_hud.customView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"37x-Checkmark.jpg"]] autorelease];

// Set the swirly-gig-doodle progress indicator to show the image
_hud.mode = MBProgressHUDModeCustomView;

// Drop the timeout display after a delay
[self performSelector:@selector(dismissHUD:) withObject:nil afterDelay:3.0];

}

Ok, we promised we'd take a look at the objects which actually perform the purchase. This is done through the use of the In-App Purchase helper class and its app-specific subclass. Since it's shorter, let's look at the subclass first. Here's the header:

#import
#import "IAPHelper.h"

@interface InAppMyAppIAPHelper : IAPHelper {
}

+ (InAppMyAppIAPHelper *) sharedHelper;

+ (void) listProducts;

@end


It's pretty straightforward. There are static declarations of two methods. The first provides a singleton instance of itself, and the second is a utility method to list the products.

Here's the implementation:

#import "InAppMyAppIAPHelper.h"

@implementation InAppMyAppIAPHelper

// Singleton instance of self
static InAppMyAppIAPHelper * _sharedHelper;


// Return the singleton instance of self
+ (InAppMyAppIAPHelper *) sharedHelper {

// return it if it's been instantiated already
if (_sharedHelper != nil) {
return _sharedHelper;
}

// It's not yet been created, so go ahead and create it
_sharedHelper = [[InAppMyAppIAPHelper alloc] init];

// and return it
return _sharedHelper;

}


// initialize
- (id)init {

// create a list of product identifier
NSSet *productIdentifiers = [NSSet setWithObjects:
@"com.kanjisoft.JlptVocabularyQuiz.AdditionalVocab",
nil];

// invoke superclass's method, which iterates through this list, checks
// NSPrefs, and if it's been purchased, add it to the "productsPurchased array
if ((self = [super initWithProductIdentifiers:productIdentifiers])) {

}

// done
return self;

}

// Method to list products

+ (void) listProducts {

// retreive the count of products
int productsCount = [[InAppMyAppIAPHelper sharedHelper].products count];

// Log a message if it's zero
if (productsCount == 0) {
NSLog(@"InAppMyAppAPHelper, productCount was zero: %d", productsCount);
}



// Iterate through the product list
for (int i = 0; i < productsCount; i++) {

// get the product
SKProduct *product = [[InAppMyAppIAPHelper sharedHelper].products objectAtIndex:i];

// allocate a number formatter
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];

// No idea what 10_4 is
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];

// Set the style to currency
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];

// Set the locale to the price locale
[numberFormatter setLocale:product.priceLocale];

// formate the price string
NSString *formattedString = [numberFormatter stringFromNumber:product.price];

// release the formatter
[numberFormatter release];

// log the info
NSLog(@"product title: %@", product.localizedTitle);

NSLog(@"product cost: %@", formattedString);


// check against the purchase producst array and log wheter it was
// purchased or not
if ([[InAppMyAppIAPHelper sharedHelper].purchasedProducts containsObject:product.productIdentifier]) {
NSLog(@"product was purchased");

} else
{
NSLog(@"product was *not* purchased");

}

}

}


@end


Ok, let's look at the big kahouna - the IAPHelper class.


Here's the interface:

//
// IAPHelper.h
// JlptVocabularyQuiz
//
// Created by Mark Donaghue on 10/28/11.
// Copyright 2011 Kanjisoft Systems. All rights reserved.
//

#import
#import "StoreKit/StoreKit.h"

#define kProductsLoadedNotification @"ProductsLoaded"
// Add two new notifications
#define kProductPurchasedNotification @"ProductPurchased"
#define kProductPurchaseFailedNotification @"ProductPurchaseFailed"


@interface IAPHelper : NSObject {

// Set of product ids, provided by the app specific subclass.
NSSet * _productIdentifiers;

// list of all products, purchased or not
NSArray * _products;

// list of purchase products, created by checking against prefs
NSMutableSet * _purchasedProducts;

// In app purchase use
SKProductsRequest * _request;
}

@property (retain) NSSet *productIdentifiers;
@property (retain) NSArray *products;
@property (retain) NSMutableSet *purchasedProducts;
@property (retain) SKProductsRequest *request;

// get a list of products
- (void)requestProducts;

// create the list of purchased products
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers;

// buy the product
- (void)buyProductIdentifier:(NSString *)productIdentifier;

@end


Pretty self-explanatory. And finally, the implementation:


//
// IAPHelper.m
// JlptVocabularyQuiz
//
// Created by Mark Donaghue on 10/28/11.
// Copyright 2011 Kanjisoft Systems. All rights reserved.
//

#import "IAPHelper.h"


@implementation IAPHelper

// Under @implementation
@synthesize productIdentifiers = _productIdentifiers;
@synthesize products = _products;
@synthesize purchasedProducts = _purchasedProducts;
@synthesize request = _request;


// In dealloc
- (void)dealloc
{
[_productIdentifiers release];
_productIdentifiers = nil;
[_products release];
_products = nil;
[_purchasedProducts release];
_purchasedProducts = nil;
[_request release];
_request = nil;
[super dealloc];
}


// Request a list of products from app store

- (void)requestProducts {

// Create a requestt for a list of products, using the list
// of product identifiers as a parameter
self.request = [[[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers] autorelease];

// this instance is the delegate for the request object
_request.delegate = self;

// start the request
[_request start];

}


// receives response for the request with the listing of products

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {


// set the list of products to the list of product objects received in the response
self.products = response.products;

// nil out the request
self.request = nil;


// log the products received
for (id product in _products){
NSLog(@"IAHelper, received product is: %@", product);
}


// notify methods set up on the notification center of the product's receipt
[[NSNotificationCenter defaultCenter] postNotificationName:kProductsLoadedNotification object:_products];
}


// invoked from the app-specific subclasss, which provides
// a list of the product ids
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers {

// self to super
if ((self = [super init])) {

// Store product identifiers recieved from the subclass
_productIdentifiers = [productIdentifiers retain];


// Check for previously purchased products
NSMutableSet * purchasedProducts = [NSMutableSet set];

// iterate through the product identifiers
for (NSString * productIdentifier in _productIdentifiers) {

// see if the product id is in prefs
BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productIdentifier];

// if it is
if (productPurchased) {

// add it to the list of purchase products
[purchasedProducts addObject:productIdentifier];

// log it
NSLog(@"Previously purchased: %@", productIdentifier);
}
else {

// log that it's not in the list of purchased products
NSLog(@"Not purchased: %@", productIdentifier);
}
}

// set the purchased products array
self.purchasedProducts = purchasedProducts;

}
return self;
}



- (void)recordTransaction:(SKPaymentTransaction *)transaction {
// Optional: Record the transaction on the server side...
}


// look like this is called when the product is purchased
- (void)provideContent:(NSString *)productIdentifier {

NSLog(@"Toggling flag for: %@", productIdentifier);

// set prefs to true for the product id
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:productIdentifier];

// save it
[[NSUserDefaults standardUserDefaults] synchronize];

// add it to the list of purchase prodcut
[_purchasedProducts addObject:productIdentifier];


// float the notification of the product purchase
[[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchasedNotification object:productIdentifier];

}


// call back from purchase
- (void)completeTransaction:(SKPaymentTransaction *)transaction {

NSLog(@"completeTransaction...");

// call the optional record transaction
[self recordTransaction: transaction];

// add it to the list of products purchase
[self provideContent: transaction.payment.productIdentifier];

// set the transaction to finished
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}

// restore transaction
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {

NSLog(@"restoreTransaction...");

[self recordTransaction: transaction];
[self provideContent: transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}


// transaction failed
- (void)failedTransaction:(SKPaymentTransaction *)transaction {

if (transaction.error.code != SKErrorPaymentCancelled)
{
NSLog(@"Transaction error: %@", transaction.error.localizedDescription);
}

[[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchaseFailedNotification object:transaction];

[[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}


// add transaction to the payment queue
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
}
}


// purchase the product
- (void)buyProductIdentifier:(NSString *)productIdentifier {

NSLog(@"Buying %@...", productIdentifier);

SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];

[[SKPaymentQueue defaultQueue] addPayment:payment];

}

@end



Ok, that's it.

Monday, November 14, 2011

Jonathan Stark's Phone Gap tutorial - Ch03 - part 3

Ok, moving on with Jonathan Stark's tutorial. In this post, we're going to implement the javascript required to create a button. You can see the tutorial for an explanation. Don't forget to change the domain name to your own! If you don't have one, get one. They're cheap, and I had better luck with it than using the "file://". But that's maybe just me. Here's the javascript.

var hist = [];
var startUrl = 'index.html';
$(document).ready(function(){
loadPage(startUrl);
});
function loadPage(url) {
$('body').append('
Loading...
');
scrollTo(0,0);
if (url == startUrl) {
var element = ' #header ul';
} else {
var element = ' #content';
}
$('#container').load(url + element, function(){
var title = $('h2').html() || 'Hello!';
$('h1').html(title);
$('h2').remove();
$('.leftButton').remove();
hist.unshift({'url':url, 'title':title});
if (hist.length > 1) {
$('#header').append('
'+hist[1].title+'
');
$('#header .leftButton').click(function(){
var thisPage = hist.shift();
var previousPage = hist.shift();
loadPage(previousPage.url);
});
}
$('#container a').click(function(e){
var url = e.target.href;
if (url.match(/jonathanstark.com/)) {
e.preventDefault();
loadPage(url);
}
});
$('#progress').remove();
});
}

When you run it, it works - there's some text that appears that corresponds to the back button and actually works. Here's what you should see:




However, the "button" really needs to look like a button. That's where the css for the button comes in:

Example 3.11. Add the following to iphone.css to beautify the back button with a border image


#header div.leftButton {
font-weight: bold;
text-align: center;
line-height: 28px;
color: white;
text-shadow: rgba(0,0,0,0.6) 0px -1px 0px;
position: absolute;
top: 7px;
left: 6px;
max-width: 50px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border-width: 0 8px 0 14px;
-webkit-border-image: url(images/back_button.png) 0 8 0 14;
}





I think the line height might be the height of the text, not sure though. The shadow is black with an alpha (transparency) of .6. There's some description of them in the tutorial.


And now you get a nice, iPhone-ish looking back button:





The tutorial has a nice image which shows the undesirable gray box that appears over the back button when you highlight it (trying holding the button down to get a good look at it).

To get rid of that effect, all you have to do is add the following line to the css:

-webkit-tap-highlight-color: rgba(0,0,0,0);

And the effect disappears. Note that I had to start my iOS to have the change take effect - something which has occurred several times.

To get a darker effect when the button is clicked, first add this to the css:

#header div.leftButton.clicked {
-webkit-border-image: url(images/back_button_clicked.png) 0 8 0 14;
}


Then, add add the "clicked" line to the javascript:

$('#header .leftButton').click(function(e){
$(e.target).addClass('clicked'); <==== add this
var thisPage = hist.shift();
var previousPage = hist.shift();
loadPage(previousPage.url);
});

I'm having a tough time spotting this, but maybe when I try it on the iPod touch.

Note - make sure you put the "e" in there!

Ok, that wraps up this part. We'll hopefully finish up chapter 3 on the next go-round.

Jonathan Stark's Phone Gap tutorial - Ch03 - part 2

Today we continue to walk through Chapter 3 of Jonathan Stark's tutorial on creating iPhone apps without objective C.

Picking up where we left off, the next step is to only "hijack" links to your own site. If there's a link to an different site, you want to allow it to work normally. This is done by adding these lines to the hijack_links method:

var url = e.target.href;
if (url.match(/jonathanstark.com/)) {
//...execute the preventDefault and the load paghe

So now the method looks like something like this:

function hijackLinks() {
$('#container a').click(function(e){
var url = e.target.href;
if (url.match(/kanjisoft.com/)) {
e.preventDefault();
loadPage(e.target.href);
}
});
$('h1').html(title);
$('h2').remove();
$('#progress').remove();
}




So, to test this, we can add some external links and make sure they work. Let's modify lines development.html to include some links in the content section:

<div id="content">
<ul>
<li><a href="about.html">About</a></li>
<li><a href="blog.html">Blog</a></li>
</ul>

<h2>Development</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>




This get the following page:



If you click on the links, it just takes you to the respective page on your site. Now, let's change the "blog" link to be a google link:


<li><a href="http://www.google.com">Google</a></li>


So, it looks like this:





And if we click on it, we get the normal Google page:





That's it for now. We'll keep on going with chapter three in the next post.

Saturday, November 12, 2011

Walking through Jonathan Stark's Phone Gap tutorial - Ch03

Here's Chapter 3.

Important point, *easy to miss*: iphone.html is the code you need to be invoking from the browser, not index.html. It uses javascript to invoke index.html.

Huge gotcha: the code won't work unless you modify iphone.js to say "mydomain.com" instead of "jonathanstark.com".

Start with the posted html:


<html>
<head>
<title>Jonathan Stark</title>
<meta name="viewport" content="user-scalable=no, width=device-width" />
<link rel="stylesheet" href="iphone.css" type="text/css" media="screen" />
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="iphone.js"></script>
</head>
<body>
<div id="header"><h1>Jonathan Stark</h1></div>
<div id="container"></div>
</body>
</html>


Remember, this is iphone.html. It runs a javascript which loads index html.

Copy the posted code into "iphone.css". It's similar to chapter 2.

Now, create iphone.js with this code:

$(document).ready(function(){
loadPage();
});
function loadPage(url) {
if (url == undefined) {
$('#container').load('index.html #header ul', hijackLinks);
} else {
$('#container').load(url + ' #content', hijackLinks);
}
}
function hijackLinks() {
$('#container a').click(function(e){
e.preventDefault();
loadPage(e.target.href);
});
}


This does two things - first, it copies the area specified by the tags (either #header ul, or #content) into the #container section of iphone.html. It then turns every link into copied into a javascript function.

Note that there are no links to be copied in the #content section of the links, so there will be no links displayed except those from index.html.

If you run it in the iPhone simulator, it looks like this:





Next, get the "image loading" effect. Add this to the first line of the "loadPage" function:

$('body').append('
Loading...
');


And this to the end of the hijacklinks function:

$('#progress').remove();



When the page loads, you should see a little "Loading..." just below the "Jonathan Stark" header, which should disappear when the page shows up.

The next step is to add the css to make it look prettier...

#progress {
-webkit-border-radius: 10px;
background-color: rgba(0,0,0,.7);
color: white;
font-size: 18px;
font-weight: bold;
height: 80px;
left: 60px;
line-height: 80px;
margin: 0 auto;
position: absolute;
text-align: center;
top: 120px;
width: 200px;
}


The radius rounds the corners of the the box. the background color makes it black with an alpha (transparency) of 70%. The text color is white, it's 60 px from the left. Not sure what the purpose of line-hight and margin are, but they work. Now you should get a nice black box with white text showing up when the page loads.

There's a gotcha in the the next cool trick, which is to take each page's h2 and make the page's h1. Don't be fooled by the use of the word "title", he doesn't mean the html title. You do this by adding these lines just before the progress indicator is removed:

var title = $('h2').html() || 'Hello!';
$('h1').html(title);
$('h2').remove();



Note that if there's no h2, it will show "Hello". You shouldn't be getting that, but I think you might. I did.

Note that it will replace the previous h1 with the old h2, and get rid of the old h2.

This is what you should get. You can see it clearly in the before after at:

http://ofps.oreilly.com/titles/9780596805784/ch03_id35816678.html#beforeMovingHeadingToToolbar

and

http://ofps.oreilly.com/titles/9780596805784/ch03_id35816678.html#afterMovingHeadingToToolbar

See how the about moved up to replace "Jonathan Stark"?

However, if you get the hello, as I did, don't be shocked. I think the problem is that the initial invocation of iphone.html is stays in effect through the substitute command - index.html never gets called. And there is no h2 in iphone.html. The rest of the the links get transferred to by use of the loadPage(e.target.href);


loadPage(e.target.href);


To prevent this, you could do something like set a hidden variable the block that checks for the empty url, and check it later to see of you're on the first page, and if so account for that in the move logic. But, in the interests of moving on, we'll leave it at "hello". It works fine on the linked pages.

The next step is adding an elipse. I'm kind of off track again, because it looks like he's working from the chapter 2 linked pages. But, this is just to demonstrate wrapping, so I'll change the "Consulting Clinic" header to "A Consulting Clinic with an Edge for Your Company"

That leave an image that looks like this:



Add the ellipsis by adding this to the #header ht in the css:


max-width: 160px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;


Max width is just what it says - a max width. The overflow:hidden means "chop off" and content extend beyond the (max) width. white-space: nowrap prevents wrapping, and of course text overflow ellipsis tells it to create an ellipsis in an overflow situation. Let's check it out:







Cool.


The next step is to add this:

scrollTo(0,0);


The reason is, if you are somewhere on a page, and you click a link to get to another page, it will take you to the same section of the new page as the old. Well, it's the same page, iphone.html, and stark suggests this is the reason - you're not going to a new page. The javascript keeps you on the same page. To make this less egregious, you put the scrollto(0,0) command in.

To test this, we could try throwing a bunch of text into a couple of pages, iphone.html and consulting_clinic.html.

So, before we make the change, the first page load shows this:





When we link, it goes to the same area of the "new" but really the same page:




And finally after in inserting the (scroll 0,0), and going through the same sequence, we see it's gone to the top of the page:




We'll wrap this up tomorrow.

Tuesday, November 1, 2011

In App Purchase - tracking down an invalid product id

Ok, the "good" news I've actually gotten to the point where I'm receiving an invalid product id from the app store when I try to list the products available. The bad news is that, like the maddening "invalid binary" on app submission, there is no indication what soever of *why* the effing product id is invalid. Apple ... never mind.

Luckily, there is one man attempting to help this situation out. It's Troy Brant (I guess that's his name) on troybrant.net. He's made a list of possible cause of this error at

http://troybrant.net/blog/2010/01/invalid-product-ids/

Here's the list:

Have you enabled In-App Purchases for your App ID?

// Uhmmm...huh? Did I? Probably, let's check out iTunesConnect. Hmmmm don't see
// anything there. Let's get back to this.


Have you checked Cleared for Sale for your product?

// Yes. that's checked.


Have you submitted (and optionally rejected) your application binary?

// Yes, it's int developer rejected status

Does your project’s .plist Bundle ID match your App ID?

// This is a confusing question. My bundle id on itunes is com.myapp.myprofile
// This matches my entry in the plist. However on iTunes the app id is some number
// also, if you look in the the binary info on itunes, you see info under entitlements
// which includes something under "keychain access" that looks like an app id
// (the com.mycompany.myapp style) but is preceded with a some big alphanumeric number.


Have you generated and installed a new provisioning profile for the new App ID?

// Yes, I'm just about positive.

Have you configured your project to code sign using this new provisioning profile?

// Yes, I'm sure I went through that process.

Are you building for iPhone OS 3.0 or above?

// Yes, 4.3.3

Are you using the full product ID when when making an SKProductRequest?


// does this include the number? But yes I think I am.

Have you waited several hours since adding your product to iTunes Connect?

// Yes, many hours.

Are your bank details active on iTunes Connect? (via Mark)

// Yes

Have you tried deleting the app from your device and reinstalling? (via Hector, S3B, Alex O, Joe, and Alberto)

This worked??? My God, it worked. Thank you, Hector, S3B, Alex O, Joe, and Alberto and of course Troy!



Is your device jailbroken? If so, you need to revert the jailbreak for IAP to work. (via oh my god, Roman, and xfze)

We'll tackle that in the next post.


Monday, October 31, 2011

iOS and In app purchases - what's wrong with this picture?

Well, I'm in the middle of my next submitting stuff to apple struggle. For some reason, it always seems to be this titanic, time-consuming effort. What is the matter with those guys?

Anyway, here's where I'm at. I have an paid app on the app store. So, I've gotten through that hurdle. I even submitted an update to it. So, I've managed to successfully bash my way through that whole process.

I've also added in-app purchase code to my app. I've added a product in iTunes connect which can be purchased by my app. It's got the same bundle-id, everything.

The problems start of course with testing. You knew this wasn't going to be easy, didn't you?

When I run the code to test download the available product ids (I only have one), I don't get anything back from the download. Nor error messages or anything - just an empty list.

So, what could be the cause of this? I decided to look at the in-app products page, and saw this:

The first In-App Purchase for an app must be submitted for review at the same time that you submit an app version. You must do this on the Version Details page. Once your binary has been uploaded and your first In-App Purchase has been submitted for review, additional In-App Purchases can be submitted using the table below.



So, I hadn't submitted a new version yet. So, I did, and think this maybe let me do something like resbumit the app product, or save it, or something. So, at this point I've submitted both the app and the product. And the product now has a status of "wating for review". However, the app itself is showing the infomous "invalid binary" error.


So, I'll have to resolve the invalid binary error. But, what I don't know is if this affects the status of the product. The product is currently "waiting for review". Will it be waiting for review until I get a valid binary? Could the fact that I don't have the product approved be *causing* the invalid binary? Somehow, I doubt that. I hope not.

Actually, it's the same problem I had before - you just have to reselect your app distribution certificate when submitting it via organizer.

Ok, then according to this tutorial,

http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/

if you've added your product, you also have to submit an app-binary. Then cancel the app binary (you can do this in binary details, button on the upper right corner).

So, now you will basically have you app in "developer rejected" mode, and your in-app purchase in "waiting for review" status.

This is confirmed by this posting in SO:

http://stackoverflow.com/questions/6570163/invalid-product-id-in-inapp-purchase

So, my next step is to retry running the app.

Ok, wall the reason it wasn't printing "invalid product ID" is that my program wasn't checking for it. It turns out, if I run this piece of code (as suggested in the first url listed):

for (NSString *invalidProductId in response.invalidProductIdentifiers)
{
NSLog(@"Invalid product id: %@" , invalidProductId);
}

I get this output:

2011-11-01 14:42:36.274 JlptVocabularyQuiz[2751:707] IAHelper, received products results...
2011-11-01 14:42:36.282 JlptVocabularyQuiz[2751:707] Invalid product id: com.kanjisoft.JlptVocabularyQuiz.AdditionalVocab1

So, there you go. I've been getting this all along, probably, just didn't know. It's tuesday, and I originally cleared that message on Sunday night, I think. So, it's been nearly 48 hours, and still I'm getting the problem.

So, then next thing to do is go through this checklist:

Have you enabled In-App Purchases for your App ID?
Have you checked Cleared for Sale for your product?
Have you submitted (and optionally rejected) your application binary?
Does your project’s .plist Bundle ID match your App ID?
Have you generated and installed a new provisioning profile for the new App ID?
Have you configured your project to code sign using this new provisioning profile?
Are you building for iPhone OS 3.0 or above?
Are you using the full product ID when when making an SKProductRequest?
Have you waited several hours since adding your product to iTunes Connect?
Are your bank details active on iTunes Connect? (via Mark)
Have you tried deleting the app from your device and reinstalling? (via Hector, S3B, Alex O, Joe, and Alberto)
Is your device jailbroken? If so, you need to revert the jailbreak for IAP to work. (via oh my god, Roman, and xfze)

We'll tackle that in the next post.

Tuesday, October 25, 2011

Putting adds into an iOS app

Well, after taking a couple of days off after successfully *submitting* version 1.1 of the app for development, let's take a look at what involved in putting advertising into our app. I'm excited about this because I understand advertising on the iPhone (via iAd) is significantly better than that offered by Android. Either way, I'm not expecting to get rich.

Ok, let's google iAd iphone or something like that.

Ok, here we go:

http://developer.apple.com/iad/

Rich, Captivating Ads that Keep Users in Your Apps

iAd rich media ads combine the emotion of television with the interactivity of the web to deliver a unique, compelling experience like no other on a mobile device. Users can dive into immersive ads without ever leaving your app. When they tap the ad, your app pauses and with a single tap they close the ad and resume exactly where they left off in your app. And users have been delighted, spending an average of one minute interacting with iAd rich media ads.
Watch the highlight video
Generate More Revenue from Your Apps

iAd is the best way to generate revenue from your apps when compared to other mobile advertising platforms. Hundreds of iOS developers are already making more than $50,000 per quarter serving iAd rich media ads.

iAd is also easy to implement. Since it’s built into iOS, you don’t have to worry about integrating another SDK. Just decide where you want ads to appear in your app and with a minimal amount of code your app is ready to serve iAd rich media ads — Apple does the rest. We sell the ads and serve them to your app while you collect 60 percent of the advertising revenue generated.



Great. I especially like the easy to implement, built into iOS part.

Ok, let's see.

Arghh! High school kids everywhere. I've already moved once. As soon as I get set up, a new pack takes up the table next to me.

Ok, I did this:

Click through the Developer Advertising Services Agreement in iTunes Connect
The first step in joining the iAd Network is to click through the Developer Advertising Services Agreement, which can be found in the Contracts, Tax, and Banking Information module. In addition, you will be asked to set up your banking and tax information if you don't have a paid app on the App Store.




Next:

Enable your app for iAd rich media ads
Once you have clicked through the Developer Advertising Services Agreement in iTunes Connect, you can enable your app for iAd rich media ads in the Manage Your Applications module within iTunes Connect.



Ok, let's look at "Manage your appications".

Ok, before I enable them, I think I need to do something about actually creating the adds.

Here we go:

Creating banner views

Use the ADBannerView class to dedicate a portion of the screen to display banner advertisements in your user interface.

Once created, a banner view automatically downloads new iAd rich media ads to display to your users. View the following resources to learn how to create banner views in your app.



Ok, so it's the AddBannerView class I'm looking for.


Wow - if I click through to the video, there are like 105 of them. Oh, there's one for in-app purchases. Sweet. It's all at this url:

http://developer.apple.com/videos/wwdc/2011/?id=505

Well, I don't have the bandwidth here at the cafe to view them, so, let's look for text links.

Here's one:

http://answers.oreilly.com/topic/1747-how-to-implement-iad-on-your-iphone-applications/

With the introduction if iOS 4, Apple have also introduced iAds, a mobile advertising platform that aims to convey a new concept of interactive marketing on your smart-device. Through CSS3 and multi-touching, you will get a WebKit-powered framework to deliver optimal user experience right within your app. This layer in fact sits on top of your application, so it won’t use your app-reserved memory, as well as not leaving the app when the user interacts with the ad, but rather displayed on top of it.


Putting Ads in your app

The ADBannerView is the core class within this framework. which is simply a view and should be part of the view controller, that you have in your app.

// Ok, this is important (above)

This class manages and retrieves and displays the ads from the iAd network, managing the user interaction. The only thing you need to worry about is:

placement of the banner within your view controller; // ok
respond to networking issues (this will be discussed later); // ok

So in Interface Builder, you drag onto your view controller, the Ad BannerView, the same way you would add any other view in IB. In XCode, you make sure to add the iAd framework to your list of frameworks.

// Good, makes sense. I've been doing stuff in iB.

Working with the Banner view lifecycle

OK, so you have the banner, the next thing to concern yourself with is the managing of connectivity and various inventory changes that might occur throughout the user experience.

That is, the banner queries the iAd network for a new ad (inventory content), and this depends on connectivity. ADBannerView may not always have content in it, because you may not have any network signal on your iPhone, you may have your phone in Airplane mode.

// ok

So the two lifecycle states you have is managed through the ADBannerViewDelegate callbacks:

bannerViewDidLoadAd - Has ad content.
// ok

bannerView:didFailToReceiveAdWithError - When you have network issues and it failed to load the ad during it’s cycle. You also may not have any inventory because of how set up your ad targeting, where there is no ads based on your filter settings.

// ok

So, if we do get a failure, which is not a real error, but just something to let you know that you perhaps would not want to have an empty box, allowing you to hide (place offscreen) and back on screen when you do have content. Space is prime real-estate in the mobile world. An example of how to do this:

#pragma mark -
#pragma mark ADBannerViewDelegate methods

(void)bannerView:(ADBannerView *)bannerdidFailToReceiveAdWithError: (NSError *)error
{
//hide banner
[self moveBannerFromScreen];
}


(void)bannerViewbannerdidLoadAd: (AdBannerView *)banner
{
//show when we have content
[self moveBannerBackOn];
}

(void)moveBannerBackOn
{
//bring back to frame
CGRect theBannerFrame = self.bannerView.frame;
theBannerFrame.origin.y = self.view.frame.size.height - theBannerFrame.size.height;

//shrink existing table
CGRect originalTableFrame = self.tableView.frame;
CGFloat newTableHeight = self.view.frame.size.height - theBannerFrame.size.height;
CGRect newTableFrame = originalTableFrame;
newTableFrame.size.height = newTableHeight;

self.tableView.frame = newTableFrame; //you may want to animate this
self.bannerView.frame = theBannerFrame;

}

(void)viewDidLoadAd {
...
//hide at start until you know you get ad
[self moveBannerFromScreen];
...
}

// I guess viewDidLoadAd is different than bannerViewdidLoadAdd

Responding to actions

OK now you are seeing the ad. The ideal thing to do is reduce activity in your app (pause action when the ad is showing), and to ensure the best experience for the user when viewing the ad. Also you should save minimal state (i.e the tab you are on, rather than content), and when the ad is complete, resume the ap.

In ADBannerViewDelegate, when a user action begins on the ad (you pausing your app) you get the following callback:


- (BOOL) bannerViewActionShouldBegin: (ADBannerView *)banner willLeaveApplication: (BOOL)willLeave
{
//pause your app here
...
return yes;
}

//When the user comes back you get the callback:
- (BOOL) bannerViewActionDidFinish: (ADBannerView *)banner
{
//resume your app here
...
return yes;
}



And there you have it. For more on iAds and the iAd framework, please visit the Apple Developer Centre for documentation and sample code.




Ok. Sounds good. It's based on the concept of the whole ViewController / Delegate thing.

Here's another one:

http://jamesjennin.gs/post/759225411/the-5-minute-guide-to-implementing-iad-correctly


“But James,” my lone reader asks themselves, “is it even possible in implement iAd incorrectly?” Certainly, dear reader. In fact, Apple made it very easy to do so. See, the iAd banner class (ADBannerView) is right there in the Interface Builder library, so a naive programmer (like me) might just drag that into the interface, rebuild, code sign, deploy."

Ok, this is good. It's not going to be that easy, but it looks pretty easy at this point.

Ah, here's the problem. You might get this if you submit your app:

“We noticed that your app, Privacy for Facebook, is displaying an empty iAd banner when ad content is not available. The banner within the app should be hidden whenever ad content is not being served by iAd.”

So, hiding the window isn't optional.

Basically, displaying a blank banner is a no-no. There are a couple reasons why banners may not be displayed: they haven’t rolled out or aren’t available in a particular locale, there is no ad inventory available for your specific app, or there could just be a network issue. Apple wants you to make sure you never show blank banners.



So here’s a quick and dirty way of doing it correctly (assuming a portrait orientation, landscape is left as an exercise for the reader):



1. Add an ADBannerView to your interface, but just off screen. In this example, the ad should be at the bottom of the interface, so the origin of the ADBannerView is 0,460.

Ok. So, the height is presumably 460.

2. Set the view controller as the ADBannerView’s delegate.

Ok, that's good. I can just use whatever the regular view controller is as the app delegate.

Make sure you’ve included the iAd framework in the view controller’s .h file:
3. #import

4. Have the view controller implement ADBannerViewDelegate, and add a bool bannerIsVisible; to the interface


- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
if (!bannerIsVisible)
{
NSLog(@"bannerViewDidLoadAd");
[UIView beginAnimations:@"animateAdBannerOn" context:NULL];
banner.frame = CGRectOffset(banner.frame, 0, -50);
buttonFrame.frame = CGRectOffset(buttonFrame.frame, 0, -50);
web.frame = CGRectMake(web.frame.origin.x,
web.frame.origin.y,
web.frame.size.width,
web.frame.size.height-50);
[UIView commitAnimations];
bannerIsVisible = YES;
}
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
if (bannerIsVisible)
{
NSLog(@"bannerView:didFailToReceiveAdWithError:");
[UIView beginAnimations:@"animateAdBannerOff" context:NULL];
// assumes the banner view is at the top of the screen.
banner.frame = CGRectOffset(banner.frame, 0, 50);
buttonFrame.frame = CGRectOffset(buttonFrame.frame, 0, 50);
web.frame = CGRectMake(web.frame.origin.x,
web.frame.origin.y,
web.frame.size.width,
web.frame.size.height+50);
[UIView commitAnimations];
bannerIsVisible = NO;
}

}

Ok. This is great. I'm just going to patch this code right in, and see what happens.

Ok, I just added the banner view to the start screen. Perfect. It's off the visual display - out of sight, out of mind.

Wait, I don't get step 2 - how to set the view controller as the delegate. I did it for the UIPicker, but I don't get how the other part works.

I just found and example that does this:

adView.delegate=self;

Ok, so if I do the import (next step) first, I can then do this:

IBOutlet ADBannerView *bannerView;

Here's the previous step for adding the import:

#import

Ok, now just connect it.

And then add this line on the viewDidLoad:

bannerView.delegate=self;

Ok, well whatever pieces I'm reducing to fit the ad in aren't the same ones in the post. I just need to figure out if I need to reduce the UIView and the image, or maybe just the image. I'll pick this up later.

Ok, here's what I ended up doing. Most of the recommendations seemed to have using CGIRectOffset, to move the banner up or down , like so:

// This moves the banner up 50 pixels
banner.frame = CGRectOffset(banner.frame, 0, -50);

// This moves the banner down 50 pixels
banner.frame = CGRectOffset(banner.frame, 0, 50);

The problem I had with it was using IBuilder to place the original banner where I wanted it. Since the background image is longer than the 480 (or 460) due to the fact that doing it like that was the only way I could get it to not display the top of the background image, just putting it below that wasn't any good - moving it up 50 wouldn't move it up all the way.

So I decided to just start it out at the *viewable* position. This way I could fit it precisely where I wanted to on the image. To move it to the correct position every time, I just saved the original frame on the viewDidLoad for the delegate, and then did the following on - (void)bannerViewDidLoadAd:(ADBannerView *)banner

{
NSLog(@"StartController, bannerViewDidLoadAd called");
if (!self.bannerIsVisible)
{
[UIView beginAnimations:@"animateAdBannerOn" context:NULL];
// banner is invisible now and moved onto the screen on 365 px
banner.frame = originalAdFrame;

[UIView commitAnimations];
self.bannerIsVisible = YES;
}
}


This assured that it would be placed in the original position on a viewable add.

On the

- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error

method, it seemed like no matter what I did, it was invisible anyway. So, just play it safe, I move it down 50, using the original frame as reference:


- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
NSLog(@"StartController, didFailToReceiveAdWithError called");

if (self.bannerIsVisible)
{
[UIView beginAnimations:@"animateAdBannerOff" context:NULL];

CGRect newBannerFrame = originalAdFrame;

int xVal = newBannerFrame.origin.x;
int yVal = newBannerFrame.origin.y;

newBannerFrame.origin.y = yVal + 50;

banner.frame = newBannerFrame;

[UIView commitAnimations];
self.bannerIsVisible = NO;
}
}