17

After migrating to Swift 4.2, I am getting multiple errors, one of which is weird. It seems like a bug in Xcode 10, but is there a workaround available?

do {
    try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, with: options)
} catch {
    NSLog("Could not set audio session category")
}

**** 'setCategory(_:with:)' is unavailable in Swift

Cœur
  • 37,241
  • 25
  • 195
  • 267
Deepak Sharma
  • 5,577
  • 7
  • 55
  • 131

4 Answers4

37

iOS 10+

If you are targeting iOS 10+, just transition to the new API and use:

try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])

Older iOS versions

When you try this for an app targeting an older iOS version (for example iOS 9) you will get an setCategory(_:mode:options:)' is only available on iOS 10.0 or newer Error.

This has been reported as an error in Apple's API and fixed in Xcode 10.2. For older Xcode versions (for example Xcode 10.1) there is a workaround I found. When you create an Objective-C helper as described you can still access the old API because it is still exposed for Objective-C.

Workaround 1: .perform() Method

If you want a quick inline fix without the error handling, you can call the Obj.-C API with the .perform() method:

if #available(iOS 10.0, *) {
  try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
} else { 
  // Set category with options (iOS 9+) setCategory(_:options:)       
  AVAudioSession.sharedInstance().perform(NSSelectorFromString("setCategory:withOptions:error:"), with: AVAudioSession.Category.playback, with:  [])

  // Set category without options (<= iOS 9) setCategory(_:)
  AVAudioSession.sharedInstance().perform(NSSelectorFromString("setCategory:error:"), with: AVAudioSession.Category.playback)
}

Workaround 2: Helper class method

Here are the steps how to do it right now if you want some more control over errors

  1. Create a new Objective-C file in my case AudioSessionHelper.m. When prompted if a Bridging Header File should be created, click Yes (If you don't already have one in your project)
  2. Create a new Header file AudioSessionHelper.h
  3. Insert Code
AudioSessionHelper.h
#ifndef AudioSessionHelper_h
#define AudioSessionHelper_h
#import <AVFoundation/AVFoundation.h>

@interface AudioSessionHelper: NSObject
+ (BOOL) setAudioSessionWithError:(NSError **) error;
@end

#endif /* AudioSessionHelper_h */
AudioSessionHelper.m
#import "AudioSessionHelper.h"
#import <Foundation/Foundation.h>

@implementation AudioSessionHelper: NSObject

+ (BOOL) setAudioSessionWithError:(NSError **) error {
    BOOL success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:error];
    if (!success && error) {
        return false;
    } else {
        return true;
    }
}
@end
  1. Insert your helper class into Bridging Header File
[PROJECT]-Bridging-Header.h
#import "AudioSessionHelper.h"
  1. Use it in your Swift project
if #available(iOS 10.0, *) {
    try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
} else {
    try AudioSessionHelper.setAudioSession()
}

This is a not beautiful and adds lots of unnecessary code and files to your project, so use it if you urgently want or must use Swift 4.2 on Xcode 10.1 right now. In all other cases you would be better off using Xcode 10.2.

Community
  • 1
  • 1
Benno
  • 968
  • 6
  • 15
  • Luckily, I am only targeting iOS 10 and above, so I can switch to .default mode. Question is if the .default mode is exact equivalent of the older API which does not have mode param? – Deepak Sharma Sep 20 '18 at 11:30
  • Apple describes it as [*The default audio session mode.*](https://developer.apple.com/documentation/avfoundation/avaudiosession/mode) and states [*You can use this mode with every audio session category.*](https://developer.apple.com/documentation/avfoundation/avaudiosession/mode/1616579-default) so it should be safe to use if none of the other modes fits your use case better. – Benno Sep 20 '18 at 11:35
  • Your current piece of code using `perform` isn't handling errors nicely. Your Objective-C implementation is fine, but I decided to make this workaround directly in Swift 4.1. And I made a pod for it: `pod 'AVAudioSessionSetCategorySwift'`. See my answer below. – Cœur Oct 19 '18 at 10:58
  • @ScottyBlades try to use `AVAudioSession.Category.playback` https://developer.apple.com/documentation/avfoundation/avaudiosession/category/1616509-playback – Benno Apr 17 '19 at 23:00
  • Thank you @Benno, unfortunately, that didn't work. Converting to Swift 5 made this a non-issue. – ScottyBlades Apr 17 '19 at 23:33
1

This is an issue with AVFoundation in Xcode 10's SDKs. You can work around it by writing an Objective-C function that calls through to the old API, since they're still available in Objective-C. But if you're only targeting iOS 10 or later, you can write in swift

do{
    if #available(iOS 10.0, *) {
        try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: options)
    } else {
        //the following line is now "unavailable", hence there is no way to support iOS <10
        //try AVAudioSession.sharedInstance().setCategory(.playAndRecord, with: options)
    }
} catch let error {
    print("Could not set audio session category: \(error.localizedDescription)")
}

Source: Swift Forum

Mehedi Hasan
  • 329
  • 2
  • 7
1

What I do is call setCategory(_:mode:options:). The options: parameter may be omitted, and if you have no mode, you can use a mode of .default.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    *'setCategory(_:mode:options:)' is only available on iOS 10.0 or newer*. I think people still using `setCategory(_:with:)` and looking for help on this Stack Overflow question are mostly those targeting older iOS versions, hence the success of Benno answer. – Cœur Oct 09 '18 at 11:29
0

The issue has been resolved in Xcode 10.2.


For Xcode 10.1 with iOS 9 (and older), the only other proposed solution is from Benno and is to rely on either Objective-C or perform(NSSelectorFromString("setCategory:error:")). Unfortunately, the given code doesn't let you handle errors nicely, so I'd like to introduce a solution relying on simply falling back to Swift 4.1 in a separate framework when using Xcode 10.1 (and it will work even if your project is using Swift 4.2).

The trick is simply to build a separate framework (or lib) to wrap the call to setCategory(_:) using an older version of Swift (like 4.1). In this framework, you only need to have:

import AVFoundation

extension AVAudioSession {
    @available (iOS 3.0, tvOS 9.0, watchOS 2.0, *)
    @objc open func setCategorySwift(_ category: String) throws {
        try setCategory(category)
    }
}

(also available through pod 'AVAudioSessionSetCategorySwift')

Then, back to your Swift 4.2 project (and a pod install eventually), you can use it as simply as setCategorySwift(AVAudioSession.Category.playback.rawValue). A longer example, with try-catch, may be:

import AVAudioSessionSetCategorySwift

let session = AVAudioSession.sharedInstance()
do {
    if #available(iOS 10.0, *) {
        try session.setCategory(AVAudioSession.Category.playback, mode: .default, options: [])
    } else {
        // Swift 4.2 workaround: https://github.com/coeur/AVAudioSessionSetCategorySwift
        try session.setCategorySwift(AVAudioSession.Category.playback.rawValue)
    }
    try session.setActive(true)
} catch {
    print("AVAudioSession.setCategory error: \(error)")
}
Cœur
  • 37,241
  • 25
  • 195
  • 267