I started learning Objective C and programming late last year and I’m dealing with Core Data, so I need some help.
I’m trying to select an object in VC3 and pass it to VC1 when the ‘Select’ button is pressed. Once pressed then it pops back to VC1 and display the object in the cell. I don’t think I can use a segue, because I want to go back and not forward. Also, I'm not going back to the root view controller.
Everything else works with segues, protocols and delegates except this issue. I’ve tried delegates, but they don’t seem to work, perhaps because there’s a view controller in between them or something. However, I’m very new to programming so I could be wrong.
I would appreciate some help and guidance.
The modern way (ios6+) to do this is with an unwind segue
You create one of these by defining a method that takes a single argument of a storyBoardSegue.
- (IBAction)unwindOnSelect:(UIStoryboardSegue *)segue;
Add this method to the viewController you want to unwind to.
Then, in the storyboard scene you want to unwind from... CTRL-drag from your 'Select' button to the green 'exit' symbol in the bar at the bottom of the scene. The unwind segue should appear in the popup as an option. Select it. Your button will unwind you to that destination, and code in the unwind segue method will be executed.
The segue object holds a reference to both source and destination viewControllers, so that is how you can get the data from one to the other.
- (void)unwindOnSelect:(UIStoryboardSegue *)segue
{
self.myObject = segue.sourceViewController.myProperty;
}
You will probably get an error here, "no known instance method for selector "myProperty". One way round this is to #include "viewController3" at the top of VC1's .h or .m file. You may have to typecast as well:
self.myObject = [(ViewController1*)segue.sourceViewController myProperty];
Another way is a bit of indirection:
if ([segue.sourceViewController respondsToSelector:#selector(myProperty)]) {
self.myObject = [segue.sourceViewController performSelector:#selector(myProperty)];
}
This has the benefit that you do not have to #include "viewController3", so you are a bit more decoupled, a bit more Objectified.
Related
Please note this is not an iOS question.
I have an NSView-based app (i.e. not document-based), and I’d like to bolt on a printing subsystem. I can get NSViews in my main controller to print ok. However, I want to have a special view constructed just for printing. The view should not show in the app’s window. The view contains two NSTextFields, two NSTextViews, and 5 labels.
I cannot seem to figure out a way to do this. I have tried various forms of these examples:
Add an NSView to my main view window? Seems logical, but it’s awkward in a storyboard, (I can’t position the view in the storyboard).
Programmatically create a custom NSView with a xib?
For this, I’ve tried:
#IBOutlet weak var printView: NSView!
….
let printOperation = NSPrintOperation(view: printView!)
This results in the comprehensive "fatal error: unexpectedly found nil while unwrapping an Optional value” message.
The outlets are configured correctly (I think)
A seperate ViewController? If so, how can I avoid having two print buttons — one to call the print controller, and the second, to print the PrintController’s view.
I’ve tried reading the Apple docs, but they are not the way I learn best. There are also no Print documents in Swift that I've found. I’ve waded through many SE questions, but have come up blank. Could you point me towards a solution please.
I think the specific problem here is that you're never causing the view to be loaded.
You can double check whether this is the case by overriding the viewDidLoad method on the controller. If it's never called, your view is never loaded from the nib.
Normally the UI machinery takes care of all that when you display a view controller, which is why it can be so confusing when it doesn't happen.
You should be able to trigger the view load by accessing the view property on the controller. e.g.
_ = self.view // Touch the view to force it to load
https://developer.apple.com/documentation/appkit/nsviewcontroller/1434401-view has some additional information.
You can also call loadView() directly although that's usually frowned upon.
I have a main screen wrapped in a navigation controller.
The main screen has several buttons that trigger segues to other views. One button goes to a table view.
A selection in this table should trigger a segue that is normally performed by one of the buttons on the main view.
I was assuming that I need to unwind to the main screen first, and then trigger a segue from the unwind segue programmatically, but what happens when I do that is that it performs the programmatic segue first, then unwinds, and ends up on the main screen again.
What is the correct way to deal with this situation? I don't want to be able to go back to the table view after the programmatically called segue, the back button should then go to the main view.
In case it helps to explain my use case a little more: The table view is a list of levels. A selection should launch my game view with the level picked in the table view.
My unwind segue in my main view:
#IBAction func backFromLevelSelectionUnwindSegue(segue:UIStoryboardSegue) {
performSegueWithIdentifier("playSegue", sender: self)
}
obviously playSegue is the segue to the game view controller.
An answer to a similar question suggests setting a boolean flag and then performing the segue in viewDidAppear, but it seems like that viewDidAppear should not have to know about an unwind segue that has occurred. Is there a "correct" solution that I haven't come across?
In your backFromLevelSelectionUnwindSegue, your are still in a unwind segue context. So call performSegueWithIdentifier after the context finished using dispatch_async or performSelector:withObject:afterDelay like below.
#IBAction func
backFromLevelSelectionUnwindSegue( segue:UIStoryboardSegue ) {
dispatch_async( dispatch_get_main_queue() ) {
self.performSegueWithIdentifier( "playSegue", sender: self )
}
}
Would it be possible for you to perform a segue to the game view without unwinding it to the main screen? Then when you unwind the segue, unwind it to the main view instead of back to table view? If I remember correctly, we are able to unwind past the presenting view controller.
This is a matter of opinion, but in your case I think you should simply have a segue between the tableview and the game, with any extra data necessary being passed to the tableview VC from the main VC.
If you need go back multiple viewControllers in a UINavigationVC, I would look at using popToRootViewController or use an unWind between them. e.g. call unwind from the 3rd viewController, with the handler in the 1st viewController
It's possible to have multiple unwind segues that go back to different places. In the scenario given the view controllers should be arranged Main->LevelSelect->Game
and then when game ends you have buttons for 2 or 3 unwind segues. First one is exitToGameStart that allows the player to restart the same level. exitToLevelSelect allows player to choose a new level. And optionally exitToMainMenu goes all the way back to the start. For a full example, see Apple's UnwindSegue sample, and in particular the "Start Over" button on the last table that performs the exitToQuizStart unwind segue and the "Return To The Menu Screen" that performs the exitToHomeScreen unwind segue. Code for the receiving methods are below:
QuestionViewController.m
//! Unwinds from the ResultsViewController back to the first
//! QuestionViewController when the user taps the 'Start Over' button.
//
// This is an unwind action. Note that the sender parameter is a
// 'UIStoryboardSegue*' instead of the usual 'id'. Like all unwind actions,
// this method is invoked early in the unwind process, before the visual
// transition. Note that the receiver of this method is the
// destinationViewController of the segue. Your view controller should use
// this callback to update its UI before it is redisplayed.
//
- (IBAction)exitToQuizStart:(UIStoryboardSegue *)sender
{
// The user has restarted the quiz.
[self.currentQuiz resetQuiz];
}
MainMenuViewController.m
//! Unwinds from the ResultsViewController back to the MainMenuViewController
//! when the user taps the 'Return to the Home Screen' button.
//
// This is an unwind action. Note that the sender parameter is a
// 'UIStoryboardSegue*' instead of the usual 'id'. Like all unwind actions,
// this method is invoked early in the unwind process, before the visual
// transition. Note that the receiver of this method is the
// destinationViewController of the segue. Your view controller should use
// this callback to retrieve information from the sourceViewController. Used
// properly, this method can replace existing delegation techniques for
// passing information from a detail view controller to a previous view
// controller in the navigation hierarchy.
//
- (IBAction)exitToHomeScreen:(UIStoryboardSegue *)unwindSegue
{
// Retrieve the score from the ResultsViewController and update the high
// score.
ResultsViewController *resultVC = (ResultsViewController*)unwindSegue.sourceViewController;
self.highScore = MAX(resultVC.currentQuiz.percentageScore, self.highScore);
}
Note: to build the old project in the latest Xcode, open the storyboard, file inspector, builds for, pick a newer iOS version.
I want to make behavior like messaging app. I have been browsing Stack Overflow for solutions for this, and indeed there are plenty:
Leaving inputAccessoryView visible after keyboard is dismissed
This was the one that I found. But it seems things are a little different in iOS8. If I do the same thing in new iOS8 sdk, i get error:
'UIViewControllerHierarchyInconsistency', reason: 'child view controller:<UICompatibilityInputViewController: 0x7fdcb3441b10> should have parent view controller:<ViewController: 0x7fdcb3b1e9f0> but requested parent is:<UIInputWindowController: 0x7fdcb684c000>'
In order to test this more I made a sample project, just one controller with view on the bottom:
Outlet is connected to bottom view, that only has UITextField on it. Am I missing something and how do i get the desired behvior?
iOS8 has a retain cycle with the inputAccessoryView. Here's a good post that seems to have a good workaround:
http://derpturkey.com/uitextfield-docked-like-ios-messenger/
You are adding the someView to multiple superViews, which leads to inconsistent hierarchies (which it is telling you).
When the keyboard gets activated, it calls the inputAccessoryView() method to see if it needs to stick anything on top of the keyboard, and adds that to its own superView. But you already added it to the view through your storyboard.
Now there are 2 ways you can solve this:
Make a .xib with your view and return that one in your inputAccessoryView(), not adding it to any superview yourself (the keyboard will.
Or make it completely in code using NSLayoutConstraint.
You can add the following code to your ViewController which will persist the view even when the keyboard is hidden.
override func canBecomeFirstResponder() -> Bool {
return true
}
Look at this GitHub repo for an example.
I'm fairly new to Mac development and am slightly confused by the new "storyboard" feature in Xcode 6. What I'm trying to do is segue from one view controller to another in the same window. As of right now, all the different NSViewControllerSegues present the view controller in a new window, be it a modal or just another window. What I'd like to do is just segue within the same window, much in the same way one would on iOS (though an animated transition is not crucial). How would this be achieved?
If you provide a custom segue (subclass of NSStoryboardSegue) you can get the result you are after. There are a few gotchas with this approach though:
the custom segue will use presentViewController:animator so you will need to provide an animator object
because the presented view is not backed by a separate Window object, you may need to provide it with a custom NSView just to catch out mouse events that you don't want to propagate to the underlying NSViewController's view
there's also a Swift-only glitch regarding the custom segue's identifier property you need to watch out for.
As there doesn't seem to be much documentation about this I have made a small demo project with custom segue examples in Swift and Objective-C.
I also have provided some more detail in answer to this question.
(Reviving this as it comes up as first relevant result on Google and I had the same problem but decided against a custom segue)
While custom segues work (at least, the code given in foundry's answer worked under Swift 3; it needs updating for Swift 4), the sheer amount of work involved in writing a custom animator suggests to me that their main use case is custom animations.
The simple solution to changing the content of a window is to create an NSWindowController for your window, and to set its contentViewController to the desired viewController. This is particularly useful if you are following the typical pattern of storyboards and instantiate a new ViewController instance every time you switch.
However.
The NSStoryboard documentation says, quite clearly in macOS, containment (rather than transition) is the more common notion for storyboards which led me to look again at the available tools.
You could use a container view for this task, which adds a NWViewController layer instead of the NSWindowController outlined above. The solution I've gone with is to use an NSTabViewController. In the attributes inspector, set the style to 'unspecified', then select the TabView and set its style to 'tabless'.
To change tabs programatically, you set the selectedTabViewItemIndexof your TabViewController.
This solution reuses the same instance of the ViewControllers for the tab content, so that any data entered in text fields is preserved when the user switches to the other 'tab'.
Simple way with no segues involved to replace the current view controller in the same window:
if let myViewController = self.storyboard?.instantiateController(withIdentifier: "MyViewController") as? MyViewController {
self.view.window?.contentViewController = myViewController
}
I'm implementing an example, in that example, I read in data from a database, put it in an array, sort it, and it's displayed using the RootViewController. The DB read and array load happen before the RVC code. So, it works, I get the data in the window created by the RVC and there's a nav controller there as well.
I want to add a button or something to the nav controller so that when you hit it, it sends a value back to the RootViewController.m file, then based on that value, I want to resort the array and display it once again in the RootViewController window.
I'm not sure how to do this. What changes would I have to make to the .xib and the RootViewController.m file?
Please, I'm a confused nube. %-0 Thank you very much.
There's a fair amount to this, so I'll give some general points and if any of them cause problems, it may be easier to work out smaller details.
In you RVC's viewDidLoad method, you can create a button and set it as the right or left button in your controller's navigationItem.
You can associate a tap on that button with a method in your controller that can do whatever you want when the button is tapped. (But a button doesn't send values, really, so you may have to explain more about that idea.)
I assume the RVC has a table view if you're showing array contents, so once the array (mutable array, I'd assume) is re-sorted, you can tell the table view to reload its data.
In answer to your secondary question, once you have resorted your array (or generally updated your data however you wish) you can force the table view to reload programmatically
[tableView reloadData];
Where 'tableView' is your instance variable pointing to your table view