Segue Doesn't Work in Macos

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?

Sample Image

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:

  1. How to use a custom segue class
  2. An issue with KeyEquivalents
  3. An issue with the Responder Chain
  4. 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.

Sample Image

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.

view controller presentation attribute


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



Leave a reply



Submit