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

Posts under Combine tag

123 Posts

Post

Replies

Boosts

Views

Activity

How to access ApplicationMusicPlayer.shared.state in an ObservableObject class?
I am aware that ApplicationMusicPlayer.shared.state is implemented as an observed object, but I'm wondering if it's possible to store that instance in a class annotated with ObservableObject so that I can use objectWillChance or other properties and then forward the values and state I care about view @Published properties. I'm thinking something like this: class SomeViewModel: ObservableObject {     @Published var isPlaying: Bool = false     init() {         ApplicationMusicPlayer.shared.state.objectWillChange.sink { [self]             isPlaying = ApplicationMusicPlayer.shared.state.playbackStatus == .playing         }     } } If I create this class and stick in a SwiftUI View I can see the init being called, but the objectWillChange never seems to fire. I can however do this and it seems to work inside of the SwiftUI View: struct SomeView: View {     @ObservedObject private var playerState = ApplicationMusicPlayer.shared.state     @State private var isPlaying: Bool = false     var cancellableBag = Set<AnyCancellable>()     init() {         self.isPlaying = playerState.playbackStatus == .playing         playerState.objectWillChange.sink { [self] in             print(playerState.playbackStatus)         }.store(in: &cancellableBag)     } } interesting objectWillChange fires twice for every state update, but I could deal with that if I could just use my own ObservableObject. I'd rather my music player not be so tightly coupled to the view I'm writing.
2
0
1.6k
Dec ’22
Looping over AsyncSequence gives a different result than subscribing with a 'sink'.
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.
1
0
1.3k
Nov ’22
swiftui+combine: why isFavoriteO changed when scroll the LazyVGrid?
I have a LazyVGrid, every item with is favorite button. and use combine to debounce user input($isFavoriteI), when isFavoriteO changed, then modify the items. it works fine, but when i scroll the list, log will print: "X, isFavorite changed as false/true)", what cause isFavoriteO changed and why? because of item reusing in list? how to avoid it? index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true import SwiftUI import Combine struct Item { var index: Int var favorite: Bool } var items = [ Item(index: 0, favorite: true), Item(index: 1, favorite: false), Item(index: 2, favorite: true), Item(index: 3, favorite: false), Item(index: 4, favorite: true), Item(index: 5, favorite: false), Item(index: 6, favorite: true), Item(index: 7, favorite: false), // Item(index: 8, favorite: true), // Item(index: 9, favorite: false), // Item(index: 10, favorite: true), // Item(index: 11, favorite: false), // Item(index: 12, favorite: true), // Item(index: 13, favorite: false), // Item(index: 14, favorite: true), // Item(index: 15, favorite: false), // Item(index: 16, favorite: true), // Item(index: 17, favorite: false), // Item(index: 18, favorite: true), // Item(index: 19, favorite: false), ] struct ViewModelInListTestView: View { var body: some View { ScrollView(showsIndicators: false) { LazyVGrid(columns: [GridItem(.adaptive(minimum: 200), spacing: 4, alignment: .center)], spacing: 4) { ForEach(items, id: \.index) { item in ItemView(item: item) } } }.navigationTitle("ViewModel In List") } } struct ItemView: View { let item: Item @ObservedObject var viewModel: ViewModel init(item: Item) { print("ItemView.init, \(item.index)") self.item = item self.viewModel = ViewModel(item: item) } var body: some View { HStack { Text("index \(item.index)") Spacer() Image(systemName: viewModel.isFavoriteI ? "heart.fill" : "heart") .foregroundColor(viewModel.isFavoriteI ? .red : .white) .padding() .onTapGesture { onFavoriteTapped() } .onChange(of: viewModel.isFavoriteO) { isFavorite in setFavorite(isFavorite) } } .frame(width: 200, height: 150) .background(Color.gray) } func onFavoriteTapped() { viewModel.isFavoriteI.toggle() } func setFavorite(_ isFavorite: Bool) { print("index \(item.index), isFavorite changed as \(isFavorite)") items[item.index].favorite = isFavorite } class ViewModel: ObservableObject { @Published var isFavoriteI: Bool = false @Published var isFavoriteO: Bool = false private var subscriptions: SetAnyCancellable = [] init(item: Item) { print("ViewModel.init, \(item.index)") let isFavorite = item.favorite isFavoriteI = isFavorite; isFavoriteO = isFavorite $isFavoriteI .print("index \(item.index) isFavoriteI:") .dropFirst() .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main) .removeDuplicates() .eraseToAnyPublisher() .print("index \(item.index) isFavoriteO:") .receive(on: DispatchQueue.main) .assign(to: \.isFavoriteO, on: self) .store(in: &amp;subscriptions) } } }
1
0
1.8k
Nov ’22
UserDefaults publisher for non-standard suite not working correctly
Please see the MRE below. Changing UserDefault suite to a custom one doesn't update the subscription beyond the first value. public extension UserDefaults { @objc dynamic var value1: Int { get { integer(forKey: "value1") } set { set(newValue, forKey: "value1") } } static var other: UserDefaults { UserDefaults(suiteName: "other-defaults")! } } struct ContentView: View { private let sub = UserDefaults.other.publisher(for: \.value1).sink { print("SUB", $0) } var body: some View { Button("Add") { UserDefaults.other.value1 += 1 debugPrint("SET", UserDefaults.other.value1) } .onReceive(UserDefaults.other.publisher(for: \.value1)) { debugPrint("UI", $0) } } } Only the SwiftUI onReceive subscription is working. SUB 0 "UI" 0 "SET" 1 "UI" 1 "SET" 2 "UI" 2 It works (though with multiple calls) if I set the standard UserDefaults suite. struct ContentView2: View { private let sub = UserDefaults.standard.publisher(for: \.value1).sink { print("SUB", $0) } var body: some View { Button("Add") { UserDefaults.standard.value1 += 1 debugPrint("SET", UserDefaults.standard.value1) } .onReceive(UserDefaults.standard.publisher(for: \.value1)) { debugPrint("UI", $0) } } } SUB 0 "UI" 0 SUB 1 SUB 1 "SET" 1 "UI" 1 "UI" 1 SUB 2 SUB 2 "SET" 2 "UI" 2 "UI" 2
0
0
2.3k
Nov ’22
Any tips about my architecture to monitor `CMDeviceMotion` updates, store them, process them and display them at the same type?
I am currently working on monitoring the motions of a person using headphones a the  CMHeadphoneMotionManager. Since I want to process the motion, I need the updates to be as frequently as possible to improve the quality of the data. In addition to processing the data, I want to display it live in a chart and save the data to  CoreData  in order to access them later and try other methods for processing on previously recorded data. I created a UML diagram of my current approach: The  MotionManagerProtocol  is the protocol of the class MotionManager, which handles the CMHeadphoneMotionManager. MotionManager.start looks like this: func start() { self.manager.startDeviceMotionUpdates(to: OperationQueue.main) { motion, error in if let motion { let timestamp = CACurrentMediaTime() self.timeInterval = self.oldTimestamp < 0 ? 0 : timestamp - self.oldTimestamp self.oldTimestamp = timestamp self.userAcceleration = motion.userAcceleration self.rotationRate = motion.rotationRate self.attitude = motion.attitude } if let error { print(error) } } } The attributes like  userAcceleration  etc are just setting the value of the  CurrentValueSubject  so that other classes can subscribe to changes in them. The MotionRecorder, MotionViewModel and SpeedCalculator are simply subscribing to changes in the CurrentValueSubject of the MotionManager to be notified when new data is available. In order to be sure, that all are using the same MotionManager, I created a Singleton for it. Is this structure of my code a decent approach or are there anything I can change to make it scale better and receive the new motion data as fast as possible? Since I am pretty early in the process, I can don’t really loose anything if I have to change the whole architecture of my code, so feel free to give advice in this direction as well. Any advice and tips on what to learn in order to improve this project are very much appreciated and TIA. PS: I am using SwiftUI as my framework of choice
0
0
991
Nov ’22
Publishing changes from background threads is not allowed
I'm trying to use Combine with a view but am getting this error message at runtime: Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates. I tried adding ".receive(on: RunLoop.main)" before each ".eraseToAnyPublisher()", but that didn't resolve the issue. Here the code block that I'm assuming is causing the problem: func emailAvailable(_ email: String, completion: @escaping (Bool) -> ()) -> () { if email != "" { print("SignupModel: emailAvailable(): Validating email '" + email + "'...") let pattern = try! Regex(pattern: emailRegex) if pattern.matches(email) { guard let url = URL(string: "https://api.myapi.network/users/email/availability?email=" + email.lowercased()) else { fatalError("emailAvailable(): Missing URL") } var request = URLRequest(url: url) request.setValue(K.apiKey, forHTTPHeaderField: "x-api-key") let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in if let error = error { print("SignupModel: emailAvailable(): Request error: ", error) completion(false) } guard let response = response as? HTTPURLResponse else { return } if response.statusCode == 200 { guard let data = data else { print("SignupModel: emailAvailable(): Response contained no data!") return } DispatchQueue.main.async { do { let decodedResp = try JSONDecoder().decode(EmailAvailResponse.self, from: data) if (decodedResp.available) { print("SignupModel: emailAvailable(): Email available, returning true!") completion(true) } else { print("SignupModel: emailAvailable(): Email NOT available, returning false!") completion(false) } } catch let error { print("SignupModel: emailAvailable(): Error decoding: ", error) completion(false) } } } else { print("SignupModel: emailAvailable(): Response status = \(response.statusCode)") completion(true) } } dataTask.resume() } else { print("SignupModel: emailAvailable(): Exiting gracefully because email '" + email + "' is not a valid email!") completion(true) } } else { print("SignupModel: emailAvailable(): Exiting because email is nil!") completion(true) } } lazy var emailValidationAvailable: ValidationPublisher = { return $email .debounce(for: 0.5, scheduler: RunLoop.main) .removeDuplicates() .flatMap { email in return Future { promise in self.emailAvailable(email) { available in promise(.success(available ? .success : .failure(message: "Email is already registered!"))) } } } .dropFirst() .receive(on: RunLoop.main) // <<—— run on main thread .eraseToAnyPublisher() }() Anyone know what I'm missing? Thanks in advance! Aaron
0
0
3.3k
Nov ’22
Having an @EnvironmentObject in the view causes Tasks to be executed on the main thread
I ran into an issue where my async functions caused the UI to freeze, even tho I was calling them in a Task, and tried many other ways of doing it (GCD, Task.detached and the combination of the both). After testing it I figured out which part causes this behaviour, and I think it's a bug in Swift/SwiftUI. Bug description I wanted to calculate something in the background every x seconds, and then update the view, by updating a @Binding/@EnvironmentObject value. For this I used a timer, and listened to it's changes, by subscribing to it in a .onReceive modifier. The action of this modifer was just a Task with the async function in it (await foo()). This works like expected, so even if the foo function pauses for seconds, the UI won't freeze BUT if I add one @EnvironmentObject to the view the UI will be unresponsive for the duration of the foo function. Minimal, Reproducible example This is just a button and a scroll view to see the animations. When you press the button with the EnvironmentObject present in the code the UI freezes and stops responding to the gestures, but just by removing that one line the UI works like it should, remaining responsive and changing properties. import SwiftUI class Config : ObservableObject{ @Published var color : Color = .blue } struct ContentView: View { //Just by removing this, the UI freeze stops @EnvironmentObject var config : Config @State var c1 : Color = .blue var body: some View { ScrollView(showsIndicators: false) { VStack { HStack { Button { Task { c1 = .red await asyncWait() c1 = .green } } label: { Text("Task, async") } .foregroundColor(c1) } ForEach(0..<20) {x in HStack { Text("Placeholder \(x)") Spacer() } .padding() .border(.blue) } } .padding() } } func asyncWait() async{ let continueTime: Date = Calendar.current.date(byAdding: .second, value: 2, to: Date())! while (Date() < continueTime) {} } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } Disclaimer I am fairly new to using concurrency to the level I need for this project, so I might be missing something, but I couldn't find anything related to the searchwords "Task" and "EnvironmentObject".
2
0
1.4k
Oct ’22
I can't use value inside my UICollectionView from API ( Swift, MVVM, UIKit)
I'm trying to make simple app that shows a list with values from API. The thing is that I can't use that same way like previous projects. I got a error message: failure(Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))) ViewController import UIKit import Combine class PokedexViewController: UIViewController { var subscriptions = Set<AnyCancellable>() private var pokedexListVM = PokedexListViewModel() var collectionView: UICollectionView? override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground setupCollectionView() } private func setupCollectionView() { let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 0, right: 5) layout.itemSize = CGSize(width: view.bounds.size.width, height: 60) collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout) collectionView?.register(PokedexCollectionViewCell.self, forCellWithReuseIdentifier: PokedexCollectionViewCell.identifier) collectionView?.delegate = self collectionView?.dataSource = self view.addSubview(collectionView ?? UICollectionView()) //self.view = view setupViewModel() } private func setupViewModel() { pokedexListVM.$pokemons .receive(on: RunLoop.main) .sink(receiveValue: { [weak self] _ in self?.collectionView!.reloadData() }).store(in: &subscriptions) let stateHandler: (PokedexListViewModelState) -> Void = { state in switch state { case .loading: print("loading") case .finishLoading: print("finish") case .error: print("error") } } pokedexListVM.$state .receive(on: RunLoop.main) .sink(receiveValue: stateHandler) .store(in: &subscriptions) } } extension PokedexViewController: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return pokedexListVM.pokemons.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: PokedexCollectionViewCell.identifier, for: indexPath ) as? PokedexCollectionViewCell else { return UICollectionViewCell() } cell.configure(with: "\(pokedexListVM.pokemons[indexPath.row].name)") return cell } } ViewModel import Foundation import Combine enum PokedexListViewModelState { case loading case finishLoading case error } class PokedexListViewModel: ObservableObject { @Published private(set) var pokemons: [Pokedex.Results] = [] @Published private(set) var state: PokedexListViewModelState = .loading private var subscriptions = Set<AnyCancellable>() private var url = "https://pokeapi.co/api/v2/pokemon?offset=0&limit=898" //private let url = "https://jsonplaceholder.typicode.com/posts/" init() { loadPokemons() } private func loadPokemons() { state = .loading let valueHandler: ([Pokedex.Results]) -> Void = { [weak self] items in self?.pokemons = items } let completionHandler: (Subscribers.Completion<Error>) -> Void = { [weak self] completion in switch completion { case .failure: self?.state = .error print(completion) case .finished: self?.state = .finishLoading } } URLSession.shared.dataTaskPublisher(for: URL(string: url)!) .map{ $0.data } .decode(type: [Pokedex.Results].self, decoder: JSONDecoder()) .sink(receiveCompletion: completionHandler, receiveValue: valueHandler) .store(in: &subscriptions) } } Model import Foundation struct Pokedex: Codable { struct Results: Codable { let name: String let url: String } var results: [Results] var count: Int var next: String var previous: String? } I use pokeAPIV2 from that link: https://pokeapi.co/api/v2/pokemon?offset=0&limit=898 { "count": 1154, "next": "https://pokeapi.co/api/v2/pokemon?offset=3&limit=3", "previous": null, "results": [ { "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" }, { "name": "ivysaur", "url": "https://pokeapi.co/api/v2/pokemon/2/" }, { "name": "venusaur", "url": "https://pokeapi.co/api/v2/pokemon/3/" } ] } I want to use the "name" data from that API. One thing that I noticed is that API starts from "{" and previous ones starts from "[".
3
0
1.1k
Oct ’22
Combine with UITableView
Hello, I'm trying to work out a simple example to fill table view data with Combine. The following is what I have. import Foundation struct MyModel: Decodable { let id: String let type: String } import UIKit import Combine class APIClient: NSObject { var cancellable: AnyCancellable? let sharedSession = URLSession.shared func fetchData(urlStr: String, completion: @escaping ([MyModel]?) -> Void) { guard let url = URL(string: urlStr) else { return } let publisher = sharedSession.dataTaskPublisher(for: url) cancellable = publisher.sink(receiveCompletion: { (completion) in switch completion { case .failure(let error): print(error) case .finished: print("Success") } }, receiveValue: { (result) in let decoder = JSONDecoder() do { let post = try decoder.decode([MyModel].self, from: result.data) completion(post) } catch let error as NSError { print("\(error)") completion(nil) } }) } } import Foundation class ViewModel: NSObject { @IBOutlet var apiClient: APIClient! var dataModels = [MyModel]() func getGitData(completion: @escaping () -> Void) { let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events" apiClient.fetchData(urlStr: urlStr) { (models) in if let myModels = models { self.dataModels = myModels.map { $0 } } completion() } } } import UIKit import Combine class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // MARK: - Variables var cancellable: AnyCancellable? @IBOutlet var viewModel: ViewModel! @Published var models = [MyModel]() // MARK: - IBOutlet @IBOutlet weak var tableView: UITableView! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() viewModel.getGitData { self.models = self.viewModel.dataModels } cancellable = $models.sink(receiveValue: { (result) in DispatchQueue.main.async { [weak self] in guard let strongSelf = self else { return } strongSelf.tableView.reloadData() } }) } // MARK: - TableView func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return models.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell") let dataModel = models[indexPath.row] cell?.textLabel?.text = dataModel.id cell?.detailTextLabel?.text = dataModel.type return cell! } } I'm not quite comfortable with the lines of code under my view controller (ViewController) in using Combine. How can I make them better? Muchos thankos.
3
0
3.7k
Oct ’22
Unexpected behaviour with async values of a filtered Publishers.CombineLatest
When I run the following code in the Playground (I'm on XCode 13.4): import Combine import Foundation import _Concurrency let subject = CurrentValueSubject<Int, Never>(0) let faultyPublisher = Publishers.CombineLatest(subject, Just(-1))   .filter { firstValue, _ in firstValue != 0 } let publisherToTest = faultyPublisher Task {   for await output in publisherToTest.values {     print("RECEIVED \(output)")   } } Task {   try await Task.sleep(nanoseconds: 1_000_000_000)   subject.send(1) } I expect to see the output: RECEIVED (1, -1). Instead I see no output. Is this a bug with Publishers.CombineLatest + .filter + values: AsyncPublisher? Some other note: if you assign publisherToTest to one of these, the behaviours are what I would expect: let workingPublisher1 = Publishers.CombineLatest(subject, Just(-1))   .filter { firstValue, _ in firstValue != 1 } Above outputs: RECEIVED (0, -1) let workingPublisher2 = Publishers.CombineLatest(Just(-1), subject)   .filter { _, secondValue in secondValue != 0 } Above outputs: RECEIVED (-1, 1) let workingPublisher3 = Publishers.CombineLatest(subject, Just(-1))   .filter { firstValue, _ in firstValue != 0 }   .flatMap(maxPublishers: .max(2)) { Just($0) } Above outputs: RECEIVED (1, -1)
0
1
1.1k
Aug ’22
How to avoid retain cycle in custom SwiftUI view closure
I'm trying to create a wrapper around a WKWebView for SwiftUI, and I'm not sure how to prevent a memory leak. I've created an ObservableObject which handles controlling the WebView, and a custom view that displays it: public class WebViewStore: ObservableObject { var webView: WKWebView = WKWebView() // List of event handlers that will be called from the WebView var eventHandlers: [String: () -> Void] = [:] deinit { // This is never called once an action has been set with the view, // and the content view is destroyed print("deinit") } // All the WebView code, including custom JavaScript message handlers, custom scheme handlers, etc... func reloadWebView() { } } The web view needs to be able to communicate with JavaScript, so I've added an onAction() method which gets called when the WebView gets a javascript event. View wrapper: struct WebView: NSViewRepresentable { let store: WebViewStore func makeNSView(context: Context) -> WKWebView { return store.webView } func updateNSView(_ view: WKWebView, context: Context) {} } extension WebView { /// Action event called from the WebView public func onAction(name: String, action: @escaping () -> Void) -> WebView { self.store.eventHandlers[name] = action return self } } Usage that creates the retain cycle: struct ContentView: View { @StateObject var store = WebViewStore() var body: some View { VStack { // Example of interacting with the WebView Button("Reload") { store.reloadWebView() } WebView(store: store) // This action closure captures the WebViewStore, causing the retain cycle. .onAction(name: "javascriptMessage") { print("Event!") } } } } Is there a way to prevent the retain cycle from happening, or is there a different SwiftUI pattern that can to handle this use case? I can't use [weak self] because the view is a struct. I need to be able to receive events from the WebView and vice versa.
0
0
1.8k
Aug ’22
NavigationStack - No Back Button
I created a very simple project with a NavigationStack and a NavigationLink as follows. Code 1. NavigationLink("Go To Next View", value: "TheView")          .navigationDestination(for: String.self) { val in Text("Value = \(val)")          } This code works fine in the simple project. But when I put this simple code into my existing app's project using a NavigationStack, it navigates to the next view fine, but there is no back button. I am updating my Apple Watch target to use SwiftUI and I want to use NavigationStack. My app does use @ObservedObject and my important data is using @Publish in a singleton class which conforms to ObservableObject. But this NavigationLink code, Code 1, is extremely simple and should work fine, but it does not. The same problem happens with the following code. Code 2 NavigationLink { NextView() } label: {     MainRowView(rowText: "Saved") } When I switch to NavigationView, Code 1 is grayed out, but Code 2 works fine and has a back button. However, as you know, NavigationView is being deprecated, so I really need to fix this or have it fixed. I can't see how this could be a problem with my code as the code snippet is so simple and worked in the simple project.
4
0
2.6k
Aug ’22
Bizar leaking ViewModel in SwiftUI
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.
1
0
1.7k
Jul ’22
How to register a socket event in macOS and iOS?
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
2
1
1.4k
Jul ’22
SwiftUI detect system volume change
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!
1
1
3.8k
Jul ’22
Is using @Published in a Sendable type safe?
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?
2
0
2.7k
Jul ’22
Combine Publishers.CombineLatest has a serious demand management bug - I believe.
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] ]) }}
0
0
905
Jul ’22
Changing state asynchronously in SwiftUI
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?
1
0
2k
Jun ’22
How to access ApplicationMusicPlayer.shared.state in an ObservableObject class?
I am aware that ApplicationMusicPlayer.shared.state is implemented as an observed object, but I'm wondering if it's possible to store that instance in a class annotated with ObservableObject so that I can use objectWillChance or other properties and then forward the values and state I care about view @Published properties. I'm thinking something like this: class SomeViewModel: ObservableObject {     @Published var isPlaying: Bool = false     init() {         ApplicationMusicPlayer.shared.state.objectWillChange.sink { [self]             isPlaying = ApplicationMusicPlayer.shared.state.playbackStatus == .playing         }     } } If I create this class and stick in a SwiftUI View I can see the init being called, but the objectWillChange never seems to fire. I can however do this and it seems to work inside of the SwiftUI View: struct SomeView: View {     @ObservedObject private var playerState = ApplicationMusicPlayer.shared.state     @State private var isPlaying: Bool = false     var cancellableBag = Set<AnyCancellable>()     init() {         self.isPlaying = playerState.playbackStatus == .playing         playerState.objectWillChange.sink { [self] in             print(playerState.playbackStatus)         }.store(in: &cancellableBag)     } } interesting objectWillChange fires twice for every state update, but I could deal with that if I could just use my own ObservableObject. I'd rather my music player not be so tightly coupled to the view I'm writing.
Replies
2
Boosts
0
Views
1.6k
Activity
Dec ’22
Spreading incoming events evenly in time, using Combine
How to spread incoming messages (A, B, ...) evenly, one every second, marking them with most recent timestamps (1, 2, ...) ? output on the gray stripe, input above it: It requires buffering messages (e.g. B) when necessary, and omitting timer ticks (e.g. 4) in case there were no messages to consume, current or buffered.
Replies
0
Boosts
0
Views
1k
Activity
Dec ’22
Looping over AsyncSequence gives a different result than subscribing with a 'sink'.
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.
Replies
1
Boosts
0
Views
1.3k
Activity
Nov ’22
swiftui+combine: why isFavoriteO changed when scroll the LazyVGrid?
I have a LazyVGrid, every item with is favorite button. and use combine to debounce user input($isFavoriteI), when isFavoriteO changed, then modify the items. it works fine, but when i scroll the list, log will print: "X, isFavorite changed as false/true)", what cause isFavoriteO changed and why? because of item reusing in list? how to avoid it? index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true index 7, isFavorite changed as true import SwiftUI import Combine struct Item { var index: Int var favorite: Bool } var items = [ Item(index: 0, favorite: true), Item(index: 1, favorite: false), Item(index: 2, favorite: true), Item(index: 3, favorite: false), Item(index: 4, favorite: true), Item(index: 5, favorite: false), Item(index: 6, favorite: true), Item(index: 7, favorite: false), // Item(index: 8, favorite: true), // Item(index: 9, favorite: false), // Item(index: 10, favorite: true), // Item(index: 11, favorite: false), // Item(index: 12, favorite: true), // Item(index: 13, favorite: false), // Item(index: 14, favorite: true), // Item(index: 15, favorite: false), // Item(index: 16, favorite: true), // Item(index: 17, favorite: false), // Item(index: 18, favorite: true), // Item(index: 19, favorite: false), ] struct ViewModelInListTestView: View { var body: some View { ScrollView(showsIndicators: false) { LazyVGrid(columns: [GridItem(.adaptive(minimum: 200), spacing: 4, alignment: .center)], spacing: 4) { ForEach(items, id: \.index) { item in ItemView(item: item) } } }.navigationTitle("ViewModel In List") } } struct ItemView: View { let item: Item @ObservedObject var viewModel: ViewModel init(item: Item) { print("ItemView.init, \(item.index)") self.item = item self.viewModel = ViewModel(item: item) } var body: some View { HStack { Text("index \(item.index)") Spacer() Image(systemName: viewModel.isFavoriteI ? "heart.fill" : "heart") .foregroundColor(viewModel.isFavoriteI ? .red : .white) .padding() .onTapGesture { onFavoriteTapped() } .onChange(of: viewModel.isFavoriteO) { isFavorite in setFavorite(isFavorite) } } .frame(width: 200, height: 150) .background(Color.gray) } func onFavoriteTapped() { viewModel.isFavoriteI.toggle() } func setFavorite(_ isFavorite: Bool) { print("index \(item.index), isFavorite changed as \(isFavorite)") items[item.index].favorite = isFavorite } class ViewModel: ObservableObject { @Published var isFavoriteI: Bool = false @Published var isFavoriteO: Bool = false private var subscriptions: SetAnyCancellable = [] init(item: Item) { print("ViewModel.init, \(item.index)") let isFavorite = item.favorite isFavoriteI = isFavorite; isFavoriteO = isFavorite $isFavoriteI .print("index \(item.index) isFavoriteI:") .dropFirst() .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main) .removeDuplicates() .eraseToAnyPublisher() .print("index \(item.index) isFavoriteO:") .receive(on: DispatchQueue.main) .assign(to: \.isFavoriteO, on: self) .store(in: &amp;subscriptions) } } }
Replies
1
Boosts
0
Views
1.8k
Activity
Nov ’22
UserDefaults publisher for non-standard suite not working correctly
Please see the MRE below. Changing UserDefault suite to a custom one doesn't update the subscription beyond the first value. public extension UserDefaults { @objc dynamic var value1: Int { get { integer(forKey: "value1") } set { set(newValue, forKey: "value1") } } static var other: UserDefaults { UserDefaults(suiteName: "other-defaults")! } } struct ContentView: View { private let sub = UserDefaults.other.publisher(for: \.value1).sink { print("SUB", $0) } var body: some View { Button("Add") { UserDefaults.other.value1 += 1 debugPrint("SET", UserDefaults.other.value1) } .onReceive(UserDefaults.other.publisher(for: \.value1)) { debugPrint("UI", $0) } } } Only the SwiftUI onReceive subscription is working. SUB 0 "UI" 0 "SET" 1 "UI" 1 "SET" 2 "UI" 2 It works (though with multiple calls) if I set the standard UserDefaults suite. struct ContentView2: View { private let sub = UserDefaults.standard.publisher(for: \.value1).sink { print("SUB", $0) } var body: some View { Button("Add") { UserDefaults.standard.value1 += 1 debugPrint("SET", UserDefaults.standard.value1) } .onReceive(UserDefaults.standard.publisher(for: \.value1)) { debugPrint("UI", $0) } } } SUB 0 "UI" 0 SUB 1 SUB 1 "SET" 1 "UI" 1 "UI" 1 SUB 2 SUB 2 "SET" 2 "UI" 2 "UI" 2
Replies
0
Boosts
0
Views
2.3k
Activity
Nov ’22
Any tips about my architecture to monitor `CMDeviceMotion` updates, store them, process them and display them at the same type?
I am currently working on monitoring the motions of a person using headphones a the  CMHeadphoneMotionManager. Since I want to process the motion, I need the updates to be as frequently as possible to improve the quality of the data. In addition to processing the data, I want to display it live in a chart and save the data to  CoreData  in order to access them later and try other methods for processing on previously recorded data. I created a UML diagram of my current approach: The  MotionManagerProtocol  is the protocol of the class MotionManager, which handles the CMHeadphoneMotionManager. MotionManager.start looks like this: func start() { self.manager.startDeviceMotionUpdates(to: OperationQueue.main) { motion, error in if let motion { let timestamp = CACurrentMediaTime() self.timeInterval = self.oldTimestamp < 0 ? 0 : timestamp - self.oldTimestamp self.oldTimestamp = timestamp self.userAcceleration = motion.userAcceleration self.rotationRate = motion.rotationRate self.attitude = motion.attitude } if let error { print(error) } } } The attributes like  userAcceleration  etc are just setting the value of the  CurrentValueSubject  so that other classes can subscribe to changes in them. The MotionRecorder, MotionViewModel and SpeedCalculator are simply subscribing to changes in the CurrentValueSubject of the MotionManager to be notified when new data is available. In order to be sure, that all are using the same MotionManager, I created a Singleton for it. Is this structure of my code a decent approach or are there anything I can change to make it scale better and receive the new motion data as fast as possible? Since I am pretty early in the process, I can don’t really loose anything if I have to change the whole architecture of my code, so feel free to give advice in this direction as well. Any advice and tips on what to learn in order to improve this project are very much appreciated and TIA. PS: I am using SwiftUI as my framework of choice
Replies
0
Boosts
0
Views
991
Activity
Nov ’22
Publishing changes from background threads is not allowed
I'm trying to use Combine with a view but am getting this error message at runtime: Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates. I tried adding ".receive(on: RunLoop.main)" before each ".eraseToAnyPublisher()", but that didn't resolve the issue. Here the code block that I'm assuming is causing the problem: func emailAvailable(_ email: String, completion: @escaping (Bool) -> ()) -> () { if email != "" { print("SignupModel: emailAvailable(): Validating email '" + email + "'...") let pattern = try! Regex(pattern: emailRegex) if pattern.matches(email) { guard let url = URL(string: "https://api.myapi.network/users/email/availability?email=" + email.lowercased()) else { fatalError("emailAvailable(): Missing URL") } var request = URLRequest(url: url) request.setValue(K.apiKey, forHTTPHeaderField: "x-api-key") let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in if let error = error { print("SignupModel: emailAvailable(): Request error: ", error) completion(false) } guard let response = response as? HTTPURLResponse else { return } if response.statusCode == 200 { guard let data = data else { print("SignupModel: emailAvailable(): Response contained no data!") return } DispatchQueue.main.async { do { let decodedResp = try JSONDecoder().decode(EmailAvailResponse.self, from: data) if (decodedResp.available) { print("SignupModel: emailAvailable(): Email available, returning true!") completion(true) } else { print("SignupModel: emailAvailable(): Email NOT available, returning false!") completion(false) } } catch let error { print("SignupModel: emailAvailable(): Error decoding: ", error) completion(false) } } } else { print("SignupModel: emailAvailable(): Response status = \(response.statusCode)") completion(true) } } dataTask.resume() } else { print("SignupModel: emailAvailable(): Exiting gracefully because email '" + email + "' is not a valid email!") completion(true) } } else { print("SignupModel: emailAvailable(): Exiting because email is nil!") completion(true) } } lazy var emailValidationAvailable: ValidationPublisher = { return $email .debounce(for: 0.5, scheduler: RunLoop.main) .removeDuplicates() .flatMap { email in return Future { promise in self.emailAvailable(email) { available in promise(.success(available ? .success : .failure(message: "Email is already registered!"))) } } } .dropFirst() .receive(on: RunLoop.main) // <<—— run on main thread .eraseToAnyPublisher() }() Anyone know what I'm missing? Thanks in advance! Aaron
Replies
0
Boosts
0
Views
3.3k
Activity
Nov ’22
Having an @EnvironmentObject in the view causes Tasks to be executed on the main thread
I ran into an issue where my async functions caused the UI to freeze, even tho I was calling them in a Task, and tried many other ways of doing it (GCD, Task.detached and the combination of the both). After testing it I figured out which part causes this behaviour, and I think it's a bug in Swift/SwiftUI. Bug description I wanted to calculate something in the background every x seconds, and then update the view, by updating a @Binding/@EnvironmentObject value. For this I used a timer, and listened to it's changes, by subscribing to it in a .onReceive modifier. The action of this modifer was just a Task with the async function in it (await foo()). This works like expected, so even if the foo function pauses for seconds, the UI won't freeze BUT if I add one @EnvironmentObject to the view the UI will be unresponsive for the duration of the foo function. Minimal, Reproducible example This is just a button and a scroll view to see the animations. When you press the button with the EnvironmentObject present in the code the UI freezes and stops responding to the gestures, but just by removing that one line the UI works like it should, remaining responsive and changing properties. import SwiftUI class Config : ObservableObject{ @Published var color : Color = .blue } struct ContentView: View { //Just by removing this, the UI freeze stops @EnvironmentObject var config : Config @State var c1 : Color = .blue var body: some View { ScrollView(showsIndicators: false) { VStack { HStack { Button { Task { c1 = .red await asyncWait() c1 = .green } } label: { Text("Task, async") } .foregroundColor(c1) } ForEach(0..<20) {x in HStack { Text("Placeholder \(x)") Spacer() } .padding() .border(.blue) } } .padding() } } func asyncWait() async{ let continueTime: Date = Calendar.current.date(byAdding: .second, value: 2, to: Date())! while (Date() < continueTime) {} } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } Disclaimer I am fairly new to using concurrency to the level I need for this project, so I might be missing something, but I couldn't find anything related to the searchwords "Task" and "EnvironmentObject".
Replies
2
Boosts
0
Views
1.4k
Activity
Oct ’22
I can't use value inside my UICollectionView from API ( Swift, MVVM, UIKit)
I'm trying to make simple app that shows a list with values from API. The thing is that I can't use that same way like previous projects. I got a error message: failure(Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))) ViewController import UIKit import Combine class PokedexViewController: UIViewController { var subscriptions = Set<AnyCancellable>() private var pokedexListVM = PokedexListViewModel() var collectionView: UICollectionView? override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground setupCollectionView() } private func setupCollectionView() { let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 0, right: 5) layout.itemSize = CGSize(width: view.bounds.size.width, height: 60) collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout) collectionView?.register(PokedexCollectionViewCell.self, forCellWithReuseIdentifier: PokedexCollectionViewCell.identifier) collectionView?.delegate = self collectionView?.dataSource = self view.addSubview(collectionView ?? UICollectionView()) //self.view = view setupViewModel() } private func setupViewModel() { pokedexListVM.$pokemons .receive(on: RunLoop.main) .sink(receiveValue: { [weak self] _ in self?.collectionView!.reloadData() }).store(in: &subscriptions) let stateHandler: (PokedexListViewModelState) -> Void = { state in switch state { case .loading: print("loading") case .finishLoading: print("finish") case .error: print("error") } } pokedexListVM.$state .receive(on: RunLoop.main) .sink(receiveValue: stateHandler) .store(in: &subscriptions) } } extension PokedexViewController: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return pokedexListVM.pokemons.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: PokedexCollectionViewCell.identifier, for: indexPath ) as? PokedexCollectionViewCell else { return UICollectionViewCell() } cell.configure(with: "\(pokedexListVM.pokemons[indexPath.row].name)") return cell } } ViewModel import Foundation import Combine enum PokedexListViewModelState { case loading case finishLoading case error } class PokedexListViewModel: ObservableObject { @Published private(set) var pokemons: [Pokedex.Results] = [] @Published private(set) var state: PokedexListViewModelState = .loading private var subscriptions = Set<AnyCancellable>() private var url = "https://pokeapi.co/api/v2/pokemon?offset=0&limit=898" //private let url = "https://jsonplaceholder.typicode.com/posts/" init() { loadPokemons() } private func loadPokemons() { state = .loading let valueHandler: ([Pokedex.Results]) -> Void = { [weak self] items in self?.pokemons = items } let completionHandler: (Subscribers.Completion<Error>) -> Void = { [weak self] completion in switch completion { case .failure: self?.state = .error print(completion) case .finished: self?.state = .finishLoading } } URLSession.shared.dataTaskPublisher(for: URL(string: url)!) .map{ $0.data } .decode(type: [Pokedex.Results].self, decoder: JSONDecoder()) .sink(receiveCompletion: completionHandler, receiveValue: valueHandler) .store(in: &subscriptions) } } Model import Foundation struct Pokedex: Codable { struct Results: Codable { let name: String let url: String } var results: [Results] var count: Int var next: String var previous: String? } I use pokeAPIV2 from that link: https://pokeapi.co/api/v2/pokemon?offset=0&limit=898 { "count": 1154, "next": "https://pokeapi.co/api/v2/pokemon?offset=3&limit=3", "previous": null, "results": [ { "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" }, { "name": "ivysaur", "url": "https://pokeapi.co/api/v2/pokemon/2/" }, { "name": "venusaur", "url": "https://pokeapi.co/api/v2/pokemon/3/" } ] } I want to use the "name" data from that API. One thing that I noticed is that API starts from "{" and previous ones starts from "[".
Replies
3
Boosts
0
Views
1.1k
Activity
Oct ’22
Combine with UITableView
Hello, I'm trying to work out a simple example to fill table view data with Combine. The following is what I have. import Foundation struct MyModel: Decodable { let id: String let type: String } import UIKit import Combine class APIClient: NSObject { var cancellable: AnyCancellable? let sharedSession = URLSession.shared func fetchData(urlStr: String, completion: @escaping ([MyModel]?) -> Void) { guard let url = URL(string: urlStr) else { return } let publisher = sharedSession.dataTaskPublisher(for: url) cancellable = publisher.sink(receiveCompletion: { (completion) in switch completion { case .failure(let error): print(error) case .finished: print("Success") } }, receiveValue: { (result) in let decoder = JSONDecoder() do { let post = try decoder.decode([MyModel].self, from: result.data) completion(post) } catch let error as NSError { print("\(error)") completion(nil) } }) } } import Foundation class ViewModel: NSObject { @IBOutlet var apiClient: APIClient! var dataModels = [MyModel]() func getGitData(completion: @escaping () -> Void) { let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events" apiClient.fetchData(urlStr: urlStr) { (models) in if let myModels = models { self.dataModels = myModels.map { $0 } } completion() } } } import UIKit import Combine class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // MARK: - Variables var cancellable: AnyCancellable? @IBOutlet var viewModel: ViewModel! @Published var models = [MyModel]() // MARK: - IBOutlet @IBOutlet weak var tableView: UITableView! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() viewModel.getGitData { self.models = self.viewModel.dataModels } cancellable = $models.sink(receiveValue: { (result) in DispatchQueue.main.async { [weak self] in guard let strongSelf = self else { return } strongSelf.tableView.reloadData() } }) } // MARK: - TableView func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return models.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell") let dataModel = models[indexPath.row] cell?.textLabel?.text = dataModel.id cell?.detailTextLabel?.text = dataModel.type return cell! } } I'm not quite comfortable with the lines of code under my view controller (ViewController) in using Combine. How can I make them better? Muchos thankos.
Replies
3
Boosts
0
Views
3.7k
Activity
Oct ’22
Unexpected behaviour with async values of a filtered Publishers.CombineLatest
When I run the following code in the Playground (I'm on XCode 13.4): import Combine import Foundation import _Concurrency let subject = CurrentValueSubject<Int, Never>(0) let faultyPublisher = Publishers.CombineLatest(subject, Just(-1))   .filter { firstValue, _ in firstValue != 0 } let publisherToTest = faultyPublisher Task {   for await output in publisherToTest.values {     print("RECEIVED \(output)")   } } Task {   try await Task.sleep(nanoseconds: 1_000_000_000)   subject.send(1) } I expect to see the output: RECEIVED (1, -1). Instead I see no output. Is this a bug with Publishers.CombineLatest + .filter + values: AsyncPublisher? Some other note: if you assign publisherToTest to one of these, the behaviours are what I would expect: let workingPublisher1 = Publishers.CombineLatest(subject, Just(-1))   .filter { firstValue, _ in firstValue != 1 } Above outputs: RECEIVED (0, -1) let workingPublisher2 = Publishers.CombineLatest(Just(-1), subject)   .filter { _, secondValue in secondValue != 0 } Above outputs: RECEIVED (-1, 1) let workingPublisher3 = Publishers.CombineLatest(subject, Just(-1))   .filter { firstValue, _ in firstValue != 0 }   .flatMap(maxPublishers: .max(2)) { Just($0) } Above outputs: RECEIVED (1, -1)
Replies
0
Boosts
1
Views
1.1k
Activity
Aug ’22
Views don't update with @Published data on subclasses of ObservableObject class for iOS 14
@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?
Replies
4
Boosts
0
Views
2.7k
Activity
Aug ’22
How to avoid retain cycle in custom SwiftUI view closure
I'm trying to create a wrapper around a WKWebView for SwiftUI, and I'm not sure how to prevent a memory leak. I've created an ObservableObject which handles controlling the WebView, and a custom view that displays it: public class WebViewStore: ObservableObject { var webView: WKWebView = WKWebView() // List of event handlers that will be called from the WebView var eventHandlers: [String: () -> Void] = [:] deinit { // This is never called once an action has been set with the view, // and the content view is destroyed print("deinit") } // All the WebView code, including custom JavaScript message handlers, custom scheme handlers, etc... func reloadWebView() { } } The web view needs to be able to communicate with JavaScript, so I've added an onAction() method which gets called when the WebView gets a javascript event. View wrapper: struct WebView: NSViewRepresentable { let store: WebViewStore func makeNSView(context: Context) -> WKWebView { return store.webView } func updateNSView(_ view: WKWebView, context: Context) {} } extension WebView { /// Action event called from the WebView public func onAction(name: String, action: @escaping () -> Void) -> WebView { self.store.eventHandlers[name] = action return self } } Usage that creates the retain cycle: struct ContentView: View { @StateObject var store = WebViewStore() var body: some View { VStack { // Example of interacting with the WebView Button("Reload") { store.reloadWebView() } WebView(store: store) // This action closure captures the WebViewStore, causing the retain cycle. .onAction(name: "javascriptMessage") { print("Event!") } } } } Is there a way to prevent the retain cycle from happening, or is there a different SwiftUI pattern that can to handle this use case? I can't use [weak self] because the view is a struct. I need to be able to receive events from the WebView and vice versa.
Replies
0
Boosts
0
Views
1.8k
Activity
Aug ’22
NavigationStack - No Back Button
I created a very simple project with a NavigationStack and a NavigationLink as follows. Code 1. NavigationLink("Go To Next View", value: "TheView")          .navigationDestination(for: String.self) { val in Text("Value = \(val)")          } This code works fine in the simple project. But when I put this simple code into my existing app's project using a NavigationStack, it navigates to the next view fine, but there is no back button. I am updating my Apple Watch target to use SwiftUI and I want to use NavigationStack. My app does use @ObservedObject and my important data is using @Publish in a singleton class which conforms to ObservableObject. But this NavigationLink code, Code 1, is extremely simple and should work fine, but it does not. The same problem happens with the following code. Code 2 NavigationLink { NextView() } label: {     MainRowView(rowText: "Saved") } When I switch to NavigationView, Code 1 is grayed out, but Code 2 works fine and has a back button. However, as you know, NavigationView is being deprecated, so I really need to fix this or have it fixed. I can't see how this could be a problem with my code as the code snippet is so simple and worked in the simple project.
Replies
4
Boosts
0
Views
2.6k
Activity
Aug ’22
Bizar leaking ViewModel in SwiftUI
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.
Replies
1
Boosts
0
Views
1.7k
Activity
Jul ’22
How to register a socket event in macOS and iOS?
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
Replies
2
Boosts
1
Views
1.4k
Activity
Jul ’22
SwiftUI detect system volume change
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!
Replies
1
Boosts
1
Views
3.8k
Activity
Jul ’22
Is using @Published in a Sendable type safe?
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?
Replies
2
Boosts
0
Views
2.7k
Activity
Jul ’22
Combine Publishers.CombineLatest has a serious demand management bug - I believe.
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] ]) }}
Replies
0
Boosts
0
Views
905
Activity
Jul ’22
Changing state asynchronously in SwiftUI
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?
Replies
1
Boosts
0
Views
2k
Activity
Jun ’22