What's new in SwiftUI
WWDC21 is here! SwiftUI has taken another huge step forward, and it comes with many enhancements to current views, new views, new types, new view modifiers, and more:
don't fret! We have a whole summer ahead of us to explore all of that, in this first article, let's have a look at some of the new changes!
For the new "flagship" SwiftUI features, I recommend this article by Majid Jabrayilov.
Colors
New colors
Not all new features need to come with disrupting changes, for example, this year we gained five new colors:
HStack {
Group {
Circle()
.fill(.mint)
Circle()
.fill(.teal)
Circle()
.fill(.cyan)
Circle()
.fill(.indigo)
Circle()
.fill(.brown)
}
.frame(width: 32, height: 32)
}
New initializers
All current CGColor
/UIColor
/NSColor
Color
initializers have been soft deprecated, in favor of new initializers with explicit argument labels:
@available(
..., deprecated: ...,
message: "Use Color(uiColor:) when converting a UIColor, or create a standard Color directly"
)
extension Color {
public init(_ color: UIColor)
}
@available(iOS 15.0)
extension Color {
public init(uiColor: UIColor)
}
Styles
New (iOS) Button style
iOS's Button
gains BorderedButtonStyle
, which previously was available in all other platforms but iOS.
Button("Tap me") {}
.buttonStyle(BorderedButtonStyle())
New Toggle style
Toggle
also gains a new style, available on both macOS and iOS:
VStack {
Toggle(isOn: .constant(true), label: { Text("Toggle on")})
Toggle(isOn: .constant(false), label: { Text("Toggle off")})
Toggle(isOn: .constant(true), label: { Text("Toggle on disabled")})
.disabled(true)
Toggle(isOn: .constant(false), label: { Text("Toggle off disabled")})
.disabled(true)
}
.toggleStyle(ButtonToggleStyle())
New way to apply styles
Beside actual styles, there's a new way to declare which style we'd like to apply, let's take a plain button style for example:
Button("Tap me") {}
.buttonStyle(PlainButtonStyle())
We can now apply this style with a more succinct API:
Button("Tap me") {}
.buttonStyle(.plain)
The old way is soft deprecated, and will trigger warnings in future Xcode releases.
This is a nice shift, and it's applied to all SwiftUI components, for example:
List {
...
}
.listStyle(.grouped)
Toggle(isOn: $isOn, label: { Text("Toggle") })
.toggleStyle(.button)
TextField("Username: ", text: $username)
.textFieldStyle(.roundedBorder)
SwiftUI previews InterfaceOrientation
SwiftUI previews now have a new previewInterfaceOrientation(_:)
view modifier, allowing us to preview our screens in different orientation:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewInterfaceOrientation(.portraitUpsideDown)
ContentView()
.previewInterfaceOrientation(.landscapeLeft)
}
}
InterfaceOrientation
conforms to Identifiable
and comes with an allCases
static property, allowing us to preview any view in all orientations with a ForEach
:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ForEach(InterfaceOrientation.allCases) { interfaceOrientation in
ContentView()
.previewInterfaceOrientation(interfaceOrientation)
}
}
}
With this out of the way, I would love to see previews side by side next, instead of stacked vertically (FB7635888).
Dismiss action
In previous iOS versions, if we wanted a view (a sheet, or a navigation view) to dismiss itself, we could have used the presentationMode
environment object:
struct OldSheetView: View {
@Environment(\.presentationMode) @Binding var presentationMode
var body: some View {
Button("Dismiss Me") {
presentationMode.dismiss()
}
}
}
In iOS 15 we have a new dismiss
environment variable, on which we just call dismiss()
on itself:
struct NewSheetView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Button("Dismiss Me") {
dismiss()
}
}
}
With this new addition, I believe the old presentationMode
object should be deprecated (FB9142876), if you're aware of any reason why it shouldn't, please let me know!
Badges
We have a new badge(_:)
view modifier used to display badges on UI components:
TabView {
Text("FIVE STARS")
.badge(1)
.tabItem { Label("One", systemImage: "1.circle.fill") }
Text("FIVE STARS")
.badge(2)
.tabItem { Label("Two", systemImage: "2.circle.fill") }
Text("FIVE STARS")
.badge(3)
.tabItem { Label("Three", systemImage: "3.circle.fill") }
Text("FIVE STARS")
.badge(4)
.tabItem { Label("Four", systemImage: "4.circle.fill") }
Text("STARS")
.badge(5)
.tabItem { Label("Five", systemImage: "5.circle.fill") }
}
badge(_:)
accepts both numbers and strings, it's displayed when applied to TabView
items and Text
on List
s:
List {
Text("See badge on the right 👉🏻")
.badge(5)
Button {
// ...
} label: {
Text("Button")
.badge(10)
}
}
Property wrappers meet Optional RawRepresentable
Last year we saw the introduction of both @AppStorage
and @SceneStorage
, which we covered here, this year both property wrappers gained the possibility to be associated with an optional RawRepresentable
type:
extension AppStorage {
public init<R: RawRepresentable>(
_ key: String,
store: UserDefaults? = nil
) where Value == R?, R.RawValue == String
public init<R: RawRepresentable>(
_ key: String,
store: UserDefaults? = nil
) where Value == R?, R.RawValue == Int
}
extension SceneStorage {
public init<R: RawRepresentable>(
_ key: String
) where Value == R?, R.RawValue == String
public init<R: RawRepresentable>(
_ key: String
) where Value == R?, R.RawValue == Int
}
Allowing us to start with a blank state, without forcing us to choose a default value:
enum Fruit: Int, Identifiable, CaseIterable {
case banana
case orange
case mango
var id: Int { rawValue }
}
struct ContentView: View {
@AppStorage("fruit") private var fruit: Fruit?
var body: some View {
Picker("My Favorite Fruit", selection: $fruit) {
ForEach(Fruit.allCases, id: \.self) {
Text("\($0)" as String)
}
}.pickerStyle(SegmentedPickerStyle())
}
}
In this example, the picker will have no value selected at launch.
Conclusions
This is just a sneak peek at some of the new SwiftUI changes from this year, we will explore many more during the rest of the summer:
if you haven't already, this is a great moment to subscribe to Five Stars's feed rss or follow @FiveStarsBlog on Twitter!
What are you most excited about WWDC21? Let me know!