I am working on a library, a Swift package. We have quite a few properties on various classes that can change and we think the @Published property wrapper is a good way to annotate these properties as it offers a built-in way to work with SwiftUI and also Combine.
Many of our properties can change on background threads and we've noticed that we get a purple runtime issue when setting the value from a background thread. This is a bit problematic for us because the state did change on a background thread and we need to update it at that time. If we dispatch it to the main queue and update it on the next iteration, then our property state doesn't match what the user expects. Say they "load" or "start" something asynchronously, and that finishes, the status should report "loaded" or "started", but that's not the case if we dispatch it to the main queue because that property doesn't update until the next iteration of the run loop.
There also isn't any information in the documentation for @Published that suggests that you must update it on the main thread. I understand why SwiftUI wants it on the main thread, but this property wrapper is in the Combine framework. Also it seems like SwiftUI internally could ask to receive the published updates on the main queue and @Published shouldn't enforce a specific thread.
One thing we are thinking about doing is writing our own property wrapper, but that doesn't seem to be ideal for SwiftUI integration and it's one more property wrapper that users of our package would need to be educated about.
Any thoughts on direction? Is there anyway to break @Published from the main thread?
Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.
Post
Replies
Boosts
Views
Activity
This question was originally posted to StackOverflow, but I found it more suitable to be placed here.
Was working on migrating one of my app from AppDelegate lifecycle to SwiftUI lifecycle according to this question.
After following all the steps, The simulator simply shows a blank screen (the app does not launch at all):
There is no log in the console. However, if the app is removed from the simulator (or device) and reinstalled, it will launch the new SwiftUI lifecycle correctly. So there seems to be some problem with scene caching that causes iOS to be confused after the migration.
Am I missing something during the migration?
SwiftUI Preview Don't work.
Progress Preparing iPhone Simulator for Previews.
SwiftUI
Xcode version 13.0
macOS Big Sur version 11.6
I have a SwiftUI menu
Menu{
....
}, label : {
Image(...).accessibility(identifier: "cardMenu")
}
I used to be able to bring up the menu (before upgrading to xcode 13 (ios15)) like this
let app = XCUIApplication()
app.launch()
app.buttons["cardMenu"].tap()
But now i am unable to see the identifier in app.buttons. Can't seem to find the identifier anymore. I've tried looking for the identifier in the other app fields and changing to use text instead of identifer. No luck. These tests used to work prior to the upgrade.
Any help would be appreciated
I'm trying to implement custom NSTextContentManager and use it with NSTextView, however it seems that NSTextView expect NSTextContentStorage all the time.
final class MyTextContentManager: NSTextContentManager {
// ...
}
It's added to layout manager, and NSTextView instance finds it properly:
let textContentManager = MyTextContentManager() textContentManager.addTextLayoutManager(textLayoutManager)
however, when I use it, I see errors at:
[MyTextContentManager textStorage]: unrecognized selector sent to instance 0x600003d84870
the textStorage property is part of NSTextStorageObserving, that is not NSTextContentManager interface.
It looks like NSTextView is not ready to work with custom NSTextContentManager. What did I miss?
The Build for Previews builds all my targets including the test targets. Is there a way to configure the relevant targets? I do not see an option in the schema editor, and disabling Find Implicit Dependencies has no effect either.
Hi! Does anybody encountered a problem when selected custom keyboard disappears from keyboards list after updating to iOS 15? We have a lot of complaints from our users, once a day they need to go to Settings to re-add our keyboard extension. FB9700027 in case someone from Apple can take a look. Thanks!
Is there a way to use NSPathControl or something equivalent in SwiftUI on Mac?
Background:
I have been stuck on this annoying problem for days, trying different solution and searching apple developer forum, stackoverflow etc for answers; some have had similar problems but no suggested solution had the desired effect.
The problem is that when updating an observableObject or environmentObject down the navigation hierarchy view stack, the views get popped back to root. Viewing data from observableObject is fine, but not editing.
Scenario is:
I navigate to: root -> view1 -> view2.
I update the environmentObject in View2 but then I get pushed back to: root -> view1
I have simplified my app in order to make it more understandable. See below:
ObservableObject:
class DataStore: ObservableObject {
static let shared = dataStore()
@Published var name : Int = ""
}
RootView:
struct ContentView: View {
@StateObject var dataStore = DataStore.shared
@State private var isShowingView1 = false
var body: some View {
NavigationView{
VStack{
Text(dataStore.name)
NavigationLink(destination: View1(), isActive: $isShowingView1) { }
Button(action: {
isShowingView1 = true
})
}
}
}
}
View1:
struct View1: View {
@EnvironmentObject var dataStore: dataStore
@State private var isShowingView2 = false
var body: some View {
ScrollView{
VStack(alignment: .center) {
Text(dataStore.name)
NavigationLink(destination: View2(), isActive: $isShowingView2) { }
Button(action: {
isShowingView2 = true
}){
Text("Go to View2")
}
}
}
}
}
View2:
struct View2: View {
@EnvironmentObject var dataStore: dataStore
var body: some View {
ScrollView{
VStack(alignment: .center) {
Text(dataStore.name)
Button(action: {
dataStore.name = "updated value"
}){
Text("Update data")
}
// When updating this environmentObject the viewstack will be pushed back to View1. If view2 had been navigated to view3 and the view3 had been updating the environmentObject, then it would also be pushed back to View1.
}
}
}
}
Solution:
I spent many hours searching for solutions and trying different approaches, but nothing I tried worked. There seemed to be a few other people that had the same problem as I experienced, but the suggested solutions didn't cut it.
But then I stumbled on a solution for this problem when trying to implement another feature. So to be frank I am writing here now, not to ask this great community for help, but instead to give back to the community by providing the this solution to others that might need to see this.
The solution is really simple implement but was not so easy to come across. If you experience a problem similar to me then you will only need to update your rootView accordingly:
RootView Updated:
struct ContentView: View {
@StateObject var dataStore = DataStore.shared
@State private var isShowingView1 = false
var body: some View {
NavigationView{
VStack{
Text(dataStore.name)
NavigationLink(destination: View1(), isActive: $isShowingView1) { }
Button(action: {
isShowingView1 = true
})
}
}
.navigationViewStyle(.stack)
//ADD THIS LINE ABOVE
}
}
This one line .navigationViewStyle(.stack) fixed the problem of popping the viewstack for me. Unfortunately I can't provide you with the logic explanation for this behaviour, but it works and I am satisfied with that. Perhaps you are too, or perhaps you have insight on why this solution actually achieves the desired effect of allowing views down the hierarchy update observableObjects without being popped.
Happy coding :)
I'm trying to understand how to use .focusedSceneValue on macOS.
Given a very basic app which displays Thing-s, and have a menu for editing the things. When I run the app, nothing is selected at first and the menu is disabled. When selecting e.g. the Thing Alfa in the sidebar. the menu becomes enabled as expected. When I select another Thing, the menu is also updated as expected.
However, if I switch focus to another application, e.g. the Finder, and then switch back to my app, the menu is now disabled, even though a Thing is selected in the sidebar.
If I open another window within my app and select e.g. Gamma in the sidebar of that window the menu is updated as expected. But, when switching back to the first window the menu is disabled, although a Thing is selected.
What am I doing wrong? Xcode 13.1 and macOS Monterey 12.0.1.
See the code below (the code can also be found here: https://github.com/danwaltin/FocusedSceneValueTest)
struct Thing: Identifiable, Hashable {
let id: Int
let name: String
static func things() -> [Thing] {
return [
Thing(id: 1, name: "Alfa"),
Thing(id: 2, name: "Beta"),
Thing(id: 3, name: "Gamma")
]
}
}
@main
struct FocusedSceneValueTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
ThingCommands()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
List(Thing.things()) { thing in
NavigationLink(
destination: DetailView(thing: thing),
label: {Text(thing.name)}
)
}
Text("Nothing selected")
}
}
}
struct DetailView: View {
let thing: Thing
var body: some View {
Text(thing.name)
.focusedSceneValue(\.selectedThing, thing)
.navigationTitle(thing.name)
}
}
struct ThingCommands: Commands {
@FocusedValue(\.selectedThing) private var thing
var body: some Commands {
CommandMenu("Things") {
Button("Edit \(thingName)") {
print("*** Editing \(thingName)")
}
.disabled(thing == nil)
.keyboardShortcut("e")
}
}
private var thingName: String {
guard let thing = thing else {
return ""
}
return thing.name
}
}
struct SelectedThingKey : FocusedValueKey {
typealias Value = Thing
}
extension FocusedValues {
var selectedThing: Thing? {
get {self[SelectedThingKey.self]}
set {self[SelectedThingKey.self] = newValue}
}
}
First off, i believe i do know what the option 'Preserve Vector Data' does. But just in case, let me recap: When using PDFs or SVGs in my Asset Catalog, Xcode by default creates assets at fixed sizes from these files at buildtime. When i tick the 'Preserve Vector Data' box, the asset is scaled at runtime using the vector data, allowing for smooth scaling and crisp images at any scale.
But the question i'm asking myself now is what exactly is the drawback - most likely performance-wise - to simply activating this option for each an every SVG or PDF Asset i use in my project?
I would be very happy if someone could elaborate on this or direct me to some more in-depth documentation on Vector Assets :)
So I am banging my head, I realized my stand along Watch App had a STUPID long name of "App Name - WatchKit App" so I went into my Target and changed the Display Name to "App Name" removing WatchKit App. Well now my app won't validate when uploading to the Appstore. I get the message - Invalid Info.plist key. The key
WKExtensionDelegateClassName in bundle App Name.app/Watch/App Name WatchKit App.app is invalid.
My Info.plist has the value of
<key>WKExtensionDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).ExtensionDelegate</string>
I have confirmed that I have @WKExtensionDelegateAdaptor(ExtensionDelegate.self) var delegate in my @main for the SwiftUI App. And when I print a few values in my app launch I get the following confirmations:
Super Init - ExtensionDelegate
Contentview
applicationDidFinishLaunching for watchOS
Super Init - ExtensionDelegate
Optional(My_App_Extension.Setup)
Optional(My_App_Extension.Statistics)
Optional(My_App_Extension.Other)
applicationDidBecomeActive for watchOS
update complication
I create three classes at launch and print this in the log with print(ExtensionDelegate.shared.Setup as Any) , etc. The other lines are just confirming where I am at app startup.
This is a WatchOS8 application and I am running Xcode version Version 13.1 (13A1030d).
Hi,
I'm currently developing a SwiftUI based app with Core Data and CloudKit sync with the NSPersistentCloudKitContainer.
I found different solutions how to toggle CloudKit sync of the Core Data during runtime. The basic idea of these solutions is the following.
instantiate a new NSPersistentCloudKitContainer
set storeDescription.cloudKitContainerOptions = nil
load persistence store
Some solutions recommend to restart the app manually to avoid exactly my problem.
Issues
So far so good. How can I distribute the new viewContext through my app during runtime. In the main App I distributed the viewContext during startup via @Environment(\.managedObjectContext) and it seems not be updated automatically after a reinitialization of NSPersistentCloudKitContainer.
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistence.container.viewContext)
}
}
After deactivating the CloudKit sync I receive the following error when I try to add a new entity.
[error] warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'TestEntity' so +entity is unable to disambiguate.
Any ideas?
Regards
Sven
Hi,
I want to activate/deactivate the CloudKit Sync during App runtime in a user settings view. Basically this works fine. Every time I toggle between the NSPersistentContainer and the NSPersistentCloudKitContainer, I increase the persistence.persistenceContainerReloaded attribute and the whole view hierarchy will be reloaded. Thus all changes are passed through the whole app.
During the reload phase I have to load a new persistence store by container.loadPersistentStores(...). Unfortunately, I cannot remove the old persistence store before loading the new one. The app crashes immediately, because the store and viewContext is still in use. Therefore, I just create a new one and trigger the reload. Afterwards every view is using the new viewContext. But somewhere in the background there is still the old persistence store with CloudKit Sync active and pushes every local change to the cloud. Changes on the cloud from other devices are not received anymore.
Does someone has any idea, how to correctly unload a PersistentStore (replace NSPersistentCloudKitContainer by NSPersistentContainer) in a SwiftUI based app?
@main
struct TargetShooterApp: App {
@StateObject var persistence: Persistence = Persistence.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistence.container.viewContext)
.id(persistence.persistenceContainerReloaded)
}
}
}
Hey,
I know you can write @AppStorage("username") var username: String = "Anonymous" to access a Value, stored In the User Defaults, and you can also overwrite him by changing the value of username.
I was wondering if there is any workaround to use @AppStorage with Arrays.
Because I don't find anything, but I have a lot of situations where I would use it.
Thanks!
Max
How to get upload progress when using:
let (data, urlResponse) = try await urlSession.upload(
for: urlRequest,
from: bodyData,
delegate: nil // Something I need here maybe?
)
I have tried using:
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64) {
print("fractionCompleted : \(Float(totalBytesSent) / Float(totalBytesExpectedToSend))")
}
Within the same class but it never fires.
I specifically want the async / await capability on the upload.
I couldn't get the session.uploadTask to work with the await prefix.
I am hitting major road blocks in migrating one of my Obj-C-Cocoa applications away from -[NSView (un)lockFocus] and -[NSBitmapImageRep initWithFocusedViewRect:].
In a transcript of a presentation on WWDC2018 I read:
With our changes to layer backing, there's a few patterns I want to call out that aren't going to work in macOS 10.14 anymore. If you're using NSView lockFocus and unlockFocus, or trying to access the window's graphics contents directly, there's a better way of doing that. You should just subclass NSView and implement draw rect. ...
Of course, we all implemented -[NSView drawRect:] for decades now. The big question is, how can we do incremental (additional, event driven) drawing in our views, without redrawing the whole view hierarchy. This is the use case of -(un)lockFocus, and especially when drawing of the base view is computational expensive. Wo would have thought that people use -(un)lockFocus for regular drawing of the NSView hierarchy.
I tried to get away with CALayer, only to find out after two days experimenting with it, that a sublayer can only be drawn if the (expensive) main layer has been drawn before —> dead end road.
Now I am going to implement a context dependent -[NSView drawRect:]. Based on a respective instance variable, either of the (expensive) base presentation of the view or the simple additions are drawn. Is it that what Apple meant by … just subclass NSView and implement draw rect?
From the point of view of object oriented programming, using switch() in methods to change the behaviour of the object is ugly - to say the least. Any better options?
Ugly or not, in any case, I don’t want to redraw the whole view hierarchy only for moving a crosshairs in a diagram.
My actual use case is:
This application draws into a custom diagram NSView electrochemical measurement curves which may consist of a few thousands up to millions of data points. The diagram view provides a facility for moving crosshairs and other pointing aids over the displayed curves, by dragging/rolling with the mouse or the touch pad, or by moving it point by point with the cursor keys.
Diagram generation is computational expensive and it must not occur only because the crosshairs should be moved to the next data point.
So for navigating the crosshairs (and other pointing aids), a respective method locks the focus of said view, restores the background from a cache, caches the background below the new position of the crosshairs using -[NSBitmapImageRep initWithFocusedViewRect:], draws the crosshairs and finally unlocks the focus.
All this does not work anymore since 10.14.
Don’t over-engineering! No suggested architecture for SwiftUI, just MVC without the C.
On SwiftUI you get extra (or wrong) work and complexity for no benefits. Don’t fight the system.
Are there good examples of integrating SwiftUI, Coredata and Cloudkit working nicely together?
I have a simple SwiftUI application with CoreData and two views. One view displays all "Place" objects. You can create new places and you can show the details for the place.
Inside the second view you can add "PlaceItem"s to a place.
The problem is that, once a new "PlaceItem" is added to the viewContext, the @NSFetchRequest seems to forget about its additional predicates, which I set in onAppear. Then every place item is shown inside the details view. Once I update the predicate manually (the refresh button), only the items from the selected place are visible again.
Any idea how this can be fixed? Here's the code for my two views:
struct PlaceView: View {
@FetchRequest(sortDescriptors: []) private var places: FetchedResults<Place>
@Environment(\.managedObjectContext) private var viewContext
var body: some View {
NavigationView {
List(places) { place in
NavigationLink {
PlaceItemsView(place: place)
} label: {
Text(place.name ?? "")
}
}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
let place = Place(context: viewContext)
place.name = NSUUID().uuidString
try! viewContext.save()
} label: {
Label("Add", systemImage: "plus")
}
}
}
.navigationTitle("Places")
}
}
struct PlaceItemsView: View {
@ObservedObject var place: Place
@FetchRequest(sortDescriptors: []) private var items: FetchedResults<PlaceItem>
@Environment(\.managedObjectContext) private var viewContext
func updatePredicate() {
items.nsPredicate = NSPredicate(format: "place == %@", place)
}
var body: some View {
NavigationView {
List(items) { item in
Text(item.name ?? "");
}
}
.onAppear(perform: updatePredicate)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
let item = PlaceItem(context: viewContext)
item.place = place
item.name = NSUUID().uuidString
try! viewContext.save()
} label: {
Label("Add", systemImage: "plus")
}
}
ToolbarItem(placement: .navigationBarLeading) {
Button(action: updatePredicate) {
Label("Refresh", systemImage: "arrow.clockwise")
}
}
}
.navigationTitle(place.name ?? "")
}
}
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
var body: some View {
NavigationView {
PlaceView()
}
}
}
Thanks!