2021 SwiftSwiftUI & UI FrameworksSystem Services
WWDC21 · 27 min · Swift / SwiftUI & UI Frameworks / System Services
Bring Core Data concurrency to Swift and SwiftUI
Discover how Core Data is adopting the new concurrency capabilities of Swift 5.5, leading to more concise, efficient, and safe asynchronous code. We’ll show you how to update Core Data in your apps to work with concurrency, and detail the many other improvements throughout the framework that make working with Swift and SwiftUI more expressive and powerful.
Watch at developer.apple.com ↗Code shown on screen · 4 snippets
FetchRequest dynamic configuration: sort descriptors
private let sorts = [(
name: "Time",
descriptors: [SortDescriptor(\Quake.time, order: .reverse)]
), (
name: "Time",
descriptors: [SortDescriptor(\Quake.time, order: .forward)]
), (
name: "Magnitude",
descriptors: [SortDescriptor(\Quake.magnitude, order: .reverse)]
), (
name: "Magnitude",
descriptors: [SortDescriptor(\Quake.magnitude, order: .forward)]
)]
struct ContentView: View {
(sortDescriptors: [SortDescriptor(\Quake.time, order: .reverse)])
private var quakes: FetchedResults<Quake>
private var selectedSort = SelectedSort()
var body: some View {
List(quakes) { quake in
QuakeRow(quake: quake)
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
SortMenu(selection: $selectedSort)
.onChange(of: selectedSort) { _ in
let sortBy = sorts[selectedSort.index]
quakes.sortDescriptors = sortBy.descriptors
}
}
}
}
struct SelectedSort: Equatable {
var by = 0
var order = 0
var index: Int { by + order }
}
struct SortMenu: View {
private var selectedSort: SelectedSort
init(selection: Binding<SelectedSort>) {
_selectedSort = selection
}
var body: some View {
Menu {
Picker("Sort By", selection: $selectedSort.by) {
ForEach(Array(stride(from: 0, to: sorts.count, by: 2)), id: \.self) { index in
Text(sorts[index].name).tag(index)
}
}
Picker("Sort Order", selection: $selectedSort.order) {
let sortBy = sorts[selectedSort.by + selectedSort.order]
let sortOrders = sortOrders(for: sortBy.name)
ForEach(0..<sortOrders.count, id: \.self) { index in
Text(sortOrders[index]).tag(index)
}
}
} label: {
Label("More", systemImage: "ellipsis.circle")
}
.pickerStyle(InlinePickerStyle())
}
private func sortOrders(for name: String) -> [String] {
switch name {
case "Magnitude":
return ["Highest to Lowest", "Lowest to Highest"]
case "Time":
return ["Newest on Top", "Oldest on Top"]
default:
return []
}
}
}
} FetchRequest dynamic configuration: predicates
struct ContentView: View {
(sortDescriptors: [SortDescriptor(\Quake.time, order: .reverse)])
private var quakes: FetchedResults<Quake>
private var searchText = ""
var query: Binding<String> {
Binding {
searchText
} set: { newValue in
searchText = newValue
quakes.nsPredicate = newValue.isEmpty
? nil
: NSPredicate(format: "place CONTAINS %@", newValue)
}
}
var body: some View {
List(quakes) { quake in
QuakeRow(quake: quake)
}
.searchable(text: query)
}
} SectionedFetchRequest
extension Quake {
lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM d, yyyy"
return formatter
}()
@objc var day: String {
return dateFormatter.string(from: time)
}
}
struct ContentView: View {
(
sectionIdentifier: \.day,
sortDescriptors: [SortDescriptor(\Quake.time, order: .reverse)])
private var quakes: SectionedFetchResults<String, Quake>
var body: some View {
List {
ForEach(quakes) { section in
Section(header: Text(section.id)) {
ForEach(section) { quake in
QuakeRow(quake: quake)
}
}
}
}
}
} SectionedFetchRequest dynamic configuration: sort descriptors
extension Quake {
lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM d, yyyy"
return formatter
}()
@objc var day: String {
return dateFormatter.string(from: time)
}
@objc var magnitude_str: String {
return "\(magnitude)"
}
}
private let sorts = [(
name: "Time",
descriptors: [SortDescriptor(\Quake.time, order: .reverse)],
section: \Quake.day
), (
name: "Time",
descriptors: [SortDescriptor(\Quake.time, order: .forward)],
section: \Quake.day
), (
name: "Magnitude",
descriptors: [SortDescriptor(\Quake.magnitude, order: .reverse)],
section: \Quake.magnitude_str
), (
name: "Magnitude",
descriptors: [SortDescriptor(\Quake.magnitude, order: .forward)],
section: \Quake.magnitude_str
)]
struct ContentView: View {
(
sectionIdentifier: \.day,
sortDescriptors: [SortDescriptor(\Quake.time, order: .reverse)])
private var quakes: SectionedFetchResults<String, Quake>
private var selectedSort = SelectedSort()
var body: some View {
List {
ForEach(quakes) { section in
Section(header: Text(section.id)) {
ForEach(section) { quake in
QuakeRow(quake: quake)
}
}
}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
SortMenu(selection: $selectedSort)
.onChange(of: selectedSort) { _ in
let sortBy = sorts[selectedSort.index]
let config = quakes
config.sectionIdentifier = sortBy.section
config.sortDescriptors = sortBy.descriptors
}
}
}
}
struct SelectedSort: Equatable {
var by = 0
var order = 0
var index: Int { by + order }
}
struct SortMenu: View {
private var selectedSort: SelectedSort
init(selection: Binding<SelectedSort>) {
_selectedSort = selection
}
var body: some View {
Menu {
Picker("Sort By", selection: $selectedSort.by) {
ForEach(Array(stride(from: 0, to: sorts.count, by: 2)), id: \.self) { index in
Text(sorts[index].name).tag(index)
}
}
Picker("Sort Order", selection: $selectedSort.order) {
let sortBy = sorts[selectedSort.by + selectedSort.order]
let sortOrders = sortOrders(for: sortBy.name)
ForEach(0..<sortOrders.count, id: \.self) { index in
Text(sortOrders[index]).tag(index)
}
}
} label: {
Label("More", systemImage: "ellipsis.circle")
}
.pickerStyle(InlinePickerStyle())
}
private func sortOrders(for name: String) -> [String] {
switch name {
case "Magnitude":
return ["Highest to Lowest", "Lowest to Highest"]
case "Time":
return ["Newest on Top", "Oldest on Top"]
default:
return []
}
}
}
} Resources
Related sessions
-
32 min -
25 min -
40 min -
17 min -
34 min