0

I have an array of a Category class that has a name and parentName. I have a search bar to allow users search for categories by the category name or parentName. My full array has about 600 items. On typing the first letter it takes about 2-3 seconds and freezes all other input on the keyboard. After the first letter everything is fast.

Here is how I am filtering

return self.userData.categories.filter({$0.name.lowercased().hasPrefix(searchText.lowercased()) || ($0.parentName != nil && $0.parentName!.lowercased().hasPrefix(searchText.lowercased()))})

One piece I think it may be is SwiftUI rendering all of the rows, however the initial render is fast.

This is how I render the categories.

List(categories) { category in
    CategoryPickerRowView(category: category, isSelected: category.id == self.transaction.categoryId)
        .onTapGesture { self.transaction.categoryId = category.id }
}

Update: I noticed when the first letter is typed or deleted (when it is slow) I get this message in the logs

[Snapshotting] Snapshotting a view (0x7fb6bd4b8080, _UIReplicantView) that has not been rendered at least once requires afterScreenUpdates:YES.

1
  • 1
    Can you try filtering in a diff thread? This should avoid freezing i guess Commented Jan 30, 2020 at 5:57

3 Answers 3

1

You can use Combine to debounce your search so it happens only after the user stops typing. You can also use Combine to move your filter to the background and then move your assign back to the main queue.

class Model: ObservableObject {
  @Published var searchResults: [String] = []
  let searchTermSubject = CurrentValueSubject<String, Never>("")
  let categorySubject = CurrentValueSubject<String, Never>("")
  private var subscriptions = Set<AnyCancellable>()
  init() {
    Publishers
      .CombineLatest(
        searchTermSubject
          .debounce(for: .milliseconds(250), scheduler: RunLoop.main),
        categorySubject
      )
      .receive(on: DispatchQueue.global(qos: .userInteractive))
      .map { combined -> [String] in
        // Do search here
      }
      .receive(on: RunLoop.main)
      .assign(to: \.searchResults, on: self)
      .store(in: &subscriptions)
  }
}
Sign up to request clarification or add additional context in comments.

Comments

1

By adding .id(UUID()) to the list it fixes the problem.

List(categories) { category in
    CategoryPickerRowView(category: category, isSelected: category.id == self.transaction.categoryId)
        .onTapGesture { self.transaction.categoryId = category.id }
}.id(UUID())

Description found here: https://www.hackingwithswift.com/articles/210/how-to-fix-slow-list-updates-in-swiftui

Comments

-1

Use textfield delegate : -

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
    {
if string.isEmpty
        {
            search = String(search.dropLast())
}else{
      search=textField.text!+string
}

and use ".contain" to search name. eg: -

var search:String = ""
var searchData:NSArray?

let filtered = rewardArray?.filter { ($0.name .lowercased()).contains(search.lowercased()) }


searchData = filtered as NSArray?

and reload your collection or table with searchData

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.