NotificationCenter actions stacking ontop of eachother? - swift

I've got buttons that represent user posts from Firebase.
When the viewAppears I'm redrawing the circles to keep it up to date with information. However, each time I'm redrawing the circles it's messing up the actions I have associated with the buttons by adding 1 onto the amount of times the actions are called.
I have a class that takes care of adding the actions onto the button.
override init(frame: CGRect) {
super.init(frame: frame)
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(self.doubleTapAction(sender:)))
self.addGestureRecognizer(doubleTap)
}
func doubleTapAction(sender : UIButton) {
print("Double tapped")
NotificationCenter.default.post(name: .didDoubleTap , object: nil, userInfo: ["tagTapped" : self.tag])
}
So that code is all done from within my "buttonPost" class.
Then on my mainVC I'm adding observers for the NotificationCenter for .didDoubleTap in viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(self.didDoubleTapOnACircle(sender:)), name: .didDoubleTap , object: nil)
and finally I have the function that handles what I'm wanting it to do:
func didDoubleTapOnACircle(sender: Notification) {
print("double tapped action called")
}
What is happening is when I first load up the page buttons are being drawn in and work as they should. If I doubletap on a circle I get both the "Double tapped" from my class function and the "double tapped action called" from my observer function.
The problem is when I leave the viewcontroller that's in charge of drawing the circles and then I come back to it "didDoubleTapOnCircle" gets called twice, "double tapped action called" gets printed out twice, but "double tapped" gets printed out once. If I were to leave and come back to the main page (aka redrawing the circles 10 times), "double tapped action called" would be printed 10 times and "Double tapped" would still be called one time.
What is causing this? I do not understand why if I leave the view controller and come back I'm not getting duplicate buttons being drawn overtop of the old ones, but the gesture recognizer actions are being stacked ontop of eachother from the last time they were drawn in.
So the tl;dr is I'm representing user posts from firebase as buttons that can be doubletapped on the front page. When I initially load in the buttons work perfectly, the doubletapp action gets called a single time. Each time I leave and come back to the button page the amount of times the didDoubleTapOnCircle function is called is increased by 1.

I needed to remove the observer to the notification. I assumed because I was redrawing the buttons completely that this wasn't necessary but removing the observers fixed the issue.
Apple suggests handling observer listeners in viewWillAppear / Dissapear.
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(self.handleTapped(sender:)), name: .didTap , object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.didDoubleTapOnACircle(sender:)), name: .didDoubleTap , object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(self, name: .didTap, object: nil)
NotificationCenter.default.removeObserver(self, name: .didDoubleTap, object: nil)
NotificationCenter.default.removeObserver(self)
}

Related

Table view not updating when called from another View Controller using NotificationCenter

I have a table view in one view controller. I want to update the table view from another VC. I'm doing this using NotificationCenter like this:
In the table view VC:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.updateTableView), name: NSNotification.Name(rawValue: "updateTableView"), object: nil)
}
#objc func updateTableView() {
print("in func")
DispatchQueue.main.async {
self.listTV.reloadData()
}
}
In the opened VC:
func reloadTV() {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "updateTableView"), object: nil)
}
The print works, but the table view doesn't reload. I can only see it changing if I dismiss the VC with the table view and open it again.
I've followed the suggestions of other answers which is to use DispatchQueue, but that didn't make a difference. What am I missing?
Many thanks!
I would consider doing this by protocol and delegate instead of notifications...
This is how I do it.
When you call tableView.reloadData(). It has no effect if the tableView is hidden (isHidden == true). I believe the behavior is also the same when the tableView is effectively hidden by another ViewController (so effectively hidden).
Solution: A suggestion is to reload the tableView when the view is shown again. Few ways to do that.
one is in viewDidAppear (depends on how you transitioned between ViewControllers),
or
you can keep a reference to the tableView and after you dismiss the second ViewController (in which you were sending the notification) you provide the callback to reload the tableView. Roughly like the snippet, with self being the secondViewController you transitioned to. Dismiss that view
self.dismiss(animated: true, completion: {
self.tableView.reloadData()
}
or you can still use your notification and setup a flag and when the viewDidAppear, you then reloadData() inside viewDidAppear. However for this to work, you need to ensure that viewDidAppear is always called when the view is shown again (this will depend on how you transitioned between views).Simply override that method and print to verify.
There are other ways but the bottom line is tableView.reloadData() will be ignored if it is called when the tableView is not visible on the screen. My guess is it's an optimization made by Apple (no need to reload if no table is showing) or maybe a bug. I think they'll say it's a feature 😄
I suggest instead of using NotificationCenter, to instead set a Bool, tableViewNeedsUpdate, and check for the value of that Bool when the view with the UITableView is presented, and the UITableView is visible and able to accept reloadData() calls. After the reload is complete, set the variable back to false.

Variables are not updated in timer completion block

Here is the scenario:
I want to scroll(or shake) a horizontal collectionView after a page showing up so user could see it scrolling...
I do it with no problem 3 sec after page pops up but I don't want it to do the dance when user reach the collectionView before 2 sec & scrolls it itself.
So here is my solution for it:
var collectionDidScroll = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
collectionDidScroll = true
}
override func awakeFromNib() {
super.awakeFromNib()
Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(showScrollTutorial), userInfo: nil, repeats: false)
}
#objc func showScrollTutorial() {
if !collectionDidScroll {
collectionView.shakeCells()
}
}
The Problem:
The "collectionDidScroll" is update just fine in class, but in timer completion its always false!
It means "collectionDidScroll" is not updating in completion & it has its launch time value.
Notice:
My class is a subclass of UICollectionViewCell
I even tried dispatchQueue with timer & timer with completion block but the results are the same
The comments above answered your question, but to help you tackle similar problems in the future yourself:
Whenever you have problems like this, you should try littering your code (especially scrollViewDidScroll and showScrollTutorial) with print-statements to see if they get called at all and which values they contain. Or you can set breakpoints!
Problem Solved!
I placed 'showScrollTutorial()' function contents inside a 'DispatchQueue' to force it to run in main thread, now it works like a charm!
Thanks for everyone.

