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)
}
Related
Why isn't iOS calling viewWillAppear when our application is returning from the background, even when I've set UIModalPresentationStyle.FullScreen?
viewWillAppear is a function that responds to a view controller's state change. Background and foreground states are different; they are done at an app level.
You can still respond app state changes by using notifications:
override func viewDidAppear(_ animated: Bool) {
// ...
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func didReceiveForegroundNotification() {
// app returned from the background
}
Your view controller will listen to events until it is deallocated or removed as an observer. If you don't want to execute code when the view controller has dissappeared, you can do it on viewDidDisappear:
override func viewDidDisappear(_ animated: Bool) {
// ..
NotificationCenter.default.removeObserver(self)
}
Say you have
var someVC: UIViewController
is it possible to essentially do the following, somehow?
get a notification when {
someVC has a viewWillAppear
self.#selector(wow)
}
#objc func wow() {
print("we spied on that view controller, and it just willAppeared"
}
Is that possible ?
(Or maybe on didLayoutSubviews ?)
(I realize, obviously, you can do this by adding a line of code to the UIViewController in question. That's obvious. I'm asking if we can "add on" to it from elsewhere.)
If I understand your question correctly, you want ViewController B to receive a notification when viewWillAppear is called in ViewController A? You could do this through the Notifications framework. Keep in mind that both VC's have to be loaded for one to receive a notification.
Alternatively, if the two VC's are on the screen at the same time, then I'd recommend a delegate pattern - have VC A tell an overarcing controller class that it's viewWillAppear has been called, and this overarcing controller will then inform ViewController B.
To do this using Notifications:
(This is from memory, so please excuse typos)
class TestClassA: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// To improve this code, you'd pull out the Notification name and perhaps put it into an extension, instead of hardcoding it here and elsewhere.
NotificationCenter.default.post(Notification.init(name: Notification.Name.init(rawValue: "viewControllerAppeared")))
}
}
class TestClassB: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(viewControllerAppeared(notification:)), name: Notification.Name.init(rawValue: "viewControllerAppeared"), object: nil)
}
#objc func viewControllerAppeared(notification: NSNotification) {
print("other viewcontroller appeared")
}
}
Documentation
In order for the ViewController to "do something specific" just before the app goes into the background state...
I am to understand that this sort of thing is generally handled inside the
applicationWillResignActive(_:) method, but this method resides inside the AppDelegate class, not the ViewController.
This is my first time doing lifecycle related stuff on IOS, and so I'm not sure whether to:
1) Call a ViewController method from inside the AppDelegate class. This would mean that I have to change the method from private to public.
2) Have the ViewController implement UIApplicationDelegate
PS - Is it okay to just delete the AppDelegate class as long as the ViewController implements UIApplication delegate instead?
EDIT: I should add that this is a single-page app with only one view controller (well, I suppose it will have a settings view controller eventually... but the 'ViewController' that I am referring to will never be popped off the stack).
Thanks!
Generally you shouldn't delete the AppDelegate unless you have a really good reason. This isn't a good reason.
For your scenario I would investigate using NotificationCenter to observe the UIApplicationWillResignActive event. This event is fired every time the application will enter the background.
For more information see: Apple Docs
e.g.
func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated: animated)
NotificationCenter.default.addObserver(self, selector: #selector(youFunction), name: .UIApplicationWillResignActive, object: nil)
}
func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated: animated)
NotificationCenter.default.removeObserver(self)
}
Use NotificationCenter
In YourViewController
class YourViewController : UIViewController {
override func viewDidAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(reloadTableData(_:)), name: .UIApplicationWillResignActive, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: .UIApplicationWillResignActive, object: nil)
}
}
func reloadTableData(_ notification: Notification) {
}
Normally I can find out when a View Controller appears with
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
This won't be called though if the user presses the home button or for some other reason the app goes to the background and then returns to the foreground. To find out when the app comes to the foreground I can add an observer to the Notification Center.
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("FirstViewController")
}
#objc func appWillEnterForeground() {
print("app in foreground")
}
}
However, my problem is that I have a tabbed app I want to know which View Controller is active when the app comes back into the foreground. The Notification center just sends a general message. Even though I an setting the notification observer in the first tab, it could be on any tab when the app goes into the background.
NSNotification.Name.UIApplicationWillEnterForeground
is a notification thrown by Notification Center. So obviously that is not related to any specific VC. What you can do rather is,
#objc func appWillEnterForeground() {
if self.viewIfLoaded?.window != nil {
// viewController is visible
}
}
Though notification of App entering foreground gets triggered to every viewController observing it, only the VC which is currently loaded and visible will have its code in if condition executed. That gives you a control to decide which VC is currently visible.
EDIT 1:
All that you want to figure out is the top ViewController in navigation stack of TabBarControllerwhen app comes to foreGround, you can add the observer for NSNotification.Name.UIApplicationWillEnterForeground only in UITabBarControllerand in
#objc func appWillEnterForeground() {
var vc : UIViewController = tabBarController.viewControllers![tabBarController.selectedIndex]
while vc.presentedViewController != nil || self.childViewControllers.count != 0 {
if vc.presentedViewController != nil {
vc = vc.presentedViewController!
}
else {
vc = vc.childViewControllers.last!
}
}
print("\(vc) should be the top most vc")
}
Use the Notification Observer and your appWillEnterForeground() or any event which gets fired from the observer in all view controllers under tab controller. So whichever the view controller you came back to will get your notification event get triggered in that particular VC. If you're looking for a centralized solution, this scattered gun approach may not work.
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)