SwiftUI: Performance problems List of TextField

I'm having issues with the scrolling performance in my app. I use CloudKit to store a list of Items. I access these Items with @FetchRequest var items : FetchedResults<Item>

I created a List with a ForEach inside to iterate over the items and pass them to a ChecklistItemView. The ChecklistItemView accesses the Item as an @ObservedObject. This all works fine and I am able to edit the items with the Textfields but the performance when scrolling is very bad. On my iPhone 12 mini I get light stuttering and on my older iPad pro it gets very bad. Interestingly everything is smooth when the item is just displayed with Text(). My guess is that the Textfields update the item during binding when the TextField appears on screen. This is updating the whole list of items then triggering a redraw of all Textfields, resulting in a stuttering scrolling experience, because the view is redrawn again and again. It does not get worse with longer list. It is enough to fill the screen and then some more to be able to scroll.

Because I'm still very new to swiftUI I'm not sure how to fix this problem. Maybe it is possible to load each item in the ChecklistItemView to not update the other ones but I'm unsure on how to do that properly.

Thank you for your help in advance!

The list is setup like this:


...

@FetchRequest var items : FetchedResults<Item>

init(selectedchecklist: Checklist) {

        self.checklist = selectedchecklist

        self._items = FetchRequest(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Item.position, ascending: true)], predicate: NSPredicate(format: "checklist.id == %@", selectedchecklist.id! as CVarArg))
}

var body: some View {
    VStack(spacing: 0) {
        if(!self.items.isEmpty) {
            List {
                ForEach(self.items, id: \.id) { item in
                    ChecklistItemView(item: item, isEditingDisabled: self.$addItemMode)
                }
                .onMove(perform: move)
                .onDelete(perform: deleteItems)
            }
            .listStyle(.plain)
        }

         if(self.items.isEmpty) {
           ...
        }
    }
}

File ChecklistItemView:

import SwiftUI

struct ChecklistItemView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @ObservedObject var item: Item

    @FocusState private var isTextfieldFocused: Bool

    @Binding var isEditingDisabled: Bool

    var body: some View {
        HStack {
            if(self.isEditingDisabled) {
                Text(self.item.wrappedDesc)
                    .padding(.bottom, 2)
            } else {
                TextField("", text: self.$item.wrappedDesc, axis: .vertical)
                .onChange(of: self.item.wrappedDesc, perform: { newValue in
                    saveItem()
                })
                .disableAutocorrection(true)
                .focused(self.$isTextfieldFocused)
            }
            Spacer()
            CheckboxView(isChecked: self.$item.completed)
        }
        .toolbar{
            if(self.isTextfieldFocused) {
                ToolbarItemGroup(placement: .keyboard) {
                    Spacer()

                    Button("_done") {
                        withAnimation{
                            self.isTextfieldFocused = false
                        }
                        saveItem()
                    }
                }
            }
        }
        .contentShape(Rectangle())
        .onTapGesture {
            if (!self.isEditingDisabled) {
                self.isTextfieldFocused = true
            }
        }
    }
  

    private func saveItem() {
       ...
    }
}
  • The stuttering is likely the allocation of new TextViews. fwiw, SwiftUI is still rather new compared to UIKit. UIKit includes TableView and CollectionView, which are designed to display, and scroll thru, large lists and grids of lots of things very efficiently. It does this by re-using a limited pool of "cell" views. SwiftUI currently does not expose this. It does have LazyH and LazyV, which means that the lazy views aren't instantiated until they are displayed, but they are not re-used.

  • Okay thank you for the reply. So one way to fix it would be to wait till SwiftUI gets better. Maybe any other ideas for the meantime? LazyH & V are not an option because I use the drag and drop features of the list.

Add a Comment