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
19 Views
Hi, I have a C++ application connected to a specific socket. The problem is that when the application is not in use, macOS idle wakeups become very low. I am quite new in Apple development but as I understand applications get low priority when the idle wakeups are low. Therefore, application only wakes up when the set period of time (say a few seconds) is reached and it does not respond to requests immediately. To improve performance, I am trying to implement an event listening mechanism in the application that would be triggered by socket activity. In the documentation, there is some information on network events (see Table 7.1): https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/Timers.html I guess every messaging app is implementing a similar mechanism i.e. rather than waiting for the idle wakeup period to wake the application, an event is triggered when user data arrives at the socket application is connected to. My questions are Is my understanding of idle wakeups mechanism correct? What options are available to solve this problem and improve app performance i.e. response time? Are there any sample code snippets showing how to register that kind of a network event listener? Many thanks
Posted
by drecco.
Last updated
.
Post not yet marked as solved
0 Replies
58 Views
Hi all, Recently I stumbled upon some, for me at least, bizar memory leak. I have a viewmodel, which provides some sharing functionality. To do this it exposes 2 variables: class ViewModelOne: ObservableObject {   let tracker = DeinitTracker("ViewModelOne") //this is to easily track lifetime of instances   @Published var shareReady = false   var sharedUrls = [URL]()       func share() {     sharedUrls = [URL(string: "www.google.com")!]     shareReady = true   } } Next I have a mainview, which provides 2 subviews, with some controls to switch between the 2 subviews: enum ViewMode {   case one   case two } struct MainView: View {   @State var viewMode: ViewMode = .one   var body: some View {     VStack {       switch viewMode {       case .one:         ViewOne()       case .two:         ViewTwo()       }       HStack {         Spacer()         Button(action: {           viewMode = .one         }, label: { Image(systemName: "1.circle").resizable().frame(width: 80) })         Spacer()         Button(action: {           viewMode = .two         }, label: { Image(systemName: "2.circle").resizable().frame(width: 80) })         Spacer()       }.frame(height: 80)     }   } } struct ViewOne: View {   let tracker = DeinitTracker("ViewOne")   @StateObject var viewModel = ViewModelOne()   var body: some View {     VStack {       Button(action: {viewModel.share()},           label: { Image(systemName: "square.and.arrow.up").resizable() })       .frame(width: 40, height: 50)       Image(systemName: "1.circle")         .resizable()     }     .sheet(isPresented: $viewModel.shareReady) {       ActivityViewController(activityItems: viewModel.sharedUrls)     }   } } struct ViewTwo: View {   var body: some View {     Image(systemName: "2.circle")       .resizable()   } } ViewOne contains a button that will trigger its viewmodel to setup the urls to share and a published property to indicate that the urls are ready. That published property is then used to trigger the presence of a sheet. This sheet then shows the ActivityViewController wrapper for SwiftUI: struct ActivityViewController: UIViewControllerRepresentable {   let activityItems: [Any]   let applicationActivities: [UIActivity]? = nil   @Environment(\.presentationMode) var presentationMode       func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {     let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)     controller.completionWithItemsHandler = { _, _, _, _ in       self.presentationMode.wrappedValue.dismiss()     }     return controller   }       func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {   } } And now for the bizar part. As long as the sheet hasn't been shown, all is well and switching between subview 1 and 2 behaves as expected, where ViewOne's ViewModel is deinitialized when the ViewOne instance is destroyed. However to once the sheet with ActivityViewController has been presented in ViewOne, and then switching to ViewTwo, ViewOne is still destroyed, but ViewOne's viewmodel isn't. To track this, I have helper struct DeinitTracker that prints when it gets initialized and deinitialized: public class DeinitTracker {   static var counter: [String: Int] = [:]   public init(_ deinitText: String) {     DeinitTracker.counter[deinitText] = (DeinitTracker.counter[deinitText] ?? -1) + 1     self.deinitText = deinitText     self.count = DeinitTracker.counter[deinitText] ?? -1     print("DeinitTracker-lifetime: \(deinitText).init-\(count)")   }       let deinitText: String   let count: Int       deinit {     print("DeinitTracker-lifetime: \(deinitText).deinit-\(count)")   } } I can't figure out who is holding a reference to the ViewModel that prevents it from being deinitialized. I know it's a rather complicated explanation, but I'm hoping the scenario is clear. I've prepared a Playgrounds app to demonstrate the problem -> https://www.icloud.com/iclouddrive/0629ZP6MXMrj7GJIWHpGum6Dw#LeakingViewModel I'm hoping someone can explain what's going on. Is this a bug in SwiftUI? Or am I using it wrong by binding a viewmodel's published property to a sheet's isPresented property. If you have any questions, don't hesitate to ask.
Posted
by GeertB.
Last updated
.
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 Last updated
.
Post not yet marked as solved
0 Replies
51 Views
Hi, if values are PUBLISHED rapidly, then ALL are present in the Combine sink, but SOME of them are absent from the async loop. Why the difference? For example, in the code below, tapping repeatedly 4 times gives the output: INPUT 24, INPUT 9, INPUT 31, INPUT 45, SINK 24, SINK 9, LOOP 24, SINK 31, SINK 45, LOOP 31. import SwiftUI import Combine import PlaygroundSupport var subject = PassthroughSubject<Int, Never>() struct ContentView: View {     @State var bag = [AnyCancellable]()     @State var a = [String]()     var body: some View {         Text("TAP A FEW TIMES RAPIDLY") .frame(width: 160, height: 160)         .onTapGesture {             Task {                 let anyInt = Int.random(in: 1..<100)                 print("INPUT \(anyInt)")                 try await Task.sleep(nanoseconds: 3_000_000_000)                 subject.send(anyInt)             }         }         .task {             for await anyInt in subject.values {                 print("    LOOP \(anyInt)")             }         }         .onAppear{             subject.sink{ anyInt in                 print("  SINK \(anyInt)")             }.store(in: &bag)         }     } } PlaygroundPage.current.setLiveView(ContentView()) Thank you.
Posted Last updated
.
Post not yet marked as solved
2 Replies
101 Views
Is the following code accurate/safe? final class ObjectWithPublished: @unchecked Sendable { @Published var property: Int? } I've tried testing this with the following test, which passes: final class PublishedThreadSafetyTests: XCTestCase { func testSettingValueFromMultipleThreads() { let object = ObjectWithPublished() let iterations = 1000 let completesIterationExpectation = expectation(description: "Completes iterations") completesIterationExpectation.expectedFulfillmentCount = iterations let receivesNewValueExpectation = expectation(description: "Received new value") receivesNewValueExpectation.expectedFulfillmentCount = iterations var cancellables: Set<AnyCancellable> = [] object .$property .dropFirst() .sink { _ in receivesNewValueExpectation.fulfill() } .store(in: &cancellables) DispatchQueue.concurrentPerform(iterations: iterations) { iteration in object.property = iteration completesIterationExpectation.fulfill() } waitForExpectations(timeout: 1) } } There are also no warnings when using the address sanitizer, so it seems like subscriptions and updating @Published values is thread-safe, but the Published type is not marked Sendable so I can't be 100% sure. If this isn't safe, what do I need to protect to have the Sendable conformance be correct?
Posted Last updated
.
Post not yet marked as solved
0 Replies
62 Views
TestCombineLatest.log Hello Eskimo, check the example below... If the documentation for Publishers.Combine would be correct, it would always announce an unlimited demand to the upstream publishers - and it doesn't. There also seems to be a bug if downstream requests a demand of .max(1). Somehow I am pretty puzzled by this bug... seems there is just limited unit testing for an API? Or is there something I don't understand? I have filed a radar: FB10446601 - wondering if anyone will look at it... Testcase output attached. import Foundation import Combine import XCTest class TestCombineLatest:XCTestCase { func testCombineLatest() { let latest1 = PassthroughSubject<Int,Never>() let latest2 = PassthroughSubject<Int,Never>() var result:[[Int]] = [] var subscription:Subscription? let subscriber = AnySubscriber<(Int,Int),Never>( receiveSubscription: {sub in subscription = sub sub.request(.max(1)) // replace with sup.request(.unlimited) and the test case succeeds. }, receiveValue: { (v1,v2) in result.append([v1,v2]) return .max(1) }, receiveCompletion: {_ in} ) let publisher = Publishers.CombineLatest(latest1.print("Latest1"), latest2.print("Latest2")) .print("CombineLatest") publisher .subscribe(subscriber) latest1.send(1) latest2.send(1) latest1.send(2) //<- has no effect... latest2.send(2) latest1.send(completion: .finished) latest2.send(completion: .finished) print("Result is:\(result)") XCTAssertEqual(result, [[1,1], [2,1], [2,2] ]) }}
Posted Last updated
.
Post not yet marked as solved
1 Replies
148 Views
I'm fairly new to Swift and struggling to go beyond the basics with SwiftUI's state management. My app uses Firebase to authenticate itself with a server, so it goes through a series of state changes when logging in that the UI needs to respond to. Because logging in goes through these multiple stages, I want to use async/await to make my code more readable than using a series of completion callbacks. The trouble is I can't update my state variables from an async function if SwiftUI is observing them via @ObservedObject or similar. One way to fix that is to never update state directly from an async function, but use DispatchQueue.main.async instead. The trouble is, this adds noise, and Apple discourage it. Instead of changing how you set the state, you're supposed to change how you listen to it, by using .receive(on:). The trouble is, I think the point where I would need to do that is buried somewhere in what @ObservedObject does behind the scenes. Why doesn't it already use .receive(on:)? Can I get or write a version that does without having to spend ages learning intricate details of SwiftUI or Combine?
Posted
by realh.
Last updated
.
Post not yet marked as solved
2 Replies
260 Views
Hi everyone! I'm currently struggling with dynamically filtering data in a SwiftUi List view. In the following example, I created some example data and stored them within "TestArray". These data is dynamically filtered and grouped by the starting letter. The data of the "Testclass" objects can be changed in "EditDetails" view. Unfortunately, when changing the name (as it is the relevant property for filtering here), when closing the modal, the user does not return to DetailView but will break the view hierarchy and end up in the ContentView. I assume the issue is the update within ContentView, which is recreating the DetailView stack. Is it possible to ensure the return to view, where the modal has been opened from (DetailView)? Here is some code if you would like to reproduce the issue: import Foundation import SwiftUI import Combine struct ContentView: View {     @StateObject var objects = TestArray()     var body: some View {         NavigationView{             List { ForEach(objects.objectList.compactMap({$0.name.first}).unique(), id: \.self) { obj in                     Section(header: Text(String(obj))) {                         ForEach(objects.objectList.filter({$0.name.first == obj}), id: \.self) { groupObj in                             NavigationLink(destination: Detailview(testclass: groupObj)) {                                 Text("\(groupObj.name)")                             }                         }                     }                 }             }         }     } } struct Detailview: View {     @ObservedObject var testclass: Testclass     @State private var showingEdit: Bool = false     var body: some View {         VStack {             Text("Hello, \(testclass.name)!")             Text("\(testclass.date)!")         }         .toolbar {             ToolbarItemGroup(placement: .navigationBarTrailing) {                 Button(action: {                     self.showingEdit.toggle()                 }) {                     Image(systemName: "square.and.pencil")                 }             }         }         .sheet(isPresented: $showingEdit) {             EditDetails(testclass: testclass, showEdit: $showingEdit)         }     } } struct EditDetails: View {     @ObservedObject var testclass: Testclass     @Binding var showEdit: Bool     @State private var name: String     @State private var date: Date = Date()     init(testclass: Testclass, showEdit: Binding<Bool>) {         self.testclass = testclass         _name = State(initialValue: testclass.name)         _date = State(initialValue: testclass.date)         _showEdit = Binding(projectedValue: showEdit)     }     var body: some View {         NavigationView{             List {                 TextField("Name", text: $name)                     .onChange(of: name, perform: { newValue in                         self.testclass.name = newValue                     })                 DatePicker(selection: $date) {                     Text("Date")                 }                 .onChange(of: date, perform: { newValue in                     self.testclass.date = newValue                 })             }             .toolbar {                 ToolbarItem(placement: .navigationBarTrailing) {                     Button(action: {                         testclass.objectWillChange.send()                         showEdit.toggle()                     }) {                         Image(systemName: "xmark")                     }                 }             }         }     } } extension Sequence where Iterator.Element: Hashable {     func unique() -> [Iterator.Element] {         var seen: Set<Iterator.Element> = []         return filter { seen.insert($0).inserted }     } } class Testclass: NSObject, ObservableObject {     @Published var name: String     @Published var date: Date     init(name: String, date: Date = Date()) {         self.name = name         self.date = date         super.init()     } } class TestArray: NSObject, ObservableObject {     @Published var objectList: [Testclass]     private var cancellables = Set<AnyCancellable>()     override init() {         self.objectList = [             Testclass(name: "A1"),             Testclass(name: "B1"),             Testclass(name: "Z2"),             Testclass(name: "C1"),             Testclass(name: "D1"),             Testclass(name: "D2"),             Testclass(name: "A2"),             Testclass(name: "Z1")         ]         super.init()         objectList.forEach { object in             object.objectWillChange                 .receive(on: DispatchQueue.main) // Optional                 .sink(receiveValue: { [weak self] _ in                     self?.objectWillChange.send()                     print("changed value \(object.name)")                 })                 .store(in: &cancellables)         }     } }
Posted
by folox.
Last updated
.
Post not yet marked as solved
0 Replies
105 Views
This Mac Catalyst tutorial (https://developer.apple.com/tutorials/mac-catalyst/adding-items-to-the-sidebar) shows the following code snippet: recipeCollectionsSubscriber = dataStore.$collections .receive(on: RunLoop.main) .sink { [weak self] _ in guard let self = self else { return } let snapshot = self.collectionsSnapshot() self.dataSource.apply(snapshot, to: .collections, animatingDifferences: true) }
Posted
by Curiosity.
Last updated
.
Post not yet marked as solved
1 Replies
244 Views
I am a Swift beginner; This code in my AppDelegate, it's working! func applicationDidFinishLaunching(_ notification: Notification) {     var join = Contact.instance     var cancellable = Contact.instance.objectWillChange.sink { val in       print("val: \(val)")       print("\(join.age) will change")     }     print(join.haveBirthday())   } // val: () // 24 will change // 25 But, Call havBirthday() from inside ContentView, it's not working. There is no output from the console. struct ContentView: View {       var timer = Timer.publish(every: 1, tolerance: nil, on: .current, in: .common, options: nil).autoconnect();       var body: some View {     VStack{       Text("The name is: \(Contact.instance.name), and age is \(Contact.instance.age)")         .padding()       Button("age+1"){         let age = Contact.instance.haveBirthday()         print("changedAge: \(age)")       }     }   } } class Contact: ObservableObject{   static let instance = Contact(name:"John Appleseed",age:24)       @Published var name: String;   @Published var age: Int;       init(name:String, age:Int){     self.name = name;     self.age = age;   }       func haveBirthday()->Int{     age+=1;     return age;   } }
Posted
by jros.
Last updated
.
Post not yet marked as solved
1 Replies
273 Views
Running into a weird issue with TabViews not rerendering the view when objectWillChange.send() is called (either manually or with @Published). For context, in my real project I have a tab with form data and the adjacent tab is a summary tab which renders a few elements from the form data. The summary tab is not getting updated when the form data changes. I have created a simple demo project that demonstrates the issue. The project can be found here. The content view is just a tab view with four tabs, all of which point to the same core data object. struct ContentView: View {     @Environment(\.managedObjectContext) private var viewContext     @State private var selectedIndex: Int = 0     var tabTitles: Array<String> = ["Tab 1", "Tab 2", "Tab 3", "Tab 4"]     var body: some View {         // Create a page style tab view from the tab titles.         TabView(selection: $selectedIndex) {             ForEach(tabTitles.indices, id: \.self) { index in                 TextView(viewModel: TextViewModel(                     title: tabTitles[index],                     context: viewContext))             }         }         .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))     } } The text view just contains the title and a text field for updating the core data object in the view model. struct TextView: View {     @ObservedObject private var viewModel: TextViewModel     @State private var text: String     private var relay = PassthroughSubject<String, Never>()     private var debouncedPublisher: AnyPublisher<String, Never>     init(viewModel: TextViewModel) {         self.viewModel = viewModel         self._text = State(initialValue: viewModel.textValue)         self.debouncedPublisher = relay             .debounce(for: 1, scheduler: DispatchQueue.main)             .eraseToAnyPublisher()     }     var body: some View {         LazyVStack {             Text(viewModel.title)                 .font(.title)             TextField("write something", text: $text)                 .onChange(of: text) {                     relay.send($0)                 }         }         .padding()         .onReceive(debouncedPublisher) {             viewModel.textValue = $0         }         /// Without this the view does not update, with it the view updates properly...         .onAppear {             self.text = viewModel.textValue         }     } } And the view model is pretty simple. I've tried making item @Published, I've tried making the text value computed (with @Published item), etc. This example uses a combine publisher on item's value attribute which is a lot like what I'm doing in the main project. class TextViewModel: ObservableObject {     @Published var textValue: String {         didSet {             // Check that the new value is not the same as the current item.value or else an infinte loop is created.             if item.value != textValue {                 item.value = textValue                 try! context.save()             }         }     }     private(set) var item: Item     private(set) var title: String     private var subscriber: AnyCancellable?     private var context: NSManagedObjectContext     init(title: String, context: NSManagedObjectContext) {         self.title = title         self.context = context         let request = NSFetchRequest<Item>(entityName: "Item")         request.predicate = NSPredicate(format: "%K == %@", #keyPath(Item.key), "key")         request.sortDescriptors = [NSSortDescriptor(key: #keyPath(Item.value), ascending: true)]         let fetched = try! context.fetch(request)         let fetchedItem = fetched.first!         self.textValue = fetchedItem.value!         self.item = fetchedItem         // Create a publisher to update the text value whenever the value is updated.         self.subscriber = fetchedItem.publisher(for: \.value)             .sink(receiveValue: {                 if let newValue = $0 {                     self.textValue = newValue                 }             })     } } Item is just a simple core data property with a key: String and value: String. I know I can directly bind the view to to the text value using $viewModel.textValue. It doesn't update the view when the value changes either and I don't want that behavior in my real app for a variety of reasons. Is there something that I am missing here? Do I really need to call onAppear for all of my views within the TabView to check and see if the value is up-to-date and update it if needed? It seems a bit silly to me. I haven't really found much info out there on this. I've also tried forcing a redraw using the (super yucky) use of @State var redraw: Bool and toggling it in onAppear. That does not trigger a redraw either. The other thing I've tried that works is setting an @State isSelected: Bool on the TextView and in the ForEach setting it to index == selectedIndex. This works and may be the least revolting solution I have found. Thoughts?
Posted Last updated
.
Post not yet marked as solved
0 Replies
138 Views
So I am trying to implement a custom control using UIViewRepresentable. Basically I want to wrap a UITextField to have more precise control over the focus behavior. I would like to have this control integrate with the SwiftUI focus system. As far as I have gotten, I understand that I would probably need to have my custom coordinator refer events back and fourth between the UITextField and the @FocusState somehow, but I cannot find any documentation on this. Is there an example out there somewhere, or else is there any open-source code which shows how @FocusState is working with existing controls?
Posted
by skohan.
Last updated
.
Post not yet marked as solved
1 Replies
187 Views
Hi everyone, does anyone have a suggestion on how I can add a detail view to this grid view? import SwiftUI struct ContentView: View {     var body: some View {         NavigationView {             List {                 ImageRow()             }.navigationBarTitle(Text("Landscapes"))         }     } } struct ContentView_Previews: PreviewProvider {     static var previews: some View {         ContentView()     } } import SwiftUI import Combine struct ImageRow: View {     var body: some View {         var images: [[Int]] = [] _ = (1...18).publisher .collect(2) // Creating two columns             .collect()             .sink(receiveValue: { images = $0 })                  return ForEach(0..<images.count, id: \.self) { array in             HStack {                 ForEach(images[array], id: \.self) { number in                     Image("noaa\(number)")                         .resizable()                         .scaledToFit()                         .cornerRadius(10)                   }             }         }     } }
Posted
by iRIG.
Last updated
.
Post marked as solved
1 Replies
248 Views
Hi, I have a SwiftUI ProgressBar View that displays the percentage progress based on a percentage-value you enter as a Bindable parameter. The progress percentage-value is calculated by a ReminderHelper class, which takes two Ints as its parameters, totalDays and daysLeft, the values input to the ReminderHelper class come from a Reminder object saved in Core Data. I'm very confused as to how to structure my code to accomplish such of thing due to the poor understanding of how the SwiftUI/Combine, @Binding, @Published, @State, etc. work. Based on the code below, what I'm expecting to see is two reminders, Cut the Grass at 20% and Power Wash Siding at 50%. Again, the two Ints that determine the total percentage progress come from the Reminder object saved in Core Data and the actual total percentage result comes from the RemindersHelper class. Any idea how to accomplish what I describe above? Model: This is saved in Core Data. class Reminder:Identifiable{ var name = "" var totalDays = 0 var daysLeft = 0 init(name:String, totalDays:Int, daysLeft:Int){ self.name = name self.totalDays = totalDays self.daysLeft = daysLeft } } Helper class This needs to be in charge of calculating the total percentage that will be passed to the ProgressBar View with the values coming from the Reminder object saved in Core Data. class ReminderHelper:ObservableObject{ @Published var percentageLeft: Float = 0.80 func calculatePerentageLeft(daysLeft: Int, totalDays:Int)->Float{ percentageLeft = Float(daysLeft / totalDays) return percentageLeft } } Content View: Here I'm calling the calculatePerentageLeft method to prepare the percentageLeft property before presenting the ProgressBar. Which of course is not working. I see an error: Static method 'buildBlock' requires that 'Float' conform to 'View' struct ContentView: View { var reminders = [Reminder(name: "Cut the Grass", totalDays: 50, daysLeft: 10), Reminder(name: "Power Wash Siding", totalDays: 30, daysLeft: 15)] @StateObject var reminderModel = ReminderHelper() var body: some View { List { ForEach(reminders) { reminder in HStack{ Text(reminder.name) reminderModel.calculatePerentageLeft(daysLeft: reminder.daysLeft, totalDays: reminder.totalDays) ProgressBar(progress: reminderModel.percentageLeft) } } } } } ProgressBar View This is the view in charge of drawing and displaying the percentage value. struct ProgressBar: View { @Binding var progress: Float var body: some View { ZStack { Circle() .stroke(lineWidth:5.0) .opacity(0.3) .foregroundColor(Color.orange) Circle() .trim(from: 0.0, to: CGFloat(min(self.progress, 1.0))) .stroke(style: StrokeStyle(lineWidth: 5.0, lineCap: .round, lineJoin: .round)) .foregroundColor(Color.orange) .rotationEffect(Angle(degrees: 270.0)) .animation(.linear, value: progress) VStack{ Text(String(format: "%.0f %%", min(self.progress, 1.0)*100.0)) .font(.caption2) } } } }
Posted
by fsdolphin.
Last updated
.
Post not yet marked as solved
1 Replies
348 Views
I have an observable object in SwiftUI with multiple published properties. When my app starts I want to update the values of all of these properties by making an API call. That is easy, and I have managed that. However, every time one of these property changes, I make an update API call to the backend. For ease of use, I do any update to any of these properties will update all of the properties on the backend. The problem is the beginning. How do I "silently" update the individual properties in the first API call without setting off the published chain of events that update "all" of the API calls. This obviously leads to a problem since my initial state has several blank fields that would get overwritten in the backend. Basically, is there a way to update a "Published" value without triggering the publish subscription call?
Posted Last updated
.
Post not yet marked as solved
0 Replies
211 Views
Hello everyone. I encountered a problem while trying to reproduce a real scenario in a simulated project using Combine. The idea is the following: I have a first request to a repository which performs some async task and returns the result via a Future publisher. Based on the results of this request I want to: open a specific number of streams(publishers), merge all the streams into one monitor the stream and transform the values before the final publisher is returned return the merged stream. Here is the code: First the View Model which will make a request to the interactor. The request will return a publisher which will publish a list of results, which will be reflected in the view using the published property(the view will call openCombinedStream via a button press). class FakeViewModel: ObservableObject {     let interactor = FakeInteractor()     @Published var values: [SearchResult] = []     private var subscriber: AnyCancellable?     func openCombinedStream() {             self.subscriber = self.interactor.provideResults(for: "")                 .sink(receiveCompletion: { completion in             }, receiveValue: { newValues in                 debugPrint("-----------------------------")                 self.values = newValues                 debugPrint("-----------------------------")             })     } } The view is something like this: import SwiftUI struct ContentView: View {     @StateObject private var viewModel = FakeViewModel()     var body: some View {         VStack {             Button(action: {                 viewModel.openCombinedStream()             }, label: {                 Text("Search")                     .padding()             })             HStack  {                 VStack {                     Text("Item id")                     ForEach(vm.values, id: \.id) { item in                         Text("\(item.id)")                     }                 }                 VStack {                     Text("Item value")                     ForEach(vm.values, id: \.id) { item in                         Text("\(item.value)")                     }                 }             }         }         .padding()     } } The interactor has the following implementation: typealias SearchResult = (value: String, id: Int) typealias ResultsPublisher = AnyPublisher<[SearchResult], DomainError> typealias SearchRepoResult = Result<[SearchResult], DomainError> class FakeInteractor {     let fakeStreamingRepo = FakeStreamingRepo()     let fakeSearchRepo = FakeSearchRepo()     var subscribers: [AnyCancellable] = []     func provideResults(for query: String) -> ResultsPublisher {         let subject = PassthroughSubject<[SearchResult], DomainError>()         queue.async {             let future = self.fakeSearchRepo.provideResultsPublisher(for: "")             let futureSubscriber = future                 .sink(receiveCompletion: { _ in }, receiveValue: { newList in                     var results = newList                     let mergedSubscriber = Publishers.MergeMany(results.map{ item -> AnyPublisher<SearchResult, DomainError> in                         return self.fakeStreamingRepo.subscribe(to: item.id)                     })                         .sink(receiveCompletion: { _ in }, receiveValue: { newValue in                             if let index = results.firstIndex(where: { $0.id == newValue.id }) {                                 results[index] = newValue                                 subject.send(results)                             }                         })                     self.subscribers.append(mergedSubscriber)                 })             self.subscribers.append(futureSubscriber)         }         return subject             .eraseToAnyPublisher()     } } enum DomainError: Error { } let queue = DispatchQueue(label: "") The Search Repo: typealias SearchRequest = String class FakeSearchRepo {     func provideSearchResult(for request: SearchRequest) -> SearchRepoResult {         return .success([(value: "10", id: 1),                          (value: "20", id: 2),                          (value: "30", id: 3),                          (value: "40", id: 4),                          (value: "50", id: 5)])     }     func provideResultsPublisher(for request: SearchRequest) -> AnyPublisher<[SearchResult], DomainError> {         Future<[SearchResult], DomainError>({ promise in                 let result: SearchRepoResult = self.provideSearchResult(for: request)                 switch result {                     case .success(let response):                         sleep(3)                         promise(.success(response))                     case .failure(let error):                         promise(.failure(error))             }         })             .eraseToAnyPublisher()     } } And the streaming repo: typealias TopicRequest = Int class FakeStreamingRepo {     func subscribe(to topic: Int) -> AnyPublisher<SearchResult, DomainError> {         let subject = PassthroughSubject<SearchResult, DomainError>()         _ = Timer.scheduledTimer(withTimeInterval: Double.random(in: 0...5), repeats: true) { _ in             switch topic {                 case _ where topic == 1:                     let value = String(format: "%.2f", Double.random(in: 0...10))                     debugPrint("Sending: value for category 1: \((value))")                     subject.send((value: value, id: topic))                 case _ where topic == 2:                     let value = String(format: "%.2f", Double.random(in: 10...20))                     debugPrint("Sending: value for category 2: \(value)")                     subject.send((value: value, id: topic))                 case _ where topic == 3:                     let value = String(format: "%.2f", Double.random(in: 20...30))                     debugPrint("Sending: value for category 3: \(value)")                     subject.send((value: value, id: topic))                 case _ where topic == 4:                     let value = String(format: "%.2f", Double.random(in: 30...40))                     debugPrint("Sending: value for category 4: \(value)")                     subject.send((value: value, id: topic))                 default:                     let value = String(format: "%.2f", Double.random(in: 40...50))                     debugPrint("Sending: value for category 5: \(value)")                     subject.send((value: value, id: topic))             }         }         return subject.eraseToAnyPublisher()     } } The problem is that the publisher returned from the method openCombinedStream does not work unless I specify the runloop to be the main one with .receive(on: RunLoop.main) inside the method before the flatMap. What would be the best approach to this scenario?
Posted Last updated
.
Post not yet marked as solved
0 Replies
352 Views
Hello there, I stumbled on the issue of observing UserDefaults. My need is to "listening"/observing UD key named "com.apple.configuration.managed" which is responsible for reading provided MDM external plist. I checked that on the opened app it is possible to provide that plist, and app read this payload correctly. My problem is to observing that change, when plist is uploading. Requirement is to support iOS 13, this is why I can't use AppStorage. Despite using some similar solutions like: someone own implementation of AppStorage, and using StackOverflow solutions like this, it still doesn't work. My, I feel, the closest one solution was:   @objc dynamic var mdmConfiguration: Dictionary<String, String> {     get { (dictionary(forKey: MDM.ConfigurationPayloadKey) != nil) ? dictionary(forKey: MDM.ConfigurationPayloadKey)! as! Dictionary<String, String> : Dictionary<String, String>() }     set { setValue(newValue, forKey: MDM.ConfigurationPayloadKey)}   } } class MDMConfiguration: ObservableObject {       //@Binding private var bindedValue: Bool       @Published var configuration: Dictionary = UserDefaults.standard.mdmConfiguration {     didSet {       UserDefaults.standard.mdmConfiguration = configuration     //  bindedValue.toggle()     }   }   private var cancelable: AnyCancellable?   init() {  // init(toggle: Binding<Bool>) {     //_bindedValue = toggle     cancelable = UserDefaults.standard.publisher(for: \.mdmConfiguration)       .sink(receiveValue: { [weak self] newValue in         guard let self = self else { return }         if newValue != self.configuration { // avoid cycling !!           self.configuration = newValue         }       })   } } struct ContentView: View {       @State private var isConfigurationAvailable: Bool = false   @State private var showLoadingIndicator: Bool = true   @ObservedObject var configuration = MDMConfiguration()       var body: some View {           GeometryReader { geometry in               let width = geometry.size.width       let height = geometry.size.height                             VStack {         Text("CONTENT -> \(configuration.configuration.debugDescription)").padding()         Spacer()         if !configuration.configuration.isEmpty {           Text("AVAILABLE").padding()         } else {           Text("NIL NULL ZERO EMPTY")             .padding()         }       }        }   } } But it still doesn't ensure any changes in view, when I manually click on f.e. button, which prints the configuration in the console when it has uploaded, it does it well. Please help, my headache is reaching the zenith. I am a newbie in Swift development, maybe I did something weird and stupid. I hope so :D Thank in advance!
Posted Last updated
.
Post not yet marked as solved
2 Replies
329 Views
@Published var value: String = "" Views don't update with @Published data on subclasses of ObservableObject class for iOS 14 while it works for iOS 15. I try following code as workaround:    var value: String = "" {     willSet { objectWillChange.send() }   } This works, but does anyone have any better suggestions?
Posted
by seyma.
Last updated
.
Post not yet marked as solved
1 Replies
305 Views
I'm getting back the following error when attempting to call data for VA facility information. failure(Swift.DecodingError.valueNotFound( Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), CodingKeys(stringValue: "attributes", intValue: nil), CodingKeys(stringValue: "wait_times", intValue: nil), CodingKeys(stringValue: "health", intValue: nil), _JSONKey(stringValue: "Index 6", intValue: 6), CodingKeys(stringValue: "new", intValue: nil)], debugDescription: "Expected Double value but found null instead.", underlyingError: nil))) I check with Postman and the data is present so I'm not entirely sure why the data is failing. Here's part of my model // MARK: - Welcome struct Welcome: Codable, Identifiable {   let id = UUID()   let data: DataClass } // MARK: - DataClass struct DataClass: Codable {   let id, type: String   let attributes: Attributes } // MARK: - Attributes struct Attributes: Codable {   let name, facilityType, classification: String   let website: String   let lat, long: Double   let timeZone: String   let address: Address   let phone: Phone   let hours: Hours   let operationalHoursSpecialInstructions: String   let services: Services   let satisfaction: Satisfaction   let waitTimes: WaitTimes   let mobile: Bool   let activeStatus: String   let operatingStatus: OperatingStatus   let detailedServices: [DetailedService]   let visn: String   enum CodingKeys: String, CodingKey {     case name     case facilityType = "facility_type"     case classification, website, lat, long     case timeZone = "time_zone"     case address, phone, hours     case operationalHoursSpecialInstructions = "operational_hours_special_instructions"     case services, satisfaction     case waitTimes = "wait_times"     case mobile     case activeStatus = "active_status"     case operatingStatus = "operating_status"     case detailedServices = "detailed_services"     case visn   } } Here's my function call typealias vaFacilityData = [Welcome] Class .... { @Published var vaFacilityData: [Welcome] = []    func getVAFacilityInfo(locationURL: String) {     var URLBuilder = URLComponents(string: locationURL)     guard let url = URLBuilder?.url else { return }     var request = URLRequest(url: url) //    request.httpMethod = "GET"     request.setValue(VA_API_Key, forHTTPHeaderField: "apikey")     print("\(#function) url \(locationURL)")     URLSession.shared.dataTaskPublisher(for: request)       .subscribe(on: DispatchQueue.global(qos: .background))       .receive(on: DispatchQueue.main)       .tryMap { (data, response) -> Data in         guard           let response = response as? HTTPURLResponse,            response.statusCode >= 200 && response.statusCode < 300 else {              self.appError = AppError(errorString: "\(UserFetchError.badServerResponse)")              throw UserFetchError.badServerResponse         }         print("\(#function) response \(response.statusCode)")         print("\(#function) returning data \(data)")         return data       }       .decode(type: Welcome.self, decoder: JSONDecoder())       .sink { (completion) in         print("\(#function) completion - \(completion)")       } receiveValue: { (returnedData) in         print("\(#function) returnedData - \(returnedData)")         self.vaFacilityData = [returnedData]       }       .store(in: &cancellabes)   } } }
Posted
by robevans.
Last updated
.