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.
Related
I currently have a view controller that has a container view which functions similar to the bottom drawer in the iOS Maps application. When an image is scanned, the drawer animates onto the screen (it begins hidden off the screen at (0,700)) and displays information that comes from Firebase. Within this container, I have another container that allows me to have a PageViewController setup. This setup allows the user to swipe between 2 different sets of information (all of which needs to be loaded from Firebase).
I don't know how to reset the data within the second PageViewController so that whenever the drawer is dismissed through a button, the PageViewController system resets and I can use my function inside the secondPageViewController to reload the tableView it has inside.
TL;DR / More Information
Scanning an image is what loads the information into the ViewControllers.
I have one independent VC (ViewController) with a container view inside of it (BottomDrawerViewController). The BottomDrawerViewController also has a container inside of it which is a PageViewController with 2 pages (Swipe1ViewController & Swipe2ViewController)
The first page of the PageViewController setup works fine (Most likely because that information is loaded using an NSNotification)
I haven't been able to use an NSNotification to send and refresh this data the same way because this is not the first view controller that shows up after I post my notification.
I can't just load all my information independently in the second PageViewController because I get the pathway to the Firebase information from my original ViewController
Question: How can I reset both this data and the PageViewController so that whenever the user dismisses the bottom drawer the information on the second page is able to be refreshed on the next scan and the PageViewController starts on the first page? I thought maybe by forcing the view to disappear so that ViewDidLoad is called when it appears on screen but I am realizing this isn't possible.
I've attached some of the important code from all of the view controllers and my Firebase code in image and JSON form just in case that helps.
ViewController: -- This is the part that gets information to send to containers and what animates in the main container
ref.child("Directory").child(imageName).observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
let content = ImageInformation(snapshot: snapshot)
// TODO: Deliver Image Metadata here
let imageInfo = ["eventTitle": imageName, "source": content.Source, "date": content.Date] as [String : Any]
NotificationCenter.default.post(name: Notification.Name.drawerVC, object: nil, userInfo: imageInfo)
NotificationCenter.default.post(name: Notification.Name.newsVC, object: nil, userInfo: imageInfo)
})
UIView.animate(withDuration: 0.50) {
self.bottomDrawer.frame = CGRect(x: self.bottomPosition.x, y: self.bottomPosition.y, width: self.bottomDrawer.frame.size.width, height: self.bottomDrawer.frame.size.height)
}
BottomDrawerViewController: -- This gets a notification from ViewController and changes the drawers title. It also sends a notification back to ViewController when the view is pressed so that it can be dismissed.
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(appeared), name: Notification.Name.drawerVC, object: nil)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
view.addGestureRecognizer(tap)
}
#objc func appeared(_ notification: NSNotification) {
if let eventTitle = notification.userInfo?["eventTitle"] as? String {
self.eventTitleLabel.text = eventTitle
}
}
#objc func handleTap(_ sender: UITapGestureRecognizer) {
NotificationCenter.default.post(name: Notification.Name.mainVC, object: nil, userInfo: nil)
}
PageViewController: --This controls my PageViewController setup. Since everything is the same as a normal setup I did not include. This line allows me to send a variable from the first page to the second.
var eventTitle: String = ""
Swipe1ViewController: -- Receives a notification from ViewController and uses it to populate the Labels on screen. It also sends the event title ("Test Input" in the database) to the PageViewController to be used by the Swipe2ViewController (No code shown)
Swipe2ViewController: -- As of right now it takes the eventTitle variable from the PageViewController and uses it to get data from Firebase but this doesn't work multiple times because viewDidLoad() only runs once when the BottomDrawerViewController is swiped up. I've also tried putting the fetchHeadlines() function in viewDidAppear() but if the user dismisses and scans a completely different picture it won't update.
override func viewDidLoad() {
super.viewDidLoad()
self.newsTableView.delegate = self
self.newsTableView.dataSource = self
// fetchHeadlines()
}
override func viewDidAppear(_ animated: Bool) {
//
fetchHeadlines()
}
override func viewDidDisappear(_ animated: Bool) {
}
func fetchHeadlines() {
let pageVC = self.parent as! DrawerPageViewController
let eventTitle = pageVC.eventTitle
print("Title: " + eventTitle)
ref.child("Directory").child(eventTitle).child("News").observeSingleEvent(of: .value) { (newsSnapshot) in
for news in newsSnapshot.children.allObjects as! [DataSnapshot] {
print("Key: \(news.key)", "Value: \(news.value)")
//self.headlines.append(news)
DispatchQueue.global().async {
DispatchQueue.main.async {
// self.tableView.reloadData()
}
}
}
}
}
You shouldn't be using notifications to propagate data from one view to another. As you see, it doesn't work that well, it can be error prone, and it can be challenging to debug. Instead, you should embrace the relationship between parent and child view controllers. That is, The ViewController should pass the data to the BottomViewController, who passes the data to the PageViewController, and it's the PageViewController's job to set up the data in the sub-pages. This lets you put the data where it belongs when each view controller is put on screen.
Note that a parent view controller can access a child using the childViewControllers array. So ViewController can access the BottomViewController directly using:
if let bottomViewController = childViewControllers.first as? BottomViewController {
// set up the bottomViewController here
}
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 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)
I have an reference to a managed object called selectedItem, I load the data in on view controller and have a modal segue (over current context) to another view to edit the title property of the selectedItem.
I expect a textLabel to refresh the data when dismissing the modal view but it does not. I have used the same method to add to the table data and it worked, because I use tableView.reloadData but how can I refresh the label data using the modal segue ? or basically have the label change to the new value.
detailsViewController
var selectedItem: Item!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
self.tableView.reloadData()
titleLabel.text = selectedItem.title
}
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.text = selectedItem.title
}
EditViewController
#IBAction func save(sender: AnyObject) {
selectedItem.title = editTitle.text
var error: NSError?
if context!.save(nil){}
context?.save(&error)
self.presentingViewController?.viewWillAppear(true)
self.dismissViewControllerAnimated(true, completion: {});
}
PS: I tried to do a work around by using another segue to go back to the first view but that crashed, does anybody know why ?
You can use a NSNotification, they are pretty handy for this sort of thing, here's an example of some generic usage:
Parent View Controller
In viewDidLoad:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "udpateObject:", name: "udpateObject", object: nil)
In deinit:
NSNotificationCenter.defaultCenter().removeObserver(self, name: "udpateObject", object: nil)
Then you'll make a func that matches the selector of the observer:
func udpateObject(notification: NSNotification) {
// here you'll get the object you update in a different view
if let receivedObject = notification.object as? YOUR_OBJECT_DATA_TYPE {
self.ThisInstanceVariable = receivedObject
// Update any UI elements
}
}
Update View Controller
Wherever you update your data:
NSNotificationCenter.defaultCenter().postNotificationName("udpateObject", object: YOUR_UPDATED_OBJECT)
How can I handle global events triggered by the notification centre for example in my API class I fire an event if an error response is received e.g. (500). When that event is fired an UIAlert should be displayed on what ever view controller is active, or on logout the login view controller should be presented.
As far as I can see there is no easy way to get the current view controller in order to interact with it. (Note that my root view controller is NOT a navigation controller).
An alternative solution, that will work regardless of whether your view controllers are embedded in a UINavigationController or not, would be to subclass UIViewController. This class will handle receiving the NSNotification that an error occurred and will also handle displaying the alert:
class MyViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "errorOccured",
name: "ErrorNotification",
object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "ErrorNotification", object: nil)
}
func errorOccured() {
// Present an UIAlertViewController or the login screen.
}
}
Now, any UIViewControllers that should display an alert when the error notification is posted just have to be a subclass of MyViewController. Just make sure, if you override viewWillAppear or viewWillDisappear, that you call super.viewWillAppear or super.viewWillDisappear.
Is this way too hard to get current view controller ( when not using navigation controller ) ?
// on your app delegate
getCurrentViewController(self.window!.rootViewController!)
func getCurrentViewController(viewController:UIViewController)-> UIViewController{
if let navigationController = viewController as? UINavigationController{
return getCurrentViewController(navigationController.visibleViewController)
}
if let viewController = viewController?.presentedViewController {
return getCurrentViewController(viewController)
}else{
return viewController
}
}
For BroadCast Notification
NSNotificationCenter.defaultCenter().postNotificationName("erro400", object: nil)
For Receive
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "ErroOccure", name: "erro400", object: nil)
}
func ErroOccure()
{
//present alert from here
// do whatever you want
}
You have to Remove Notification when you finish with it.
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}