Avoid the clobber: Nest your network activity indicator updates Avoid the clobber: Nest your network activity indicator updates
  • Home
  • Posts
  • Home
  • Posts

iPhone

Avoid the clobber: Nest your network activity indicator updates

On the iPhone, when you are doing anything that uses the network, you’re supposed to let the user know something’s going on, via -[UIApplication setNetworkActivityIndicatorVisible:]. This takes a boolean.

That’s all well and good, but if you have more than one object in your app that may do things with the network simultaneously, you’re going to clobber yourself.

A nice and easy solution: Maintain an activity counter and create a category on UIApplication to maintain it, and show or hide the indicator as appropriate. Then, whenever you start doing something with the network:

[[UIApplication sharedApplication] showNetworkActivityIndicator];

[[UIApplication sharedApplication] showNetworkActivityIndicator];

…And when you’re done:

[[UIApplication sharedApplication] hideNetworkActivityIndicator];

[[UIApplication sharedApplication] hideNetworkActivityIndicator];

Here’s a category that’ll do it:

Read More

Getting Data out of the iPhone while Debugging

Here’s a utility I whipped up quickly to save out a file from a hex string, as from [NSData description] — kinda a reverse hexdump.

While doing some debugging, I realised I needed to visualise an intermediate UIImage from the iPhone’s camera. Not being able to use the simulator, and thus be able to write to a file easily, this was my solution: po UIImageJPEGRepresentation(photo, 0.8) to print out the data as a hex string, then copied it to the clipboard, saved it as a text file, and used an NSScanner to scan in each int, fix the endianness and write it out as a file.

Insane? Maybe, but it did the trick.

Read More

Easy Delayed Messaging using NSProxy and NSInvocation

Sometimes it’s necessary to perform an action some time in the future, whether it’s disabling a button for a certain time interval after it’s pressed, performing an animation after a short wait, or triggering a reload of some data.

NSTimer is great for that purpose, as well as repeatedly performing actions, but it’s most convenient utility method, scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: only takes one ‘id‘ argument. Using the NSInvocation equivalent, scheduledTimerWithTimeInterval:invocation:repeats: requires creating and setting up the NSInvocation itself, which is always verbose and a pain.

NSProxy is a wonderful little construct that lets us interact with it like we were talking to the original object. I first learned how it works from a post by Shaun Wexler which shows an easier way to send a message on the main thread, by using the NSInvocation given via the NSProxy’s forwardInvocation: method. The same technique can be used to easily create a configured NSTimer.

So, instead of some awful thing like this:

NSInvocation *i = [NSInvocation invocationWithMethodSignature:[mapView methodSignatureForSelector:@selector(selectAnnotation:animated:)]];
[i setTarget:mapView];
MKAnnotation *annotation = view.annotation;
[i setArgument:&annotation atIndex:3];
BOOL flag=YES;
[i setArgument:&flag atIndex:4];
[NSTimer scheduledTimerWithTimeInterval:0.5 invocation:i repeats:NO];

NSInvocation *i = [NSInvocation invocationWithMethodSignature:[mapView methodSignatureForSelector:@selector(selectAnnotation:animated:)]]; [i setTarget:mapView]; MKAnnotation *annotation = view.annotation; [i setArgument:&annotation atIndex:3]; BOOL flag=YES; [i setArgument:&flag atIndex:4]; [NSTimer scheduledTimerWithTimeInterval:0.5 invocation:i repeats:NO];

We can do this:

[(MKMapView*)[TPTimerProxy scheduledTimerProxyWithTarget:mapView timeInterval:0.5 repeats:NO] selectAnnotation:view.annotation animated:YES];

[(MKMapView*)[TPTimerProxy scheduledTimerProxyWithTarget:mapView timeInterval:0.5 repeats:NO] selectAnnotation:view.annotation animated:YES];

Here’s the juice:

Read More

Playing audio in time using Remote IO

I got an email today with a question about how to handle playback of audio in time, synchronised with a clock. My ‘musical notepad’ app Loopy does this, and I thought I’d briefly explain how.

Any app that makes use of the Remote IO audio unit framework (which is generally necessary for the kind of responsiveness required in a realtime musical app) provides audio to the hardware via a callback, which is periodically called when the hardware is ready for more.

The trick here is to provide the right chunk of samples in this callback for the current time position.

Loopy achieves this by:

1. Keeping track of where in the timeline we are at the time the callback is called

This is easily accomplished by keeping a record of the time the clock was started, subtracting this from the current time, and possibly performing a modulus with the tempo. For example:

  • (now - startTime) % timePerBar gives the number of time units into the current bar (lets call it timeIntoBar).
  • timeIntoBar / (timePerBar/beatsPerBar) gives the number of beats into the current bar, and
  • timeIntoBar % (timePerBar/beatsPerBar) gives us the time into the current beat.

2. Determining first if we should be playing audio at this time, and if so, which samples should be playing

This involves first converting our time units from step 1 into samples. For instance, you can convert microseconds to samples by dividing your time by 1000000/yourSampleRate. Aside: Of course, you can convert back from samples to time by multiplying instead of dividing.

Next, in the case of Loopy’s metronome, for example, we test for whether samplesIntoBeat < sound.lengthInSamples. If so, that means we should be playing audio. If the sound was a loop, of course, we could be always playing.

The offset into the sound, in samples, is just samplesIntoBeat, in the case of the simple metronome. In the case of a loop, you probably will be more interested in the number of samples into your loop — so instead of determining (now - startTime) % timePerBar, you may be interested in (now - startTime) % timePerLoop.

So, we want to return the requested number of samples starting from this offset into the sample array representing our audio.

3. Returning smooth audio in time

