Preface
Recently, I started to officially use SwiftUI to do projects, but I discovered a big problem, that is, many APIs commonly used in UIKit require very high versions to be used in SwiftUI.
To give just a few examples, the property ScrollView
of scrolling to close the keyboard keyboardDismissMode
is supported by iOS 7 in UIScrollView, but only supported by iOS 16 in SwiftUI. For another example, if UIAlertController
added to TextField
, iOS 8 supports it in UIKit, but iOS 16 only supports it in SwiftUI.
Therefore, it is impossible to use pure SwiftUI for projects at this stage, unless your project is at least compatible with iOS 15, or even 16 or above. Having said that, when some lower versions of the API are not supported, some compatibility methods need to be used. Recently, I have seen some smart ways to share them with everyone.
Wrong compatibility method
We assume that the minimum version of your project supports iOS 14. When using some incompatible APIs on 14, an error will be reported:
You can see cyan
that this color can only be used in iOS 15. One way is to add judgment VStack
under this:#available
var body: some View {
if #available(iOS 16.0, *) {
VStack {
Text("Hello, world!")
.fontWeight(.bold)
.background(Color.red)
.foregroundColor(.blue)
.padding()
.frame(width: 100, height: 100)
Text("Hello, world!")
.fontWeight(.bold)
.background(Color.red)
.padding()
.frame(width: 100, height: 100)
.foregroundColor(.cyan)
}
} else {
VStack {
Text("Hello, world!")
.fontWeight(.bold)
.background(Color.red)
.foregroundColor(.blue)
.padding()
.frame(width: 100, height: 100)
Text("Hello, world!")
.fontWeight(.bold)
.background(Color.red)
.padding()
.frame(width: 100, height: 100)
.foregroundColor(.blue)
}
}
}
There are several problems with this solution. First of all, in order to use it cyan
, the entire <code> VStack
must be wrapped by an if statement. If VStack
there is a lot of code under this <code>, there will be a lot of repeated code. Secondly, this method is not conducive to maintenance. If there is another attribute that can only be used in iOS 16, should it be if iOS 16
wrapped again? Obviously unreasonable.
Correct approach
The correct way is to achieve compatibility through extension. We can Color
extend a new newCyan
attribute to be compatible with this situation:
extension Color {
static let newCyan: Self = {
if #available(iOS 15.0, *) {
return .cyan
} else {
return .black
}
}()
}
In this case, just use it directly when you use it again newCyan
:
Text("Hello, world!")
.foregroundColor(.newCyan)
This solves the above problem.
Other examples
Using an extended compatibility scheme is a good idea, here are some other examples:
1. listRowSeparator
The method of hiding the dividing line of List can only be used in iOS 15:
Also use extensions:
func newHiddenListRowSeparator() -> some View {
if #available(iOS 15.0, *) {
return listRowSeparator(.hidden)
} else {
return self
}
}
hiddenListRowSeparator
Just change it to newHiddenListRowSeparator
.
2. ScrollView prohibits scrolling
func newScrollDisabled() -> some View {
if #available(iOS 16.0, *) {
return self.scrollDisabled(true)
} else {
return self
}
}
3. The tint method of View can only be used on iOS 15 or above.
Then write an extension for View
extension View {
func newTint(_ color: Color?) -> some View {
if #available(iOS 16.0, *) {
return self.tint(color)
} else {
return self.accentColor(color)
}
}
}
Summarize
The benefits of using extension-compatible APIs are obvious:
- Fixed duplicate code issue
- The compatibility judgment of each API is independent and highly maintainable.
- When you upgrade to the lowest version, you only need to delete the version judgment or directly change the method name.