Xcode 11.3.1真机编译报错:未声明类型‘PHAccessLevel’及‘PHPhotoLibrary’无‘presentLimitedLibraryPicker’成员
Hey there! Let's break down why you're hitting these frustrating errors only on real devices, even though your code works in the simulator and you've tried common fixes like cleaning builds.
The Root Cause
The issue comes down to a SDK version mismatch. Xcode 11.3.1 ships with the iOS 13.2 SDK, which doesn't include the PHAccessLevel type or PHPhotoLibrary.presentLimitedLibraryPicker(from:) method—these are both iOS 14+ exclusive APIs.
Even though you're testing on iOS 14/13 devices, when you compile with Xcode 11.3.1, the compiler uses the older iOS 13.2 SDK to validate your code. The simulator might seem to work because it's tied directly to your Xcode version's SDK, but real devices expose the gap between the SDK you're compiling with and the runtime APIs they support. Your previous fixes (cleaning builds, deleting Derived Data) don't help here because the problem isn't cached artifacts—it's missing API definitions in the SDK.
Solutions
1. Upgrade Xcode (Recommended)
The simplest and most reliable fix is to upgrade Xcode to version 12.0 or later. Xcode 12 was the first release to include the iOS 14 SDK, which has all the Photos framework APIs you're using. Once you upgrade, your existing code will compile and run on iOS 14+ real devices without any changes.
2. Workaround for Xcode 11.3.1 (If You Can't Upgrade)
If you're stuck on Xcode 11.3.1 for now, you can use runtime checks and dynamic method calls to avoid compilation errors. This lets you access the iOS 14 APIs without the compiler flagging them as missing. Here's how to modify your code:
import Photos import PhotosUI import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() if #available(iOS 14, *) { // Dynamically fetch PHAccessLevel.readWrite since it's not in iOS 13 SDK guard let phAccessLevelClass = NSClassFromString("PHAccessLevel"), let readWriteAccessLevel = phAccessLevelClass.value(forKey: "readWrite") as? NSNumber else { // Fall back to iOS 13 authorization flow if dynamic access fails handleLegacyAuthorization(PHPhotoLibrary.authorizationStatus()) return } // Call authorizationStatus(for:) dynamically let authorizationStatusRaw = PHPhotoLibrary.shared() .perform(Selector(("authorizationStatusForAccessLevel:")), with: readWriteAccessLevel)? .takeUnretainedValue() as? Int ?? 0 switch authorizationStatusRaw { case 3: // Raw value for PHAuthorizationStatus.limited // Call presentLimitedLibraryPicker(from:) dynamically PHPhotoLibrary.shared() .perform(Selector(("presentLimitedLibraryPickerFromViewController:")), with: self) case 4: // Raw value for PHAuthorizationStatus.authorized handleAuthorizedAccess() default: handleUnauthorizedAccess() } } else { // iOS 13 and below authorization flow handleLegacyAuthorization(PHPhotoLibrary.authorizationStatus()) } } // MARK: - Helper Methods private func handleLegacyAuthorization(_ status: PHAuthorizationStatus) { switch status { case .authorized: handleAuthorizedAccess() default: handleUnauthorizedAccess() } } private func handleAuthorizedAccess() { // Your logic for authorized access goes here print("Photo access authorized") } private func handleUnauthorizedAccess() { // Your logic for unauthorized access goes here print("Photo access not authorized") } }
A quick breakdown of this workaround:
- We use
NSClassFromStringto fetch thePHAccessLevelclass dynamically, since the compiler doesn't recognize it in Xcode 11.3.1. - We call the new authorization methods with
performSelectorto bypass compiler checks. - We use raw values for authorization statuses because
PHAuthorizationStatus.limitedisn't defined in the iOS 13 SDK.
内容的提问来源于stack exchange,提问作者Heshan

