How to display a modal window during a drag operation? - swift

In the performDragOperation(sender: NSDraggingInfo) method of the receiver of my drag operation, I want to display a modal window that requires user input. The URL of a file that is dropped will populate one field in the modal window, and the user will have to populate another NSTextField.
The problem is that the modal window does not respond to keyboard events during the drag operation, which effectively causes a deadlock until the drag times out. I cannot find a reliable way to deal with this. I have two possible workarounds, neither solves this completely:
Use dispatch_after() to tell the main thread to show the modal window 0.25 sec after the drag operation completes. This works, but is dependent on a race condition.
Instead of a modal window, simply show a regular window. Or use beginModalSession(). Less desirable than a modal window in this case.
Code:
override func performDragOperation(sender: NSDraggingInfo) -> Bool {
// Do some stuff here to get data from the sender
// Load a view controller and display it modally
let vc = MyViewController(nibName: "MyViewController", bundle: nil) {
self.presentViewControllerAsModalWindow(vc)
}
return true
}
I've tried using dispatch_async(dispatch_get_main_queue()) {} to present the view controller. I thought that would allow performDragOperation to return true, but that does not appear to be sufficient to end the drag operation completely.
I've also tried showing the view controller from concludeDragOperation(), but that does not help either.
What is the best practice to deal with this situation?

Related

Segue from SKScene and back again cause game to "freeze"

I'm developing a SpriteKit game with two different scenes (MainMenu.sks and Settings.sks). I also have a UIViewController.
From the MainMenu the user can tap "Settings" which causes the app to load the Settings.sks scene and present it.
It's done using this code:
// Move to Settings.sks
let transition = SKTransition.flipVertical(withDuration: 1.0)
let loadScene = SettingsScene(fileNamed: "SettingsScene")
loadScene?.scaleMode = self.scaleMode
self.view?.presentScene(loadScene!, transition: transition)
From the Settings.sks scene the user can tap on the SKLabelNode "Change username" which causes the app to load the UIViewController by performing a segue.
The UIViewController contains a UITextField and a UIButton. The UIButton takes the user back to MainMenu.sks.
However, when I'm doing so the it's like a new scene of MainMenu is placed upon the other causing the app to sort of "freeze" and not responding to any touch gestures. I can see from the nodeCount that the nodes are doubled.
A workaround is loading the UIViewController directly from the MainMenu.sks (also via a segue). By doing so, the app works just fine.
I'm suspecting the problem to be the way I load Settings.sks.
What am I missing? Maybe unloading the entire SKView before exiting?
Performing a segue will present a new view on top of the stack. By using a segue to return to the main menu, the app will create a new main menu on top of the UIViewController. I can't say for certain why your app freezes. But I would guess that there are some properties or functions that don't work properly when a new main menu is created and presented from your UIViewController.
I recommend unwinding the segue used to reach the UIViewController instead. This way you can return to the previous view and conserve memory.
You'll need to write an IBAction in your destination view, the main menu in this case:
#IBAction func unwindToMainMenu(segue: UIStoryboardSegue) {
}
Then ctrl-drag from your button in the UIViewController to the exit icon in storyboard. You should see an option to use the IBAction unwindToMainMenu that you just wrote. This will create an unwind segue.
A complete guide to unwind segues and multiple examples can be found in this answer: What are Unwind segues for and how do you use them?

How to detect a click from an NSToolbarItem inside an NSWindowController?

I have a toolbar on my macOS app, developed in Swift. The toolbarItem is dragable onto the NSWindowController, and I can setup an IABAction function, I just have a print in the function at the moment. And when I click on the button nothing happen the click does not seem to be recognised as an action ?
I had a few more line of code in the function but deleted it and now have just the print("test") line.
#IBAction func exportCsvClicked(_ sender: NSToolbarItem) {
print("test") }
No output observed, so I'd love to get "test" in the console when I click on this button.
Here is a list of the connections associated with the toolbarItem.
I found a way to get around the fact that the IBAction from an NSToolbarItem does not recognise the click on this item:
1/I made a customSegue from the item to the main window controller (it can go anywhere)
2/The prepare for segue function posts a notification to the notification saying that the item has been clicked.
3/The main view controller observes the notification and presents, either has a popup or a sheet (I got two buttons), the view that I have setup in the storyboard (referencing with the storyboardID). I found that you need to pass on all the necessary variable to setup the view from the main view Controller, and that there was issue of code in the viewDidLoad function of the sheet/popup view not running, I am suspecting that they might be different instances.

How to segue to previous screen using Swift without losing data in swift 4

