How to programmatically sense the iPhone mute switch?

Thanks, JPM. Indeed, the link you provide leads to the correct answer (eventually. ;) For completeness (because S.O. should be a source of QUICK answers! )...

// "Ambient" makes it respect the mute switch
// Must call this once to init session
if (!gAudioSessionInited)
{
    AudioSessionInterruptionListener    inInterruptionListener = NULL;
    OSStatus    error;
    if ((error = AudioSessionInitialize (NULL, NULL, inInterruptionListener, NULL)))
    {
        NSLog(@"*** Error *** error in AudioSessionInitialize: %d.", error);
    }
    else
    {
        gAudioSessionInited = YES;
    }
}

SInt32  ambient = kAudioSessionCategory_AmbientSound;
if (AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (ambient), &ambient))
{
    NSLog(@"*** Error *** could not set Session property to ambient.");
}

I answered a similar question here (link). The relevant code:

 -(BOOL)silenced {
     #if TARGET_IPHONE_SIMULATOR
         // return NO in simulator. Code causes crashes for some reason.
         return NO;
     #endif

    CFStringRef state;
    UInt32 propertySize = sizeof(CFStringRef);
    AudioSessionInitialize(NULL, NULL, NULL, NULL);
    AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &state);
    if(CFStringGetLength(state) > 0)
            return NO;
    else
            return YES;

    }

Some of the code in other answers (including the accepted answer) may not work if you aren't in the ambient mode, where the mute switch is respected.

I wrote the routine below to switch to ambient, read the switch, and then return to the settings I need in my app.

-(BOOL)muteSwitchEnabled {

#if TARGET_IPHONE_SIMULATOR
    // set to NO in simulator. Code causes crashes for some reason.
    return NO;
#endif

// go back to Ambient to detect the switch
AVAudioSession* sharedSession = [AVAudioSession sharedInstance];
[sharedSession setCategory:AVAudioSessionCategoryAmbient error:nil];

CFStringRef state;
UInt32 propertySize = sizeof(CFStringRef);
AudioSessionInitialize(NULL, NULL, NULL, NULL);
AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &state);

BOOL muteSwitch = (CFStringGetLength(state) <= 0);
NSLog(@"Mute switch: %d",muteSwitch);

// code below here is just restoring my own audio state, YMMV
_hasMicrophone = [sharedSession inputIsAvailable];
NSError* setCategoryError = nil;

if (_hasMicrophone) {

    [sharedSession setCategory: AVAudioSessionCategoryPlayAndRecord error: &setCategoryError];

    // By default PlayAndRecord plays out over the internal speaker.  We want the external speakers, thanks.
    UInt32 ASRoute = kAudioSessionOverrideAudioRoute_Speaker;
    AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,
                             sizeof (ASRoute),
                             &ASRoute
                             );
}
else
    // Devices with no mike don't support PlayAndRecord - we don't get playback, so use just playback as we don't have a microphone anyway
    [sharedSession setCategory: AVAudioSessionCategoryPlayback error: &setCategoryError];

if (setCategoryError)
    NSLog(@"Error setting audio category! %@", setCategoryError);

return muteSwitch;
}