Turn off gravity when a user sends the application to the background

I have a simple gravity related game in SpriteKit. Before the game starts gravity is off. When the screen is touched the following code is called:
missile?.physicsBody?.affectedByGravity = true
If you take a message and come back to it I would like gravity to switch off until you touch the screen again.
I'm guessing it is something in the AppDelegate, but I am literally stuck there.
Thank you in advance for any pointers.
First add add an observer for UIApplication.willResignActiveNotification to your mainNode like this:
NotificationCenter.default.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.willResignActiveNotification, object: nil)
now you can pause your mainNode in appMoveToBackground:
#objc func appMovedToBackground() {
mainNode.isPaused = true
}
Make sure to remove the observer in deinit:
deinit {
NotificationCenter.default.removeObserver(self)
}

Force refresh on another ViewController component with swift

I'm trying to pass a trigger between 2 view controller but i can't find something that works ...
I have the main controller with a NSTableView, source linked, View Base.
main Controller class :
class ViewController: NSViewController {
I have an array defined in Global variables.
on the viewDidLoad i add 2 elements in my array, then i use setDelegate and setDataSource.
It works fine.
myLib.myCoins = fillCoinsTable()
print ("My lib has \(myLib.myCoins.count) objects ")
mainCoinTable.setDelegate(self)
mainCoinTable.setDataSource(self)
I have a segue from the WINDOWS CONTROLLER to my second view Controller.
(It's a ToolBar button). The segue is "Sheet" kind.
This second controller allows me to add an element in my global variable arrays, with a button "Save" .
Code on the second controller
#IBAction func addCoinButton(sender: AnyObject) {
//We add the coin
let newCoin:coin = coin(n: textName.stringValue)
newCoin.Year = Int.init(textYear.stringValue)
myLib.myCoins.append(newCoin)
self.dismissController(self)
}
If i add a button on the main controller, to reloadData on the TableView, it works fine. The third element is added.
but i would like that to be automatic ....
I tried segue, but mine is not between view controller but with the windows controller.
Can you please help ?
Thanks,
Nicolas
I think, you want to add data from oneViewController and without pressing button, you want after navigation or pressing back button, you want to reload automatically tableview to show updated results, right.
If I'm not wrong then, this solution will work for you.
Follow this below steps:
Step 1 :
Add this code in your view controller, from which you want to add data in array or in database, instead of button click.
override func viewDidLoad()
{
super.viewDidLoad()
let backItem = UIBarButtonItem(title: "Back", style: .Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backItem
}
func goBack()
{
NSNotificationCenter.defaultCenter().postNotificationName("load", object: nil)
self.navigationController?.popViewControllerAnimated(true)
}
Step 2:
Now its final step.
Now add this below code, to your result view controller, where you want to automatically update the result.
func loadList(notification: NSNotification){
self.TableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadList:",name:"load", object: nil)
}
Now implement this code-stuff in your coding. Hope it works for you.
If you want the table to reload automatically when the View appears add this to viewWillAppear
self.tableView.performSelectorOnMainThread(#selector(UITableView.reloadData), withObject: nil, waitUntilDone: true)

NSTimer with drawing barChart in Background

I'm using an NSTimer in an iOS App in background, which is saving some data every 30 seconds in an array. The app shows the last 10 values (values of 5 minutes) in a linechart.
My problem is to use the function of saving data into the array every 30 seconds also in background, when the app isn't on screen. I've written a lot of themes about this, but I don't understand it.
My timer is the following:
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: (#selector(ViewController.counting)), userInfo: nil, repeats: true)
func counting() {
timerCounter += 1 //Int
if timerCounter%30==0 {
arrayOfValues.append(...) //Appending the array
reloadLineChart() // reload chart
}
}
Could anyone show me how to solve this? I know, there must be something with the background-methods in the ViewController, but don't now what to type in.
I think there must be a function, which is counting in background and a function that is reloading the chart when I'm back in the app.
I might understand that you don't want to declare the counter in app delegate, for whatever reason you might have,although I would recommend it. However you can call the functions I mentioned, from the same class in which you have defined the counter. You would need to call it like this:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "applicationWillResignActive:",
name: UIApplicationWillResignActiveNotification,
object: nil)
}
func applicationWillResignActive(notification: NSNotification) {
// stop your counter
}
Then do the same with the other function. Hope is clear.
In your app delegate you can use this method :
func applicationWillResignActive(application: UIApplication) {}
The code you add in this function will run right before your app goes in the background . Therefore you can write there the code to stop the counter.
Afterwards you need to use the following function to activate the counter again:
func applicationWillEnterForeground(application: UIApplication) {}
The code you write in this function will run when you come back to the app again. Hope it is clear enough.