Post not yet marked as solved
Context
Let say we have an actor, which have a private struct.
We want this actor to be reactive, and to give access to a publisher that publishes a specific field of the private struct.
The following code seems to work. But I do have questions.
public actor MyActor {
private struct MyStruct {
var publicField: String
}
@Published
private var myStruct: MyStruct?
/* We force a non isolated so the property is still accessible from other contexts w/o await in other modules. */
public nonisolated let publicFieldPublisher: AnyPublisher<String?, Never>
init() {
self.publicFieldPublisher = _myStruct.projectedValue.map{ $0?.publicField }.eraseToAnyPublisher()
}
}
Question 1 & 2
In the init, I use _myStruct.projectedValue, which should be strictly equivalent to $myStruct.
Except the latter does not compile. We get the following error:
'self' used in property access '$myStruct' before all stored properties are initialized
Why? AFAICT myStruct should be init’d to nil, so where is the problem?
And why does the former do compile?
Question 3
With _myStruct.projectedValue, I get a warning at compile-time:
Actor 'self' can only be passed 'inout' from an async initializer
What does that mean? Is it possible to get rid of this warning? Is it “dangerous” (can this cause issues later)?
Thanks!
Post not yet marked as solved
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.
Post not yet marked as solved
I'm having trouble reasoning about and modifying the Detecting Human Actions in a Live Video Feed sample code since I'm new to Combine.
// ---- [MLMultiArray?] -- [MLMultiArray?] ----
// Make an activity prediction from the window.
.map(predictActionWithWindow)
// ---- ActionPrediction -- ActionPrediction ----
// Send the action prediction to the delegate.
.sink(receiveValue: sendPrediction)
These are the final two operators of the video processing pipeline, where the action prediction occurs. In either the implementation for private func predictActionWithWindow(_ currentWindow: [MLMultiArray?]) -> ActionPrediction or for private func sendPrediction(_ actionPrediction: ActionPrediction), how might I access the results of a VNHumanBodyPoseRequest that's retrieved and scoped in a function called earlier in the daisy chain?
When I did this imperatively, I accessed results in the VNDetectHumanBodyPoseRequest completion handler, but I'm not sure how data flow would work with Combine's programming model. I want to associate predictions with the observation results they're based on so that I can store the time range of a given prediction label.
Post not yet marked as solved
Is it possible to use the new http traffic instrument with combines dataTaskPublisher? How can I set a label to the task?
I've been using Combine with UIKit and Cocoa. The following is a simple example.
import UIKit
import Combine
class ViewController: UIViewController {
// MARK: - Variables
private var cancellableSet: Set<AnyCancellable> = []
@Published var loginText: String = ""
@Published var passwordText: String = ""
// MARK: - IBOutlet
@IBOutlet weak var loginField: UITextField!
@IBOutlet weak var passwordField: UITextField!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: loginField)
.sink { result in
if let textField = result.object as? UITextField {
if let text = textField.text {
self.loginText = text
}
}
}
.store(in: &cancellableSet)
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: passwordField)
.sink { result in
if let textField = result.object as? UITextField {
if let text = textField.text {
self.passwordText = text
}
}
}
.store(in: &cancellableSet)
Publishers.CombineLatest($loginText, $passwordText)
.sink { (result0, result1) in
if result0.count > 3 && result1.count > 3 {
print("You are good")
} else {
print("No way!!!")
}
}
.store(in: &cancellableSet)
}
}
Now, I want to use Combine with SwiftUI. The following is SwiftUI equivalent, so far.
import SwiftUI
import Combine
struct ContentView: View {
@State var anycancellables = Set<AnyCancellable>()
@State var userText: String = ""
@State var passText: String = ""
@State var canSave: Bool = false
var body: some View {
ZStack {
VStack {
Color.white
}.onTapGesture {
UIApplication.shared.endEditing()
}
VStack {
TextField("Username", text: $userText) {
}.onChange(of: userText) { newValue in
}
SecureField("Password", text: $passText) {
}.onChange(of: passText) { newValue in
}
Spacer()
.frame(height: 20.0)
Button("Save") {
print("Saved...")
}
.foregroundColor(canSave ? Color.black : Color.gray)
.font(.system(size: 32.0))
.disabled(!canSave)
}.padding(.horizontal, 20.0)
}
}
}
So where does Combine fit into the code? I want to enable the Save button if text counts of loginText and passwordText are both greater than 3, which is done at the top with UIKit.
Muchos thankos.
I could do it with completionHandler, but I'm trying to get server data with Combine. The following is what I have.
// UIViewController //
import UIKit
import Combine
class ViewController: UIViewController {
// MARK: - Variables
private var cancellableSet: Set<AnyCancellable> = []
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events"
let viewModel = ViewModel(urlStr: urlStr, waitTime: 2.0)
viewModel.fetchData(urlText: viewModel.urlStr, timeInterval: viewModel.waitTime)
.sink { completion in
print("complete")
} receiveValue: { dataSet in
print("count: \(dataSet)")
}
.store(in: &cancellableSet)
print("Yeah...")
}
}
struct DataModel: Hashable, Decodable {
let id: String
let type: String
}
// ViewModel //
import UIKit
import Combine
class ViewModel: NSObject {
var cancellables = [AnyCancellable]()
var urlStr: String
var waitTime: Double
init(urlStr: String, waitTime: Double) {
self.urlStr = urlStr
self.waitTime = waitTime
}
func fetchData(urlText: String, timeInterval: Double) -> Future<[DataModel], Error> {
return Future<[DataModel], Error> { [weak self] promise in
guard let strongSelf = self else { return }
if let url = URL(string: urlText) {
var request = URLRequest(url: url)
request.timeoutInterval = timeInterval
let sessionConfiguration = URLSessionConfiguration.default
let publisher = URLSession(configuration: sessionConfiguration).dataTaskPublisher(for: request)
publisher.sink { completion in
print("complete")
} receiveValue: { (data: Data, response: URLResponse) in
do {
let dataModels = try JSONDecoder().decode([DataModel].self, from: data)
promise(.success(dataModels))
} catch {
print("Error while parsing: \(error)")
promise(.failure("Failure" as! Error))
}
}
.store(in: &strongSelf.cancellables)
} else {
promise(.failure("Failure" as! Error))
}
}
}
}
If I run it, I don't get an error. The app doesn't crash, either. The view controller doesn't deliver anything. What am I doing wrong? Muchos thankos.
Post not yet marked as solved
When crash by assertNoFailure, I cannot see the message.
smaple code
View
import SwiftUI
struct ContentView: View {
@ObservedObject private var viewModel = ContentViewModel()
var body: some View {
Button(action: { viewModel.crash() }, label: { Text("button") })
}
}
ViewModel
import Foundation
import Combine
class ContentViewModel: ObservableObject {
var cancellables = Set<AnyCancellable>()
private enum AddOneError: Swift.Error {
case error
}
private func addOnePublisher(_ n: Int) -> AnyPublisher<Int, AddOneError> {
Deferred {
Future { (promise) in
guard n < 10 else {
promise(.failure(AddOneError.error))
return
}
promise(.success(n + 1))
}
}
.eraseToAnyPublisher()
}
func crash() {
addOnePublisher(10)
.assertNoFailure("I want to disply this message.")
.sink { (completion) in
switch completion {
case .finished:
break
case .failure(let error):
print(error)
}
} receiveValue: { (n) in
print(n)
}
.store(in: &cancellables)
}
}
when crash
if I use
.catch { error -> AnyPublisher<Int, Never> in fatalError("I can see this message") }
instead of assertNoFailure
I can see log
Fatal error: I can see this message
How can I see the prefix of assertNoFailure? And if not, when is it used?
Post not yet marked as solved
Environment
OS: macOS Monterey 12.0.1
Xcode: 13.1
Context & Issue
I'm implementing a feature which calls an API to fetch new data as an observed value changes. In order to avoid too many API calls, I tried throttling the observed value publisher. But I found this throttling doesn't work if I apply the .eraseToAnyPublisher() method to the publisher.
After some investigation by myself, I noticed the .eraseToAnyPublisher() causes many unnecessary subscriptions and unsubscriptions to the throttled publisher. So I'm wondering if I make any mistakes and would like to get helps.
Minimal reproducible example
import SwiftUI
@main
struct SwiftUITestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class ViewModel: ObservableObject {
@Published var value: Float = 0
}
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
@State private var text = ""
var body: some View {
VStack {
Text(text)
Slider(value: $viewModel.value, in: 0...1)
}
.padding()
.onReceive(
viewModel
.$value
// .eraseToAnyPublisher() // Uncomment this line causes the issue
.throttle(for: 1, scheduler: DispatchQueue.main, latest: true)
.print()
) { value in
text = String(format: "%.2f", arguments: [value])
}
}
}
Behavior without .eraseToAnyPublisher()
If I don't apply .eraseToAnyPublisher(), this code works as expected and the .print() of the publisher will output something like:
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.0)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.0)
receive value: (0.021933686)
receive value: (0.28136003)
receive value: (0.6122359)
receive value: (0.66554797)
receive value: (0.3587148)
receive value: (0.2890992)
Behavior with .eraseToAnyPublisher()
If I apply .eraseToAnyPublisher(), the throttled publisher gets subscribed and unsubscribed many times. This makes the throttling look not working. The .print() of the publisher will output something like:
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.0)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.0)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.12430311)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.12430311)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.13761736)
receive cancel
receive subscription: (Throttle)
Question
Though I've already found a workaround (= not using .eraseToAnyPublisher()), I would like to understand why this happens. In my understanding, .eraseToAnyPublisher() only erases the type information and should not cause any behavioral changes.
Thanks
Post not yet marked as solved
I'm trying out this code in a playground in Xcode 13.2 beta (13C5066c)
import _Concurrency
import Combine
import PlaygroundSupport
import Foundation
extension Task where Success == Never, Failure == Never {
static func sleep(seconds: Double) async throws {
try await Self.sleep(nanoseconds: UInt64(1e9 * seconds))
}
}
Task {
let values = PassthroughSubject<Int, Never>()
Task {
var counter = 0
while true {
counter += 1
print("[SEND] \(counter)")
values.send(counter)
try! await Task.sleep(seconds: Double.random(in: 0.1...0.5))
}
}
for await value in values
// vvvvvvvvvvvvv
.buffer(size: Int.max, prefetch: .keepFull, whenFull: .dropOldest)
// ^^^^^^^^^^^^^
.values {
print("[RECV] \(value)")
try! await Task.sleep(seconds: 1)
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
This is modeled after real application code. For example, values could be a PassthroughSubject<Packet, NWError> (this is, in fact, what my app code looks like)
I've noticed that when doing Publisher.values to convert a Publisher into an AsyncPublisher (so we can use for await), the values aren't buffered.
In other words, if we are inside of the body of the for await loop and something is sent to the PassthroughSubject, that value is dropped unless we use .buffer beforehand.
This is demonstrated in the playground code above. The outer task receives values, but takes 1 second to process them. The inner task sends values at a fast rate (100ms–500ms). This means that values received during that 1 second period are dropped.(without the .buffer call; with that call, the problem goes away)
Is this intentional? I believe this should be more prevalent in documentation.
The documentation says that AsyncStream has a buffer:
An arbitrary source of elements can produce elements faster than they are consumed by a caller iterating over them. Because of this, AsyncStream defines a buffering behavior, allowing the stream to buffer a specific number of oldest or newest elements. By default, the buffer limit is Int.max, which means the value is unbounded.
But AsyncPublisher conforms to AsyncSequence, and not AsyncStream. Maybe this is how this can be fixed?
Post not yet marked as solved
I'm using the Mirror API to access the underlying publisher of properties wrapped with @Published. For some reason, when setting a published property, values aren't received by the publisher I'm accessing after the initial value.
In the following code, I would expect the "mirrored" publisher to receive events for eternity.
import Combine
class Dinosaur: ObservableObject {
@Published var angry: Bool = false
}
let raptor = Dinosaur()
//let subscription1 = raptor.$angry
// .print("NORMAL")
// .sink { value in }
var published = Mirror(reflecting: raptor).children.first!.value as! Published<Bool>
let publisher = published.projectedValue
let subscription2 = publisher
.print("MIRRORED")
.sink { value in }
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
raptor.angry = !raptor.angry
}
But, it only has the following output.
MIRRORED: receive subscription: (PublishedSubject)
MIRRORED: request unlimited
MIRRORED: receive value: (false)
Interestingly, if the let subscription1 = ... line is uncommented, both sinks receive each successive value successfully.
NORMAL: receive subscription: (PublishedSubject)
NORMAL: request unlimited
NORMAL: receive value: (false)
MIRRORED: receive subscription: (PublishedSubject)
MIRRORED: request unlimited
MIRRORED: receive value: (false)
MIRRORED: receive value: (true)
NORMAL: receive value: (true)
MIRRORED: receive value: (false)
NORMAL: receive value: (false)
... etc
It's almost like the compiler has to "see" the magic raptor.$angry to set up a proper subscription.
What am I doing wrong?
If I want to subscribe to four @Published variables at the same time, I can do something like the following.
Publishers.CombineLatest4($variable0, $variable1, $variable2, $variable3)
I wonder if there is any solution to subscribing to more than four variables at the same time?
Muchos thankos
I'm trying to figure out how to use URLSession with the Combine framework.
I have a class that is to fetch data as follows.
import UIKit
import Combine
class APIClient: NSObject {
var cancellables = [AnyCancellable]()
@Published var models = [MyModel]()
func fetchData(urlStr: String) -> AnyPublisher<[MyModel], Never> {
guard let url = URL(string: urlStr) else {
let subject = CurrentValueSubject<[MyModel], Never>([])
return subject.eraseToAnyPublisher()
}
let subject = CurrentValueSubject<[MyModel], Never>(models)
URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.decode(type: [MyModel].self, decoder: JSONDecoder())
.replaceError(with: [])
.sink { posts in
print("api client: \(posts.count)")
self.models = posts
}
.store(in: &cancellables)
return subject.eraseToAnyPublisher()
}
}
I then have a view model class that is to deliver data for my view controller as follows.
import Foundation
import Combine
class ViewModel: NSObject {
@IBOutlet var apiClient: APIClient!
var cancellables = Set<AnyCancellable>()
@Published var dataModels = [MyModel]()
func getGitData() -> AnyPublisher<[MyModel], Never> {
let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events"
let subject = CurrentValueSubject<[MyModel], Never>(dataModels)
apiClient.fetchData(urlStr: urlStr)
.sink { result in
print("view model: \(result.count)")
self.dataModels = result
}.store(in: &cancellables)
return subject.eraseToAnyPublisher()
}
}
My view controller has an IBOutlet of ViewModel.
import UIKit
import Combine
class ViewController: UIViewController {
// MARK: - Variables
var cancellables = [AnyCancellable]()
@IBOutlet var viewModel: ViewModel!
// MARK: - IBOutlet
@IBOutlet weak var tableView: UITableView!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
viewModel.getGitData()
.sink { posts in
print("view controller: \(posts.count)")
}
.store(in: &cancellables)
}
}
If I run it, it seems that ViewModel returns 0 without waiting for APIClient to return data. And the view controller doesn't wait, either. What am I doing wrong? Can I do it without using the completion handler?
In case you need to know what MyModel is, it's a simple struct.
struct MyModel: Decodable {
let id: String
let type: String
}
Muchos thanks
I have the following lines of code to subscribe text changes over two text fields.
import UIKit
import Combine
class ViewController: UIViewController {
var cancellables = Set<AnyCancellable>()
@Published var userText: String = ""
@Published var passText: String = ""
// MARK: - IBOutlet
@IBOutlet var usernameTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: usernameTextField)
.sink(receiveValue: { (result) in
if let myField = result.object as? UITextField {
if let text = myField.text {
self.userText = text
}
}
})
.store(in: &cancellables)
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: passwordTextField)
.sink(receiveValue: { (result) in
if let myField = result.object as? UITextField {
if let text = myField.text {
self.passText = text
}
}
})
.store(in: &cancellables)
$userText
.sink(receiveValue: { text in
print(text)
})
.store(in: &cancellables)
}
}
In the last several lines, I am printing the text change for userText. Does Combine allow me to observe two variables (userText, passText) at the same time so that I can plug them into a function? If yes, how?
Muchos Thankos.
I'm still a beginner in using Combine. I practice it on and off. Anyway, I have a view model to see changes in two text fields in my view controller as follows.
// ViewModel //
import Foundation
import Combine
class LoginViewModel {
var cancellable = [AnyCancellable]()
init(username: String, password: String) {
myUsername = username
myPassword = password
}
@Published var myUsername: String?
@Published var myPassword: String?
func validateUser() {
print("\(myUsername)")
print("\(myPassword)")
}
}
And my view controller goes as follows.
// ViewController //
import UIKit
import Combine
class HomeViewController: UIViewController {
// MARK: - Variables
var cancellable: AnyCancellable?
// MARK: - IBOutlet
@IBOutlet var usernameTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
cancellable = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: usernameTextField)
.sink(receiveValue: { result in
if let textField = result.object as? UITextField {
if let text = textField.text {
let loginViewModel = LoginViewModel(username: text, password: "")
loginViewModel.validateUser()
}
}
})
}
}
So I use NSNotification as a publisher to see text changes over one of the text fields. And I cannot see text changes over two of them at the same time. Is there a better approach in seeing text changes over two text fields at the same time using Combine?
Muchos thankos.
Post not yet marked as solved
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.
Post not yet marked as solved
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=***&query=+Apple%20+Fuji"
let searchURL = "https://api.nal.usda.gov/fdc/v1/foods/search?"
let searchQuery = "&query="+searchString
print(searchURL+devData.apiKey+searchQuery)
// guard let url = URL(string: "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=***&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) -> Data in
guard let response = output.response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode < 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?
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?
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
Post not yet marked as solved
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)
}
}
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.