Post not yet marked as solved
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
Post not yet marked as solved
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.
Post not yet marked as solved
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!
Post not yet marked as solved
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.
Post not yet marked as solved
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?
Post not yet marked as solved
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] ])
}}
Post not yet marked as solved
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?
Post not yet marked as solved
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)
}
}
}
Post not yet marked as solved
Is Combine replacing NotificationCenter and Key-Value Observing?
Post not yet marked as solved
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)
}
Post not yet marked as solved
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;
}
}
Post not yet marked as solved
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?
Post not yet marked as solved
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?
Post not yet marked as solved
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)
}
}
}
}
}
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)
}
}
}
}
Post not yet marked as solved
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?
Post not yet marked as solved
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?
Post not yet marked as solved
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!
Post not yet marked as solved
@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?
Post not yet marked as solved
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)
}
}
}