Customize handling of asynchronous events by combining event-processing operators using Combine.

Combine Documentation

Posts under Combine tag

51 Posts
Sort by:
Post not yet marked as solved
0 Replies
1k Views
I'm implementing (for the first time) a web service client in Swift using the Combine framework. For a method that makes a single call to a web service and then closes the connection, what is considered best practice – to return a Future or a Publisher? func getSomeThing() -> Future<SomeThing...> { } func getSomeThing() -> AnyPublisher<SomeThing...> { } In Flutter, a single async value would be a "Future" and multiple async values would be a "Stream". In the WWDC 2019 talk: "Introducing Combine" Apple presents a slide that hints at Futures as the async version of a single sync value. And Publishers as the async version of sync arrays. I understand that Futures in fact are Publishers. And that there may be some subtle technical nuances to consider (greedy/lazy etc). But thinking about the communicated intent, returning a Future seems to indicate a one hit wonder pipeline better than returning an AnyPublisher. But returning a Publisher seems easier and more in line with the framework, since it's pretty easy to eraseToAnyPublisher, for example from an URLSession.DataTaskPublisher, and just return that object. Mapping a DataTaskPublisher pipeline to a Future dosen't seem to be straight forward at all. Any best practice advise would be appreciated.
Posted
by
Post not yet marked as solved
1 Replies
704 Views
I'm trying to update my SwiftUI view when system volume changes. Ultimately, my use case is to display a low volume warning overlay when the system volume is low. Right now, though, let's say I'm just trying to show the current volume in a Text label. I've tried using onReceive with a publisher on AVAudioSession.sharedInstance().outputVolume, as in below, but my onReceive block only fires once when the view appears, and is not called when the volume subsequently changes. Why does the below not work, and what is the best way to update SwiftUI views when volume changes? struct Test: View { @State var volume: Float = 0 var body: some View { Text("current volume is \(volume)") .onReceive(AVAudioSession.sharedInstance().publisher(for: \.outputVolume), perform: { value in self.volume = value }) } } Thanks!
Posted
by
Post not yet marked as solved
1 Replies
1.2k Views
In a project that uses the Combine Framework, the following error suddenly occurs: Failed to build module 'Combine'; this SDK is not supported by the compiler (the SDK is built with 'Apple Swift version 5.5 (swiftlang-1300.0.24.14 clang-1300.0.25.10)', while this compiler is 'Apple Swift version 5.5 (swiftlang-1300.0.24.13 clang-1300.0.25.10)'). Please select a toolchain which matches the SDK. I must confess that I do not understand what the problem is. Is this an Xcode problem or can I solve it myself? Strangely enough, I was able to compile and run the project several times before without any problem. Furthermore, the error is not displayed in my code, but in the package https://github.com/MaxDesiatov/XMLCoder which I included using cocoapods.
Posted
by
Post marked as solved
2 Replies
500 Views
I'm using playground to experiment with Combine. I found this example on a blog. I expect it to create the Future publisher, wait a couple seconds then send the promise and complete. import Foundation import Combine import SwiftUI let future = Future<Int, Never> { promise in     print("Creating")     DispatchQueue.global().asyncAfter(deadline: .now() + 2) {         print("sending promise")         promise(.success(1))     } } future     .sink(receiveCompletion: { print("receiveCompletion:\($0)") },             receiveValue: { print("receiveValue:\($0)") }) print("end") The output I expect: Creating end sending promise receiveValue: ... receiveCompletion: ... The output I get: Creating end sending promise I don't see an indication the promise was executed. What am I doing wrong?
Posted
by
Post not yet marked as solved
0 Replies
490 Views
Unable to combine a project with dependencies that rely on combine. Getting an error saying unable to compile combine This issue started with Xcode 13 beta 3 and still persists with beta 4 for me. The project builds and runs fine in debug configuration, this error only happens when trying to build in release configuration. This is happening with a dependency linked though SPM. Not sure how to fix this
Posted
by
Post not yet marked as solved
2 Replies
447 Views
Now I want to notify value change from delegate event to another func() and returns the new value with callback(). To do that, I tried to write test program as below. However, problem happened. import SwiftUI struct ContentView: View {       let cst = CombineSinkTest()       var body: some View {     Button(action: {       cst.sinkTest(stCount: 0){ sinkResult in         print("finish")       }     }){       Text("Push")     }   } } import Foundation import Combine final public class CombineSinkTest: NSObject{       var stringPublisher = PassthroughSubject<String?, Never>()   var cancellables = [AnyCancellable]()         private var globalNum = 0   var sinkDefinedFlag = false       func sinkTest(stCount: Int, completion: @escaping (Bool) -> Void){     var localCount = stCount           childSink(count: localCount){ childResult in       localCount += 1       DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {         self.sinkTest(stCount: localCount){ stResult in           completion(true)         }       }     }   }       func childSink(count: Int, completion: @escaping (Bool) -> Void ){     globalNum += 1     let localNum = globalNum           print("globalNum: \(localNum)")           if(sinkDefinedFlag == false) {       self.stringPublisher         .sink(receiveCompletion: { print ("completion: \($0)") },          receiveValue: {           print ("received: \($0)")           print("local value: \(localNum)")           completion(true)          })         .store(in: &cancellables)       sinkDefinedFlag = true     } (a)    generateNotification()         }     (a)  private func generateNotification() {     stringPublisher.send("")   } } (a) is test-purpose. (a)'s func is planned to be delegate event. I want to let receiveValue: in .sink to use localCount number. However, localCount won't increase at all... globalNum: 1 localNum in caller of .sink: 1 received: Optional("") localNum in .sink: 1  globalNum: 2 localNum in caller of .sink: 2 received: Optional("") localNum in .sink: 1  globalNum: 3 localNum in caller of .sink: 3 received: Optional("") localNum in .sink: 1  globalNumber is increasing. And, localNum in func defining .sink. However, localCount in .sink closure won't increase. How should I change the code for passing updated localNum into .sink closure? Sorry for not good English... Best regards,
Posted
by
Post not yet marked as solved
2 Replies
904 Views
I have the following lines of code in practicing Combine. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? @Published var segmentNumber: Int = 0 // MARK: - IBOutlet @IBOutlet weak var actionButton: UIButton! // MARK: - IBAction @IBAction func segmentChanged(_ sender: UISegmentedControl) { segmentNumber = sender.selectedSegmentIndex } // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() cancellable = $segmentNumber.receive(on: DispatchQueue.main) .assign(to: \.isEnabled, on: actionButton) } } I get an error at .assign that says Value of type 'UIView?' has no member 'isEnabled' What am I doing wrong? Thank you.
Posted
by
Post marked as solved
2 Replies
498 Views
I'm trying to understand how Combine works. The following is my sample code. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? // MARK: - IBAction @IBAction func buttonTapped(_ sender: UIButton) { currentValueSubject.send(20) } // MARK: - Life cycle var currentValueSubject = CurrentValueSubject<Int, Never>(1) override func viewDidLoad() { super.viewDidLoad() let cancellable = currentValueSubject .sink { value in print("New value: \(value)") } currentValueSubject.send(5) currentValueSubject.send(10) //currentValueSubject.send(completion: .finished) currentValueSubject.send(15) //cancellable.cancel() } } If I run it with the iPhone simulator, I get New value: 1 New value: 5 New value: 10 New value: 15 If I tap the button, the app won't get a new value. I suppose that's because the subscription is cancelled at the end of viewDidLoad? If so, why does it get cancelled? I don't quite see a practical side of Combine's Subject. When is it useful? Thanks.
Posted
by
Post not yet marked as solved
2 Replies
1.4k Views
I am working on a library, a Swift package. We have quite a few properties on various classes that can change and we think the @Published property wrapper is a good way to annotate these properties as it offers a built-in way to work with SwiftUI and also Combine. Many of our properties can change on background threads and we've noticed that we get a purple runtime issue when setting the value from a background thread. This is a bit problematic for us because the state did change on a background thread and we need to update it at that time. If we dispatch it to the main queue and update it on the next iteration, then our property state doesn't match what the user expects. Say they "load" or "start" something asynchronously, and that finishes, the status should report "loaded" or "started", but that's not the case if we dispatch it to the main queue because that property doesn't update until the next iteration of the run loop. There also isn't any information in the documentation for @Published that suggests that you must update it on the main thread. I understand why SwiftUI wants it on the main thread, but this property wrapper is in the Combine framework. Also it seems like SwiftUI internally could ask to receive the published updates on the main queue and @Published shouldn't enforce a specific thread. One thing we are thinking about doing is writing our own property wrapper, but that doesn't seem to be ideal for SwiftUI integration and it's one more property wrapper that users of our package would need to be educated about. Any thoughts on direction? Is there anyway to break @Published from the main thread?
Posted
by
Post not yet marked as solved
0 Replies
272 Views
I'm playing with @EnvironmentObject to see how it works in SwiftUI. I have the main view (ContentView) where it says the user has not logged in yet. By letting the user tap a link, I want to make it such that they can log in by tapping a button. class LoginMe: ObservableObject { @Published var loggedIn = false } struct ContentView: View { @StateObject var loginMe = LoginMe() var body: some View { if loginMe.loggedIn { Text("Yes, I'm logged in") } else { NavigationView { VStack { Text("No, not logged in yet") .padding(.vertical) NavigationLink(destination: LoginView()) { Text("Tap to log in") } } .navigationBarTitle("User") } .environmentObject(loginMe) } } } struct LoginView: View { @EnvironmentObject var loginMe: LoginMe var body: some View { /* Toggle(isOn: $loginMe.loggedIn) { Text("Log in") }.padding(.horizontal) */ Button("Login") { loginMe.loggedIn.toggle() } } } So far, when the user taps a button in the LoginView view, the screen goes back to ContentView and the navigation simply disappears. How can I change my code so that the status will change back and forth in in the LoginView view by tapping a button and then so that they can return to ContentView the navigation return button? I think the problem is that I need to use @State var in ContentView and @Binding var in LoginView. Things are kind of confusing. Muchos thankos.
Posted
by
Post not yet marked as solved
0 Replies
303 Views
Here's the model of the API that I'm using // MARK: - Welcome struct NOAAWeatherDecoder: Codable, Identifiable {   var id = UUID()   let type: String   let geometry: Geometry   let properties: Properties   enum CodingKeys: String, CodingKey {     case type, geometry, properties   } } // MARK: - Properties struct Properties: Codable {   let updated: Date   let units, forecastGenerator: String   let generatedAt, updateTime: Date   let validTimes: String   let elevation: Elevation   let periods: [Period] } // MARK: - Elevation struct Elevation: Codable {   let value: Double   let unitCode: String } // MARK: - Period struct Period: Codable {   let number: Int   let name: String   let startTime, endTime: Date   let isDaytime: Bool   let temperature: Int   let temperatureUnit: TemperatureUnit   let temperatureTrend: JSONNull?   let windSpeed, windDirection: String   let icon: String   let shortForecast, detailedForecast: String } I am able to get upto periods, but nothing after that. Here's the part of the code where I'm running into the issue   ForEach(noaaWeatherData.weathernoaadata) { day in Text(day.properties.periods[0]) After this I'm not entirely sure. } Thanks!
Posted
by
Post marked as solved
1 Replies
311 Views
I am learning combine and one of the thing that is holding me back is the confusion of the semantic meaning of Cancellables. Why are they called Cancellables. I have tried to search for this answer on google but I didn’t find sufficient answers to satisfy my curiosity. Also, what is the advantage of using combine bindings over computed properties in SwiftUI. Thanks
Posted
by
Post not yet marked as solved
1 Replies
434 Views
I have been working on this for a while and haven't found a solution. I have a populated public core data database (used readonly in the app) that I need to pull from on launch. This ONLY works with SwiftUI via @FetchRequest or FetchRequest -- NSFetchResultsController, attempting to do it with persistentCloudKitContainer.viewContext.perform {} doesn't fetch (all do work after relaunching the app). The only thing that works is FetchRequest, but I need to do things in my model as soon as I get that data from CloudKit (create local data in response). How can I set, in my model, allItems to the wrappedValue property of the FetchRequest or @FetchRequest ? Since it is from cloudkit just setting the variable won't work, I need to use combine, but I can't wrap my head around how. If anyone has any suggestions -- I'd much appreciate the assistance. class MyViewModel: ObservableObject {     typealias PublisherType = PassthroughSubject<MyViewModel, Never>     let objectWillChange = ObservableObjectPublisher()     let objectDidChange = ObservableObjectPublisher() @Published var allItems: [Item] = [] struct MyView: View {     @FetchRequest var fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)])     var body: some View {            List(fetchRequest.wrappedValue) { (item:Item) in Text(item.name) } }
Posted
by
Post marked as solved
1 Replies
343 Views
I have an app that uses Realm as a data storage solution and Combine as a declarative framework. Ever since I upgraded to iOS 15, my app has become unusable because of what looks like a navigation bug. It works fine with iOS 14. The gist of what I believe the bug is, is that when I navigate to a child view and the parent view is updated, the navigation jumps back to the parent view instead of staying in the child view (https://www.youtube.com/watch?v=c-PAPy-RCqg) So in my example, ContentView loads data Realm and creates a list of DetailViews to display the data. When the DetailView is displayed, it writes some back to the Realm table. I have a sample app demonstrating the problem here: https://github.com/skywalkerdude/navsample. Since it is using Cocoapods, make sure you open NavSample.xcworkspace and not NavSample.xcodeproj.
Post not yet marked as solved
0 Replies
288 Views
I have two API call's for data and if openWeatherData fails it prevents planData.trainingdata from loading. Is there a way to workaround this so planData still loads?          List(planData.trainingdata) { index in           ForEach(openWeatherData.openWeatherData) { day in ... } } Here's the API call for openWeather class DownloadOpenWeatherData: ObservableObject {   @Published var openWeatherData: [OpenWeatherDecoder] = []   let locationManager = LocationManager()   var openWeatherCancellabes = Set<AnyCancellable>()   init() {     print("OpenWeather: loading init")     locationManager.startLoc()     getOpenWeatherData(weatherUrl: "https://api.openweathermap.org/data/2.5/onecall?lat=\(locationManager.getLat())&lon=\(locationManager.getLong())&exclude=hourly,minutely&appid")     locationManager.stopLoc()   }   func getOpenWeatherData(weatherUrl: String) {     guard let weatherUrl = URL(string: weatherUrl) else { return }     print("getOpenWeather url \(weatherUrl)")     URLSession.shared.dataTaskPublisher(for: weatherUrl)       .subscribe(on: DispatchQueue.global(qos: .background))       .receive(on: DispatchQueue.main)       .tryMap { (data, response) -> Data in         print(response)         guard           let response = response as? HTTPURLResponse,            response.statusCode >= 200 && response.statusCode < 300 else {           throw URLError(.badServerResponse)         }         print("data \(data)")         return data       }       .decode(type: OpenWeatherDecoder.self, decoder: JSONDecoder())       .sink { (completion) in         print(completion)       } receiveValue: { (returnedWeatherData) in         self.openWeatherData = [returnedWeatherData]         print("returnedWeatherData \(returnedWeatherData)")       }       .store(in: &openWeatherCancellabes)   } }
Posted
by
Post marked as solved
1 Replies
302 Views
Hola, I have the following simple lines of code. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? @Published var labelValue: String? // MARK: - IBOutlet @IBOutlet weak var textLabel: UILabel! // MARK: - IBAction @IBAction func actionTapped(_ sender: UIButton) { labelValue = "Jim is missing" } // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() cancellable = $labelValue .receive(on: DispatchQueue.main) .assign(to: \.text, on: textLabel) } } I just wonder what is the point of specifying the main thread with .receive? If I comment out the receive line, the app will still run without a problem. Muchos thankos
Posted
by
Post marked as solved
1 Replies
843 Views
Let me say that I have an IBOutlet object like @IBOutlet weak var deleteButton: UIButton! RxCocoa can make this button tap observable like deleteButton.rx.tap It doesn't look like Combine lets us observe a button tap. Am I right? I find one approach found at the following URL. https://www.avanderlee.com/swift/custom-combine-publisher/ And Combine has no native approach? And you still have to use the IBAction?
Posted
by
Post not yet marked as solved
5 Replies
582 Views
I am using a Combine URLSession to pull data from the Food Data Central API and the Data I am receiving is not being decoded with the JSON decoder. I know the Data is being received because I use the String(data:encoding: .utf8) as a debug print and I can see the downloaded data correctly in the console. I get an error message after the .decode completion failure that says "The data couldn't be read because it isn't in the correct format." I am guessing I have to add something like the "encoder: utf8" statement in the .decode function. Or maybe transform the data in the .tryMap closure before returning. But I have searched the documentation and other sources and have not found anywhere that discusses this. I am a fairly new to Swift (my first real app), I am hoping someone more-experienced can point me in the right direction. My code is as follows: private func fdcSearch(searchFor searchText: String) {         let query = "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=***&amp;query=+Apple%20+Fuji"         let searchURL = "https://api.nal.usda.gov/fdc/v1/foods/search?"         let searchQuery = "&amp;query="+searchString         print(searchURL+devData.apiKey+searchQuery) //        guard let url = URL(string: "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=***&amp;query=AppleFuji") else {         guard let url = URL(string: searchURL+devData.apiKey+searchQuery) else {             print("Guard error on url assignment") // debug statement             return         }         print("In fdcSearch") // debug statement         fdcSearchSubscription = URLSession.shared.dataTaskPublisher(for: url)             .subscribe(on: DispatchQueue.global(qos: .default))             .tryMap { (output) -&gt; Data in                 guard let response = output.response as? HTTPURLResponse, response.statusCode &gt;= 200 &amp;&amp; response.statusCode &lt; 300 else {                     print("bad server response") // debug statement                     throw URLError(.badServerResponse)                 }                 print("got output") // debug statement                 if let dataString = String(data: output.data, encoding: .utf8) { // debug statement                         print("got dataString: \n\(dataString)") // debug statement                     } // debug statement                 return output.data             }             .receive(on: DispatchQueue.main)             .decode(type: [FDCFoodItem].self, decoder: JSONDecoder())             .sink { (completion) in                 switch completion {                 case .finished:                     print("Completion finished") // debug statement                     break                 case .failure(let error):                     print("Completion failed") // debug statement                     print(error.localizedDescription)                 }             } receiveValue: { [weak self] (returnedFoods) in                 self?.foods = returnedFoods                 print("returnedFoods: \(returnedFoods)") // debug statement                 print("self?.foods: \(String(describing: self?.foods))") // debug statement             }     } Any suggestions on how to handle this?
Posted
by
Post not yet marked as solved
0 Replies
798 Views
Hello, I'm trying to work out a simple example to fill table view data with Combine. The following is what I have. import Foundation struct MyModel: Decodable { let id: String let type: String } import UIKit import Combine class APIClient: NSObject { var cancellable: AnyCancellable? let sharedSession = URLSession.shared func fetchData(urlStr: String, completion: @escaping ([MyModel]?) -> Void) { guard let url = URL(string: urlStr) else { return } let publisher = sharedSession.dataTaskPublisher(for: url) cancellable = publisher.sink(receiveCompletion: { (completion) in switch completion { case .failure(let error): print(error) case .finished: print("Success") } }, receiveValue: { (result) in let decoder = JSONDecoder() do { let post = try decoder.decode([MyModel].self, from: result.data) completion(post) } catch let error as NSError { print("\(error)") completion(nil) } }) } } import Foundation class ViewModel: NSObject { @IBOutlet var apiClient: APIClient! var dataModels = [MyModel]() func getGitData(completion: @escaping () -> Void) { let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events" apiClient.fetchData(urlStr: urlStr) { (models) in if let myModels = models { self.dataModels = myModels.map { $0 } } completion() } } } import UIKit import Combine class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // MARK: - Variables var cancellable: AnyCancellable? @IBOutlet var viewModel: ViewModel! @Published var models = [MyModel]() // MARK: - IBOutlet @IBOutlet weak var tableView: UITableView! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() viewModel.getGitData { self.models = self.viewModel.dataModels } cancellable = $models.sink(receiveValue: { (result) in DispatchQueue.main.async { [weak self] in guard let strongSelf = self else { return } strongSelf.tableView.reloadData() } }) } // MARK: - TableView func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return models.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell") let dataModel = models[indexPath.row] cell?.textLabel?.text = dataModel.id cell?.detailTextLabel?.text = dataModel.type return cell! } } I'm not quite comfortable with the lines of code under my view controller (ViewController) in using Combine. How can I make them better? Muchos thankos.
Posted
by