Wednesday, September 17, 2008

WRNI Radio - now available in the iTunes App Store!

I'm happy to announce that WRNI Radio, our first iPhone application, is now available as a free download in the iTunes App Store.

WRNI is Rhode Island's only NPR radio station. The WRNI Radio app allows one to listen to the radio station live over the Internet via the iPhone Edge, 3G, of WiFi connection. This app was made as a free public work for WRNI in partnership with Glad Works, the ad agency responsible for WRNI's web site.

SynergyKM Mouse Scrolling Fix

SynergyKM is a very handy little Mac utility that allows you to share one keyboard and mouse across multiple computers. It's based on the utility called Synergy, which runs on many different computer platforms (Mac, Windows, Linux, etc.). So, if you are sitting at a desk with multiple monitors for various systems, you can choose one system as the master and use its sole keyboard and mouse.

Here's an example of using SynergyKM to designate the layout of the various monitors.

Once you define the layout of the various monitors and have Synergy running on all the machines, you can simply move the mouse off of one screen and it will automatically appear on the next screen. You can even share the cut-and-paste buffer across the machines.

All this is very nice, but SynergyKM hasn't been updated since 2006. One problem I've discovered is if you are on a Mac and are using a scroll wheel or trackpad to perform scrolling on a different monitor, the scrolling is super crazy fast.

Fortunately, Synergy is an open source project, so I was able to figure out the problem. SynergyKM is using a deprecated function for generating mouse scrolling events. The problem - for those interested - was the older method for generating a mouse scrolling event was based on scrolling numbers of "lines" of text. However, the values being uses for the scrolling deltas were based on pixel differences, not line differences. The newer method (CGEventCreateScrollWheelEvent) for generating scrolling events can be configured to use either line or pixel counts.

It's only a few lines of code that need changing. Specifically, edit the COSXScreen.cpp file, and search for the function COSXScreen::fakeMouseWheel(). Replace it with this:

COSXScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
CGEventRef my_event;

if (xDelta != 0 || yDelta != 0) {
my_event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 2, mapScrollWheelFromSynergy(yDelta), -mapScrollWheelFromSynergy(xDelta));
CGEventPost(kCGHIDEventTap, my_event);
//CGPostScrollWheelEvent(2, mapScrollWheelFromSynergy(yDelta), -mapScrollWheelFromSynergy(xDelta));

Also, look for the function COSXScreen::mapScrollWheelFromSynergy() and replace it with this:

COSXScreen::mapScrollWheelFromSynergy(SInt32 x) const
// use server's acceleration with a little boost since other platforms
// take one wheel step as a larger step than the mac does.
// return static_cast(3.0 * x / 120.0);

// No boost in acceleration
return static_cast(x / 120.0);

Then, just recompile the project and take the new synergyc file generated and replace the existing synergyc executable on all your client machines. If you are using SynergyKM, synergyc will be located in:


(NOTE: you can not replace the file if Synergy is currently running.)

If you'd prefer to not have to recompile the file yourself, you can get my compiled version here. It's about 370K in size (compressed).

Thursday, September 11, 2008

iPhone Music Sleep Timer tip

Here's a very simple tip for using the iPhone and iPod Touch on a "sleep" timer when listening to music.

Open the Clock app and choose the Timer feature. Choose how long you'd like the device to stay on, and for the "When Timer Ends" setting, choose "Sleep iPod/iPhone". This will cause whatever app is running when the timer occurs to stop, even if the display of the iPhone/iPod is already shut off. It's quite handy if you are listening to music or using a third party app that provides streaming media and you want the iPhone/iPod to auto quit after a certain period of time.

I've seen several cases in iPhone forums where people have been requesting this feature in various apps, so I assume it must not be a well known that this feature is already built into the iPhone / iPod Touch.

Wednesday, September 10, 2008

Controlling iPhone UI elements from a background thread

