I am building an app using SwiftUI that should display work orders I get from a web service. I have successfully got them from a web service using the following function:
func getAllWorkOrders(completion: @escaping ([WorkOrder]) -> ()) {
guard let url = URL(string: "http://somedomain/api/workOrders")
else {
fatalError("URL is not correct")
}
let conn = URLSession(configuration: URLSessionConfiguration.ephemeral, delegate: self, delegateQueue: nil)
conn.dataTask(with: url) { data, _, _ in
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
let workOrders = try! decoder.decode([WorkOrder].self, from: data!)
DispatchQueue.main.async {
completion(workOrders)
}
}.resume()
}
while my WorkOrder struct look like this:
struct WorkOrder: Identifiable, Codable {
var id: Int
var ticket: Int?
var date: Date
var description: String
var astue: Bool
var state: WorkOrderState
var jobs: [WorkOrderJob]
var project: Project
}
I have also created a view model which looks like this:
class WorkOrderListViewModel: BindableObject {
var workOrders = [WorkOrder]() {
didSet {
didChange.send()
}
}
var astue = false {
didSet {
didChange.send()
}
}
var workOrderState = 0 {
didSet {
didChange.send()
}
}
var searchTerm = "" {
didSet {
didChange.send()
}
}
var filteredWorkOrders: [WorkOrder] {
var filtered = workOrders
if astue {
filtered = filtered.filter {
$0.astue == true
}
}
if workOrderState > 0 && workOrderState < 5 {
filtered = filtered.filter {
$0.state.id == workOrderState
}
}
if !searchTerm.isEmpty {
filtered = filtered.filter {
$0.description.lowercased().contains(searchTerm.lowercased()) || "\($0.ticket ?? 0)".contains(searchTerm.lowercased())
}
}
return filtered
}
init() {
fetchWorkOrders()
}
func fetchWorkOrders() {
WebService().getAllWorkOrders {
self.workOrders = $0
}
}
let didChange = PassthroughSubject<Void, Never>()
}
In it, I have a couple of filtering criteria: searchTerm, workOrderState and astue. I have a filtering view bound to those 3 properties. Given that web service is quite primitive and there is no paging or filtering (which means that I can get only all work orders, approximately 2k of them), and I need to filter them by one or many of the criteria, I have a calculated propery filteredWorkOrders in the view model which does the required filtering. My problem arises when I do some filtering. In UI, a have a TextField bound to searchTerm, SegmentedControl bound to workOrderState and Toogle bound to astue. When I apply one of the filtering criteria which drastically changes the number of records (for example when switching from workOrderState=0 to workOrderState=1 where it goes from 2k to just 50), UI gets unresponsive and the filtering lasts 6-7 seconds. However, when changes aren't that dramatic (adding astue=true which makes count of work orders go from 50 to 5) it happens in an instant.
SwiftUI view for the list and a row look like this:
struct WorkOrders : View {
@ObjectBinding var data = WorkOrderListViewModel()
var body: some View {
List {
SearchField(data: data)
ForEach(self.data.filteredWorkOrders) { workOrder in
NavigationLink(destination: WorkOrderDetails(workOrder: workOrder)) {
WorkOrderRow(workOrder: workOrder)
}
}
}
.listStyle(.grouped)
.navigationBarTitle(Text("Work orders (\(String(format: "%d", data.filteredWorkOrders.count)))"), displayMode: .large)
.navigationBarItems(trailing: Button(action: refresh) {
Image(systemName: "arrow.clockwise")
})
}
private func refresh() {
data.fetchWorkOrders()
}
}
struct WorkOrderRow : View {
@State var workOrder: WorkOrder
var body: some View {
HStack {
ZStack {
Circle()
.fill(backgroundColor())
.frame(width: 20, height: 20, alignment: Alignment(horizontal: .leading, vertical: .center))
}
VStack(alignment: .leading) {
HStack {
if workOrder.astue {
Image(systemName: "bolt.fill")
}
Text(String(format: "%d", workOrder.ticket ?? 0))
.font(.caption)
Spacer()
Image(systemName: "calendar")
Text(workOrder.dateString)
.font(.caption)
}
Text(workOrder.description)
.lineLimit(nil)
}
.padding([.horizontal])
}
}
private func backgroundColor() -> Color {
switch workOrder.state.id {
case 1:
return Color.red
case 2:
return Color.yellow
case 3:
return Color.green
case 4:
return Color.blue
default:
return Color.clear
}
}
}
What am I missing?