Segue doesn't work in macOS
The mistake is not in the code.
You have to connect the segue to the ViewController
, not to the WindowController
.
Connect the slider action to the IBAction
(not to the segue) and the segue from ViewController
to SecondViewController
(not from the slider).
And if the class of the second view controller is SecondViewController
it should be indicated in the window controller. Where does SecondVC
come from?
And once again the suggestion to create an extension of NSStoryboardSegue.Identifier
extension NSStoryboardSegue.Identifier {
static let secondVC = NSStoryboardSegue.Identifier("SegueIdentifierForSecondVC")
}
and to use it in these two methods. And force cast the segue.destinationController
. It must not crash if everything is hooked up properly.
@IBAction func segueData(_ sender: NSSlider) {
self.performSegue(withIdentifier: .secondVC, sender: nil)
}
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier, identifier == .secondVC {
let secondViewController = segue.destinationController as! SecondViewController
secondViewController.imagesQty = slider.integerValue
}
}
Finally NSImage
got its own initializer taking an URL
let image = NSImage(contentsOf: photos[i])
Swift performSegueWithIdentifier not working
[Assuming that your code is not crashing, but rather just failing to segue]
At least one problem is:
self.performSegueWithIdentifier("Test", sender: self)
should be:
dispatch_async(dispatch_get_main_queue()) {
[unowned self] in
self.performSegueWithIdentifier("Test", sender: self)
}
Remember that all UI operations must be performed on the main thread's queue. You can prove to yourself you're on the wrong thread by checking:
NSThread.isMainThread() // is going to be false in the PF completion handler
ADDENDUM
If there's any chance self
might become nil, such as getting dismissed or otherwise deallocated because it's not needed, you should capture self weakly as [weak self]
not unowned
, and use safe unwrapping: if let s = self { s.doStuff() }
or optional chaining: self?.doStuff(...)
ADDENDUM 2
This seems to be a popular answer so it's important to mention this newer alternative here:
NSOperationQueue.mainQueue().addOperationWithBlock {
[weak self] in
self?.performSegueWithIdentifier("Test", sender: self)
}
Note, from https://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift:
NSOperation vs. Grand Central Dispatch (GCD)
GCD [dispatch_* calls] is a lightweight way to represent units of work that are going to be executed concurrently.
NSOperation adds a little extra overhead compared to GCD, but you can add dependency among various operations and re-use, cancel or suspend them.
ADDENDUM 3
Apple hides the single-threaded rule here:
NOTE
For the most part, use UIKit classes only from your app’s main thread.
This is particularly true for classes derived from UIResponder or that
involve manipulating your app’s user interface in any way.
SWIFT 4
DispatchQueue.main.async(){
self.performSegue(withIdentifier: "Test", sender: self)
}
Reference:
https://developer.apple.com/documentation/uikit
Custom NSStoryboardSegue not adding NSViewController to Responder Chain
From the way I understand it, your question has four parts:
- How to use a custom segue class
- An issue with KeyEquivalents
- An issue with the Responder Chain
- An issue with focus
Solution Demo
I have the following example functioning correctly with your custom segue class, a key equivalents example, a responder chain example, and a focus example (assuming you mean TextField focus).
As you can see in the gif below, you can click the Segue button to transition using your PushSegue
segue. Then click the Dismiss button to go back. The right and left arrow are the key equivalents for the Segue and Dismiss buttons respectively. The responder chain successfully passes data from the SheetController
(red screen) to the MainViewController
(gray screen). Also, the textFields properly get focused when segueing. I'll explain each feature in further detail.
PushSegue
I took your PushAnimator
subclass that you posted and just added a red background to the view so that I could see it visually. I did this by adding the following line within the animatePresentation
method of your PushAnimator
class:
viewController.view.layer?.backgroundColor = CGColor.init(red: 1, green: 0, blue: 0, alpha: 1)
Key Equivalents
The Segue button is assigned the keyEquivalent of NSRightArrowFunctionKey
and the Dismiss button is assigned NSLeftArrowFunctionKey
. You can tap the right and left arrow keys to segue between the two views. To setup the right arrow, I added the following in the MainViewController
viewDidLoad()
function:
let array = [unichar(NSRightArrowFunctionKey)]
mainViewSegueButton.keyEquivalent = String(utf16CodeUnits: array, count: 1)
To setup the left arrow, I added the following in the SheetController
viewDidLoad()
function:
let array = [unichar(NSLeftArrowFunctionKey)]
dismissButton.keyEquivalent = String(utf16CodeUnits: array, count: 1)
Responder Chain
Since you did not describe the specific issue you're having with the responder chain, I just implemented a simple test to see if it works. I will set SheetController
's dismissButton
with a nextResponder of MainViewController
. I have the following in SheetController
's viewDidLoad()
:
// SheetController viewDidLoad()
// Set Accessibility Label
dismissButton.setAccessibilityLabel("DismissButton")
// Set button target and action
dismissButton.target = nil
dismissButton.action = #selector(MainViewController.dismissAction(_:))
// Set nextResponder
dismissButton.nextResponder = self.parent
To explain the above: I first set up the dismissButton
with an accessibility label so that MainViewController
can identify the button later. Setting the button's target to nil means that it will look towards its nextResponder to handle any events that it detects. I set the button's action to the dismissAction
method, which is declared within MainViewController
. And finally, I set the dismissButton
's nextResponder to MainViewController
.
Back in the MainViewController
, I set up the dismissAction
method below. It just increments a counter variable and displays that counter along with the button's accessibilityLabel (same access label that we setup earlier) to the textField. The final line dismisses the SheetController
.
// MainViewController
func dismissAction(_ sender: AnyObject) {
counter += 1
let buttonAccessLabel = (sender as! NSButton).accessibilityLabel()
helloWorldTextField.stringValue = "Action #" + counter.description + " from: " + buttonAccessLabel!
self.dismissViewController(self.presentedViewControllers!.first!)
}
Focus
For this, I assume you're referring to textField focus. In SheetController
, I have the sheetFocusTextField
focus in viewDidAppear
:
override func viewDidAppear() {
super.viewDidAppear()
sheetFocusTextField.becomeFirstResponder()
}
In MainViewController
, I have mainFocusTextField
focus in the dismissAction
method:
mainFocusTextField.becomeFirstResponder()
Github Link
https://github.com/starkindustries/mac-os-segue-test
References
- Apple API Reference: keyEquivalent
- StackOverflow: Set NSButton's keyEquivalent
- Apple API Reference: Event Architecture
- StackOverflow: What is the formal definition of FirstResponder
- Apple API Reference: Responder Object
App crashes on performSegue in viewDidAppear
Well I feel kinda stupid now I was subclassing the view controllers under the default ViewController class automatically generated by apple instead of the UIViewController class.
Table View (Inside View Controller) Segue Not Working
It doesn't need to come from the tableView itself. The segue should come from the VC where the tableView is contained.
Your code would still work since your
performSegue(withIdentifier: "classifySegue", sender: self)
is invoking a perform segue from self which is your ViewController
NOTE: In your didSelect function, you don't need to get the cell again, just set your selectedItem to self.options[indexPath.row] since you're only trying to get the text that you pass in the cell initially.
OS X storyboards: using show segue without allowing duplicate new windows to show?
Edit: While the answer below does work, it is definitely not the best way. In your storyboard, select the view controller for the destination view then go to the attributes inspector and change Presentation from Multiple to Single. That's it, no code required.
Not sure this is the best way but in the NSViewController
that is pushing the segue, you could add a property for the destination NSViewController
and, in your prepareForSegue:sender:
method, assign the destination view controller. Finally, in the shouldPerformSegueWithIdentifier:sender:
method, check to see if the destination view controller is assigned, and, if so, bring its window to the front and return NO
meaning don't perform the segue, otherwise, return YES
. Here's a quick example (to be included in the NSViewController
with the button to initiate the segue):
@interface ViewController ()
@property (weak) NSViewController *pushedViewController;
@end
@implementation ViewController
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if (self.pushedViewController) {
[self.pushedViewController.view.window makeKeyAndOrderFront:self];
return NO;
}
return YES;
}
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
self.pushedViewController = segue.destinationController;
}
@end
When you close the window containing the destination view controller, this will set the pushedViewController property of the original view controller to nil so the segue will perform if the window is not already opened. Again, there may be a better way to do this. Hope this helps.
Jon
Why will some segue to display a sheet work fine and another in the same storyboard show the sheet and not execute and code
This was the missing click of the Module under the Custom Class of the view controller in the storyboard.
Setting it to the name of the app solved the problem.
Lesson learned, create a custom class, you have to have a module to supply it
Why is interface builder not showing my unwind segue?
To re-iterate: not only an Action for a Storyboard Unwind Segue has to be placed in the same source file as class
definition for an unwind-to (destination) View Controller (e.g., @IBAction func prepareForUnwind(segue: UIStoryboardSegue)
, detected as prepareForUnwind[With]Segue
in a "Presenting Segues" list), but also that View Controller cannot have ANY extensions in ANY secondary source files. You have to merge class
definition and all extensions into a single source file.
(As of Xcode 8.2.1.)
Related Topics
How to Convert Curl Request to Swift Using Alamofire
How to Load Lcr Image in Tvos Apps
How to Hide The Top Bar (With Buttons) Usin Swift and Macos
Swift Framework with Opencv Dynamic Framework, Library Not Loaded
How to Have a Searchbar Which Shows Suggestions with Different UItableview
How to Assign Elements of a Dictionary to JSON Object in Vapor 3
Swiftui Navigationview Starting Inside Itself
How to Drag a Working Slider Using Swiftui
How to Run Terminal Command in Swift from Any Directory
Uialertview or UIalertcontroller to Display Only Once in Swift
Functional Programming Way of Doing Array Conversion
Trim Video Always Fail When Use Avassetexportpresetpassthrough
Calculate Age with Textfield Swift 4
Collision Detection Leading to Color Detection
Firebase Cloud Firestore - Initializing a Collection