I've a ViewController with a NSButton linked to a IBAction let's say perform(). The button is key equivalent to the return key.
However the return key is also used in for a other temporary action. In an other file no related to previous ViewController, I have a NSTextfield which is editable, so the return key would validate the text changes.
I would like to validate my text without firing perform() function called from the key equivalent button.
For the moment the only solution I found is to send a notification textIsBeingEditing when my text field becomeFirstResponder and an other when the delegate function controlTextDidEndEditing.
Here is the selector of my notification:
#objc func trackNameIsBeingEditedNotification(_ notification: Notification) {
guard let value = notification.userInfo?["trackNameIsBeingEdited"]
as? Bool else {
return
}
if value {
backButton.keyEquivalent = ""
backButton.action = nil
} else {
myButton.keyEquivalent = "\r"
myButton.action = #selector(self.perform(_:))
// Here the `perform()` function is fired but I would avoid this behaviour…
}
}
Isn't any way to cancel the key event in order to prevent perform() to be fired just after I set myButton.action = #selector(self.perform(_:)) ?
I see a function called flushBufferedKeyEvents() but I totally doesn't know how to use it
Don't use notifications, use the delegate method optional func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool to validate the value of the text field.
Related
Here are steps to reproduce:
Activate AVAudioSession with .playback category.
Register for AVAudioSession.interruptionNotification
Create two AVPlayers and start them
Interrupt playback by calling Siri/receiving a call by Skype, Cellular and etc.
Expected behavior:
Receiving notification of the audio session interruption with .began state at the start and .ended at the end. Also, as a side effect, Siri doesn't respond to commands.
Real behavior:
Only .began notification is called.
To bring back .ended notification (which is used to continue playback) remove one player.
Question: how to handle the audio session interruption with more than 1 AVPlayer running?
Here I created a simple demo project: https://github.com/denis-obukhov/AVAudioSessionBug
Tested on iOS 14.4
import UIKit
import AVFoundation
class ViewController: UIViewController {
private let player1: AVPlayer? = {
$0.volume = 0.5
return $0
}(AVPlayer())
private let player2: AVPlayer? = {
$0.volume = 0.5
return $0 // return nil for any player to bring back .ended interruption notification
}(AVPlayer())
override func viewDidLoad() {
super.viewDidLoad()
registerObservers()
startAudioSession()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
player1?.replaceCurrentItem(with: makePlayerItem(named: "music1"))
player2?.replaceCurrentItem(with: makePlayerItem(named: "music2"))
[player1, player2].forEach { $0?.play() }
}
private func makePlayerItem(named name: String) -> AVPlayerItem {
let fileURL = Bundle.main.url(
forResource: name,
withExtension: "mp3"
)!
return AVPlayerItem(url: fileURL)
}
private func registerObservers() {
NotificationCenter.default.addObserver(
self, selector: #selector(handleInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil
)
}
private func startAudioSession() {
try? AVAudioSession.sharedInstance().setCategory(.playback)
try? AVAudioSession.sharedInstance().setActive(true)
}
#objc private func handleInterruption(_ notification: Notification) {
print("GOT INTERRUPTION")
guard
let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
else {
return
}
switch type {
case .began:
print("Interruption BEGAN")
[player1, player2].forEach { $0?.pause() }
case .ended:
// This part isn't called if more than 1 player is playing
print("Interruption ENDED")
[player1, player2].forEach { $0?.play() }
#unknown default:
print("Unknown value")
}
}
}
I just ran into the same issue, and it was driving me crazy for a few days. I'm using two AVQueuePlayer (a subclass of AVPlayer) to play two sets of audio sounds on top of each other, and I get the AVAudioSession.interruptionNotification value of .began when there is an incoming call, but there is no .ended notification when the call ends.
That said, I've found that for some reason, .ended is reliably sent if you instead use two instances of AVAudioPlayer. It also works with one instance of AVAudioPlayer mixed with another instance of AVQueuePlayer. But for some reason using two instances of AVQueuePlayer (or AVPlayer) seems to break it.
Did you ever find a solution for this? For my purposes I need queuing of tracks so I must use AVQueuePlayer, so I'll probably file a bug report with Apple.
Working on apple watch notification integration ::- i m not able to get action event of button click in push notification.
//IOS code
1.make function in AppDelegate file for integration custom push notification.
func registerSettingsAndCategories() {
let categories = NSMutableSet()
let acceptAction = UIMutableUserNotificationAction()
acceptAction.title = "View"
acceptAction.identifier = "view"
acceptAction.activationMode = UIUserNotificationActivationMode.Background
acceptAction.authenticationRequired = false
let inviteCategory = UIMutableUserNotificationCategory()
inviteCategory.setActions([acceptAction],forContext: UIUserNotificationActionContext.Default)
inviteCategory.identifier = "myCategory"
categories.addObject(inviteCategory)
// Configure other actions and categories and add them to the set...
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound],categories: (NSSet(array: [inviteCategory])) as? Set<UIUserNotificationCategory>)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
}
2.in AppDelegate file delegate method for handle notification:
func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [NSObject : AnyObject], completionHandler: () -> Void) {
print("handleActionWithIdentifier Appdel : \(userInfo) \(identifier)")
print(userInfo)
completionHandler()
}
3.In NotificationController file handle notification method in Watchkit Extension target :
override func handleActionWithIdentifier(identifier: String?, forRemoteNotification remoteNotification: [NSObject : AnyObject]) {
print("handleActionWithIdentifier \(remoteNotification)")
if identifier == "myCategory"{
}
}
i know that if i will click on "view" button then handleActionWithIdentifier method should be called but in my case handleActionWithIdentifier method is not called.i m stucked in this issue for last 2 days.
so anyone can help me for resolving this issue?
I'm subclassing NSButtonCell to customize the drawing (customizable theme). I'd like to customize the way checkboxes and radio buttons are drawn.
Does anyone know how to detect whether a button is a checkbox or radio button?
There is only -setButtonType:, no getter, and neither -showsStateBy nor -highlightsBy seem to give any unique return values for checkboxes that don't also apply to regular push buttons with images and alternate images.
So far I've found two (not very pretty) workarounds, but they're the kind of thing that'd probably get the app rejected from MAS:
Use [self valueForKey: #"buttonType"]. This works, but since the method is not in the headers, I presume this is something Apple wouldn't want me to do.
Override -setButtonType: and -initWithCoder: to keep track of the button type when it is set manually or from the XIB. Trouble here is the XIB case, because the keys used to save the button type to disk are undocumented. So again, I'd be using private API.
I'd really like this to be a straight drop-in replacement for NSButtonCell instead of forcing client code to use a separate ULIThemeSwitchButtonCell class for checkboxes and a third one for radio buttons.
A button does not know anything about its style.
From the documentation on NSButton
Note that there is no -buttonType method. The set method sets various button properties that together establish the behavior of the type. -
You could use tag: and setTag: (inherited by NSButton from NSControl) in order to mark the button either as a checkbox or a radio button. If you do that programatically then you should define the constant you use. You can also set the tag in Interface Builder, but only as an integer value (magic number).
In initWithCoder, here is my adaptation of the BGHUDButtonCell.m solution, updated for Mac OS Sierra:
-(id)initWithCoder:(NSCoder *)aDecoder {
if ( !(self = [super initWithCoder: aDecoder]) ) return nil;
NSImage *normalImage = [aDecoder decodeObjectForKey:#"NSNormalImage"];
if ( [normalImage isKindOfClass:[NSImage class]] )
{
DLog( #"buttonname %#", [normalImage name] );
if ( [[normalImage name] isEqualToString:#"NSSwitch"] )
bgButtonType = kBGButtonTypeSwitch;
else if ( [[normalImage name] isEqualToString:#"NSRadioButton"] )
bgButtonType = kBGButtonTypeRadio;
}
else
{
// Mac OS Sierra update (description has word "checkbox")
NSImage *img = [self image];
if ( img && [[img description] rangeOfString:#"checkbox"].length )
{
bgButtonType = kBGButtonTypeSwitch;
}
}
}
This is strange to me that it's missing from NSButton. I don't get it. That said, it's easy enough to extend NSButton to store the last set value:
import Cocoa
public class TypedButton: NSButton {
private var _buttonType: NSButton.ButtonType = .momentaryLight
public var buttonType: NSButton.ButtonType {
return _buttonType
}
override public func setButtonType(_ type: NSButton.ButtonType) {
super.setButtonType(type)
_buttonType = type
}
}
Swift 5.5
This is my approach. I use a standard naming convention in my app that relies on plain language identifiers. All my UI elements incorporate their respective property names and what type of UI element is associated with the property. It can make for some pretty long IBOutlet and IBAction names, but remembering tag numbers is way too complicated for me.
For example:
#IBOutlet weak var serveBeerCheckbox: NSButton!
#IBOutlet weak var headSize0RadioButton: NSButton!
#IBOutlet weak var headSize1RadioButton: NSButton!
#IBOutlet weak var headSize2RadioButton: NSButton!
\\ etc.
If there are UI properties that need to be stored, I name those without the type of UI element:
var serveBeer: Bool = true
var headSize: Int = 1
Bare bones example:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var serveBeerCheckbox: NSButton!
#IBOutlet weak var headSize0RadioButton: NSButton!
#IBOutlet weak var headSize1RadioButton: NSButton!
#IBOutlet weak var headSize2RadioButton: NSButton!
var serveBeer: Bool = true
var headSize: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func buttonClicked(button: NSButton) {
guard let identifier = button.identifier else { return }
if identifier.rawValue.contains("Checkbox") {
switch button.identifier {
case serveBeerCheckbox.identifier:
// Do something with the Checkbox
serveBeer = (serveBeerCheckbox?.state == .on)
default:
// Another checkbox button
}
} else if identifier.rawValue.contains("RadioButton") {
switch button.identifier {
case headSize0RadioButton.identifier:
headSize = 0
case headSize1RadioButton.identifier:
headSize = 1
case headSize2RadioButton.identifier:
headSize = 2
default:
}
} // You could continue checking for different types of buttons
print("Serve beer? \(serveBeer ? "Sure!" : "Sorry, no.")")
if serveBeer {
switch headSize {
case 1:
print("With one inch of head.")
case 2:
print("With two inches of head!")
default:
print("Sorry, no head with your beer.")
}
}
}
}
As you can see, one could write a very generic method that can work on any type of UI element and use the rawValue of the identifier string with .contains() to isolate the type of element being worked with.
I have found using this approach allows me to initialize a UI with a lot of different elements pretty quickly and efficiently without having to recall tag numbers.
I'm having a problem finding out which NSTextfield is focused.
I am building a multi-language form and have several NSTextfields for data entry. I have to change the text input source for some of the NSTextfields during data entry, and I need it to happen automatically.
For now, I can change the text input source as I mentioned here without problem.
The problem that I have is to change the input source right when the NSTextfield becomes focused. If I use the controlTextDidBeginEditing: delegate method it changes the source input after typing the first letter.
This means that I lose the first word I typed in proper language.
Is there any delegate to find it ?
You can subclass your NSTextField and override - (BOOL)becomeFirstResponder (NSResponder) to respond to this kind of event.
You can also try control:textShouldBeginEditing: instead.
You will need to subclass NSTextField
Swift 3+
class FocusingTextField : NSTextField {
var isFocused : Bool = false
override func becomeFirstResponder() -> Bool {
let orig = super.becomeFirstResponder()
if(orig) { self.isFocused = true }
return orig
}
override func textDidEndEditing(_ notification: Notification) {
super.textDidEndEditing(notification)
self.isFocused = false
}
override func selectText(_ sender: Any?) {
super.selectText(sender)
self.isFocused = true
}
}
self.view.window?.firstResponder inside your view controller would give a NSTextView
I have the code all done for my keydown actions, but i dont know what to do with the first responder that every site i go to seems to skim over. Can anyone tell me how to set it up to recognise keydown actions in cocoa objectivec?
Thanks
First, keyDown: is an event message, not an action message. Note that its argument is an NSEvent, not a UI object of some sort (such as an NSControl or NSMenuItem).
Action messages go down the responder chain, in which case the “first responder” is not special. Each responder will hand any action message it doesn't know how to handle off to its next responder. This is the “responder chain”. The first responder is simply whatever responder is at the head of the responder chain—i.e., is first. You would simply need to be in that chain, behind anything that doesn't know how to respond to the action being passed down it.
But since this is an event message, things are different. You need to be the key view, which is the first responder.
And that's all there is to it. You need to respond to the keyDown: message (and possibly related ones) in a view, and that view needs to be the first responder to receive the message.
The NSResponder class reference and Cocoa Event-Handling Guide will tell you more.
Here is what I've done and it works well. (Swift 3 for macOS Sierra)
override func viewDidLoad() {
keyIsDown = false // variable defined in the NSViewController
NSEvent.addLocalMonitorForEvents(matching: .keyUp) { (aEvent) -> NSEvent? in
self.keyUp(with: aEvent)
return aEvent
}
NSEvent.addLocalMonitorForEvents(matching: .keyDown) { (aEvent) -> NSEvent? in
self.keyDown(with: aEvent)
return aEvent
}
}
Now I also override these:
override var acceptsFirstResponder: Bool { return true }
override func becomeFirstResponder() -> Bool { return true }
override func resignFirstResponder() -> Bool { return true }
And these:
override func keyUp(with event: NSEvent) {
keyIsDown = false
if event.keyCode == 1 {
print("s key released")
}
}
override func keyDown(with event: NSEvent) {
if keyIsDown == true {
return
}
keyIsDown = true
// Whatever you'd like to do (check to see which key released, etc.)
}
That should get you started.