I have a table view as a side menu on a ViewController xib that has to be added as a subview by clicking on a particular menu button.A logout button exists on side menu bottom.I want to perform popToRootViewController on click of logos button.
I am adding menu like this :
menuViewController?.view.frame = self.transparentView.frame
menuViewController?.didMove(toParentViewController: self)
menuViewController?.view.frame.size.width = (mapView.frame.size.width)/1.5
menuViewController?.view.tag=10
self.transparentView.addSubview((menuViewController?.view)!)
if you have single Navigation Controller chain in your application you can easily use below code to navigate on RootViewController.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if let navigationController = appDelegate.window?.rootViewController as? UINavigationController {
navigationController.popToRootViewController(animated: true)
}
Hope this will helps.
Use NotificationObserver to achieve this goal.
Objective-C
In your mainViewController's viewDidLoad method write below code.
[[NSNotificationCentre defaulCentre] addObserver:self withName:"LoggoutNotificationMessage" selector:#selector(shouldLogout:) withObject:nil];
now add this method in this class
-(void) shouldLogout {
//Code to Pop to Root VC
}
Now in your ViewController class where you have implemented side menu's tableView delegate and datasource.
In didSelectRowAtIndexPath: method write below line.
[[NSNotificationCentre defaultCentre] postNotification:"LoggoutNotificationMessage" withObject:nil]
Swift 3.0
// in main ViewController.swift's viewDidLoadMethod
NotificationCenter.default.addObserver(self, selector: #selector(shoudLogout), name: NSNotification.Name(rawValue: "LogoutNotificationMessage"), object: nil)
#objc func shoudLogout() {
//Pop to root vc here
}
Now in your ViewController class where you have implemented side menu's tableView delegate and datasource.
In didSelectRowAtIndexPath: method write below line.
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "LogoutNotificationMessage"), object: nil)
Related
I have my storyboard set up with a main ViewControllerand a NavigationController with a secondary ViewController embedded. The main ViewController and NavigationController are connected with a show segue that is triggered on a button press. The photo attached shows my storyboard.
When the button is clicked, the label in LabelViewController should change, and the show segue should trigger and show the NavigationViewController (with LabelViewController embedded). The last half works, and the NavigationViewController is shown, but the label's value never changes.
To change the label's value, I am using NotificationCenter's addObserver and post. While there are other methods, I need to use NotificationCenter to pass through data (userData).
Code
Inside of the first ViewController I have this single line of code inside of an IBAction that triggers on the button's press.
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "changeLabelValue"), object: nil, userInfo: ["labelValue": "Changed value"])
Then, inside of LabelViewController's viewDidLoad I have another line of code for the observer.
NotificationCenter.default.addObserver(self, selector: #selector(changeLabelValue(_:)), name: NSNotification.Name(rawValue: "changeLabelValue"), object: nil)
The selector function, changeLabelValue looks like this.
func changeLabelValue(_ notification: NSNotification) {
label.text = notification.userInfo?["value"] as? String
}
Problem
As said above, the show segue triggers and I am shown the NavigationViewController, but the selector function (changeLabelValue) inside of LabelViewController never gets called.
Your function is never called because it is only resisted to receive function calls in the second view controller's viewDidLoad. However, when you press your button in your first view controller, your second view controller hasn't been loaded.
To correctly pass data, you need to use prepareForSegue. So in your first view controller, you need to first check if your segue destination is an UINavigationController because your second view controller is embedded inside. If so, check if the topViewController of your navigation controller is your SecondViewController. If both are true, pass the data and it will be available in viewDidLoad section of your SecondViewController. Sample code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? UINavigationController {
if let secondViewControoler = destinationViewController.topViewController as? SecondViewController {
secondViewControoler.yourProperty = self.yourLabel.text
}
}
}
UPDATE
The following is not valid in Swift 3. Check out #FangmingNing's answer.
You would need to add #objc to your changeLabelValue function as the observer must inherit from an Objective-C NSObject.
#objc func changeLabelValue(_ notification: NSNotification) {
label.text = notification.userInfo?["value"] as? String
}
I am creating an app that opens with ViewController1, then a button opens another view contoller (ViewController2) with a modally segue. Then ViewController2 has a button that opens another view controller (ViewController3) using another modally segue. Both 2 and 3 view contollers have a dismiss button that dismisses the view controller.
The problem is that whenever ViewController3 uses the dismiss button, it dismisses to ViewController 2 when I want it to dismiss to ViewController1. I've tried using the dismiss action to dismiss ViewController2 one the button is pressed, but then the segue doesn't get committed.
This may be confusing so please ask questions if you need help understanding. Thanks so much in advace!
(I am using Swift 3 and Xcode 8)
Two options off the top of my head:
1. Use NotificationCenter and Send a dismiss notification that ViewController2 is listening for
2. Set a parentViewController reference on ViewController3, and then call dismiss on the parent after dismissing itself.
This happen because all the 3 viewcontroller are in stack,whenever u dismiss the viewcontroller in top it moves to the one below it.
To navigate to viewcontroller1 use:-
if let viewcontroller1 = navigationController?.viewControllers[0]{
_ = navigationController?.popToViewController(viewcontroller1, animated: true)
}
it's simple!
//*** in ViewController2
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.onCloseViewController2), name: NSNotification.Name(rawValue: "closeViewController2"), object: nil)
func onCloseViewController2() {
//this function is called from notification (sent by vc3)
self.navigationController?.dismiss(animated: true, completion: nil);
}
#IBAction func closeView2FromButton() {
//Directly close modal
self.onCloseViewController2();
}
//*** in ViewController3 (tap button)
#IBAction func closeView3FromButton() {
//dismiss vc3 and send a notification to vc2
self.navigationController?.dismiss(animated: true, completion: {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "closeViewController2"), object: nil);
});
}
Remember to later remove the observers when you do not need them in your code ;)
I am working on SWRevealViewController in which I have a scenario in which -
Demo code link - https://www.dropbox.com/s/02fjub838hv95cr/SWRevealVC.zip?dl=0
1) I have to present a ViewController's view*(NotificationVC in my storyboard)* in my FrontVC.
**2)**This childVC's View has a button*(createNotification)* which is connected to another viewController*(createNotificationVC)* via custom segue(SWRevealViewControllerSeguePushController),which has a back button
**3)**Pressing the back button user returns to the frontVC again with some message to be passed to the FrontVC.
For this message passing,I am using notification Pattern.
In my frontVC -
override func viewDidLoad()
{
super.viewDidLoad()
self.revealViewController().delegate = self
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
if self.revealViewController() != nil
{
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
self.view.addGestureRecognizer(self.revealViewController().tapGestureRecognizer())
}
let NtfctnVC = self.storyboard?.instantiateViewControllerWithIdentifier("NtfctnVC")
addChildViewController(NtfctnVC!)
NtfctnVC!.view.frame = CGRectMake(0, 0,self.customView.frame.width, self.customView.frame.height)
customView.addSubview(NtfctnVC!.view)//add the childVC's view
NtfctnVC!.didMoveToParentViewController(self)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "methodOfReceivedNotification:", name:"NotificationIdentifier", object: nil)
}
func methodOfReceivedNotification(notification: NSNotification)
{
//Take Action on Notification
let userInfo = notification.userInfo
print(userInfo!["sentTag"] as? Int)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
In the createNotificationVC's back button action,I have -
#IBAction func bck(sender: AnyObject)
{
let frontVC = self.storyboard?.instantiateViewControllerWithIdentifier("frontNVC") //frontNVC is the navigation controller of frontVC
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: nil, userInfo: ["sentTag":2])
self.revealViewController().pushFrontViewController(frontVC, animated: true)//here the frontVC is presented with no animation.
}
Problem -
1)The animation while performing custom segue is not been shown.
2)Notifications message are not being passed to the frontVC.
Please help me regarding this as I have tried lot many stuff & googling, but in vain. Thanks.
This shows my SWR connection with front & rear VC
This shows my NotificationVC/childVC's connection. *Mark:*ChildVC button connected via custom segue to createNotificationVC.
1.SWRevealViewController have SW_Front & SW_REAR .
2.To use this you need to implement this hierarchy
->swrevealviewcontroller -->swfront--->viewcontroller
-->swrear---->viewcontroller
3.in your case why that code doesn't work means .
4.From sw_rear you have to use this animation ,then it will work sure.
The top three answers can solve my questions. It is hard to pick which one is the best. So, I just pick the one who is the first to answer my question. Sorry for amateur and iOSEnthusiatic. Thank you for your help. I appreciate it.
ViewController 1 has a table view.
My question is how to reload the table view only if I click back from view controller 2, and not reload the table view if I click back from view controller 3.
Right now, my code for back button is
#IBAction func backButtonTapped(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
In view controller 1. I know that the table view would be reloaded from either view controller 2 or 3
override func viewDidAppear(animated: Bool) {
loadTable()
}
I tried to put loadTable() in viewDidLoad and try to write the below code for back button in view controller 2. But, it doesn't work.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("UserHomePageViewController") as! UserHomePageViewController
controller.viewDidLoad()
Any suggestion what I should do? Thank you for your help.
EDIT:
I think this is an easier way to do it, but it still does not work as I thought. I guess it is because the viewDidAppear is executed before the call of reloadTableBool. Correct? Is there any way to fix it? Thank you. You help would be appreciated.
class 2ViewController
#IBAction func backButtonTapped(sender: AnyObject) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("1ViewController") as! 1ViewController
print("viewcontroller2 before call: \(controller.reloadTableBool)")
controller.reloadTableBool = false
print("viewcontroller2 after call: \(controller.reloadTableBool)")
self.dismissViewControllerAnimated(true, completion: nil)
}
class 1ViewController
var reloadTableBool = true
override func viewDidAppear(animated: Bool) {
print("viewcontroller1: \(reloadTableBool)")
if reloadTableBool == true {
loadTable()
}
}
When I click back on view controller 2, it prints
viewcontroller2 before call: true
viewcontroller2 after call: false
viewcontroller1: true
Here is a link to a question I answered a couple days ago. Use the navigation controller delegate to handle the back button. In your second view controller, set the delegate to self and reload the tableview when you press the back button.
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
if let controller = viewController as? FirstViewController {
controller.tableView.reloadData()
}
}
NOTE:
I'm assuming you're using the back button of the navigation controller here.
EDIT: Another example using your manually added back button:
#IBAction func backButtonTapped(sender: AnyObject) {
if let viewControllers = app.window?.rootViewController?.childViewControllers {
viewControllers.forEach { ($0 as? FirstViewController)?.tableView.reloadData() }
}
self.dismissViewControllerAnimated(true, completion: nil)
}
Seeing as you are using a navigation controller:
#IBAction func backButtonTapped(sender: AnyObject) {
navigationController?.viewControllers.forEach { ($0 as? FirstViewController)?.tableView.reloadData() }
self.dismissViewControllerAnimated(true, completion: nil)
}
If displaying vc2 is performed by vc1 and is always sure to invalidate the data in vc1, you could do the following:
add a needsReload boolean instance variable to vc1
set it to true whenever you display vc2 (and when instanciating vc1 eg in awakeFromNib if coming from a storyboard)
only perform the content of loadTable if needsReload is true (maybe refactor this logic into a loadTableIfNeeded)
don't forget to set needsReload to false in the end of loadTableIfNeeded
This invalidation pattern is found throughout UIKit, see for example UIView setNeedsLayout/layoutIfNeeded. The advantage is that even if several events cause the data to invalidate, it will only actually get refreshed when you need it.
In your situation it has the additional advantage of keeping the logic contained in vc1 and not creating unnecessary coupling between your VCs, which is always good.
---UPDATE: sample implementation (ObjC but you'll get the idea)
You only need to handle this in VC1, forget about all the back button stuff in VC2. This implementation will mark VC1 for reload as soon as VC2 is presented, but will actually reload only on viewWillAppear, when VC2 is dismissed.
---UPDATE 2: Added a conditional reload based on a delegate callback
Note that _needsReload is now set in the delegate callback, not when VC2 is first presented. Instead we set VC1 as the delegate of VC2. (_needsReload logic is actually unnecessary using this method, kept it for reference)
//VC2: add a delegate to the interface
#class VC2;
#protocol VC2Delegate
- (void) viewController:(VC2*)myVC2 didFinishEditingWithChanges:(BOOL)hasChanges;
#end
#interface VC2
#property (nonatomic, weak) id<VC2Delegate> delegate
#end
#implementation VC2
- (IBAction) finishWithChanges
{
[self.delegate viewController:self didFinishEditingWithChanges:YES];
}
- (IBAction) finishWithoutChanges
{
[self.delegate viewController:self didFinishEditingWithChanges:NO];
}
#end
//VC1: implement the VC2Delegate protocol
#interface VC1 () <VC2Delegate>
#end
#implementation VC1
{
BOOL _needsReload
}
- (void) awakeFromNib
{
//adding this for completeness but the way you did it in Swift (at init) is correct
[super awakeFromNib];
_needsReload = YES;
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self reloadTableIfNeeded];
}
- (IBAction) displayVC2
{
VC2* myVC2 = ... //instanciate your VC2 here
myVC2.delegate = self; //set as the delegate of VC2
[self presentViewController:myVC2 animated:YES completion:nil];
}
- (void) viewController:(VC2*)myVC2 didFinishEditingWithChanges:(BOOL)hasChanges
{
_needsReload = hasChanges;
[self reloadTableIfNeeded];
}
- (void) reloadTableIfNeeded
{
if (_needsReload) {
[self.tableView reloadData];
_needsReload = NO;
}
}
#end
You can use notification approach easily for this.
Add observer in your 1st ViewController in viewDidLoad method.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "reloadTable:", name: "reloadTable", object: nil)
func reloadTable(notification : NSNotification){
let isReload : NSNumber = notification.userInfo!["isReload"] as! NSNumber
if (isReload.boolValue) {
self.tableView.reloadData()
}
}
Then post notification like this from your 2nd and 3rd ViewController respectively when you call dismissViewController.
// From 2nd viewcontroller
NSNotificationCenter.defaultCenter().postNotificationName("reloadTable", object: nil, userInfo: ["isReload" : NSNumber(bool: false)])
// From 3rd viewcontroller
NSNotificationCenter.defaultCenter().postNotificationName("reloadTable", object: nil, userInfo: ["isReload" : NSNumber(bool: true)])
I have two views with a label on one of them. On the second view, there is a button. What I want to achieve here is to be able to press the button and it updates the label on the first view. How do I do that? I can't access the IBOutlet from the second view. Is there something that I have to do to the IBOutlet to make it public etc?
You can use NSNotificationCenter for that.
First of all in your viewDidLoad method add this code in your firstViewController class:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshlbl:", name: "refresh", object: nil)
which will addObserver when your app loads.
And add this helper method in same viewController.
func refreshlbl(notification: NSNotification) {
lbl.text = "Updated by second View" //Update your label here.
}
After that in your secondViewController add this code when you dismiss your view:
#IBAction func back(sender: AnyObject) {
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
Now when you press back button from secondView then refreshlbl method will call from firstView.
use custom delegate method create a delegate in second view and access that view delegate function in first view. or use NSNotification or use NSUserdefaults