53

I have an app that uses local notifications and supports iOS 10. I am trying to add iOS 9 support which requires me to use the old location notification API. I am trying to use @available and #available on my iOS 10 code and I can't figure out how to get my center variable to only be for devices running iOS 10.

When I set my target from iOS 10 to 9 I get the error message for this variable:

UNUserNotificationCenter is only available on iOS 10.0 or newer.

It suggests I add @available(iOS 10.0, *) to my entire class which I don't want to do since there is code in this class that will be used for iOS 9. I appreciate any suggestions on how to limit my center property to just iOS 10.

class ViewController: UIViewController, UITextFieldDelegate {
  
  let center = UNUserNotificationCenter.current()
  ...
}
Vukašin Manojlović
  • 3,717
  • 3
  • 19
  • 31
chickenparm
  • 1,570
  • 1
  • 16
  • 36

11 Answers11

80

Solution That Works On Xcode 14 (and lower)

Here is one potential solution (thanks to blog post). The idea is to use a stored property with a type of Any and then create a computed property that will cast the stored property (and instantiate it if necessary).

private var _selectionFeedbackGenerator: Any? = nil
@available(iOS 10.0, *)
fileprivate var selectionFeedbackGenerator: UISelectionFeedbackGenerator {
    if _selectionFeedbackGenerator == nil {
        _selectionFeedbackGenerator = UISelectionFeedbackGenerator()
    }
    return _selectionFeedbackGenerator as! UISelectionFeedbackGenerator
}
kgaidis
  • 14,259
  • 4
  • 79
  • 93
56

Update as of Xcode 14.0.0 beta 1

Unfortunately it seems that lazy properties are now considered stored properties, so this workaround no longer works. Writing your own computed var with backing storage is the best solution for now.

Edit: It turns out that this approach was an error all along, it just didn't produce a diagnostic. That was fixed with Swift 5.7: https://github.com/apple/swift/pull/41112


Previous answer

I know this is an older question but I wanted to add an answer for people who come here via Google as I did.

As kgaidis and Cœur mentioned, you can use @available on computed properties. However, lazy variables are considered computed properties and so you can use @available on them too. This has the nice benefit of removing the boilerplate of the extra stored property and the forced casts - in fact, it leaves no evidence of the property in your pre-iOS 10 code.

You can simply declare it like this:

@available(iOS 10.0, *)
private(set) lazy var center = UNUserNotificationCenter.current()

Unfortunately there's no way to make it completely read-only but the private(set) at least makes it read-only outside of the class.

Jayson
  • 1,689
  • 14
  • 26
41

I have this error in Flutter project in XCode 14. One of the external packages were using:

@available(iOS 14.0, *)

My current fix is:

Update version in Podfile platform :ios, '14.0'

Reset pods cd ios && rm -rf Pods/ Podfile.lock && pod install --repo-update

Issue: https://github.com/pichillilorenzo/flutter_inappwebview/issues/1216

mirkancal
  • 4,762
  • 7
  • 37
  • 75
  • Is there a way to solve it without setting the global platform as iOS 14? – jed1 Sep 14 '22 at 19:27
  • You can check PRs for the packages and use them from GitHub until they merge. For example, flutter_inappview has one that's not been merged yet. https://github.com/pichillilorenzo/flutter_inappwebview/issues/1216#issuecomment-1245077988 And personally, I don't see any problem since 7 years old iPhone 6S had iOS 14. https://gs.statcounter.com/ios-version-market-share/ – mirkancal Sep 15 '22 at 06:14
  • 1
    So there is a quick temp fix, you can PODS.... and then look for flutter_inappwebview, click on it and go to Building settings, search for "IOS Deployment Target", change it to iOS 14, if you don't want to change the entire POD File to be Platform 14 – Bobby Sep 15 '22 at 17:05
  • Performing 'flutter clean' and 'flutter pub get' before resetting pods fixed the issue for me. – berkaykurkcu Sep 16 '22 at 14:17
7

@available could be used around a whole class or one or more functions, but not for properties.

Regarding your UNUserNotificationCenter usage, current returns a singleton that never changes, so why not just remove the center constant, and just use UNUserNotificationCenter.current() where center is used?

James Chen
  • 10,794
  • 1
  • 41
  • 38
3

Similar idea than kgaidis, by using a separate stored property of a type accepted in any version. But Any may be too generic, as it cannot be declared weak for instance, so you may want to replace it with a conforming protocol in some situations:

private weak var _notificationCenterDelegate: NSObjectProtocol?
@available(iOS 10.0, *)
var notificationCenterDelegate: UNUserNotificationCenterDelegate? {
    return _notificationCenterDelegate as? UNUserNotificationCenterDelegate
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
2

I faced this issue when updated to xCode 14.*

pod update
pod install 

worked for me

Deep Dave
  • 159
  • 1
  • 8
1

For objective-c

@property (nonatomic, strong) CustomClass *object API_AVAILABLE(ios(10.0));

sainecy
  • 39
  • 3
1

Starting from @kgaidis solution, I did this for apple sign in button:

private var _appleButton: UIControl? = nil
@available(iOS 13.0, *)
private var appleButton: ASAuthorizationAppleIDButton {
    if _appleButton == nil {
        let btn = ASAuthorizationAppleIDButton()
        btn.addTarget(self, action: #selector(handleAppleSignInPress), for: .touchUpInside)
        _appleButton = btn
    }
    return _appleButton as! ASAuthorizationAppleIDButton
}

This way I still use appleButton with @available(iOS 13.0, *)

if #available(iOS 13.0, *) {
    holderView.addSubview(appleButton)
}
MBH
  • 16,271
  • 19
  • 99
  • 149
0

The code I use for feedback generators in my apps which supports iOS 9. As you can see, it's simple and it has no force casts. The main idea is to store value in Any? property and use it via computed one.

private var storedFeedbackGenerator: Any? = nil
@available(iOS 10.0, *)
private var feedbackGenerator: UISelectionFeedbackGenerator {
    if let generator = storedFeedbackGenerator as? UISelectionFeedbackGenerator {
        return generator
    }

    let generator = UISelectionFeedbackGenerator()
    generator.prepare()
    storedFeedbackGenerator = generator
    return generator
}
Timur Bernikovich
  • 5,660
  • 4
  • 45
  • 58
0

Stored properties cannot be marked potentially unavailable with '@available

Just change NSPersistentCloudKitContainer to NSPersistentContainer and check its working fine or not

-4
let validClass = NSClassFromString("UNUserNotificationCenter") != nil

Use validClass to decide code specific for iOS 10 like :

if validClass
   // iOS 10 code
else
   // Earlier than iOS 10 code
Rhm Akbari
  • 362
  • 3
  • 12
  • 1
    This is not a good way to check for availability. Apple's preferred method is the `if #available()` syntax. However, neither method (this answer or `if #available`) can be used on stored properties. – willrichman Jun 20 '17 at 00:06