Note that if you just go returning any old set of samples, willy-nilly, you're going to get nasty clicks and pops from discontinuities you get by not matching the start of your next buffer to the last one.

To ensure smoothness, Loopy keeps track of the offset of the last samples we returned, and just return the immediately following bunch of samples — unless we're more than some threshold number of samples out of time, in which case we'll suffer the pop in order to stay synchronised. Actually, you can even generally avoid the pop if you smoothly blend buffers over a short time, removing any discontinuity.

Final words

The example above was a relatively trivial one, for a metronome sound. For longer audio that may span multiple bars, you'll probably want to perform a modulus by the length of your audio clip, possibly quantised to your time signature, and possibly using a per-loop time base, so you can start the loop at any point in your timeline and have it begin from the start. This is something Loopy doesn't currently do — Loopy will keep your loops synchronised so when you start a loop playing, it'll play whatever part corresponds to the current timeline, not from the start of the loop. Maybe it'll be an option in the future?

I wrote a little about the timing of loops in my second article on Loopy's implementation.

Read More

iPhone debugging tip: Breaking on exceptions and reading their content

Just a quick one: This may be obvious to many devs, but it’s worth noting. One common and useful debugging technique is breaking on exceptions, so that you can see exactly where in your app’s flow a breakpoint occurs.

This can be done by adding -[NSException raise] and objc_exception_throw to your breakpoints list.

Once an exception happens, you can then check out the exception itself to see what went wrong. The approach varies between platforms. If you’re in the simulator (or any Mac OS X app running on Intel), the exception will be stored in the $eax register. Take a look by typing:

po $eax

If you’re on the iPhone, it’ll be $r0, so:

po $r0

Read More

A trick for capturing all touch input for the duration of a touch

If you’ve ever tried to implement an interactive control that makes use of gestures within a UITableView, or tried to implement a view that you can drag around, like pins on a MKMapView, you’ll know that you’re either generally thwarted by the scroll view (and the table view will just scroll, instead of your control getting the vertical drag gesture), or as soon as the touch moves outside the bounds of your view, you’ll get no more touchesMoved events, making for a very frustrating dragging interaction.

Some platforms give you a way to capture all pointer input for a time, but this doesn’t appear to be available out of the box on the iPhone — at least, I haven’t found it.

Here’s a way to make it work:

  • Subclass UIWindow and replace sendEvent: with your own method
  • Provide a way for objects to be registered with your UIWindow subclass to gain ‘touch priority’ – either add a property that taken a UIView/UIResponder, or add a mutable array to be able to register multiple views.
  • When you receive a touch began event, check to see if the touch was within the bounds of any ‘priority’ views – if they are, the view subsequently gets all events until the touch ended event.
Read More

iPhone/Mac animation for custom classes: Property animation for more than just CALayer

I recently wrote a custom view — a 3D vintage-looking pull lever — that provided a continuous property to control the state. I wanted to animated this smoothly, a-la CABasicAnimation, but couldn’t find a built-in way to do so.

So, I wrote a class that provides similar functionality to CABasicAnimation, but works on any object. I thought I’d share it.

Features:

  • From/to value settings (currently only supports NSNumber and scalar numeric types, but easily extendable)
  • Duration, delay settings
  • Timing functions: Linear, ease out, ease in, and ease in/ease out
  • Animation chaining (specify another configured animation for chainedAnimation, and it’ll be fired once the first animation completes)
  • Delegate notification of animation completion
  • Uses a single timer for smooth lock-step animation
  • Uses CADisplayLink if available, to update in sync with screen updates

Use it like this:

- (void)startMyAnimation {
  TPPropertyAnimation *animation = [TPPropertyAnimation propertyAnimationWithKeyPath:@"state"];
  animation.toValue = [NSNumber numberWithFloat:1.0]; // fromValue is taken from current value if not specified
  animation.duration = 1.0;
  animation.timing = TPPropertyAnimationTimingEaseIn;
  [animation beginWithTarget:self];
}

- (void)startMyAnimation { TPPropertyAnimation *animation = [TPPropertyAnimation propertyAnimationWithKeyPath:@"state"]; animation.toValue = [NSNumber numberWithFloat:1.0]; // fromValue is taken from current value if not specified animation.duration = 1.0; animation.timing = TPPropertyAnimationTimingEaseIn; [animation beginWithTarget:self]; }

Make sure you also include the QuartzCore framework, used to access CADisplayLink, if it’s available.

It’s BSD-licensed.

Grab it here:
TPPropertyAnimation.zip

Read More

Links for May 30th through August 8th

Links for May 30th through August 8th:

  • cufón – fonts for the people A very impressive framework that embeds any font into a website, via javascript and the canvas element. Great cross-browser support.
  • mikeash.com: Method Replacement for Fun and Profit Method replacement and method swizzling in Objective-C.
  • Core Data Tutorial: How To Use NSFetchedResultsController | Ray Wenderlich
  • TwitThis – Use Multiple Twitter Clients on your iPhone Application The class TwitterClientManager loads a list list of supported Twitter clients is loaded from a plist file, which can be extended to support more clients in the future;
    Each Twitter client is represented by an instance of the TwitterClient class;
    The user can choose his preferred Twitter client at any time, and launch the application by a simple touch; the TwitterClientManager class stores the selected value in the user settings.
Read More

Hi! I'm Michael Tyson, and I run A Tasty Pixel from our home in the hills of Melbourne, Australia. I occasionally write on a variety of technology and software development topics. I've also spent 3.5-years travelling around Europe in a motorhome.

I make Loopy, the live-looper for iOS, Audiobus, the app-to-app audio platform, and Samplebot, a sampler and sequencer app for iOS.

Follow me on Twitter.

Posts pagination

« 1 … 4 5 6 … 9 »
© 2021 A Tasty Pixel.