Using Xcode 8, Swift 3 and Interface Builder with Storyboards and a very basic view controller featuring only a button (Play) and a slider (volume).
I'm trying to use my NSSlider while pressing a button at the same time. For instance, I need to be able to keep pressing "Play" while slowly lowering the volume. What happens instead is that only one button can be in use at a time. So, as long as I am pressing "Play" using the return key, I can't adjust the volume and vice versa.
In this example, the Play button is triggered by the "return" key.
Here is the code in ViewController.swift. This is just an example app I created quickly to study the problem.
Is there an Interface Builder setting on the NSSLider that I am missing, or can I do something in my code to achieve what I am trying to do?
import Cocoa
import AVFoundation
class ViewController: NSViewController {
var audioPlayer = AVAudioPlayer()
var sliderValue: Float = 0
#IBOutlet weak var volumeSliderOutlet: NSSlider!
override func viewDidLoad() {
super.viewDidLoad()
do{
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "bell ding", ofType: "wav")!))
}
catch {
print(error)
}
audioPlayer.prepareToPlay()
// Do any additional setup after loading the view.
}
#IBAction func playButtonAction(_ sender: NSButton) {
if audioPlayer.isPlaying {
audioPlayer.currentTime = 0.0
audioPlayer.play()
} else {
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
#IBAction func volumeSliderAction(_ sender: NSSlider) {
if audioPlayer.isPlaying {
sliderValue = self.volumeSliderOutlet.floatValue
audioPlayer.volume = sliderValue
} else {
return
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
I've also tried this, based on advice from #inspector_60, but it doesn't work either. Still can't handle the slider while continuing to press the shortcut key for the button "return".
override func keyDown(with event: NSEvent) {
if (event.keyCode == 36) {
self.playButtonAction(nil)
}
else {
return
}
}
func playButtonAction(_ sender: NSButton?) {
if audioPlayer.isPlaying {
audioPlayer.currentTime = 0.0
audioPlayer.play()
} else {
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
IBAction are UI events so not suitable for multiple simultaneous clicks (you have only one mouse pointer). Use handling key events in order to handle keyboard events for simultaneous actions.
You need to also implement the mouse events, these links should help:
Apple document about mouse events and specifically Filtering Out Key Events During Mouse-Tracking Operations
This stack overflow answer on how to implement this approach
This post that will give you more information and example - Ron's answer links to a question not that different from yours
Related
I have users complaining that there is no sound in my application. I found that this is usually because the silent switch is turned on, and with the handy reply tool I can usually tell them this... however this seem to be an ongoing issue and I need to fix this. I found a way to disable the silent switch with the following code.
import UIKit
import AVKit
import AVFoundation
class InitialViewController: UIViewController {
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
//print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
//print("AVAudioSession is Active")
} catch _ as NSError {
//print(error.localizedDescription)
}
} catch _ as NSError {
//print(error.localizedDescription)
}
}
}
Fortunately, this code works, but I want users to still have control over whether this code is enabled or not. By default, I want it enabled and let the users toggle this feature on or off. For my other view controller I have this:
import UIKit
import AVFoundation
class CustomTableView: UITableViewController {
let defaults = UserDefaults.standard
let OverrideSwitch2 = "OverrideSwitch2"
#IBOutlet weak var OverrideSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let fixFin = defaults.value(forKey: OverrideSwitch2) {
OverrideSwitch.isOn = fixFin as! Bool
}
}
#IBAction func `switch`(_ sender: UISwitch) {
defaults.set(sender.isOn, forKey: "OverrideSwitch2")
if (sender.isOn == true){
//Code to switch feature on
} else {
//Code to switch feature off
}
}
}
I have set up an NSUserDefault on the switch so the project remembers the preference, and since it's a simple boolean I could just paste the code in both switch states with one being 'false.' However, this switch is on another view controller and I need my code to be on the initial view controller, otherwise the code doesn't work until the user goes into preferences to change it. I need some help on setting up a switch that can toggle this code on/off and for the UserDefault to remember it.
So I can't believe I figured it out, but I just created another UserDefault that would set a boolean. If true, the code for the AVAudioSession gets run. If false, nothing gets executed. This UserDefault gets toggled through the switch, which also has a UserDefault to remember the switch state.
The Code is as follows:
import UIKit
import CoreGraphics
import AVFoundation
class CustomTableView: UITableViewController {
//This is for the NSUserDefaults
let defaults = UserDefaults.standard
let OverrideSwitch2 = "OverrideSwitch2"
//This Outlet connects the Switch
#IBOutlet weak var OverrideSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
//These if statements only run if one is true or false
//True: AVAudioSession is initialized; Silent Switch is Overridden
if UserDefaults.standard.bool(forKey: "Key") == true {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
//print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession")
} catch _ as NSError {
//print(error.localizedDescription)
}
} catch _ as NSError {
//print(error.localizedDescription)
}
}
//False: No Code is executed, silent switch works as normal
if UserDefaults.standard.bool(forKey: "Key") == false {
//No Code Here
}
//This NSUserDefault remembers if the switch is on or off
if let switchState = defaults.value(forKey: OverrideSwitch2) {
OverrideSwitch.isOn = switchState as! Bool
}
}
//This IBAction is connected to the switch; It toggles the UserDefault which your app will remember next time you run it
#IBAction func switchIsChanged(_ OverrideSwitch: UISwitch) {
defaults.set(OverrideSwitch.isOn, forKey: "OverrideSwitch2")
if OverrideSwitch.isOn == true {
UserDefaults.standard.set(true, forKey: "Key")
} else {
UserDefaults.standard.set(false, forKey: "Key")
}
}
}
Simply put, if you toggle the switch 'On' the NSUserDefault runs a function to initialize the session which will only run if the bool is 'true.' If it's false then nothing is run. All I need now is alert telling the user that enabling or disabling the override will only work once they restart the app.
I am new to Swift and Xcode, but I have been reading on here and watching videos on YouTube to guide me along with starting my app. I can't seem to get my button to save its state once the app is closed and re-opened. I used UserDefault To Save Button State as an example, but following it still did not get the button state saved.
I set the state with the Interface Builder and so far have the below code:
#IBAction func ownedButton(_ sender UIButton) {
sender.isSelected = !sender.isSelected
UserDefaults.standard.set(sender.isSelected, forKey: "isSaved")
UserDefaults.standard.synchronize()
}
Clicking the button will keep it selected until clicked again, so it is partially working. It looks like I need some code to in the viewDidLoad section, but I haven't been able to figure out what it should be.
Thank you for any help!
A habit from my Objective-C days is to write a wrapper around the properties in UserDefaults. This way, everything is compile-time checked and the use of strings as keys is minimized:
// Properties.swift
import Foundation
fileprivate var standardDefaults = UserDefaults.standard
class Properties {
static func registerDefaults() {
standardDefaults.register(defaults: [
kIsButton1Selected: false,
kIsButton2Selected: true
])
}
fileprivate static let kIsButton1Selected = "isButton1Selected"
static var isButton1Selected: Bool {
get { return standardDefaults.value(forKey: kIsButton1Selected) as! Bool }
set { standardDefaults.set(newValue, forKey: kIsButton1Selected) }
}
fileprivate static let kIsButton2Selected = "isButton2Selected"
static var isButton2Selected: Bool {
get { return standardDefaults.value(forKey: kIsButton2Selected) as! Bool }
set { standardDefaults.set(newValue, forKey: kIsButton2Selected) }
}
}
Then in your View Controller:
override func viewDidLoad() {
// Always call registerDefaults before you use UserDefaults
// for the first time in your app
Properties.registerDefaults()
button1.isSelected = Properties.isButton1Selected
button2.isSelected = Properties.isButton2Selected
}
#IBAction func ownedButton(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
switch sender {
case button1:
Properties.isButton1Selected = sender.isSelected
case button2:
Properties.isButton2Selected = sender.isSelected
default:
break
}
}
You can replace the switch with key-value observing but remember to remove the KVO on deinit.
I want to detect keyboard input in my NSViewController.
The idea is to have several actions performed if the user presses certain keys followed by ENTER/RETURN.
I have checked if keyDown would be a appropriate way. But I would receive an event any time the user has pressed a key.
I also have though on using an NSTextField, set it to hidden and let it have the focus.
But maybe there are other better solution.
Any ideas?
Thanks
I've finally got a solution that I like.
First it has nothing todo with any hidden UI Elements but rather let the viewcontroller detect keyboard input.
var monitor: Any?
var text = ""
override func viewDidLoad() {
super.viewDidLoad()
self.monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: myKeyDownEvent)
}
override func viewWillDisappear() {
//Clean up in case your ViewController can be closed an reopened
if let monitor = self.monitor {
NSEvent.removeMonitor(monitor)
}
}
// Detect each keyboard event
func myKeyDownEvent(event: NSEvent) -> NSEvent {
// keyCode 36 is for detect RETURN/ENTER
if event.keyCode == 36 {
print(text)
text = ""
} else {
text.append( event.characters! )
}
return event
}
This might be an easy question, but I am new to Swift and do not know for which terms to google for.
For a menu bar application, I have implemented a custom function called doSomething that works just fine when it is bound to some button:
Class MainViewController:
{
#IBAction func doSomething(sender: NSButton)
{
// Do something when NSButton is pressed
}
}
However, I need to distinguish between left- and right click on the button, which in my case is a NSStatusBarButton. Following the suggestion from this answer, I have written the following into my AppDelegate.swift:
#NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-2)
var mainViewController: MainViewController?
func applicationDidFinishLaunching(notification: NSNotification)
{
if let button = statusItem.button
{
button.action = #selector(customClickAction)
button.sendActionOn(Int(NSEventMask.RightMouseDownMask.rawValue | NSEventMask.LeftMouseDownMask.rawValue))
}
}
func customClickAction(sender: NSButton)
{
let event:NSEvent! = NSApp.currentEvent!
if (event.type == NSEventType.RightMouseDown)
{
print("Right mouse button down")
}
else if (event.type == NSEventType.LeftMouseDown)
{
print("Left mouse button down")
mainViewController?.doSomething(_:) // THIS DOES NOT WORK
}
}
}
The above code snippet gives me the error message 'Expression resolves to an unused function' in XCode. I cannot figure out how to properly call the function doSomething from the MainViewController class within the customClickAction function, or equivalently, how to redirect the action of the statusItem.button via customClickAction to doSomething. I apologize if this question might seem too trivial for the Swift experts, but I am really in despair trying to figure this one out.
EDIT:
If the function customClickAction was not existing, I would simply write button.action = #selector(mainViewController?.show(_:)) in applicationDidFinishLaunching to call the function and everything works. However, part of my problem is that doing the same in my custom function would overwrite the binding once the left mouse button has been pressed for the first time.
Here is a question from someone who had the same Expression resolves to an unused function problem. In his/her case the problem was that the functions was called without the () after the function, so stop instead of stop() (if that makes sense).
OK, so now that we know what the compiler is trying to tell us, we can try to figure out what to do to solve it.
In your case the problem is that you need to send a sender parameter to your doSomething method, and that parameter must be of type NSButton as you've declared your self.
#IBAction func doSomething(sender: NSButton)
{
// Do something when NSButton is pressed
}
So, in your case, if you just pass the sender which you get as a parameter to your customClickAction along like so:
func customClickAction(sender: NSButton)
{
let event:NSEvent! = NSApp.currentEvent!
if (event.type == NSEventType.RightMouseDown)
{
print("Right mouse button down")
}
else if (event.type == NSEventType.LeftMouseDown)
{
print("Left mouse button down")
mainViewController?.doSomething(sender)
}
}
Then the compiler seems happy.
Here is the entire AppDelegate
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-2)
var mainViewController: MainViewController? = MainViewController() //initialized
func applicationDidFinishLaunching(notification: NSNotification)
{
if let button = statusItem.button
{
button.action = #selector(customClickAction)
button.sendActionOn(Int(NSEventMask.RightMouseDownMask.rawValue | NSEventMask.LeftMouseDownMask.rawValue))
}
}
func customClickAction(sender: NSButton)
{
let event:NSEvent! = NSApp.currentEvent!
if (event.type == NSEventType.RightMouseDown)
{
print("Right mouse button down")
}
else if (event.type == NSEventType.LeftMouseDown)
{
print("Left mouse button down")
mainViewController?.doSomething(sender)
}
}
}
Hope that helps you.
Followup to your edit
You write
If the function customClickAction was not existing, I would simply write button.action = #selector(mainViewController?.show(_:)) in applicationDidFinishLaunching to call the function and everything works.
Its possible that I'm misunderstanding everything here, but why don't you then "just" assign the doSomething method to your button.action and then moves the check for left or right button to that method?
So you'd say:
button.action = #selector(mainViewController?.doSomething(_:))
and your doSomething would look like this:
#IBAction func doSomething(sender: NSButton)
{
// Do something when NSButton is pressed
let event:NSEvent! = NSApp.currentEvent!
if (event.type == NSEventType.RightMouseDown)
{
print("Right mouse button down")
}
else if (event.type == NSEventType.LeftMouseDown)
{
print("Left mouse button down")
}
}
"Left mouse button down" and "Right mouse button down" shows up in my console if I do that.
Swift 3 update
As #ixany writes in the comments:
Is it me or Swift 3? :) Seems that your solution needs to be updated to Swift 3 since I'm not able to adapt it using the current Xcode Beta
I've tried to upgrade the syntax to Swift 3.0 but there seems to be some problems with it.
The first problem is that I could not go directly to the doSomething method of MainViewController when assigning the selector, so I added a "local" step so to speak.
The AppDelegate now looks like this:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
let statusItem = NSStatusBar.system().statusItem(withLength: -2)
var mainViewController: MainViewController? = MainViewController()
func applicationDidFinishLaunching(_ aNotification: Notification) {
if let button = statusItem.button
{
button.action = #selector(AppDelegate.localButtonAction(sender:))
button.sendAction(on: [NSEventMask.leftMouseDown, NSEventMask.rightMouseDown])
}
}
func localButtonAction(sender: NSStatusBarButton) {
mainViewController?.doSomething(sender: sender)
}
}
(Notice the ´localButtonAction´ method, I hope someone else has a prettier way to do this)
And the MainViewController looks like this:
import Cocoa
class MainViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
#IBAction func doSomething(sender: NSStatusBarButton) {
// Do something when NSButton is pressed
let event:NSEvent! = NSApp.currentEvent!
if (event.type == NSEventType.rightMouseDown)
{
print("Right mouse button down")
}
else if (event.type == NSEventType.leftMouseDown)
{
print("Left mouse button down")
}
}
}
The problem with this is that the event is never of type rightMouseDown as far as I can see. I'm probably doing something wrong and I hope that someone else can make this work, but now it compiles at least and is Swift 3.0 syntax (using Xcode 8.0 - beta 6 :))
Hope that helps you #ixany
I have implemented text-to-speech in my app and it works fine with the code I currently use. Basically an algo creates a text, and then if the user clicks on the UIButton the text is being spoken.
Challenge: I want to enable the same UIButton to pause the synthesizer, if the button has already been tapped (i.e. text is currently being spoken) and then resume speaking where it left off, if the button is being tapped again.
I know there are a few functions in the AVFoundation Reference but I am unable to implement them correctly.
Does anyone know how to do this in Swift?
import UIKit
import AVFoundation
#IBOutlet var generatedText: UILabel!
#IBAction func buttonSpeakClicked(sender: UIButton){
var mySpeechSynthesizer:AVSpeechSynthesizer = AVSpeechSynthesizer()
var mySpeechUtterance:AVSpeechUtterance = AVSpeechUtterance(string:generatedText.text)
mySpeechUtterance.rate = 0.075
mySpeechSynthesizer .speakUtterance(mySpeechUtterance)
}
AVSpeechSynthesizer Class Reference
Have you tried these methods?
- pauseSpeakingAtBoundary: and - continueSpeaking
And these are some properties, (paused and speaking ) that can help you determine the state of the synthesizer.
Code like this should work: mySpeechSynthesizer.pauseSpeakingAtBoundary(AVSpeechBoundary.Immediate)
In Main.storyboard, make two UIElements:
a UITextView from which the text will be read.
a UIButton to play, pause, and resume reading the text.
Create an outlet from the UITextView to the #IBOutlet and an action from the UIButton to the #IBAction in the code below. The following code is a working example, and should be your ViewController.swift:
import UIKit
import AVFoundation
class ViewController: UIViewController {
// Synth object
let synth = AVSpeechSynthesizer()
// Utterance object
var theUtterance = AVSpeechUtterance(string: "")
// Text element that the synth will read from.
#IBOutlet weak var textView: UITextView!
// Function that starts reading, pauses reading, and resumes
// reading when the UIButton is pressed.
#IBAction func textToSpeech(_ sender: UIButton) {
// The resume functionality
if (synth.isPaused) {
synth.continueSpeaking();
}
// The pause functionality
else if (synth.isSpeaking) {
synth.pauseSpeaking(at: AVSpeechBoundary.immediate)
}
// The start functionality
else if (!synth.isSpeaking) {
// Getting text to read from the UITextView (textView).
theUtterance = AVSpeechUtterance(string: textView.text)
theUtterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
theUtterance.rate = 0.5
synth.speak(theUtterance)
}
}
// Standard function
override func viewDidLoad() {
super.viewDidLoad()
}
// Standard function
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Here how you can achieve this
First create a class variable of AVSpeechSynthesizer and initialize it
let synth = AVSpeechSynthesizer()
This is the button click method ( for playing the audio, or pausing it if it is already playing
#IBAction func onAVButtonClicked(_ sender: Any) {
if synth.isSpeaking {
// when synth is already speaking or is in paused state
if synth.isPaused {
synth.continueSpeaking()
}else {
synth.pauseSpeaking(at: AVSpeechBoundary.immediate)
}
}else{
// when synth is not started yet
let string = attrStr.string
let utterance = AVSpeechUtterance(string: string)
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
synth.speak(utterance)
}
}