If you are trying to control UI elements (such as setting a UILabel's text) from a background thread, it will not work using the normal methods provided by the UI class.

For example, suppose you want to change the text of a UILabel. Normally, you'd just call:

[myLabel setText:@"this is my text"];

However, this won't work if you make that call from a background thread. The problem is the main thread needs to perform the UI updates. (Cocoa Fundamentals Guide, page 134: "All UIKit objects should be used on the main thread only.")

Fortunately, Apple provides a method to call an object's selector, but have the processing occur in the main thread. This is done via the performSelectorOnMainThread method. Here's how one would set a UILabel object's text from a background thread:

[myLabel performSelectorOnMainThread : @ selector(setText: ) withObject:@"this is my text" waitUntilDone:YES];

This is also very handy to know if you are trying to control the status of a UIActivityIndicatorView from within a background thread. Since the UIActivityIndicatorView is something that is normally used to indicate a background task is busy doing something, it's nice if you can control it's visible status via the background thread. Here's how you would do it:

[busyIcon performSelectorOnMainThread : @ selector(stopAnimating ) withObject:nil waitUntilDone:YES];

It's important to note that unlike the call when using the setText: selector, in this case you must omit the colon (:) in the selector name. If you don't your app will throw an exception when this message is passed to your object.

Monday, September 8, 2008

Proper usage of the MPVolumeView class

This post is no longer relevant regarding the details on keeping the volume slider in sync with the system volume. The undocumented call is no longer needed. In fact, if you use the call to "_updateVolumeFromAVSystemController", Apple will now reject your app during the review process.

I leave this post here for historical reference, but it is no longer relevant and should NOT be used!!

When dealing with audio in an app on the iPhone and iPod Touch, it is sometimes useful to have a volume control slider displayed onscreen. Apple supplies a class called MPVolumeView which is supposed to perform this function, however the documentation on it is quite poor.

Specifically, the MPVolumeView class provides a slider, and as you drag it, it adjusts the system volume. However, what the class doesn't provide is a method to automatically have the slider move in response to someone adjusting the volume via the volume buttons on the side of the iPhone. So, you can set the volume with the slider, but then if you adjust the volume with the buttons on the side of the phone, the slider is not reflecting the proper volume. Many iPhone apps exhibit this problem and personally I find it annoying.

It turns out, the reason so many apps behave this way is because there's not an easy documented method to query the system volume (at least not one I could find!). The main method for querying the system volume is in the Celestial private framework and you aren't supposed to use private frameworks in released apps.

Fortunately, someone discovered there is a system notifications that is generated whenever the volume changes. Also, the MPVolumeView slider can respond to a message called "_updateVolumeFromAVSystemController" which will cause it to adjust it's position based on the current system volume level.

So, given the two pieces of information, one can register a notification process to automatically adjust the MPVolumeView slider whenever the volume is changed using the buttons on the side of the phone.

Here's how it can be done - check out the code at pastebin. I didn't write this code, I just happened to run into it when searching for better information about MPVolumeView. There isn't really much in the way of comments in the code, but hopefully it's easy enough to follow.

UPDATE: the pastebin code expired, so here's my own version of the code. It was based on the pastebin code. I don't include the declarations from the .h file, but it should be pretty easy to figure out that part. You'll want to put this in the viewDidLoad method for whatever view will be holding the MPVolumeView.

- (void)viewDidLoad {

// create a frame to hold the MPVolumeView
CGRect frame = volumeViewHolder.bounds; // CGRectMake(0, 5, 180, 0);
volumeView = [[[MPVolumeView alloc] initWithFrame:frame] autorelease];
[volumeView sizeToFit];
[volumeViewHolder addSubview:volumeView];

// Find the volume view slider - we'll need to reference it in volumeChanged:
for (UIView *view in [volumeView subviews]){
if ([[[view class] description] isEqualToString:@"MPVolumeSlider"]) {
volumeViewSlider = view;

[[NSNotificationCenter defaultCenter] addObserver:self

- (void) volumeChanged:(NSNotification *)notify
//NSLog(@"volume changed");

// NOTE::: Do NOT use this call. It is no longer necessary and Apple will reject
// your app if you use it!

[volumeViewSlider _updateVolumeFromAVSystemController];

NOTE: when you compile this, you will get a warning that says "'UIView' may not respond to '- updateVolumeFromAVSSystemController'" but it can be safely ignored. The compiler is complaining that it can't verify volumeViewSlider is an object that can receive that message. This is some of the magic that makes Objective-C so powerful. You can send messages to objects even though the compiler doesn't know if the object supports the message. It's also an easy way to shoot yourself in the foot if you aren't certain of what you are doing.

In this instance, we know the volumeViewSlider object does support that message since we hand picked it by recursively searching the MPVolumeView until we got to the MPVolumeSlider piece of the object.

Thanks go out to the anonymous person who posted that original snippet of pastebin code. It was exactly what I was looking for.

[Edit: I must emphasize, the above code should NOT be used!! Apple will now reject an app which uses it. There's a demo project showing how to use some other features of the MPVolumeView class here.

Sunday, September 7, 2008

Possible first iPhone app

A friend of mine works for a local web design company. This past Friday (3 days ago) he asked whether I might be able to make an iPhone app to complement a client's new web site. The client is a radio station, so he wanted an app which would be able to play the radio station's Shoutcast stream. I had a bit of free time this weekend and was able to spend a large chunk of time on it.

Today I'm happy to say I have now written an iPhone app that supports streaming media over WiFi, Edge, and 3G. It still need s a bit of polish - mainly to handle a few random error conditions (related to the unreliability of AudioFileStreamParseBytes() to properly parse the mp3 frame headers) - but the bulk of the work is done.

The biggest struggle was wading through Apple's documentation on Audio File Streams, Network Streams, and the like. The documentation is fine as a reference guide for specific function definitions, but it's really very very weak on examples. And that's the challenging part - tying it all together - so examples would have made it a much easier task.

Hopefully the app will be sent to the iTunes Store for approval within the week. At least, I hope there won't be any technical reasons it shouldn't be. I don't yet know about any bureaucratic ones, though. Maybe there might be some non technical hold-ups related to client approval and what-not.

Tuesday, September 2, 2008

Squash those bugs!

I just had to laugh when reading this sentence in the iPhone OS Programming Guide:

Xcode provides several debugging environments you can use to find and squash bugs in your code

So, with Xcode, unlike other IDEs, we don't just debug code. We get to squash bugs! I never really thought of debugging as such an aggressive activity.

If we squash bugs in our code, won't their guts still be splattered among the other lines of code, though?

This whole thing is just a really poor analogy...

All content copyright © 2009  Brian Stormont, unless otherwise noted.   All rights reserved.