The situation gets even worse if you need to triple-check to support, for example, older widgets without interaction, iOS 17 widgets with interaction, and the new iOS 18 control widgets.
If you use the above workaround with three versions, it will result in an error: Function declares an opaque return type 'some Widget', but the return statements in its body do not have matching underlying types
Code with error:
var body: some Widget {
if #available(iOS 18.0, *) {
return body_iOS18
} else if #available(iOS 17.0, *) {
return body_iOS17
} else {
return body_old
}
}
So you have to create ugly things like:
var body: some Widget {
if #available(iOS 17.0, *) {
return body_new
} else {
return body_old
}
}
@available(iOSApplicationExtension 17.0, *)
var body_new: some Widget {
if #available(iOS 18.0, *) {
return body_iOS18
} else {
return body_iOS17
}
}
Post
Replies
Boosts
Views
Activity
It's all the same behaviour in iOS 17.3 and Xcode 15.2. Do you have any clarification from Apple about your feedback?
I have same issue with Xcode 13.2 (13C90), SwiftUI and #available in if statement. Project works fine with old Xcode and in Debug configuration, but crashes with old versions of iOS (iOS 13 & 14) in Release configuration (or using TestFlight).
Steps to reproduce:
Create a new SwiftUI iPhone Application project.
Set the iOS target to 14
Create a simple view with an if #available statement and any new iOS 15 modifier. For example:
struct ContentView: View {
var body: some View {
if #available(iOS 15.0, *) {
List {
Text("iOS 15 new feature")
.listRowSeparator(.hidden)
}
} else {
List { Text("Old style") }
}
}
}
Change the build configuration in the launch scheme to Release and run the app on a device or simulator with iOS 14.
Application will crash with error:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
* frame #0: 0x00007fff2f38f127 libswiftCore.dylib`swift::ResolveAsSymbolicReference::operator()(swift::Demangle::__runtime::SymbolicReferenceKind, swift::Demangle::__runtime::Directness, int, void const*) + 55
frame #1: 0x00007fff2f3b15dd libswiftCore.dylib`swift::Demangle::__runtime::Demangler::demangleSymbolicReference(unsigned char) + 141
frame #2: 0x00007fff2f3ae5a8 libswiftCore.dylib`swift::Demangle::__runtime::Demangler::demangleType(__swift::__runtime::llvm::StringRef, std::__1::function<swift::Demangle::__runtime::Node* (swift::Demangle::__runtime::SymbolicReferenceKind, swift::Demangle::__runtime::Directness, int, void const*)>) + 168
frame #3: 0x00007fff2f394974 libswiftCore.dylib`swift_getTypeByMangledNameImpl(swift::MetadataRequest, __swift::__runtime::llvm::StringRef, void const* const*, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>) + 516
frame #4: 0x00007fff2f39212d libswiftCore.dylib`swift::swift_getTypeByMangledName(swift::MetadataRequest, __swift::__runtime::llvm::StringRef, void const* const*, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>) + 477
frame #5: 0x00007fff2f39235b libswiftCore.dylib`swift_getTypeByMangledNameInContext + 171
frame #6: 0x000000010305add7 AvailableBug`__swift_instantiateConcreteTypeFromMangledName at <compiler-generated>:0
...
It looks like SwiftUI is trying to initialize the content of the if statement and crashes on an unknown modifier or element.
If creates a simple wrapper structure that defers initialization to the "body", everything works fine.
Workaround:
struct LazyContent<Content: View>: View {
let content: ()->Content
init (@ViewBuilder content: @escaping ()->Content) {
self.content = content
}
var body: some View {
content()
}
}
struct ContentView: View {
var body: some View {
if #available(iOS 15.0, *) {
LazyContent {
List {
Text("iOS 15 new feature")
.listRowSeparator(.hidden)
}
}
} else {
List { Text("Old style") }
}
}
}