I would like to perform a function after the windowDidResize event ended. While this block is running, it prints the following:
NotificationCenter.default
.publisher(for: NotificationName.windowDidResize)
.sink { _ in
// Perform something
print("WindowDidResize")
}.store(in: &cancellableBag)
Print output:
WindowDidResize
WindowDidResize
WindowDidResize
WindowDidResize <-- Want to run a function here
How can I perform a function after the last windowDidResize only once? I'd like to hide some UI elements while the window is being resize (because it make operation sooo slow) and redraw them after the window was resized (with delay of 0.25 sec or something else).
Swift 5.3 | Xcode 12.2 | macOS 11.1
You can implement the optional method windowDidEndLiveResize which will be called only once at the end of the window resizing:
You can also monitor didendliveresizenotification
import Cocoa
class ViewController: NSViewController, NSWindowDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear() {
super.viewWillAppear()
view.window?.delegate = self
}
func windowDidEndLiveResize(_ notification: Notification) {
print(#function)
}
}
Related
In one of the view controller files in my project, there are two functions, one is called in viewdidload and another is called by Notification and observers. Those functions do exactly the same thing, and I was wondering if I get rid of one of the functions, especially the one without using #objc in front. (otherwise I get an error)
override func viewDidLoad() {
super.viewDidLoad()
configureNotifications()
displayItems()
}
func displayItems() {
fetchLiveEvents { [weak self] in
self?.applySnapshot(animatingDifferences: true)
}
}
func configureNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(updateExistingItem), name: .updateExistingItem, object: nil)
}
#objc func updateExistingItem() {
fetchLiveEvents { [weak self] in
self.applySnapshot(animatingDifferences: true)
}
}
Since I'm using the notification canter, I cannot get rid of #objc in front of updateExistingItem function. However, the updateExistingItem and displayItems are doing exactly something, so I feel it's kinda redundant and I was thinking to get rid of displayItems function from the viewDidLoad and call updateExistingItem (probably change the name) in viewdidLoad instead.
Is there any convention in Swift programming that keeps both #objc and normal function when they are doing the same thing? or is it just a personal preference and doesn't matter to leave both of them?
viewDidLoad just call once when the screen is present if you go to another screen by pushing a viewcontroller or presenting a controller and comeback to this controller the viewDidLoad didn't triggered it will never called again until the next run / terminate the app and open again.
so your function is called by the notification to run again when this screen appear.
// just called once
override func viewDidLoad() {
super.viewDidLoad()
configureNotifications()
displayItems()
}
// just called every time when you popped a viewController or tap on tab bar items using tabbar controller
override func viewWillAppear() {
super.viewDidLoad()
displayItems()
}
in your scenario may be you came back to this screen by present some other screen and do some functionality there and call the notification to be trigger on this screen so nothing will trigger if your present a screen by modal presentation style over full screen
That's why called the notification to start displaying item again
override func viewDidLoad() {
super.viewDidLoad()
configureNotifications()
displayItems()
}
// called once
func displayItems() {
fetchLiveEvents { [weak self] in
self?.applySnapshot(animatingDifferences: true)
}
}
func configureNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(updateExistingItem), name: .updateExistingItem, object: nil)
}
// called every time when you trigger notifcation
#objc func updateExistingItem() {
fetchLiveEvents { [weak self] in
self.applySnapshot(animatingDifferences: true)
}
}
I am reading a file from disk on Mac. The following code in a function keeps making memory usage go up. I don't have any variable to reference the "content", just test to read it in and think the system will release it after the function exit. but it does not. System actually only releases the memory when the for-loop is done.
My question is, how to let system to release the "content" object after the read function exits? I even delay 10s after the function call in the loop.
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func onRead(_ sender: NSButton) {
for n in 0..<100
{
readFile()
sleep(10)//not enough time for system to recycle the memory??
}
}
func readFile()
{
print ("test read file")
let content = FileManager.default.contents(atPath: "/Users/swiftuser/Documents/testfile.txt")
}
}
In Xcode 10.1 with Swift 4.2 I'm having a memory leak when I add a local monitor for key down events in my NSViewController, that it is be instanced as minimal version (without nib and xib).
override func loadView() {
self.view = NSView()
self.view.wantsLayer = true
}
override func viewDidLoad(){
super.viewDidLoad
NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: handler)
}
lazy var handler:(NSEvent)->NSEvent? = { [ weak self ,unowned picker = picker] event in
picker.keyDown(with: event)
return event
}
This memory leak does not have much information:Memory leak
EDIT
In deinit method removeMonitor is called
deinit {
NSEvent.removeMonitor(self)
}
EDIT 2
Issue solved :
override func loadView() {
self.view = NSView()
self.view.wantsLayer = true
}
var monitor:Any? // This is essential
override func viewDidLoad(){
super.viewDidLoad
monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: handler)
}
lazy var handler:(NSEvent)->NSEvent? = { [ weak self ,unowned picker = picker] event in
picker.keyDown(with: event)
return event
}
deinit {
NSEvent.removeMonitor(monitor)
}
From the Apple Docs;
Note
The monitor Block is called for all future events that match mask. You must call removeMonitor(_:) to stop the monitor. Under garbage collection, the monitor (and everything the Block references) will not be collected until removeMonitor(_:) is invoked.
Meaning that the monitor will continue to look for matching events until removeMonitor() is invoked. So your system is using extra memory to keep looking for events, and if you never call this - it could lead to a fairly large memory leak. As it says even with garbage collection, this object is still allocated - because it is looking for events that could take place at any time (so it is not guaranteed that this will be collected). Make sure you call this when you want the system to stop looking for events.
You could also do something like this in your handler.
You can return the event unmodified, create and return a new NSEvent object, or return nil to stop the dispatching of the event.
I believe I understand what the dispatch queue is doing when I call it, but I'm not sure when exactly I should use it and what it's advantages are when I do use it.
If my understanding is correct, DispatchQueue.main.async { // code } will schedule the code contained within the closure to run on the main dispatch queue in an asynchronous manner. The main queue has the highest priority, and is typically reserved for updating UI to maximize App responsiveness.
Where I'm confused is: What exactly is the difference in updating UI elements within a dispatch queue closure versus just writing the code outside the closure in the same spot? Is it faster to execute the code in the body of a view did load method rather than sending it to the dispatch queue? If not, why?
Code Example:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
}
Versus:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
updateUI()
}
}
}
Which one is will update the UI faster?
The primary use of DispatchQueue.main.async is when you have code running on a background queue and you need a specific block of code to be executed on the main queue.
In your code, viewDidLoad is already running on the main queue so there is little reason to use DispatchQueue.main.async.
But isn't necessarily wrong to use it. But it does change the order of execution.
Example without:
class MyViewController: UIViewController {
func updateUI() {
print("update")
}
override func viewDidLoad() {
super.viewDidLoad()
print("before")
updateUI()
print("after")
}
}
As one might expect, the output will be:
before
update
after
Now add DispatchQueue.main.async:
class MyViewController: UIViewController {
func updateUI() {
print("update")
}
override func viewDidLoad() {
super.viewDidLoad()
print("before")
DispatchQueue.main.async {
updateUI()
}
print("after")
}
}
And the output changes:
before
after
update
This is because the async closure is queued up to run after the current runloop completes.
I just ran into the exact situation discribed in your Question: viewDidLoad() calling DispatchQueue.main.async.
In my case I was wanting to modify Storyboard defaults prior to displaying a view.
But when I ran the app, the default Storyboard items were momentarily displayed. The animated segue would finish. And only THEN would the UI components be modified via the code in viewDidLoad(). So there was this annoying flash of all of the default storyboard values before the real values were edited in.
This was because I was modifying those controls via a helper function that always first dispatched to the main thread. That dispatch was too late to modify the controls prior to their first display.
So: modify Storyboard UI in viewDidLoad() without dispatching to the Main Thread. If you're already on the main thread, do the work there. Otherwise your eventual async dispatch may be too late.
The simple Swift 4 example below should stop when the computer's display goes to sleep.
class Observer {
var asleep = false
func addDNC () {
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: nil, using: notificationRecieved)
}
func notificationRecieved (n: Notification) {
asleep = true
}
}
let observer = Observer ()
observer.addDNC ()
while (!observer.asleep) {}
print ("zzzz")
However, the program gets stuck in the while loop. What am I doing wrong, and what is the proper way to wait for a Notification?
I have tried using a selector (#selector (notificationRecieved), with #objc in the function declaration, of course), to no avail.
Start a template app in Xcode and modify the ViewController.swift to do this:
import Cocoa
class Observer {
var asleep = false
func addDNC () {
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: nil, using: notificationRecieved)
}
func notificationRecieved (n: Notification) {
print("got sleep notification!")
asleep = true
}
}
class ViewController: NSViewController {
let observer = Observer ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
observer.addDNC ()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
The difference between your code and mine is that I'm not doing the wacky sleepy polling thing you're doing (that's going to lead to a spinning pizza cursor), and I'm also setting observer to be a property off the ViewController object, so the observer property sticks around as long as the view controller does.