I know, this is one of those "Not working in iOS XX" questions, but I'm completely stuck...
So I have an ObservableObject
class that inherits from NSObject
, because I need to listen to the delegate methods of UISearchResultsUpdating
.
class SearchBarListener: NSObject, UISearchResultsUpdating, ObservableObject {
@Published var searchText: String = ""
let searchController: UISearchController = UISearchController(searchResultsController: nil)
override init() {
super.init()
self.searchController.searchResultsUpdater = self
}
func updateSearchResults(for searchController: UISearchController) {
/// Publish search bar text changes
if let searchBarText = searchController.searchBar.text {
print("text: \(searchBarText)")
self.searchText = searchBarText
}
}
}
struct ContentView: View {
@ObservedObject var searchBar = SearchBarListener()
var body: some View {
Text("Search text: \(searchBar.searchText)")
.padding()
/// more code that's not related
}
}
The problem is that even though print("text: \(searchBarText)")
prints fine, the Text("Search text: \(searchBar.searchText)")
is never updated (in iOS 13). It works fine in iOS 14.
Here's a minimal reproducible example:
class SearchBarTester: NSObject, ObservableObject {
@Published var searchText: String = ""
override init() {
super.init()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
print("updated")
self.searchText = "Updated"
}
}
}
struct ContentView: View {
@ObservedObject var searchBar = SearchBarTester()
var body: some View {
NavigationView {
Text("Search text: \(searchBar.searchText)")
.padding()
}
}
}
After 5 seconds, "updated" is printed in the console, but the Text
doesn't change. In iOS 14, the Text
changes to "Search text: Updated" as expected.
However, if I don't inherit from NSObject
, both iOS 13 and iOS 14 work!
class SearchBarTester: ObservableObject {
@Published var searchText: String = ""
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
print("updated")
self.searchText = "Updated"
}
}
}
I think the problem has something to do with inheriting from a class. Maybe it's something that was fixed in iOS 14. But does anyone know what is going on?
Edit
Thanks @Cuneyt for the answer! Here's the code that finally worked:
import SwiftUI
import Combine /// make sure to import this
class SearchBarTester: NSObject, ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
@Published var searchText: String = "" {
willSet {
self.objectWillChange.send()
}
}
override init() {
super.init()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
print("updated")
self.searchText = "Updated"
}
}
}
It appears to be on iOS 13, if you subclass an object and do not conform to
ObservableObject
directly (as inclass SearchBarTester: ObservableObject
), you'll need to add this boilerplate code:However, calling the default
objectWillChange
will still not work, hence you'll need to define it yourself again: