Add gestures and animation in Swift - xcode

I am making an application which is inspired from iOS8 voice messages and trying to add 'gestures and animations(like slide left and cancel the record,slide rightto Upload the voice record.)' but it doesn't work at all.Here is the code snippet below.
// Swipe left and cancel
let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: "swipeLeftCancel")
swipeLeftGesture.direction = UISwipeGestureRecognizerDirection.Left
self.view.addGestureRecognizer(swipeLeftGesture)
// Swipe right and upload
let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: "swipeRightUpload")
swipeRightGesture.direction = UISwipeGestureRecognizerDirection.Right
self.view.addGestureRecognizer(swipeRightGesture)
session.requestRecordPermission({(granted: Bool)-> Void in
if granted {
self.setupRecorder()
} else {
println("Permission to record not granted")
}
})
func swipeLeftCancel(sender: UISwipeGestureRecognizer) {
// slide left and cancel
}
func swipeRightUpload(sender: UISwipeGestureRecognizer) {
// slide right and upload
)
Whole code(before adding UISwipeGestureRecognizer) is in here ー> https://github.com/chansuke/GoForIt
Anyone give some advice?

Your action selectors should be named swipeLeftCancel: and swipeRightUpload::
let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: "swipeLeftCancel:")
The colon at the end is necessary because your functions accept an argument sender. This is because in Objective-C, your method would be declared as - (void)swipeLeftCancel:(id)sender, and its selector would be swipeLeftCancel:. In Swift this makes a lot less sense, but it's just something you have to remember when you use selectors.

// adding Gesture:
let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action:"swipeLeft")
swipeLeftGesture.direction = UISwipeGestureRecognizerDirection.Left
self.view.addGestureRecognizer(swipeLeftGesture)
Where swipeLeft is your selector fired on Gesture action.

Related

Global Drag and Drop Detection on MacOS for Files and Promised Files

I am working on a MacOS app that can process images. The idea is the app hides out of the way most of the time, but if you drag an image from another app or the finder, my app will show and you can drag the image on top of it. Basically a drop zone on standby for when you need it.
Everything is working perfectly except I can't figure out how to only show the app for certain types of draggable items (URLs, fileURLs, FilePromises and Images). What I have now shows the app for any kind of drag, even selecting text on a page or clicking and clicking and dragging through the menu bar.
I've tried looking at the NSPasteboard for dragging, but that doesn't seem to be updated at drag time. I've seen some posts about using accessibility to see what's under the mouse, but that seems brittle and I'm not yet understanding how to do it.
Here is the code I'm using to detect global drag and drop:
dragMonitor = NSEvent.addGlobalMonitorForEvents(matching:.leftMouseDragged) { event in
if !self.isDragging {
self.isDragging = true
if let dropzoneViewController = self.dropzoneViewController, dropzoneViewController.shouldShowForDrag(event: event) {
self.show()
}
}
}
upMonitor = NSEvent.addGlobalMonitorForEvents(matching:.leftMouseUp) { event in
if self.isDragging {
self.hide()
self.isDragging = false
}
}
That function, in turn, calls the following, which applies the app's logic for determining whether to handle a drag or not.
func shouldShowForDrag(event: NSEvent) -> Bool {
return self.dropTarget.canHandleDrop(NSPasteboard(name: .drag))
}
For clarity's sake, here's how the app handles drags once they are over the app's window:
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
isReceivingDrag = false
if let dropTarget = dropTarget, dropTarget.canHandleDrop(draggingInfo.draggingPasteboard) {
dropTarget.handleDrop(draggingInfo.draggingPasteboard)
return true
} else {
return false
}
}
The only difference between those two checks is the global check (shouldShowForDrag(event:)) uses NSPasteboard(name: .drag) which is not current at the time NSEvent.addGlobalMonitorForEvents(matching:) fires. The logic when the draggable enters my window uses the provided pasteboard (draggingInfo.draggingPasteboard) which, of course, is accurate to what's being dragged.
Finally, here's the basic logic for determining what drags to accept:
func canHandleDrop(_ pasteBoard: NSPasteboard) -> Bool {
let urlFilteringOptions = [NSPasteboard.ReadingOptionKey.urlReadingContentsConformToTypes:NSImage.imageTypes]
if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options:urlFilteringOptions) as? [URL], urls.count > 0 {
return true
} else if let filePromises = pasteBoard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) as? [NSFilePromiseReceiver], filePromises.count > 0 {
return true
} else if let images = pasteBoard.readObjects(forClasses: [NSImage.self], options: [:]) as? [NSImage], images.count > 0 {
return true
}
return false
}
The first two clauses are the most important. Detecting NSImages is not strictly required.
I know it can be done because I'm using other apps (to do similar, but different, things), and they work exactly like I'm trying to achieve. But so far I'm banging my head against the wall.
Thanks

