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
Related
I have a mainViewController containing a tableView and each cell pushes a specific customViewController. When it gets popped I want to trigger different function. So my question is how to I identify the viewController that got popped after it got popped.
The first log reads
lastObject = Optional(<Project.ViewController: 0x61900009c480>) but in general the idea does not work. Cannot find anything similar on the web. Any ideas? Thanks in advance
override func viewDidAppear(_ animated: Bool) {
print("lastObject = \(String(describing: (self.navigationController?.viewControllers.last)))")
if ((navigationController?.viewControllers.last?.isKind(of:WebViewController.self)) == true){
print("last is web")
}else{
print("is not web")
}
Only way is you could subclass the UINavigationController and override - popViewControllerAnimated. I haven't tried this yet, but possibly a solution.
Maybe not a best solution but for an temp solution:
If you have have very less cell in the UITableView then you could save the ViewController name in UserDefaults when the viewController is popped and validate the name in the viewDidAppear of mainViewController
override func viewDidAppear(_ animated: Bool) {
if (UserDefaultsValue == "FirsViewController"){
print("last is FirsViewController")
} else if(UserDefaultsValue == "SecondViewController") {
print("last is SecondViewController")
}
}
Create one protocol for all controllers something like this
protocol WhichPoppedDelegate: class {
func controllerDidPopped(controller: UIViewController)
}
assign delegates for each in mainContorller and just call that protocol before you call
navigationController?.popViewController(animated: true)
It is possible in some cases (iPhone X, iOS 13) to dismiss presented view controllers with a gesture, by pulling from the top.
In that case, I can't seem to find a way to notify the presenting view controller. Did I miss something?
The only I found would be to add a delegate method to the viewDidDisappear of the presented view controller.
Something like:
class Presenting: UIViewController, PresentedDelegate {
func someAction() {
let presented = Presented()
presented.delegate = self
present(presented, animated: true, completion: nil)
}
func presentedDidDismiss(_ presented: Presented) {
// Presented was dismissed
}
}
protocol PresentedDelegate: AnyObject {
func presentedDidDismiss(_ presented: Presented)
}
class Presented: UIViewController {
weak var delegate: PresentedDelegate?
override func viewDidDisappear(animated: Bool) {
...
delegate?.presentedDidDismiss(self)
}
}
It is also possible to manage this via notifications, using a vc subclass but it is still not satisfactory.
extension Notification.Name {
static let viewControllerDidDisappear = Notification.Name("UIViewController.viewControllerDidDisappear")
}
open class NotifyingViewController: UIViewController {
override open func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.post(name: .viewControllerDidDisappear, object: self)
}
}
There must be a better way to do this?
From iOS 13 Apple has introduced a new way for the users to dismiss the presented view controller by pulling it down from the top. This event can be captured by implementing the UIAdaptivePresentationControllerDelegate to the UIViewController you're presenting on, in this case, the Presenting controller. And then you can get notified about this event in the method presentationControllerDidDismiss. Here is the code example :-
class Presenting: UIViewController, UIAdaptivePresentationControllerDelegate {
func someAction() {
let presented = Presented()
presented.presentationController?.delegate = self
present(presented, animated: true, completion: nil)
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
// Only called when the sheet is dismissed by DRAGGING.
// You'll need something extra if you call .dismiss() on the child.
// (I found that overriding dismiss in the child and calling
// presentationController.delegate?.presentationControllerDidDismiss
// works well).
}
}
Note:
This method only gets triggered for dismissing by swiping from the top and not for the programmatic dismiss(animated:,completion:) method.
You don't need any custom delegate or Notification observer for getting the event where the user dismisses the controller by swiping down, so you can remove them.
Adopt UIAdaptivePresentationControllerDelegate and implement presentationControllerDidAttemptToDismiss (iOS 13+)
extension Presenting : UIAdaptivePresentationControllerDelegate {
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
presentationController.presentingViewController.presentedDidDismiss(self)
}
}
UIPresentationController has a property presentingViewController. The name is self-explanatory. You don't need the explicit delegate protocol.
The method is actually called to be able to show a dialog for example to save changes before dismissing the controller. You can also implement presentationControllerDidDismiss()
And do not post notifications to controllers which are related to each other. That's bad practice.
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) {
}
What is the iOS equivalent for onRestart() used on Android?
onRestart() is called when current activity is being re-displayed to the user (the user has navigated back to it).
I believe you need viewWillAppear method:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//your code here
}
It is called every time right before view is going to be shown. So it will be called when view is shown for the first time as well.
If you want to avoid running your code for the first time viewWillAppear is called, you will have to add a flag property and check if it has been set previously.
If you're trying to capture whenever the scene in question comes into view, there are two cases you might be concerned about:
If you transition to this scene (or dismissing/popping back to this scene) from within the app. In that case, use viewWillAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
update() // your routine for updating what is displayed to the user
}
If your app is running and you press the "home" button (or go to another app), and then later return to the your app (before it is terminated), viewDidAppear is not called. To detect that scenario, you can observe .UIApplicationDidBecomeActive:
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(forName: .UIApplicationDidBecomeActive, object: nil, queue: nil) { [weak self] notification in
self?.update()
}
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
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)
}