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

Posts under Combine tag

123 Posts

Post

Replies

Boosts

Views

Activity

Dynamic list sections with observed data with SwiftUI
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)         }     } }
2
1
2.6k
Jun ’22
How do you determine which threads run loop to receive events, in the context of Combine publishers?
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) }
0
0
651
May ’22
objectWillChange.sink why not working in swift
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;   } }
1
0
2.1k
May ’22
SwiftUI TabView Off-Screen Tabs Not Redrawing.
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?
1
0
2.4k
Apr ’22
Is there any documentation available on how to work with FocusState for a custom control using UIViewRepresentable?
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?
1
2
802
Apr ’22
How to add DetailView?
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)                   }             }         }     } }
2
0
971
Apr ’22
How to dynamically update a SwiftUI View with an @Bindable value
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) } } } }
1
0
2.5k
Apr ’22
Silently Update Published Data
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?
1
2
2.8k
Mar ’22
Combine repositories sink with Future
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?
0
0
1.1k
Mar ’22
Observing UserDefaults on iOS 13
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!
0
0
1.6k
Mar ’22
Combine Returning Completion error for an API call of nil
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)   } } }
1
0
1.2k
Feb ’22
Actor ‘self’ can only be passed ‘inout’ from an async initializer
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!
1
0
2.3k
Feb ’22
Swift Combine API client; return Future or AnyPublisher?
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&lt;SomeThing...&gt; { } func getSomeThing() -> AnyPublisher&lt;SomeThing...&gt; { } 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.
1
0
2.5k
Jan ’22
Apple sample code "Detecting Human Actions in a Live Video Feed" - accessing the observations associated with an action prediction
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.
0
0
848
Jan ’22
Publishers.CombineLatest in SwiftUI
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.
3
0
2.2k
Jan ’22
Using Combine-Future to Fetch Server Data
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.
6
0
1.9k
Dec ’21
message isn's displayed when crash by assertNoFailure
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?
1
0
874
Dec ’21
SwiftUI & Combine - AnyPublisher subscription at onReceive gets cancelled frequently
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
1
0
1.7k
Nov ’21
Dynamic list sections with observed data with SwiftUI
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)         }     } }
Replies
2
Boosts
1
Views
2.6k
Activity
Jun ’22
Technology vectors of Combine, NotificationCenter, and KVO
Is Combine replacing NotificationCenter and Key-Value Observing?
Replies
0
Boosts
0
Views
1.5k
Activity
May ’22
How do you determine which threads run loop to receive events, in the context of Combine publishers?
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) }
Replies
0
Boosts
0
Views
651
Activity
May ’22
objectWillChange.sink why not working in swift
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;   } }
Replies
1
Boosts
0
Views
2.1k
Activity
May ’22
SwiftUI TabView Off-Screen Tabs Not Redrawing.
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?
Replies
1
Boosts
0
Views
2.4k
Activity
Apr ’22
Is there any documentation available on how to work with FocusState for a custom control using UIViewRepresentable?
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?
Replies
1
Boosts
2
Views
802
Activity
Apr ’22
How to add DetailView?
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)                   }             }         }     } }
Replies
2
Boosts
0
Views
971
Activity
Apr ’22
How to dynamically update a SwiftUI View with an @Bindable value
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) } } } }
Replies
1
Boosts
0
Views
2.5k
Activity
Apr ’22
Silently Update Published Data
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?
Replies
1
Boosts
2
Views
2.8k
Activity
Mar ’22
Combine repositories sink with Future
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?
Replies
0
Boosts
0
Views
1.1k
Activity
Mar ’22
Observing UserDefaults on iOS 13
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!
Replies
0
Boosts
0
Views
1.6k
Activity
Mar ’22
Combine Returning Completion error for an API call of nil
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)   } } }
Replies
1
Boosts
0
Views
1.2k
Activity
Feb ’22
Actor ‘self’ can only be passed ‘inout’ from an async initializer
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!
Replies
1
Boosts
0
Views
2.3k
Activity
Feb ’22
Swift Combine API client; return Future or AnyPublisher?
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&lt;SomeThing...&gt; { } func getSomeThing() -> AnyPublisher&lt;SomeThing...&gt; { } 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.
Replies
1
Boosts
0
Views
2.5k
Activity
Jan ’22
Apple sample code "Detecting Human Actions in a Live Video Feed" - accessing the observations associated with an action prediction
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.
Replies
0
Boosts
0
Views
848
Activity
Jan ’22
HTTP Traffic Instrument using Combine
Is it possible to use the new http traffic instrument with combines dataTaskPublisher? How can I set a label to the task?
Replies
1
Boosts
0
Views
1.5k
Activity
Jan ’22
Publishers.CombineLatest in SwiftUI
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.
Replies
3
Boosts
0
Views
2.2k
Activity
Jan ’22
Using Combine-Future to Fetch Server Data
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.
Replies
6
Boosts
0
Views
1.9k
Activity
Dec ’21
message isn's displayed when crash by assertNoFailure
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?
Replies
1
Boosts
0
Views
874
Activity
Dec ’21
SwiftUI & Combine - AnyPublisher subscription at onReceive gets cancelled frequently
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
Replies
1
Boosts
0
Views
1.7k
Activity
Nov ’21