When I display my app with a keyboard shortcut or by tapping an icon in the status bar I set first responder. makeFirstResponder: always succeeds (returns true) with a not-nil window and a not-nil NSTextField. However it doesn't always 'take the cursor' (i.e. move the flashing | to the NSTextField).
For example,
display app - takes cursor ✓
tap outside app
display app again - does not take cursor (even though makeFirstResponder returns true).
Here's how I'm trying to do this:
//Find the key window. I don't think this is the problem because self.window produces the same results.
var keyWindow:NSWindow = popover.contentViewController!.view.window!
for window in NSApplication.sharedApplication().windows{
if (window.keyWindow){
Swift.print("window \(window) is the key window")
keyWindow = window
}
}
//iFR is a variable that I use to keep track of which NSTextField I want to focus on. It's always a valid textField and most of the time (but not always) it's the only NSTextField in the view.
if (initialFirstResponder != nil)
{
if keyWindow.makeFirstResponder(initialFirstResponder)
{
Swift.print("first responder success")
}else
{
//Never happens:
Swift.print("first responder FAIL")
}
}else
{
Swift.print("no initial firstResponder")
}
I feel like I'm bludgeoning the responder chain into submission but,
NSApp.activateIgnoringOtherApps(true)
causes the cursor to always be captured (and asked nicely) to go where I want it to.
Related
I have a couple of UIKit pop-up menu buttons with identical menu items on the same screen in a Swift app. The buttons are built by calling a function that uses an array of strings to create the list of menu items.
The problem is that depending on the button's vertical position on the screen, the menu items may appear in the order specified by the function, or reversed. If the button is in the upper half of the screen, the menu items are listed in the correct order. If the button is in the lower half of the screen the menu items are listed in reverse order.
I would prefer the menu items to appear in the same order regardless of the button's position on the screen. I could check the button location and have the menu creation function reverse the order, but that seems kind of clunky. I am hoping there's a cleaner way to override this behaviour.
The code and array used to create the button menus:
let buttonMenuItems = ["Spring","Summer","Autumn","Winter"]
func createAttributeMenu(menuNumber: Int)->UIMenu {
var menuActions: [UIAction] = []
for attribute in buttonMenuItems {
let item = UIAction(title: attribute) { action in
self.updateMenu(menuID: menuNumber, selected: attribute)
}
menuActions.append(item)
}
return UIMenu(title: "", children: menuActions)
}
The result is this:
Versions I'm using now in testing: Xcode 14.1, iOS 16.1, but I have seen this behaviour on earlier versions as well. (back to iOS 14.x)
Starting with iOS 16, there is a .preferredMenuElementOrder property that can be set on the button:
case automatic
A constant that allows the system to choose an ordering strategy according to the current context.
case priority
A constant that displays menu elements according to their priority.
case fixed
A constant that displays menu elements in a fixed order.
Best I can tell (as with many Apple definitions), there is no difference between .automatic and .priority.
From the .priority docs page:
Discussion
This ordering strategy displays the first menu element in the UIMenu closest to the location of the user interaction.
So, we get "reversed" order based on the position of the menu relative to the button.
To keep your defined order:
buttonNearTop.menu = createAttributeMenu(menuNumber: 1)
buttonNearBottom.menu = createAttributeMenu(menuNumber: 2)
if #available(iOS 16.0, *) {
buttonNearBottom.preferredMenuElementOrder = .fixed
buttonNearTop.preferredMenuElementOrder = .fixed
} else {
// out of luck... you get Apple's "priority" ordering
}
I have NSTextView which pops up a NSPopOver that contains another NSTextView. In some cases, I'd like to pass the keyboard events (arrow keys, mostly) from the "top" text view to the bottom one.
I have overridden keyDown on the topmost text view and tried various ways of forwarding the keyboard event to the view below.
Just calling bottomTextView.keyDown(with: event) doesn't do anything. The closest I've gotten is using window.sendEvent(event).
override func keyDown(with event: NSEvent) {
if (NSLocationInRange(Int(event.keyCode), NSMakeRange(123, 4)) &&
self.string.count == 0 && event.modifierFlags.contains(.shift)) {
// Pass the event through somehow
return
}
super.keyDown(with: event)
}
Through trial and error, I found one way which actually did pass on the events: calling the methods twice.
// Make the bottom text field first responder
self.window?.makeFirstResponder(self.delegate.bottomTextView)
self.window?.makeFirstResponder(self.delegate.bottomTextView)
// Send the keydown event to window
self.window?.sendEvent(event)
self.window?.sendEvent(event)
However, this causes strange issues, such as the topmost text view getting a one-byte string as its contents.
Is there a correct way to actually forward any key events from a text view to another, or is it even possible?
I can present the Character Picker Palette (emoji) with:
NSApplication.shared.orderFrontCharacterPalette(self)
But I can't find any way to dismiss the Character Picker Palette from code when I'd like to.
I have tried iterating through all open windows, and closing all but the main window like this, but although my print statement reports 2 other windows, it does not close the character picker palette :
let openwindows = NSApplication.shared.windows
for win in openwindows {
if win == self.view.window {
print("Found and ruled out the main window") // successfull
}else{
print(win.className)
// finds two other windows and prints
// _NSCandidateBarCorrectionPanel
// NSCorrectionSubPanel
//
win.close() // Has no effect
// win.orderOut(self) // Has no effect
// win.performClose(nil) // sounds system beep
}
}
I have a UITableView, which I want to put into an editing state if certain conditions are met. The primary way to toggling edit is through an edit button.
So the view elements I have are
let tableView = UITableView()
let editButton = UIButton()
And whether the tableView should be in editing mode is fed from:
let editing = BehaviorSubject(value: false)
Which will be hooked up to the tableView using something like:
editing.subscribeNext { isEditing in
tableView.setEditing(isEditing, animated: true)
}
When the edit button is tapped, I want that to push a new value to editing, that is the negation of the most recent value sent to editing. The most recently value may have been set by a tap on editButton, or it may have come from somewhere else.
I don't understand how to combine the stream for the button press with the stream for editing in such a way that allows this without an infinite loop e.g.
Obervable.combineLatest(editButton.rx_tap.asObservable(), editing) { _, isEditing in
editing.onNext(!isEditing)
}
I'm aware that the tableView has an editing property, but I don't want to rely on that as I am looking for a more general solution that I can re-use elsewhere. I'm also not looking to track the value of isEditing in an instance var, or even as a Variable(), as I am looking for a stateless, stream based solution (if this is at all possible).
Thank you!
With some help from the RxSwift GitHub issues forum I've now worked it out :). The key was withLatestFrom. I've included an example of this below in case it will help anyone else. editButton is the primary way to trigger editing mode on or off, and I've included an event sent via tableView.rx_itemSelected as an additional input example (in this case, I want editing to end any time an item is selected).
let isEditing = BehaviorSubject(value: false)
let tableView = UITableView()
let editButton = UIButton()
tableView.rx_itemSelected
.map { _ in false }
.bindTo(isEditing)
editButton.rx_tap.withLatestFrom(isEditing)
.map { !$0 }
.bindTo(isEditing)
isEditing.subscribeNext { editing in
tableView.setEditing(editing, animated: true)
}
Note: This solution sends .Next(false) to isEditing every time an item is selected, even if the table isn't currently in editing mode. If you feel this is a bad thing, and want to filter rx_itemSelected to only send .Next(false) if the table is actually in editing mode, you could probably do this using a combination of withLatestFrom and filter.
What if you define editing as a Variable instead of a BehaviourSubject. A Variable cannot error out which makes sense in this case. The declaration would look like this:
let editing = Variable(value: false)
You could subscribe to a button tap and change the value of editing to the negated current one:
editButton.rx_tap.asObservable().subscribeNext { editing.value = !editing.value }
With changing the value property of editing this method is called
editing.subscribeNext { isEditing in
tableView.setEditing(isEditing, animated: true)
}
All of this is not tested, but might lead you in the right direction for the right solution.
I'm trying to switch between the UIReturnKeyGo and the UIReturnKeyNext buttons depending on whether or not the user has entered some text in the fields or not, but it's not working the way I was hoping it would. Here's what I'm doing.
I created the following method:
- (void)setReturnKey {
if (self.isLoggingIn) {
if ([self.userField.text length] > 0 ||
[self.passField.text length] > 0) {
self.userField.returnKeyType = UIReturnKeyGo;
self.passField.returnKeyType = UIReturnKeyGo;
} else {
self.userField.returnKeyType = UIReturnKeyNext;
self.passField.returnKeyType = UIReturnKeyNext;
}
}
}
Then I added the above method to the textFieldDidBeginEditing delegate method, which will run anytime a field becomes active, and I created a textFieldDidChange method that is executed anytime text is entered or deleted. Unfortunately this is not working the way I hoped it would. The go button only appears when both fields are filled out and you actively click on another field, it doesn't update when you begin typing. So I can type in my username, then click next and start typing in my password, but the go button doesn't appear when I start entering my password, it will only show if I click the user field again.
Any idea what I'm doing wrong here or if there's a better way to accomplish this?
This is a bad idea. You'll just confuse the users. Keep the User field as Next and the Pass field as Go.