I have a weird issue on my iOS app where, after a fresh install, if I try to add calendar events (after accepting the native calendar permissions prompt), my eventStore singleton never shows any available calendars, and defaultCalendarForNewEvents is always nil.
I've tried to debug it, and the odd part is that, if I try po EKEventStore().calendars(for: .event), then I can see them all (still on fresh install).
If I then kill the app and reopen it, my eventStore singleton suddenly starts working as expected.
I'm not sure if this behaviour has been documented before, it seems very strange to me and I couldn't find other posts with this specific issue...
Hereafter a snippet of my CalendarManager class and how the eventStore singleton is declared.
class CalendarManager {
// MARK: Variables
static let sharedInstance = CalendarManager()
fileprivate init() { }
let eventStore = EKEventStore()
// further business logic...
}
And further down, the code where I encounter issues:
func createEvent(for workout: Workout, sendReminder: Bool = false) -> Bool {
// If I add a breakpoint below and check a new instance of EKEventStore,
// I can see all calendars & defaultCalendarForNewEvents,
// however this `guard` never lets us through...
guard let calendar = CalendarManager.sharedInstance.eventStore.defaultCalendarForNewEvents else { return false }
// create event with default calendar...
}
What am I doing wrong?
Make sure you aren't initializing EKEventStore before requesting calendar permissions.
Related
EDIT: I've added a rewording of my question at the bottom.
So I have an app that I've been working on for a long time. I used it to teach myself Xcode / Swift and now that I have a Mac again, I'm trying to learn SwiftUI as well by remaking the app completely as a new project.
I have an Entity in CoreData that only ever contains 1 record, which has all of the attributes of the settings I'm tracking.
Before, I was able to make an extension to my Entity, Ent_Settings that would allow me to always get that specific record back.
class var returnSettings: Ent_Settings {
//this is inside of extension Ent_Settings
let moc = PersistenceController.shared.container.viewContext
var settings: Ent_Settings?
let fetchRequest: NSFetchRequest<Ent_Settings> = Ent_Settings.fetchRequest()
do {
let results = try moc.fetch(fetchRequest)
if results.count == 0 {
Ent_Settings.createSettingsEntity()
do {
let results = try moc.fetch(fetchRequest)
settings = results.first!
} catch {
error.tryError(tryMessage: "Failed performing a fetch to get the Settings object after it was created.", loc: "Ent_Settings Extension")
}
} else {
settings = results.first!
}
} catch {
error.tryError(tryMessage: "Fetching Settings Object", loc: "Ent_Settings Extension")
}
return settings!
}
It took me a while to figure out how to get the moc now that there is no AppDelegate, but you can see I figured out in the code and that seems to work great.
However, when I go to a View that I made in SwiftUI, I'm getting weird results.
In the Simulator, I can run my app and see the changes. Leave the view and come back and see the changes are saved. However, if I rebuild and run the app it loses all data. So it seems it's not remembering things after all. Basically, it's volatile memory instead of persistent memory. But if I put another function in the extension of Ent_Settings to update that attribute and then build and run again, it will persistently save.
I'm wondering if it has something to do with how the context is managed, perhaps it's not seeing the changes since it is a different "listener"?
Here is what I'm doing to make the changes in the View. (I tried removing the extra stuff that just made it harder to see)
import SwiftUI
struct Settings_CheckedView: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var picker1 = 0
var body: some View {
Form {
Section(header: Text("..."),
footer: Text("...")) {
VStack {
HStack { ... }
Picker("", selection: $picker1) {
Text("Off").tag(0)
Text("Immediately").tag(1)
Text("Delay").tag(2)
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: picker1, perform: { value in
settings.autoDeleteSegment = Int16(value)
viewContext.trySave("Updating auto-delete segment value in Settings",
loc: "Ent_Settings Extension")
})
}
}
...
...
.onAppear(perform: {
settings = Ent_Settings.returnSettings
self.picker1 = Int(settings.autoDeleteSegment)
self.picker2 = Int(settings.deleteDelaySegment)
})
}
}
And here is the code that I'm using for the trySave() function I have on the viewContext
extension NSManagedObjectContext {
func trySave(_ tryMessage: String, loc: String) {
let context = self
if context.hasChanges {
do {
try self.save()
} catch {
print("Attempted: \(tryMessage) -in \(loc)")
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
This code doesn't seem to actually save anything to the Database.
I also tried adding this code into Settings_CheckedView to see if that would make a difference, but I got even weird results.
#FetchRequest(sortDescriptors:[])
private var settingsResult: FetchedResults<Ent_Settings>
But that returns zero results even though I know the Ent_Settings has 1 record. So that makes me wonder if I am actually creating two databases with the way I'm doing it.
I'm really new to SwiftUI and CoreData seems to be one of those things that is hard to get information about. Hoping someone can point out where I'm going wrong. I'd love to be able to make CoreData changes in both Swift and SwiftUI.
Thank you for any help you can give and let me know if you need anything else added in.
EDITED: To try to reword question
So ultimately, I'm wanting to do some stuff with CoreData inside of the extension for that entity. My database has a lot of aspects to it, but I also made an entity in there to store settings information and since that one isn't related to anything else, I figured that would be a good starting point in learning SwiftUI. I have everything working in UIKit, but I'm specifically trying to learn SwiftUI as well.
From how I understand SwiftUI, it is meant to replace the storyboard and make the lifecycle stuff much easier. So classes I have that deal with CoreData should still do that external to SwiftUI.
As you can see above in my Settings_CheckedView view above, I'm referencing that single settings record from an extension in Ent_Settings. Basically, everything inside of that extension takes care of returning that single record of Settings, checking if that record exists and creating it if it doesn't (first time running app basically)
Ideally, I'd like to keep the functionality inside the extension of Ent_Settings, but my problem is I can't get the correct instance of the moc to persistently save it.
#main
struct MyApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
MainMenuView().onAppear(perform: {
if Ent_Lists.hasAnyList(persistenceController.container.viewContext) == false {
Ent_Lists.createAnyList(persistenceController.container.viewContext)
}
if Ent_Section.hasBlankSection(persistenceController.container.viewContext) == false {
Ent_Section.createBlankSection(persistenceController.container.viewContext)
}
//self.settings = Ent_Settings.returnSettings(persistenceController.container.viewContext)
})
//ContentView()
//.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
I'm pretty certain that let persistenceController = PersistenceController.shared is initializing the persistent controller used throughout the app. Either creating it or retrieving it.
So, I tried this first inside of Ent_Settings:
let moc = PersistenceController.shared.container.viewContext
but I think that this might be creating a new PersistenceController outside the one made inside of MyApp
I also tried let moc = EZ_Lists_SwiftUI.PersistenceController.shared.container.viewContext but I'm pretty sure that also makes a new instance given I can only access the upper case PersistenceController and not the lower case one.
I also tried just passing the moc from the view like this: class func createSettingsEntity(_ moc: NSManagedObjectContext) { but I get an error about the moc being nil, so I'm guessing the moc can't be sent by reference from a Struct.
Thread 1: "+entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'Ent_Settings'"
And just to be clear, I'm adding this to the view: #Environment(\.managedObjectContext) private var viewContext adding this #State property to the View: #State private var settings: Ent_Settings! and setting it within .onAppear with this: self.settings = Ent_Settings.returnSettings(self.viewContext)
So what I'm really looking for is how I access the moc from within that extension of Ent_Settings. In my app that was purely UIKit and Storyboards I used this: let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext but as there is no longer an AppDelegate, I cannot use that anymore. What replaces this to get moc when you are doing SwiftUI but working outside of the Views, like in a class or something like I am? Thank you. Hopefully this extra information helps explain my issue.
If you are only using coredata to store settings, I would look into UserDefaults and if that matches your use case.
https://developer.apple.com/documentation/foundation/userdefaults
If you for some reason need to use CoreData, I would recommend enabling CloudKit, as CloudKit will allow you to actually view what data is saved via a web console.
Also, SwiftUI does not create an AppDelegate for you, you are still able to just create your own AppDelegate.swift file as you would with UIKit.
Just define the #UIApplicationDelegateAdaptor to your custom AppDelegate.swift file in your top most struct (the one with #main) before the struct declaration.
#main
struct MainAppView: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
Okay, a bit of egg on my face. I finally figured out why I was having so much trouble. When I was making my app, I commented out everything related to the apple provided ContextView and commented out too much.
//ContentView()
//.environment(\.managedObjectContext, persistenceController.container.viewContext)
I lost the part where the environment object was getting setup. After putting that back in I'm able to save to the moc now.
In my Entity class extension I'm accessing the moc this way:
let moc = MyApp.PersistenceController.shared.container.viewContext
Thank you for everyone who looked and tried to help. Turns out I was shooting myself in the foot.
Using Xcode 10.1 running the app on mini iPad OS 12.1.1
I am posting a notification to NotificationCenter and the handler function updates UILabel setupProgress to show the progress of a data import.
This used to work fine but has recently stopped working, very possibly as a result of something I have done, but I can't think what.
PM in the code is a function which prints to the console - and it tells me that self.setupProgress.text is, in fact, set correctly and changes as expected as the data loads, however the corresponding UILabel does not update.
The initial UILabel text is set like this
if !self.update_type.isEmpty && self.update_type == "setup_import" {
self.setupProgress.text = "I've told the server that this is a clean install"
}
and that works fine - but then, as the import progresses, in the handler function (below) I get no updates until
import_progress == "And now the end is upon us ..."
at which point the UILabel updates correctly and everything carries on as expected.
func handleImportNotification(_ notification:Notification) {
self.setupProgress.text = import_progress
// should show something like
// `Importing F4RES : 0 of : 1395`
// `Importing F4RES : 500 of : 1395`
// etc...
PM(#line, function: #function, str1: self.setupProgress.text)
// prints the correct updates to the console
if import_progress == "And now the end is upon us ..." {
self.makeShopOptions()
self.loadingImage.stopAnimating()
self.performSegue(withIdentifier: "setup_to_splash", sender: self)
}
}
The app continues to work just fine - just without the expected UILabel updates in between.
Thanks in advance for any ideas.
You are posting a notification to the Notification Center. Would you mind showing how and when?
Assuming you're using NotificationCenter.default.post notificationName: method, the handler should take an argument of type Notification. Otherwise it won't respond to notification updates.
The handler function should look like:
func handleImportNotification(notification: Notification) { ... }
And the observer:
var observer: NSObjectProtocol?
func someFunction() {
observer = NotificationCenter.default.addObserver(forName: object: queue: using: { (Notification) in
//Update UILabel or call the associated function
}))
}
Try this.
I am validating urls from NSSavePanel using the delegate's panel(_:validate) method, throwing error in case of invalid url. In such case the NSSavePanel presents an alert, which I want to customize (meaning present some human readable description) depending on the error thrown, keeping the save panel window open and then letting you choose another path.
LocalizedError works just fine when not using App Sandbox but in a sandboxed app the getter for error description is never called and the message in the alert is generic "Operation couldn't be completed. (#yourErrorType)", which I guess is somehow caused by the different inheritance chain for sandboxed NSSavePanels.
I am struggling figuring a way around this - is it possible to customize the alert somehow while still keeping the app sandboxed?
Addendum: Permissions for User Selected File => r/w. Running the following example produces different alerts with/without sandbox.
func runSavePanel()
{
let panel = NSSavePanel()
let delegate = SavePanelDelegate()
panel.delegate = delegate
_ = panel.runModal()
}
class SavePanelDelegate: NSObject, NSOpenSavePanelDelegate {
func panel(_ sender: Any, validate url: URL) throws {
throw CustomError.whatever
}
}
enum CustomError: LocalizedError {
case whatever
var errorDescription: String? {
get {
return "my description"
}
}
}
So, after a bit of further digging I can tell the solution of the riddle finally although I can only guess the reasons why it was made tricky by Apple. Apparently NSError exclusively needs to be used. The customization has to be done in userInfo, say
let userInfo = [NSLocalizedDescriptionKey: "yourLocalizedDescription", NSLocalizedRecoverySuggestionErrorKey: "yourSuggestion"]
throw NSError(domain: "whatever", code: 0, userInfo: userInfo)
etc. By the way subclassing NSError doesn't work, the Sandbox will just happily ignore you :)
I’ve created a Swift macOS app which uses SMJobBless to create a helper with escalated privileges. This works fine—the helper gets installed to /Library/Privileged Helper Tools and an accompanying LaunchDaemon gets created in /Library/LaunchDaemons. However, the helper is unable to start successfully. Instead, it crashes with an “Illegal instruction: 4” message.
I’ve prepared the helper to respond to XML connections by implementing the NSXPCListenerDelegate protocol. Here‘s my Helper main.swift code:
import Foundation
class HelperDelegate: NSObject, NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
newConnection.exportedInterface = NSXPCInterface(with: HelperToolProtocol.self)
newConnection.exportedObject = HelperTool()
newConnection.resume()
return true
}
}
let delegate = HelperDelegate()
let listener = NSXPCListener.service()
listener.delegate = delegate
listener.resume()
The crash occurs on the last line, listener.resume().
I tried to launch the helper app manually from the command line (which is identical to what the LaunchDaemon does) and, again, it crashes with the above error message printed to stdout. I don’t have any more ideas on how to test this for the root cause. My implementation is more than rudimentary, following Apple’s guidlines for implementing XM services. Also, the various posts on SO regarding XML services haven’t helped me in resolving this issue. Has anyone of you tried to create a privileged helper in Swift successfully? BTW, the app is not sandboxed.
For the sake of completeness, here’s the code for the HelperTool class referenced in my HelperDelegate class above:
import Foundation
class HelperTool: NSObject, HelperToolProtocol {
func getVersion(withReply reply: (NSData?) -> ()) {
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString" as String) as? String ?? "<unknown version>"
let build = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String ?? "<unknown build>"
if let d = "v\(version) (\(build))".data(using: .utf8, allowLossyConversion: false) {
reply(d as NSData)
}
}
}
And finally the HelperToolProtocol:
import Foundation
#objc(HelperToolProtocol) protocol HelperToolProtocol {
func getVersion(withReply: (NSData?) -> ())
}
Thanks for any help!
After days of testing I finally found a solution which makes my XPC helper launch correctly and respond to any messages. The problem lies in the last three lines of the main.swift module which currently read
let listener = NSXPCListener.service()
listener.delegate = delegate
listener.resume()
which, as put in the question, make the helper crash immediately upon the very last line.
I took these lines directly from Apple’s Creating XPC Services documentation. Here’s the documentation for the NSXPCListener resume() function:
If called on the service() object, this method never returns. Therefore, you should call it as the last step inside the XPC service's main function after setting up any desired initial state and configuring the listener itself.
The solution is to not call the NSXPCListener.service() singleton object but rather instantiate a new NSXPCListener object using the init(machServiceName:)initializer passing the same Mach service name that is being used on the main app’s XPC connection. As resume() in this case would resume immediately—thus terminating the helper—you have to put it on the current run loop to have it run indeterminately. Here’s the new, working code:
let listener = NSXPCListener(machServiceName: "Privilege-Escalation-Sample.Helper")
listener.delegate = delegate
listener.resume()
RunLoop.current.run()
I am unable to get NSUndoManager to function in Swift Xcode release 6.1 GM Seed 2. I've read several times the article How do I register NSUndoManager in Swift? but I can't make it work in 6.1.
Here is a code snippet showing my attempt:
func zoomFractalClick(imagePoint: NSPoint?) {
//NSLog("Point zoomed \(imagePoint)")
if imagePoint == nil { return }
let tag = zoomPopUp.selectedItem!.tag
// Try setting undo/redo
undoManager.registerUndoWithTarget(self, selector: Selector("resetFractalDef"),
object: StructWrapper<MJFractalDefinition>(object: fractalDef))
//(undoManager.prepareWithInvocationTarget(self) as AppDelegate).
// resetFractalDef(StructWrapper<MJFractalDefinition>(object: fractalDef))
fractalDef.zoomBounds(centerPoint: imagePoint!, zoomPower: Double(tag))
drawFractal()
}
func resetFractalDef( fractalDef: StructWrapper<MJFractalDefinition>) {
self.fractalDef = fractalDef.object
drawFractal()
}
class StructWrapper<T> {
let object: T
init( object: T) {
self.object = object
}
}
Using the 'simple' method, everything compiles and runs correctly even saving the Undo. However when I select Undo from the Edit menu, I get the error that it can't find the selector 'resetFractalDef'.
I've also tried the prepare with invocation method of Undo, and it fails at the point where it tries to prepare the undo with invocation target reporting 'EXC_BREAKPOINT'.
Please, any help would be appreciated. I am a Java programmer learning to program in the Apple environment mostly enjoying the using Swift.