Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - ObservableObject that inherits from NSObject does not update in iOS 13

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"
        }
    }
}
like image 551
aheze Avatar asked Nov 30 '20 00:11

aheze


People also ask

What is nsobjectprotocol in Swift?

The group of methods that are fundamental to all Objective-C objects. This protocol is imported into Swift with the name NSObjectProtocol. An object that conforms to this protocol can be considered a first-class object. Such an object can be asked about its: Class, and the place of its class in the inheritance hierarchy. Conformance to protocols.

What is observableobject in SwiftUI?

SwiftUI ObservableObject Tutorial September 11, 2019 ObservableObject is a protocol that’s part of the Combine framework. It is used within a custom class/model to keep track of the state.

How do I create a swiftuiobservableobjecttutorial?

Enter SwiftUIObservableObjectTutorial as the Product Name, select the Use SwiftUI checkbox, and click Next. Choose a location to save the project on your Mac. In the canvas, click Resume to display the preview. If the canvas isn’t visible, select Editor > Editor and Canvas to show it. Add a new file to the project. Select File -> New -> File.

How to create a single view app using SwiftUI on Mac?

In the template selector, select iOS as the platform, select the Single View App template, and then click Next. Enter SwiftUIObservableObjectTutorial as the Product Name, select the Use SwiftUI checkbox, and click Next. Choose a location to save the project on your Mac. In the canvas, click Resume to display the preview.


1 Answers

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>()
like image 197
Cuneyt Avatar answered Oct 14 '22 05:10

Cuneyt