6

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.

"Updated" printed in console but simulator does not change

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"
        }
    }
}    

"Updated" printed in console and simulator also changes

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"
        }
    }
}
aheze
  • 24,434
  • 8
  • 68
  • 125
  • Interesting. Can you specify the iOS versions please? (i.e 13.x.x & 14.x.x) – Cuneyt Nov 30 '20 at 01:47
  • 1
    I tested on 13.0 and 14.0. – aheze Nov 30 '20 at 02:03
  • Please don’t edit the answer into the question, the answers section is for that – Cristik Nov 30 '20 at 04:56
  • @Cristik this was only because the answer didn't specify where to define `objectWillChange` and the `import Combine`, so I thought it would be more clear... but I'll keep in mind :) – aheze Nov 30 '20 at 05:07

1 Answers1

2

It appears to be on iOS 13, if you subclass an object and do not conform to ObservableObject directly (as in class SearchBarTester: ObservableObject), you'll need to add this boilerplate code:

@Published var searchText: String = "" {
    willSet {
        objectWillChange.send()
    }
}

However, calling the default objectWillChange will still not work, hence you'll need to define it yourself again:

let objectWillChange = PassthroughSubject<Void, Never>()
Cuneyt
  • 931
  • 5
  • 11
  • Thanks for the answer! But it didn't work... I actually already tried this solution from this [answer](https://stackoverflow.com/a/62192636/14351818) – aheze Nov 30 '20 at 02:22
  • 1
    Sorry about that! I am just downloading iOS 13.0 simulators, will update my answer accordingly later. – Cuneyt Nov 30 '20 at 02:23
  • 1
    Found a solution :) Please have a look. I am not sure how it works this way though, SwiftUI and Combine are complicated sometimes! – Cuneyt Nov 30 '20 at 02:35