How to detect a Pan Gesture inside a NSTouchBarView

Is it possible to detect a finger pan on a NSTouchBarView?
Sorry for the lack of code but I don't even know where to start.
MacOS is not made for finger touches but the TouchBar is but I do not see how to do it on a NSTouchBarView
I don't know specifically about using NSTouchBarView, but using a pan recognizer in a touch bar usually works like this: create a view, then create a NSPanGestureRecognizer (don't forget to set the target and action) then add the recognizer to the previously created view. Finally, create your NSCustomTouchBarItem and assign the previously created view to the item's view. Quick example in Swift:
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
switch identifier {
case NSTouchBarItemIdentifier.yourCustomItem:
return itemWithRecognizer(identifier: identifier)
default:
return nil
}
}
func itemWithRecognizer(identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem {
let customView = NSView()
customView.wantsLayer = true
let recognizer = NSPanGestureRecognizer()
recognizer.target = self
recognizer.action = #selector(doSomething)
customView.addGestureRecognizer(recognizer)
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = customView
return item
}
func doSomething() {
// gesture was activated
}

Prevent UISegmentedControl from sending action method automatically

I'm programming an app for tvOS and am using a UISegmentedControl. My issue is that as I'm scrolling through items, it sends its action method as soon as I remain on an item for a second or two. I would like to disable this behaviour and rather fire the action only if user clicks it.
Does anyone know how to do it?
I had the same problem with my tvOS app and solved with this:
#IBOutlet weak var searchSegmentedControl: UISegmentedControl!
override func viewDidLoad() {
let tap = UITapGestureRecognizer(target: self, action: #selector(didClickSegmentedSearchButton))
tap.numberOfTapsRequired = 1
view.addGestureRecognizer(tap)
}
func didClickSegmentedSearchButton() {
switch searchSegmentedControl.selectedSegmentIndex {
case 0:
// Restaurant
case 1:
// Hotel
case 2:
// Attraction
case 3:
// Shopping
default:
// Unknown
}
}

I have an audio player that starts on my home screen

Whenever I go back to my home screen it replays the music over the already playing music. I tried making an if statements that 'obviously' doesn't work (because I barely know any swift!). Here is my home screen code:
var myAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
let myFilePathString =
NSBundle.mainBundle().pathForResource("16 March of the Resistance", ofType: "m4a")
if let myFilePathString = myFilePathString
{
let myFilePathURL = NSURL(fileURLWithPath: myFilePathString)
do{
try myAudioPlayer = AVAudioPlayer(contentsOfURL: myFilePathURL)
myAudioPlayer.play()
}catch
{
print("error")
}
}
}
How can I stop it from playing on top of itself?
Here is a picture of my storyboard
enter image description here
The music player is on StartScreen, but when I click the back button, it starts another music player over the current one.
I need code which says
is myAudioPlayer playing?
if yes do not play song again
else play "16 March of the resistance"
enter image description here
You need to check if you are already playing. You can do this by the playing property of the AVAudioPlayer class.
Guard your viewDidLoad with say:
if !myAudioPlayer.playing
Note that I don't know any Swift.
I'm not sure what you mean by "my home screen", is it the device or your main VC? Why are you overriding viewDidLoad()? A previous player could never be playing in the viewDidLoad() call, unless for a leak which is what's needing fixing.
Otherwise, you just need to declare your player as an optional, rather than creating it in the declaration. As for the replaying or overlapping, just check if you player exists, although you wouldnt do this in your viewDidLoad(). ViewDidLoad() is called when the view of the VC is created and the view loaded, which doesn't relate to your lifecycle of the audio.
Depending on your requirements, you might want to create the player somewhere else that is plays throughout the app, rather than restarting when visiting that VC. Otherwise the code below will avoid the duplication. The problem was this var myAudioPlayer = AVAudioPlayer() which would duplicate players and one wasn't being released.
var myAudioPlayer: AVAudioPlayer?
func viewDidLoad() {
// this should never be needed with correct player setup
if let player = myAudioPlayer where player.playing {
return
}
if let pathString = NSBundle.mainBundle().pathForResource("16 March of the Resistance", ofType: "m4a")
{
let url = NSURL(fileURLWithPath: pathString)
do{
try myAudioPlayer = AVAudioPlayer(contentsOfURL: url)
myAudioPlayer?.play()
} catch {
print("error")
}
}
}
Update
So there is no need to make the VC do this. Create a Class of e.g. AudioController, which could even be a singleton. Initialise it in your AppDelegate and trigger this player setup code after initialisation. Your view controllers dont need to know about the audio controller. IF e.g. you do want to modify the volume from a setting page, you just access the singleton object and set its volume.
This way you keep all your audio controller code nicely separated. Dont just think of functionality, think of overall software architecture, with objects having clear responsibilities. Keep your ViewControllers light, they shouldnt be doing much processing.
Take a look at this Singletons , its really easy to setup a singleton in Swift nowadays.
class AudioController {
static let sharedInstance = AudioController()
var audioPlayer: AVAudioPlayer?
let kVolumeKey = "VolumeKey"
let kHasSavedInitialVolumeKey = "HasSavedInitialVolumeKey"
var volume: Float = 0.5
func setup() {
self.loadVolume()
self.setupPlayer()
}
func updatePlayerVolume(volume: Float) {
self.audioPlayer?.volume = volume
self.volume = volume
self.saveVolume()
}
func saveVolume() {
NSUserDefaults.standardUserDefaults().setBool(true, forKey: kHasSavedInitialVolumeKey)
NSUserDefaults.standardUserDefaults().setFloat(self.volume, forKey: kVolumeKey)
NSUserDefaults.standardUserDefaults().synchronize()
}
func loadVolume() {
if NSUserDefaults.standardUserDefaults().boolForKey(kHasSavedInitialVolumeKey) {
self.volume = NSUserDefaults.standardUserDefaults().floatForKey(kVolumeKey)
}
}
func setupPlayer() {
if let pathString = NSBundle.mainBundle().pathForResource("16 March of the Resistance", ofType: "m4a")
{
let url = NSURL(fileURLWithPath: pathString)
do{
try audioPlayer = AVAudioPlayer(contentsOfURL: url)
audioPlayer?.volume = self.volume
audioPlayer?.play()
} catch {
print("error")
}
}
}
}
So, you can just setup the player from your AppDelegate's didFinishLaunching
AudioController.sharedInstance.setupPlayer()
And then you can access the AudioController using AudioController.sharedInstance.whatever when you need to get or set. Also, you will need to implement the players delegate methods for handling the end of the song, possibly triggering a new song to play from a playlist(create a new object for this) or whatever. Your player delegate code is also nicely separated now.
Looking at your storyboard it seems that when you click the Back button on the second ViewController you allocate another start screen and you push it on the screen.
It seems that you're using a UINavigationController if so when the back button is tapped you should call:
self.navigationController?.popViewControllerAnimated(true)
If your showing it as a modal you should dismiss is like this:
self.dismissViewControllerAnimated(true, completion: nil)

Add completion handler to presentViewControllerAsSheet(NSViewController)?

I am attempting to present a sheet configuration view (AddSoundEffect) for my main window/view controller (I'm using storyboards), and when the configuration view controller is dismissed, take the values entered in the AddSoundEffect view and pass that back to the main view. My current code in the main view controller:
presentViewControllerAsSheet(self.storyboard!.instantiateControllerWithIdentifier("AddSoundEffect") as! AddSoundViewController
And in the AddSoundViewController.swift file, the code to dismiss it is:
self.dismissViewController(self)
To pass the data, I have a class-independent tuple that I save data to. How do I add a completion handler to presentViewControllerAsSheet, and (optionally) is there a better way to pass the data between view controllers?
Setup: Xcode version 6.4, OS X 10.10.4
Delegation pattern is the easiest way for you.
// Replace this with your tuple or whatever data represents your sound effect
struct SoundEffect {}
protocol AddSoundViewControllerDelegate: class {
func soundViewController(controller: AddSoundViewController, didAddSoundEffect: SoundEffect)
}
//
// Let's say this controller is a modal view controller for adding new sound effects
//
class AddSoundViewController: UIViewController {
weak var delegate: AddSoundViewControllerDelegate?
func done(sender: AnyObject) {
// Dummy sound effect info, replace it with your own data
let soundEffect = SoundEffect()
//
// Call it whenever you would like to inform presenting view controller
// about added sound effect (in case of Done, Add, ... button tapped, do not call it
// when user taps on Cancel to just dismiss AddSoundViewController)
//
self.delegate?.soundViewController(self, didAddSoundEffect: soundEffect)
// Dismiss self
self.dismissViewControllerAnimated(true, completion: {})
}
}
//
// Let's say this controller is main view controller, which contains list of all sound effects,
// with button to add new sound effect via AddSoundViewController
//
class SoundEffectsViewController: UIViewController, AddSoundViewControllerDelegate {
func presentAddSoundEffectController(sender: AnyObject) {
if let addSoundController = self.storyboard?.instantiateViewControllerWithIdentifier("AddSoundEffect") as? AddSoundViewController {
addSoundController.delegate = self
self.presentViewController(addSoundController, animated: true, completion: {})
}
}
func soundViewController(controller: AddSoundViewController, didAddSoundEffect: SoundEffect) {
// This method is called only when new sound effect is added
}
}
Another way is to use closures:
// Replace this with your tuple or whatever data represents your sound effect
struct SoundEffect {}
//
// Let's say this controller is a modal view controller for adding new sound effects
//
class AddSoundViewController: UIViewController {
var completionHandler: ((SoundEffect) -> ())?
func done(sender: AnyObject) {
// Dummy sound effect info, replace it with your own data
let soundEffect = SoundEffect()
//
// Call it whenever you would like to inform presenting view controller
// about added sound effect (in case of Done, Add, ... button tapped, do not call it
// when user taps on Cancel to just dismiss AddSoundViewController)
//
self.completionHandler?(soundEffect)
// Dismiss self
self.dismissViewControllerAnimated(true, completion: {})
}
}
//
// Let's say this controller is main view controller, which contains list of all sound effects,
// with button to add new sound effect via AddSoundViewController
//
class SoundEffectsViewController: UIViewController {
func presentAddSoundEffectController(sender: AnyObject) {
if let addSoundController = self.storyboard?.instantiateViewControllerWithIdentifier("AddSoundEffect") as? AddSoundViewController {
addSoundController.completionHandler = { [weak self] (soundEffect) -> () in
// Called when new sound effect is added
}
self.presentViewController(addSoundController, animated: true, completion: {})
}
}
}
Or many other ways like sending notification, ... Whatever suits your needs. But delegation pattern or closures is the best way to go in this specific case.
I missed that your question is about NSViewController. This example is for iOS, but same pattern can be used on OS X without any issues.
The easiest way to detect sheet opening or closing is to use the Sheet Notifications:
class ViewController: NSViewController, NSWindowDelegate {
override func viewDidLoad(){
NSApplication.sharedApplication().windows.first?.delegate = self
}
func windowDidEndSheet(notification: NSNotification) {
}
func windowWillBeginSheet(notification: NSNotification) {
}
}

Resources