Not sure if it's a bug or what. I made an app that add Vim bindings all over macOS, but it fails in Catalyst apps. The text is readable mostly—although line numbers are wrong and line ranges too—but I can't set the selected text. It does nothing expect selecting the text, but not replacing it with the new one. It works well in all native apps that support the Accessibility.
Am I doing things wrong or is this a confirmed bug? (I've been reporting bugs in the Accessibility API for two years, with no change from Apple's part.)
The code is fairly simple. As an example:
func ....() {
var selectedTextRange = CFRange()
selectedTextRange.location = 2
selectedTextRange.length = 3
let newValue = AXValueCreate(.cfRange, &selectedTextRange)
guard AXUIElementSetAttributeValue(axFocusedElement, kAXSelectedTextRangeAttribute as CFString, newValue!) == .success else { return false }
guard AXUIElementSetAttributeValue(axFocusedElement, kAXSelectedTextAttribute as CFString, "hehe" as CFTypeRef) == .success else { return false }
return true
}
The func returns true. But in Catalyst apps there's a selection from location 2 to 5, but the text is not replaced by "hehe".
I've got an Xcode project also that shows the issue: https://github.com/godbout/AXBugsWithCatalystApps
Thanks.
So, in my previous question, I ended up figuring out my own issue, (I would recommend taking a look at that before reading this one), but the 20 seconds of glory was cut short when I realized that the outcome was similar across all users on the app, which is what I didn't want and totally forgot about.
With the function down below, I can purchase the event and the buttons will show up for that event and go away if I cancel, and it's unique for each event, which I adore. Now, the problem with the function down below is that if I make a purchase on user1 account and the buttons show up and stay there how they're supposed to, when I log into user2 account and perhaps want to purchase that same event, the buttons are already showing up even though user2 hasn't done anything.
getSchoolDocumentID { (schoolDocID) in
if let schID = schoolDocID {
self.db.document("school_users/\(schID)/events/\(self.selectedEventID!)").getDocument { (documentSnapshot, error) in
if let error = error {
print("There was an error fetching the document: \(error)")
} else {
guard let docSnap = documentSnapshot!.get("purchased") else {
return
}
if docSnap as! Bool == true {
self.viewPurchaseButton.isHidden = false
self.cancelPurchaseButton.isHidden = false
self.creditCard.isHidden = true
self.purchaseTicketButton.isHidden = true
} else {
self.creditCard.isHidden = false
self.purchaseTicketButton.isHidden = false
}
}
}
}
}
So i tried to solve the problem on my own but ran into a roadblock. I tried to make a subcollection of events_bought when users purchase an event and have the details stored in fields that I can call later on in a query. This was something I thought I could use to make the purchases unique amongst all users.
The function below looks through events_bought subcollection and pulls up a field and matches it with a piece of data on the displayedVC, the issue is if the event hasn't been purchased and I go on it with that user, it crashes and says how the document reference path has the wrong number of segments which I don't get because it's the same as the function above, so I realized that the path wouldn't exist and tried to figure out ways to validate the path and came up with the function down below.
getEventsBoughtEventID { (eventBought) in
if let idOfEventBought = eventBought {
let docPath = self.db.document("student_users/\(self.user?.uid)/events_bought/\(idOfEventBought)")
if docPath.path.isEmpty {
self.creditCard.isHidden = false
self.purchaseTicketButton.isHidden = false
} else {
self.db.document("student_users/\(self.user?.uid)/events_bought/\(idOfEventBought)").getDocument { (documentSnapshot, error) in
if let error = error {
print("There was an error trying to fetch this document: \(error)")
} else {
guard let docSnapEventName = documentSnapshot!.get("event_name") else {
return
}
if docSnapEventName as! String == self.selectedEventName! {
self.viewPurchaseButton.isHidden = false
self.cancelPurchaseButton.isHidden = false
self.creditCard.isHidden = true
self.purchaseTicketButton.isHidden = true
}
}
}
}
}
}
I wasn't really sure if it would work or not so I tried my luck, but I still end up getting the same document reference errors. If anyone can figure out how I can validate a document path and use logic to make certain things happen, that would be great. Thanks.
So i finally figured out how to come about doing this. It was a 4 hour grind and struggle but i got it, with a few bugs of course. So i found out the reason my app crashed was not just because of the path segments, but cause of the fact that the idOfEventBought didn't exist for some events because those events weren't purchased yet and that there was no subcollection called events_bought even created yet.
Firstly, I added a test document in a subcollection called events_bought when a user signs up, which makes sense because it would have to be made eventually anyways.
db.document("student_users/\(result?.user.uid)/events_bought/test_document").setData(["test": "test"])
This line of code allowed me to come up with my next method, that can verify if an event was bought or not.
func checkIfUserMadePurchase(shouldBeginQuery: Bool) -> Bool {
if shouldBeginQuery == true {
getEventsBoughtEventID { (eventBought) in
if let idOfEventBought = eventBought {
self.docListener = self.db.document("student_users/\(self.user?.uid)/events_bought/\(idOfEventBought)").addSnapshotListener(includeMetadataChanges: true) { (documentSnapshot, error) in
if let documentSnapshot = documentSnapshot {
if documentSnapshot.exists {
self.creditCard.isHidden = true
self.purchaseTicketButton.isHidden = true
self.viewPurchaseButton.isHidden = false
self.cancelPurchaseButton.isHidden = false
}
}
}
}
}
return true
} else {
creditCard.isHidden = false
purchaseTicketButton.isHidden = false
viewPurchaseButton.isHidden = true
cancelPurchaseButton.isHidden = true
return false
}
}
I used this method to verify if the event has been purchased yet, and if it hasn't show the right buttons.
I then call it in the process of when the purchase button in my UIAlertController is pressed.
self.checkIfUserMadePurchase(shouldBeginQuery: true)
Lastly, I create a function that uses logic to verify is the event has been purchased, and if it has been purchased, do something specific. I then call this function in the viewDidLoad() , viewWillAppear(), and viewWillDisappear().
func purchasedStatusVerification() {
db.collection("student_users/\(user?.uid)/events_bought").whereField("event_name", isEqualTo: self.selectedEventName!).getDocuments { (querySnapshot, error) in
if let querySnapshot = querySnapshot {
if querySnapshot.isEmpty {
self.checkIfUserMadePurchase(shouldBeginQuery: false)
} else {
self.checkIfUserMadePurchase(shouldBeginQuery: true)
}
}
}
}
With all this in place, my app runs how i want to, I can successfully purchase an event and it won't show up in another users account. There are a few bugs like when a new event is created, the wrong and the right buttons are all displayed, but the wrong buttons go away after logging in and out. Also, the isHidden() method moves pretty slow, when i load the vc and the event has a status of purchased, the purchaseTicketButton is there for a split second, then disappears, which is quite annoying. All in all, I figured it out, and will try to improve it near production time.
In your document path "student_users/\(self.user?.uid)/events_bought/\(idOfEventBought)" you use self.user?.
? will produce
student_users/Optional(uid)/events_bought string,
but not
student_users/uid/events_bought string.
Use self.user! or if let user = self.user {
Apologies this is probably a real newbie question. I have some API code which goes away and calls a web service. When it returns I'd like to grab the value and display it in the UI but I'm struggling to see what means allows me to do this. I have this so far in my ViewController.....
'''
#IBAction func getToken(_ sender: NSButton) {
let cli = WSTokenServiceClient()
let rq = SecurityTokenRequest()
rq.UserName = "byname"
rq.Password = "abcdefg"
cli.GetUserWSToken(securityTokenRequest: rq, completionHandler: {(rep, err) in
NSLog(rep?.xmlResponseString ?? "none") //This successfully contains the string value I want
//token.stringValue = "jim" ///This doesn't work, it errors: NSControl.stringValue must be used from main thread only
})
}'''
I'd be grateful for any tips of where to go to solve this.
Thanks
You need
DispatchQueue.main.async {
self.token.stringValue = "jim"
}
I am donating an INInteraction and everything is working ok, however I can't understand why the donation is showing up duplicated in Spotlight.
Is there a property that needs to be set in order to prevent this??
let viewUsageIntent = UsageIntent()
var susbcribers = [INObject]()
for sub in account.subscribers {
let inObject = INObject(identifier: sub.phoneNumber, display: sub.id)
susbcribers.append(inObject)
}
viewUsageIntent.suggestedInvocationPhrase = phrase
viewUsageIntent.ban = account.ban
viewUsageIntent.subs = susbcribers
let interaction = INInteraction(intent: viewUsageIntent, response: nil)
interaction.donate(completion: {
error in
if let err = error {
MyAppServices.Logger.error(tag: "UsageIntentDonation", message: "Donation for ban \(account.ban) could not be completed: \(err.localizedDescription)")
}
})
Anybody dealing with this issue?. Thanks.
This is not a bug. This is the default behaviour when you set your simulator or iPhone to display recent shortcuts in the developer section. I was just confused by it.
Is not a duplication, it is just displaying the newest one over older ones for development sake
I want to stop a the user's Macbook from automatically sleeping while one the app is running. How would I do that? I have seen this
https://developer.apple.com/library/content/qa/qa1340/_index.html
and this:
Disable sleep mode in OS X with swift
and this:
Using Swift to disable sleep/screen saver for OSX
But there is no simple line of code that I can put or a delegate method that makes this simple like there is in iOS? I just want to have the entire app work properly and stop the computer from automatically going to sleep. If someone puts in it to sleep manually, then obviously that is fine. I don't want to prevent the user from putting the computer to sleep. But as long as my app is running I don't want the computer to sleep. Like when you are watching a movie, the app does not go to sleep. How do I do that?
I feel like this is the code that I need but do I just run this function in the viewdidload and it will work? Is it that simple?
var assertionID: IOPMAssertionID = 0
var success: IOReturn?
func disableScreenSleep(reason: String = "Unknown reason") -> Bool? {
guard success != nil else { return nil }
success = IOPMAssertionCreateWithName( kIOPMAssertionTypeNoDisplaySleep as CFString,
IOPMAssertionLevel(kIOPMAssertionLevelOn),
reason as CFString,
&assertionID )
return success == kIOReturnSuccess
}
As I already mentioned in comments you need to remove the guard otherwise it will simply return nil and will never disable the screen sleep. You can also simplify your method, return void and change your success property to a Bool property like disabled to monitor the screen sleep state:
var assertionID: IOPMAssertionID = 0
var sleepDisabled = false
func disableScreenSleep(reason: String = "Disabling Screen Sleep") {
if !sleepDisabled {
sleepDisabled = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep as CFString, IOPMAssertionLevel(kIOPMAssertionLevelOn), reason as CFString, &assertionID) == kIOReturnSuccess
}
}
func enableScreenSleep() {
if sleepDisabled {
IOPMAssertionRelease(assertionID)
sleepDisabled = false
}
}
To disable the screen simply just call the method inside viewDidLoad of your first view controller:
override func viewDidLoad() {
super.viewDidLoad()
disableScreenSleep()
print("sleep Disabled:", sleepDisabled)
}