I have a button from my create account page that opens up another view controller that has some information in it.
i move it by doing a segue:
self.performSegue(withIdentifier: "sugueToRules", sender: self)
on my other Viewcontroller i have a back button that connects form the storyboard, to the create account one (the precious one)
when i go back i lose all my data that i put on the create account,
so i must open a new instance of it.
how do i keep all my data of it when i go back ?
do i have to save a local file somewhere and load it when i come back ?
i'm using swift 4
Make sure that they are managed by a UINavigationController. Then the back button will do its job just fine (no need for you to 'wire' something -- this is the default behaviour). Right now it appears that you are allocating a new controller each time you go back.
The same applies to a modally presented controller (with a custom close button for example), although you have to dismiss (or use an exit segue) instead of firing up a segue pointing back.
Passing segue to previous screen will creates a new instance of the previous viewcontroller. It it better to be bound in navigation controller and popout the presented screen using when back navigation is not required.
self.navigationController?.popViewController(animated: true)
self.dismiss(animated: true, completion: nil)
you need to use UINavigationController for navigating UIViewController like -

Update text field values in multiple tabs with single button

I have 6 tabs in my application.
Each tab has a text field.
There is one button in the sixth tab, which when pressed, should reset the text fields in all the other 5 tabs to blank.
I am unable to figure out any direction in which I should look.
I am pretty new to Xcode, so please pardon my ignorance on this topic.
One thing I have tried so far is to set global variable.
When I press the button, I update the global variable to ""
But then I go to the other tabs, the value has not been updated.
My understanding is that I an not writing the code in the right place.
Currently, I have written this code in viewDidLoad function of each tab's viewController class:
override func viewDidLoad() {
super.viewDidLoad()
telephone.text = ViewLine.GlobalVariable.phone1
timerData.text = ViewLine.GlobalVariable.concept1
}
phone1 and concept1 are my global variables.
Would anyone be able to suggest where should I write this code? or is there a better way to do this?
Thanks
Write code in viewWillAppear
override func viewWillAppear() {
super.viewWillAppear()
telephone.text = ViewLine.GlobalVariable.phone1
timerData.text = ViewLine.GlobalVariable.concept1
}
I think that what you want is the first 5 view controllers to react to a tap on a button in the 6th view controller.
In that case - don't use global variables.
Use notifications:
When the button did tap, send a notification. The first 5 view controllers should subscribe to this notification channel. When each of the first 5 view controllers receive a notification they should change the text to "".
Here is a link on notifications:
https://www.raywenderlich.com/160653/design-patterns-ios-using-swift-part-22

UISwipeGestureRecognizer implementation with Swift

I have a tabbed application with three tabs and I want to make the transition between them with a Swipe Gesture Recogniser.
I dragged and dropped one Swipe Gesture Recogniser from the Object Library to the first View and one to the third View. I added two on the second View so I can swipe both left and right.
I changed the Swipe to the Attribute Inspector so it will swipe right or left as needed
Using the Assistant editor I used Control+Drag to create Actions to the proper ViewController.swift files.
What else should I do so the swiping should work?
All of the guides I found online are in Objective-C. Does anyone know how to complete these Events in Swift?
You have to apply custom view controller transitions. Otherwise your tab transition will be neither animated (by default tab switches are without animation) nor interactive (you cannot control the speed with your gesture).
A good starting point is Colin Eberhardt's VC Transitions Library. It's in Objective-C but you can use it from Swift.
Just a warning: Custom view controller transitions are an immature part of iOS 7 or 8. If another view transition is initiated while a custom one is still running, the UI will more or less lock up. And it easily happens: user presses yet another tab before the transitions ends, your code switches tab and pushes a view controller at the same time, user manages to press a button during transition that opens another view controller etc.
EDIT: I have added a sample project to github and it fully works. Feel free to copy it or wok on it.
You don't need to add them in the story board. First add this code to your initializer (in most cases viewDidLoad):
var swipeGesture = UISwipeGestureRecognizer(target: self, action: "SwipeToNextViewController:")
swipeGesture.direction = UISwipeGestureRecognizerDirection.Left
view.addGestureRecognizer(swipeGesture)
var swipeGestureReverse = UISwipeGestureRecognizer(target: self, action: "SwipeToPreviousViewController:")
swipeGestureReverse.direction = UISwipeGestureRecognizerDirection.Right
view.addGestureRecognizer(swipeGestureReverse)
Now that your viewController has the gesture, SwipeToNextViewController() will be called if you swipe left, and SwipeToPreviousViewController() will be called when swiped Right.
Write the code for the two functions associated with the gestures:
func SwipeToNextViewController(gestureRecognizer:UISwipeGestureRecognizer)
{
performSegueWithIdentifier("next", sender: self)
}
func SwipeToPreviousViewController(gestureRecognizer:UISwipeGestureRecognizer)
{
performSegueWithIdentifier("previous", sender: self)
}
These two functions will be called when view has been swiped left or right. Add your code in it (in this case performSegue) and you can segue to another viewController.
Hope it helps :)