How to remove unsupported ARPlaneAnchor.Classification enum cases when running on iOS 12?

Viewed 404

Apple announced that with ARKit 3 (iOS 13), ARPlaneAnchor.Classification is getting two new values:

enter image description here

My project is targeting iOS 12.1.

I have the following code in my app:

extension ARPlaneAnchor.Classification {
    var description: String {
        switch self {
        case .wall:
            return "Wall"
        case .floor:
            return "Floor"
        case .ceiling:
            return "Ceiling"
        case .table:
            return "Table"
        case .seat:
            return "Seat"
        case .window:
            return "Window"
        case .door:
            return "Door"
        case .none(.unknown):
            return "Unknown"
        default:
            return ""
        }
    }
}

When I attempt to run it on an iPhone X running iOS 13, it runs just fine and executes the code path for window and door.

When I attempt to run it on an iPhone 8 running iOS 12.4, it crashes upon startup:

dyld: Symbol not found: _$sSo13ARPlaneAnchorC5ARKitE14ClassificationO4dooryA2EmFWC
Referenced from: /var/containers/Bundle/Application/...
Expected in: /usr/lib/swift/libswiftARKit.dylib
in /var/containers/Bundle/Application/...

Keywords for search: ARPlaneAnchor ARKit Classification door

If I comment out the case door and case window it runs just fine.

This is obviously a bug in that the enum cases for door and window are only available on iOS 13.

When I command-click the definition, I see that Apple defined it as so:

/**
 A value describing the classification of a plane anchor.
 */
@available(iOS 12.0, *)
public enum __ARPlaneClassification : Int {


    /** The classification is not any of the known classes. */
    case none

    /** The classification is not any of the known classes. */
    case wall

    /** The classification is not any of the known classes. */
    case floor

    /** The classification is not any of the known classes. */
    case ceiling

    /** The classification is not any of the known classes. */
    case table

    /** The classification is not any of the known classes. */
    case seat

    /** The classification is not any of the known classes. */
    case window

    /** The classification is not any of the known classes. */
    case door
}

or sometimes it looks like this:

@available(iOS 12.0, *)
extension ARPlaneAnchor {

    public enum Classification {

        // ...

        /** The classification is not any of the known classes. */
        case none(ARPlaneAnchor.Classification.Status)

        case wall

        case floor

        case ceiling

        case table

        case seat

        case window

        case door
    }

    public var classification: ARPlaneAnchor.Classification { get }
}

As you can see in both examples, a new iOS 13 enum (e.g. door) appears exactly the same as an older iOS 12 enum (e.g. wall). I believe this is what causes it to compile just fine but crash on startup.

How do I get the code to run just fine on both devices running iOS 13 and those running iOS 12?


Update: I tried to use an availability check such as:

extension ARPlaneAnchor.Classification {
    var description: String {
        if #available(iOS 13, *) {
            return description_13()
        } else {
            return description_12()
        }
    }

    @available(iOS 12, *)
    private func description_12() -> String {
        switch self {
        case .wall:
            return "Wall"
        case .floor:
            return "Floor"
        case .ceiling:
            return "Ceiling"
        case .table:
            return "Table"
        case .seat:
            return "Seat"
        case .none(.unknown):
            return "Unknown"
        default:
            return ""
        }
    }

    @available(iOS 13, *)
    private func description_13() -> String {
        switch self {
        case .window:
            return "Window"
        case .door:
            return "Door"
        default:
            return description_12()
        }
    }
}

However, this produced the same exact "Symbol not found" error as before. Any suggestions for how to get this to work?

1 Answers

I was facing the same problem and I ended up with reflection. Not nice but seems work well.

extension ARPlaneAnchor.Classification {
    var description: String {
        switch self {
        case .wall:
            return "Wall"
        case .floor:
            return "Floor"
        case .ceiling:
            return "Ceiling"
        case .table:
            return "Table"
        case .seat:
            return "Seat"
        case .none(.unknown):
            return "Unknown"
        default:
          return description_13 ?? ""
        }
    }

    private var description_13: String? {
        let reflection = String(reflecting: self)
        let components = reflection.components(separatedBy: ".")
        switch components.last {
        case "window":
            return "Window"
        case "door":
            return "Door"
        default:
            return nil
        }
    }
}
Related