I have three main view controllers A, B, C, which are presented in the order by show segue. All the three view controllers are embedded in their own navigation controller. After dismissing a modal controller D presented on C, I would like to go back to controller A and reload data before it appears.
I referred to the solution from this question to unwind segue and wrote an action in controller A (I use AViewController to represent the class of controller A):
#IBAction func prepareForUnwindSegue(segue: UIStoryboardSegue?) {
if let destController = segue?.destinationViewController as? AViewController {
destController.reload()
}
}
override func canPerformUnwindSegueAction(action: Selector, fromViewController: UIViewController, withSender sender: AnyObject) -> Bool {
return self.respondsToSelector(action)
}
I dragged-exit between controller A and C by the above action, and name the unwind segue unwindToA. Then in the class of controller C, named CViewcontroller, I call the modal view D in one ActionSheet as follow:
let presentDAction = UIAlertAction(title: "Present D", style: .Default, handler: {(_) -> Void in
let controller: AnyObject? = self.storyboard!.instantiateViewControllerWithIdentifier("DView")
if let c = controller as? DViewController {
let navi = UINavigationController(rootViewController: c)
navi.modalPresentationStyle = UIModalPresentationStyle.FullScreen
// the following block wait and execute after view controller D dismissed
c.refreshAndUnwind = {[weak self](refresh) in
if let weakSelf = self {
if(refresh) {
weakSelf.performSegueWithIdentifier("unwindToA", sender: weakSelf)
}
}
}
}
})
The view controller did successfully unwind along with reloading data. However, it seems that the navigation bar remains still on controller C, and its title disappear. I have tried to drag-exit the navigation controller between A and C instead and modify the unwind segue action call to weakSelf.navidationController!.performSegueWithIdentifier("unwindToA", sender: weakSelf), but it doesn't work. Did I do anything wrong on my segue unwinding that cause this problem?
Sometimes using storyboard is not the best way to do things. I'd do this like this in your case:
var viewControllers = self.navigationController!.viewControllers
var controllerA = viewControllers[0] as! ControllerA
controllerA.reloadStuff()
var newViewControllers = [ controllerA ]
self.navigationController!.setViewControllers(newViewControllers, animated: true)
The code is much cleaner this way. You can set the navigationItem's title in viewDidAppear on each view if you have problems with it, you can be sure that it will be always called.
So the problem is simple: After I tried to modify my storyboard and remove those not-necessary navigation controllers, it performs just fine.
Related
Basically for this simple game app I have 2 different UIViewControllers called ViewController and PreviewController. PreviewController is opening view with the title screen and a label titled "Start game". When the label is tapped, it initiates a modal view controller (the ViewController class that has all the views for the actual game itself) and calls the "EnterNewGame" method from ViewController that sets up the game. Right now the issue I have is when calling this method, only part of the method seems to be running.
Here is the function in PreviewController that is being initiated upon tap:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
self.present(ViewController(), animated: true, completion: {() -> Void in
ViewController().enterNewGame()
})
}
And here is the EnterNewGame() method from ViewController
func enterNewGame() {
//show suit indicators when starting a new game
bluePlayerSuitsHidden = false
redPlayerSuitsHidden = false
game.blueTurn = true
self.setBackground()
self.cleanUpBoard()
self.createBoard()
self.displayBoard()
self.setSuitIndicators()
self.highlightCards()
playButton.isEnabled = false
}
Right now, when the label is tapped the screen transitions to the modal view controller but only displays a black screen with only one of the game setups (setting a few images on the top of the screen) working properly. I am sure that the EnterNewGame method works properly to actually start the game because I have tested it in isolation, so I think I am just not setting up the modal view controller properly or I have to call the method differently. Any help is appreciated, thanks.
Controller on which you're calling your method ins't the same instance as controller which you're presenting, you need constant (also your code can be simplified by avoiding using self references and writing name of completion parameter with specifing closure's parameter and return type)
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
let controller = ViewController()
present(controller, animated: true) {
controller.enterNewGame()
}
}
Also, you can call this method on some other method inside your certain controller like viewDidLoad, viewWillAppear or you can create factory method which would return you certain set controller.
This last part leads me to idea: look how you instantiate your controller and look carefully if you don't need to instantiate it through storyboard or nib file.
class ViewController: UIViewController {
class func instantiate() -> ViewController {
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Identifier") as! ViewController
// let controller = ViewController(nibName: "ViewController", bundle: nil)
controller.enterNewGame()
return controller
}
}
Usage:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
present(ViewController.instantiate(), animated: true)
}
I have a view controller (containing my menu) presented on top of another view controller (my application).
I would need to access the presenting view controller (below my menu) from the presented view controller (my menu), for example to access some variables or make the presenting view controller perform one of its segues.
However, I just can't figure out how to do it.
I'm aware of the "presentingViewController" and "presentedViewController" variables but I didn't manage to use them successfully.
Any Idea ?
Code (from the presented VC, which as a reference to the AppDelegate in which the window is referenced) :
if let presentingViewController = self.appDelegate.window?.rootViewController?.presentingViewController {
presentingViewController.performSegue(withIdentifier: "nameOfMySegue", sender: self)
}
Here is a use of the delegation Design pattern to talk back to the presenting view controller.
First Declare a protocol, that list out all the variables and methods a delegate is expected to respond to.
protocol SomeProtocol {
var someVariable : String {get set}
func doSomething()
}
Next : Make your presenting view controller conform to the protocol.
Set your presenting VC as the delegate
class MainVC: UIViewController, SomeProtocol {
var someVariable: String = ""
func doSomething() {
// Implementation of do
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Your code goes here.
if let destVC = segue.destination as? SubVC{
destVC.delegate = self
}
}
}
Finally, when you are ready to call a method on the presenting VC (Delegate).
class SubVC: UIViewController {
var delegate : SomeProtocol?
func whenSomeEventHappens() {
// For eg : When a menu item is selected
// Set some Variable
delegate?.someVariable = "Some Value"
// Call a method on the deleate
delegate?.doSomething()
}
}
Assuming that VCApplication is presenting VCMenu, in VCMenu you can access VCApplication with:
weak let vcApplication = self.presentingViewController as? VCApplicationType
Your example self.appDelegate.window?.rootViewController?.presentingViewController is looking for the ViewController that presented the rootViewController - it will be nil.
EDIT
Per TheAppMentor I've added weak so there are no retain cycles.
What is the best way to pass a UInavigationController and also pass variables to a new viewController. I know how to do one or the other but not both at the same time. Thank you in advance
this is my current code
func(){
let vc = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! UINavigationController
let posts = self.postList[indexPath.row]
//this is the var that i want to past
//vc.previousViewMessageId = posts.postKey
self.presentViewController(vc, animated: true, completion: nil)
}
If I understand you correctly, you have a view controller that can present a second VC. And this VC is embedded in a UINavigationController. What you don't know how to do, is to pass data from the first VC, to the navigation controller, then to the second VC.
Here is a brute force solution. It's not beautiful, but it works anyway.
Make your own UINavigationController subclass:
class DataPasserController: UINavigationController {
var previousViewMessageId: SomeType?
override func viewDidLoad() {
if let vc = self.topViewController as? YourSecondViewController {
vc.previousViewMessageId = self.previousViewMessageId
}
}
}
Now you can add a navigation controller in the storyboard, set its class to DataPasserController, and connect the second VC to it as its root view controller.
Now suppose you have got an instance of DataPasserController by calling instantiateViewControllerWithIdentifier, you can do this:
yourDataPasserControllerInstance.previousViewMessageId = posts.postKey
And present the instance!
To pass a value to your Navigation Controller's Root View Controller, you access viewControllers[0] and cast it to the class of your Messages View Controller (the controller that has the previousViewMessageId property):
func () {
let messagesNC = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! UINavigationController
let messagesVC = messagesNC.viewControllers.first as! MessagesViewController
messagesVC.previousViewMessageId = postList[indexPath.row].postKey
presentViewController(messagesNC, animated: true, completion: nil)
}
What you have there is simply presenting a view controller... You are skipping the navigation controller.
What you need to do is present the new view controller inside the navigation controller. Once you have done that, it will show correctly. You can also pass the variables after you've created the vc variable.
This presents the new viewController (vc) within the navigation controller...
self.navigationController?.pushViewController(vc, animated: false)
This sets the variable in the new viewController (vc) (you are correct)
vc.previousViewMessageId = posts.postKey
So complete:
func(){
let vc = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! MessagesViewController
let posts = self.postList[indexPath.row]
//this is the var that i want to past
vc.previousViewMessageId = posts.postKey
navigationController?.pushViewController(vc, animated: false)
}
PS. While not part of the question, I feel I should still mention... Use of the word self should be left to necessity only. In other words, don't use it when it isn't needed. for example self.postList[indexPath.row] :)
https://github.com/raywenderlich/swift-style-guide#use-of-self
I have a Custom Segue exhibiting a view controller, and then have a corresponding Custom Unwind Segue. This code has worked fine in iOS 8, and is implemented by engendering subclasses of UIStoryboardSegue and implementing the perform method. Then I override the following method in my custom Navigation Controller
func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String) -> UIStoryboardSegue {
var segue: UIStoryboardSegue
if (fromViewController is MyViewController.self) {
segue = CustomSegue(identifier: identifier, source: fromViewController, destination: toViewController)
//Custom Unwind Segue
}
else {
var unwindSegue: UIStoryboardSegue = super.segueForUnwindingToViewController(toViewController, fromViewController: fromViewController, identifier: identifier)
//Normal Unwind Segue
segue = unwindSegue
}
return segue
}
In iOS 9, segueForUnwindingToViewController is deprecated. It still works for the MyViewController CustomSegue; however, the default unwind segue no longer works for any other unwind segue. Although calling the method on super returns an unwind segue, the segue never occurs, the view controller is never popped, and the user can never go back to the previous screen. So just to be clear, if I use a regular show segue, the corresponding unwind segue calls the deprecated method, which calls the method on super, and does not work.
If Controller B unwinds to controller A, then in controller A:
#IBAction func viewController-B-DidExit(segue: UIStoryboardSegue) {
let controller = segue.sourceViewController as! viewController-B
}
Then in viewContoller B connect the object you want to exit from to the exit.
I have a main view controller that has been setup in Interface Builder to open a table view controller via popover segue connected to a button. I want to be able to dismiss the popover when an item inside of my popover table view is selected in didSelectRowAtIndexPath. In Objective-c I can just typecast the the segue in the prepareForSegue delegate to a UIStoryboardPopoverSegueand pass along its UIPopoverController to the table view controller. However, in Swift my downcast fails because it sees the segue as type UIStorybaordPopoverPresentationSegue (when stepping through with the debugger) which doesn't appear to be a public API.
Here's my code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "ShowCollectionsSegue" {
if let collController:CollectionsTableViewController! = segue.destinationViewController as? CollectionsTableViewController {
if let popoverSegue = segue as? UIStoryboardPopoverSegue { // <-- This fails
collController.popover = popoverSegue.popoverController
}
}
}
}
How do I coerce the segue to a UIStoryboardPopoverSegue in order to access its popoverController property?
I'm open to solving the problem of dismissing the popover in response to a table view cell tap a different way, but it seems that when using a segue from the storyboard, the only way to dismiss the popover is by holding onto a reference to the popover controller somehow and the only way to do that as far as I can tell is to cast the segue to a popover segue which Swift doesn't want to let me do. Any ideas?
A strange problem, indeed. I noticed in the documentation, that UIStoryboardPopoverSegue does not inherit from any class. That explains why the cast does not work - UIStoryboardSegue is not its superclass. So I just tried to create a new object - it looks weird but works:
let popoverSegue = UIStoryboardPopoverSegue(
identifier: segue.identifier,
source: self,
destination: segue.destinationViewController as UIViewController)
println("Is there a controller? \(popoverSegue.popoverController.description)")
// YES !!
EDIT
But this controller will not dismiss the popover :(
The fix is to specify the segue in Interface Builder as "Deprecated Segues : Popover". Then the code would be as expected
let popoverSegue = segue as UIStoryboardPopoverSegue
if let destination = segue.destinationViewController as? TableViewController {
destination.delegate = self
self.popoverController = popoverSegue.popoverController
}