- I tried putting my locally hosted pages in a subdirectory after all, and they don’t work. Looks like a detour into mod_rewrite is required.
- I wanted to add an entry to the user’s calendar using html5. I found some sample code that uses WAC‘s deviceapis variable, but no information on how to gain access to this. This stack overflow post suggests that there is no way to do it with phone gap (at least as of late 2011), but there is a suggestion of generating an iCalendar file on the fly. And this doesn’t look hard to do, e.g. this stack overflow post and the wikipedia entry explain the format. I find (using my increasingly valuable localhost) that if I just point the browser to a test.ics file, it immediately opens up iCalendar with the new event. And likewise, if a php script outputs the appropriate format and filename (as per the stack overflow post above). Time zone formatting is discussed here.
Just came across Indie Game: the Movie – I’ll have to go see it!
Time for sound. In iOS it looks like sound can be played by AVAudioPlayer, and recorded by AVAudioRecorder. The broader framework is called AVFoundation. However, at a very low level there is also the Core Audio framework, which includes the Audio Toolbox and Audio Unit frameworks.
There are some great sample projects available at Apple’s Audio & Visual Starting Point:
- AddMusic shows how to pick songs from the iTunes library. This also includes setting up an AVAudioSession, which I’m not sure is necessary.
- avTouch shows how to play sounds using AVAudioPlayer.
- SpeakHere shows how to record sounds using AQRecord. On closer inspection this looks extremely daunting.
- AVAudioRecorder is another approach that looks a lot easier, as documented in this Stack Overflow post, and this Techotopia post. However, there is a subtlety when you try to play the recorded sound in an AVAudioPlayer – for some reason using
self.recordingPlayer = [[AVAudioPlayer alloc]
does not ever call the delegate routines when the URL is that of the recorded sound (in either the simulator or the device). So instead, I used
NSData* recordedData = [NSData
self.recordingPlayer = [[AVAudioPlayer alloc]
- which worked fine. Another gotcha is to make sure the AVAudioPlayer object is not disposed of at the end of the method, but before the playing actually happens. My solution was to make it an instance variable (as in the sample code above).
- Audio UI Sounds (SysSound) shows how to play alert sound-effects, but on closer inspection comes with the warning this should not be used for other sound effects, e.g. in games.
- OpenAL is one solution, with sample code called oalTouch. This comes with some C code (MyOpenALSupport.c) which looks handy, if a little daunting. Note that to call this with Automatic Reference Counting (ARC), you need to use
(__bridge CFURLRef) and then delete the line
CFRelease(fileURL);. I forgot this last part and it took a while to realise what was going on. There is also a great basic tutorial by Ben Britten.
The Multimedia Programming Guide – Audio has more context. And as usual, Ray Wenderlich has a great summary and tutorial.
I found strange behaviour with AVAudioSession.
- You can provide a category to the session, and I chose
AVAudioSessionCategorySoloAmbient, which is meant to silence sound from other apps. However it seems to also silence OpenAL and other AVAudioPlayer sounds from the same app. Switching to
AVAudioSessionCategoryAmbient solved this problem for me.
- I had set my category to
AVAudioSessionCategoryAmbient while setting up the audio recorder, but changed it to
AVAudioSessionCategoryRecord just before the
record command; I changed it back again after recording stops. This worked on the simulator, but on the device the call to
record simply returned NO with no explanation, and nothing was recorded. I spent most of a day trying to work out why. The answer: use the category
AVAudioSessionCategoryPlayAndRecord while setting up the recorder.
Native iPhone apps can access geolocation data in the background. Can HTML5? Based on this stack overflow posting, I suspect not.
Mark and I worked together today, and the aim was to get the camera working in the app, using code on the server.
We also decided to host a local version of the server on my Mac, to streamline testing. It was getting a bit cumbersome to use Filezilla to transfer changes to the server code back to the server every time we wanted to test a bug. This was pretty tricky to get working with phone gap, so here’s a summary of how we did it:
- We downloaded MAMP (Macintosh, Apache, PHP and MySQL packaged together).
- We then needed to transfer both the server web pages and the server database to my Mac, and then point the local copy of the web pages and the phone gap shell to the local host.
- Go to the server’s phpMyAdmin and export the relevant database. We found that gzipping it did not work, but zipping it did.
- On the MAMP local host homepage (localhost:8888) there is a link to phpMyAdmin, where you can import the above file.
- MAMP looks for the webpages at Applications/MAMP/htdocs by default. We chose to put all our local copy of the server webpages there (though in retrospect I should probably have put them in a subdirectory), and then add a new project in Eclipse using existing files in that directory (though the workspace remains elsewhere).
- Note that files that start with a dot, like .htaccess, do not display by default in the Finder, so when copying files around, they can get left behind. I needed to recopy .htaccess into the htdocs directory.
- In our local webpages, we changed the mysql_connect statement to use localhost:8889, with user root and password root. Obviously we will not put this change on the server.
- To run this on a physical device (whether it’s a phone gap app or a regular objective-c app), we cannot refer to ‘localhost’ but the actual local IP address. We looked this up in the terminal (although it is also in the System Preferences -> Network, or you can search the Mac for Network Utilities – it is not the same as you find at whatismyip.com) and added it to the whitelist, and referred to it in the source code instead of racingtadpole.com. To complicate matters, this address has changed for me over time.
Voila! Dinner is ready, so I must end there.
I’ve been reading Tom Butler-Bowdon’s “Never too late to be great”, so it seems appropriate to say: today is the first day of the next 10 years. On leave from my normal work, I have devoted the day to programming – a passion of mine since 5th grade, when I used to write out code on paper to type in on my friend Russell’s computer (before my family had one). Now I have two weeks to focus on a few projects. I won’t say what they are yet, but they promise to be awesome. Of course.
In the process, I got to help my son put together his 7th birthday present (a lego fire temple – lego has transformed from space to ninja since last I looked); enjoy a beautiful sunny May day; put out some washing; and pick up the kids from school. And finish the day by setting the bread machine to have some corn bread ready in the morning. Which is a long way of saying, I had a great first day.
This diary has its origins in some advice I received last week: inch closer to your goals every day. (Though I think it was more eloquent in its original, now forgotten, form.) It seems pertinent to record this inching process, since otherwise it will be lost in time.
What did I achieve today?
My friend Mark and I are working on a website that will also be an app, and I’m working on “app”-ifying the existing website. Mark’s built the site using jQuery mobile and it’s looking good in a web browser on both a desktop and an iPhone. But we want it to be an app, with access to the phone’s phone-y capabilities. So we have decided to use Phone Gap, which effectively provides a shell app into which you can put a whole lot of webby content (html and stuff). Loading Phone Gap 1.6 and getting the traditional “Hello World” demo going on an iPhone was not too hard (I have already used Xcode a bit).
My goals for the day were: point the app to Mark’s server so that his website suddenly looks like an app; and try to get the app to take a photo.
These goals were happily achieved. To point the app to a server, you need to:
- Add the server’s URL to the “white list” (e.g. in the project in Xcode, double-click on Supporting Files/Cordova.plist, and add “*.racingtadpole.com” to ExternalHosts, for example).
- Set OpenAllWhitelistURLsInWebView to YES, so that links open in the app rather than in a separate web browser.
- In index.html, add
<meta http-equiv=”REFRESH” content=”0; url=http://www.racingtadpole.com”>
so that the app immediately (with a 0 second delay) shows the contents of the website.
- I found there is always a white flash before the website displayed. I am told using the latest versions of jQuery and jQuery Mobile should fix this; I’ll report back on this tomorrow.
Getting the app to take a photo is well described in the Apache Cordova API documentation. The only hassle I had was I forgot to change the reference near the top of the full example from
- My plan is to put cordova-1.6.0.js on the server and truly have a tiny shell of an app. But will this be “app”y enough for the App Store?