Availablility check is incorrect on visionOS

With this sample code here:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello world")
            .hoverEffect(isEnabled: true)
    }
}

private extension View {
    func hoverEffect(isEnabled: Bool) -> some View {
        if #available(iOS 17.0, *) {
            // VisionOS 2.0 goes in here?
            return self
                .hoverEffect(.automatic, isEnabled: isEnabled)
        } else {
            return self
        }
    }
}

You would expect if the destination was visionOS it would go into the else block but it doesn't. That seems incorrect since the condition should be true if the platform is iOS 17.0+.

Also, I had this similar code that was distriubted via a xcframework and when that view is used in an app that is using the xcframework while running against visionOS there would be a runtime crash (EXC_BAD_ACCESS). The crash could only be reproduced when using that view from the xcframework and not the local source code. The problem was fixed by adding visionOS 1.0 to that availability check. But this shouldn't have been a crash in the first place.

Does anyone have thoughts on this or possibly an explanation?

Thank you!

Answered by DTS Engineer in 808617022

Hello @klinee101,

You would expect if the destination was visionOS it would go into the else block but it doesn't. That seems incorrect since the condition should be true if the platform is iOS 17.0+.

This is a common misconception about what the "*" does in that availability condition. The Swift Language Guide states, "The last argument, *, is required and specifies that on any other platform, the body of the if executes on the minimum deployment target specified by your target."

So, it is correct that on visionOS, your code takes the "if" branch, not the "else" branch.

One solution for you might be conditional compilation (in addition to an availability condition):

private extension View {
    func hoverEffect(isEnabled: Bool) -> some View {
        
#if os(iOS)
        if #available(iOS 17.0, *) {
            return self
                .hoverEffect(.automatic, isEnabled: isEnabled)
        } else {
            return self
        }
#else
        return self
#endif
    }
}

Best regards,

Greg

Hello @klinee101,

You would expect if the destination was visionOS it would go into the else block but it doesn't. That seems incorrect since the condition should be true if the platform is iOS 17.0+.

This is a common misconception about what the "*" does in that availability condition. The Swift Language Guide states, "The last argument, *, is required and specifies that on any other platform, the body of the if executes on the minimum deployment target specified by your target."

So, it is correct that on visionOS, your code takes the "if" branch, not the "else" branch.

One solution for you might be conditional compilation (in addition to an availability condition):

private extension View {
    func hoverEffect(isEnabled: Bool) -> some View {
        
#if os(iOS)
        if #available(iOS 17.0, *) {
            return self
                .hoverEffect(.automatic, isEnabled: isEnabled)
        } else {
            return self
        }
#else
        return self
#endif
    }
}

Best regards,

Greg

That is good to know. I actually wanted the body of the if to be executed for visionOS but didn't understand why it was working. Thank you.

Can you explain why this situation crashes?

Also, I had this similar code that was distriubted via a xcframework and when that view is used in an app that is using the xcframework while running against visionOS there would be a runtime crash (EXC_BAD_ACCESS). The crash could only be reproduced when using that view from the xcframework and not the local source code. The problem was fixed by adding visionOS 1.0 to that availability check. But this shouldn't have been a crash in the first place.

@klinee101,

I'm happy to look into it, can you attach a symbolicated crash log?

Best regards,

Greg

Thank you! Here is the crash log.

Hello @klinee101,

The crash log appears to indicate Memory Corruption, you may want to try debugging with the various sanitizers listed in Investigate the crash with Xcode.

It sounds like you have a highly reproducible case with a precompiled framework, if you can provide a focused sample that reproduces, I'm happy to look into it further :)

Best regards,

Greg

Availablility check is incorrect on visionOS
 
 
Q