I'm trying to work out the best way to update a view when the app comes into the foreground. Originally I had assumed that viewWillAppear would do the trick, but it appears I was incorrect.
I understand from other posts the correct way to do this is with the Notification Center:
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)`
So far so good; this allows me to work out when this happens correctly. However it's possible I may have several views in a stack and I don't want lots of updates happening unless the view is actually visible.
I thought the following code would allow me to do this:
#objc func willEnterForeground() {
if(self.isBeingPresented) {
updateView()
}
}
But unfortunately isBeingPresented is always false when the view is restoring from the background.
Does anybody have any suggestions on the best way to tell if the current view is the 'top' view in the stack?
Related
I'm working on a simple notes app for macOS. I have a home page which is has a NSTableView that displays all your notes, when you click the new note button a new View appears where you can create a new note. Once you click the note it adds the new note to the database and should reload the table view data, but I need to stop the current run and run the program again to see the changes.
I used this post to achieve the same effect on iOS but it seems to not work on MacOS
So how do I adapt:
override func viewDidLoad() {
super.viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
In the home page VC
and the line:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
Inside of the saveNewNote IBAction to work in macOS? also are you even able to use the NotificationCenter in macOS apps or is it only on iOS?
NSNotificationCenter is part of Foundation framework, so it's definitely available on macOS.
You should be able to use it the same way you've been using it on iOS.
If you have an IBAction called saveNewNote, inside that method you can posy the notification the way you wrote.
In the view controller which owns the table, add the observer like you wrote, and reload the table...
If it doesn't work, we might need some code example of how you set it up on the Mac app the better understand what isn't working.
I have this code, taken from this answer: https://stackoverflow.com/a/29099066/406322
extension NSNotificationCenter {
func setObserver(observer: AnyObject, selector: Selector, name: String?, object: AnyObject?) {
NSNotificationCenter.defaultCenter().removeObserver(observer, name: name, object: object)
NSNotificationCenter.defaultCenter().addObserver(observer, selector: selector, name: name, object: object)
}
}
Now, in my view controller, I am setting my observers in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
setObservers()
}
func setObservers() {
NSNotificationCenter.defaultCenter().setObserver(self, selector: #selector(BaseController.handleComment(_:)), name: "newComment", object: nil)
}
However, even with using this extension, where the observer is removed before getting added, each time I exit the view controller, and return to it, I get multiple notifications (one extra each time).
How is this possible?
If you need this setObserver extension, you are very likely doing something wrong. You should be able to balance your registration and removal easily. If you can't, your notification management is very likely too complicated or in the wrong place.
Typically the correct place to add observations is in viewWillAppear (or viewDidAppear, either is fine), and remove them in viewDidDisappear (or viewWillDisappear). This ensures that you do not receive notifications while you are offscreen, even if the view controller still exists (which is common).
If your view controller requires that it receive notifications while it is offscreen, then you have a design problem. View controllers should only manage onscreen views. If they're doing anything else, you have put too much of the model into the controller.
As #rmaddy notes, your specific problem is likely that you have two instances of this view controller. That's may be fine or it might be a mistake (it depends on how the view controller works). But if you balance adding and removing your registration when going on and offscreen, that part will be fine.
I would like to let the user know when new content is available via the image of a button. Take snapchat for instance: the user is aware of new snaps because the left button turns red (on the mainView). I would like to do the same.
I already processed the notification in the background in
didReceiveRemoteNotification. Thanks to this post, I was able to awake the app in the background when a new APN arrives.
Now I would like to update the image of a button straight from applicationWillEnterForeground. This is the only way that comes to my mind as the view is already loaded when the user clicks on the app (not the banner or the notification center).
Any help would be greatly appreciated. Thank you.
If anybody ever comes across the same problem...
Trying to interact directly with a ViewController from the AppDelegate was obviously a terrible idea. Instead I came across this post that tells me how to know when a ViewController enters the foreground. Here is #nsgulliver answer:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"doYourStuff", name:
UIApplicationWillEnterForegroundNotification, object: nil)
}
func doYourStuff(){
// your code
}
Then remove the observer:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
I have created a local notification in Swift which gives the option to end a current game without having to go back in to the app. That's all fine and works as it should. The issue I'm having is that if the user does this, I don't want them to go back to the Game view controller if that happens to be the last view that was open when the app entered the background. I would like them to go back to the app's Home view controller instead.
I expected to be able to add a perform segue to my Game view controller in the following way, should the criteria match. I tried adding it to viewDidAppear(), but it didn't work:
override func viewDidAppear(animated: Bool) {
if isThereACurrentGame() == false {
performSegueWithIdentifier("unwindToHomeScreen", sender: self)
}
}
Is this something to do with viewDidAppear() not being called when the app comes back to the foreground? If so, what might an alternative be?
P.S. My isThereACurrentGame() function works as it should, as does the performSegueWithIdentifier elsewhere in the view controller, so these aren't the cause of the problem.
In Objective-C for Cocoa Apps it's possible to use such way to keep window always on top?
How to achieve the same with Swift?
self.view.window?.level = NSFloatingWindowLevel
Causes build error Use of unresolved identifier 'NSFloatingWindowLevel'
To change the window level you can't do it inside viewDidload because view's window property will always be nil there but it can be done overriding viewDidAppear method or any other method that runs after view is installed in a window (do not use it inside viewDidLoad):
Swift 4 or later
override func viewDidAppear() {
super.viewDidAppear()
view.window?.level = .floating
}
For older Swift syntax check the edit history
I would prefer this way. This ignores all other active apps, and makes your app upfront.
override func viewWillAppear() {
NSApplication.sharedApplication().activateIgnoringOtherApps(true)
}
While the other answers are technically correct - when your app will or did resigns active, setting the window level to .floating will have no effect.
.floating means on top of all the windows from the app you are working on, it means not on top of all apps windows.
Yes there are other levels available you could set, like kCGDesktopWindowLevel which you can and should not set in swift to make your window float above all.
None of them will change the fact that your window will go behind the focused and active apps window. To circumvent i chose to observe if the app resigns active notification and act accordingly.
var observer : Any;
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(
forName: NSApplication.didResignActiveNotification,
object: nil,
queue: OperationQueue.main ) { (note) in
self.view.window?.level = .floating;
// you can also make your users hate you, to take care, don't use them.
//NSApplication.shared.activate(ignoringOtherApps: true)
//self.view.window?.orderFrontRegardless();
}
}
another way could be subclassing NSWindow and override the property .level with an always returning .floating, but the code above is less work and keeps control in the place where you want to set the window floating.
I spent a long time trying to make this work. I then realized there was a simple answer, just worded in a different way. Here it is: Change macOS window level with SwiftUI (to make a floating window)
As explained there:
You can access your windows with NSApplication.shared.windows and set the level for each one.
for window in NSApplication.shared.windows {
window.level = .floating
}
EDIT: you can use other levels, including .screenSaver (highest, I think) and ```.normal`` if you want to return to standard behavior. Source: https://developer.apple.com/documentation/appkit/nswindow/level