NotificationCenter obersever not called - swift

I have UITabBarController and two ViewControllers.
In the first vc I have a video, after it ends the data transfer to another controller is triggered. But unfortunately nothing happens in the second controller.
func fetchCaloriesData() {
refWorkout = workout!.title
Database.database().reference().child("programs").child(refWorkout).child("calories").observeSingleEvent(of: .value) { (snapshot) in
self.caloriesData = snapshot.value as! String
print(self.caloriesData)
self.dictionary["calories"] = self.caloriesData
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "sendCalories"), object: nil, userInfo: self.dictionary)
print("Заполнили словарь")
print(self.dictionary["calories"])
//self.caloriesDelegate?.update(newText: caloriesData)
// self.dietVC.BurnedLabel.text? = caloriesData
}
}
Everything is work. When I try print I can see: Optional ("...")
In second vc in viewDidLoad I have next code.
NotificationCenter.default.addObserver(self, selector: #selector(gotNotification), name: NSNotification.Name("sendCalories"), object: nil)
and next
#objc func gotNotification(notification: Notification) {
guard let userInfo = notification.userInfo else {return}
guard let calories = userInfo["calories"] as? String else {return}
print("Наблюдатель")
print(calories)
BurnedLabel.text! = calories
}
Nothing happens. What am I doing wrong ?

NotificationCenter.default.addObserver(self, selector: #selector(gotNotification)
this will be called only if view is loaded before notification is triggered,
so you need to pass data in different way when you tapped to open second view controller. After that observer is in on state, so he is monitoring changes. Basically if you downloading something asynchronous and that is not finished while you open second view controller, after that if downloading is done, your second view will be updated, be sure to update UI on main thread. In your case make variable with calories object in second controller, then from first controller update main tab bar controller(add calories property there as well) with delegate method(make protocol to pass calories) so you don't shoot notification everywhere and from there update second controller.
That is how you will have fresh data always.
guard let firstController = controllers[0] as? FirstController else {return}
firstController.caloriesDelegate = self
guard let secondViewController = controllers[0] as? SecondController else {return}
secondViewController.calories = self.calories // in main tab bar after updating

Related

JSQMessagesviewcontroller can't perform segues

so I created a chat view controller using the JSQMessagesViewController following this tutorial here: https://learnappmaking.com/chat-app-ios-firebase-swift-xcode/#comment-1930 my code is more or less the same, I didn't tweak anything significant in it, the tutorial is only for a single view controller so I added another view controllers for the app but every time it perform segues, I get the error SIGABRT, no matter if I segues with performSegue or with the back button in navigation bar, it keeps giving signal SIGABRT. any help would be appreciated.
this is my viewdidload:
override func viewDidLoad() {
super.viewDidLoad()
senderId = "1111"
senderDisplayName = "Bob"
title = "Steve"
inputToolbar.contentView.leftBarButtonItem = nil
collectionView.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
let query = Constants.refs.databaseChats.queryLimited(toLast: 10)
_ = query.observe(.childAdded, with: { [weak self] snapshot in
if let data = snapshot.value as? [String: String],
let id = data["sender_id"],
let name = data["name"],
let text = data["text"],
!text.isEmpty
{
if let message = JSQMessage(senderId: id, displayName: name, text: text)
{
self?.messages.append(message)
self?.finishReceivingMessage()
}
}
})
// Do any additional setup after loading the view.
}
SIGABRT (signal abort) is typically from a referencing error in your storyboard. Did you ever change the name of a class or make a connection from a button of one view controller to another and then delete it? If you changed the name of a class you must make the sure the name in the code of the class matches that. If you deleted a button connection between view controllers, click on the controller itself and under the connections tab you must delete it.

Handle deep link notification

I'm adding deep linking to my app and getting stuck at handing off the URL to the view controller. I try to use notification but it doesn't work all the times.
My App Delegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let notification = Notification(name: "DeepLink", object: url)
NotificationCenter.default.post(notification)
return true
}
}
And View Controller:
class ViewController: UIViewController {
func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.handleDeepLink), name: "DeepLink", object: nil)
}
#objc private func handleDeepLink(notification: Notification) {
let url = notification.object as! URL
print(url)
}
}
Problem: when my app is cold launched the notification is not handled, which I guess is due to the View Controller not having time to register itself as the observer yet. If I press the Home button, go to Notes and click on the deep link a second time, everything works as it should.
Question: how can I register the View Controller to observe the notification when the app is cold launched? Or is there a better way to send the URL from the App Delegate to the View Controller?
I think an initial assumption is to assume your view controller does not have time to register itself which means that the connection between URL and View controller must be decoupled and reside outside of the view controller.
I would then use some kind of lookup to instantiate the view controller when a URL is received.
For example,
if url.contains(“x”) {
let vc = ViewController(...)
cv.url = URL // Pass contextual data to the view controller.
// Present or push cv
}
As your app gets more complex you have to manage more challenging scenarios, like standing-up entire controller stacks, or removing presented controllers.
I followed the example in the LaunchMe app. Here's what solved my problem:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
// My root view controller is a UINavigationController
// Cast this to whatever class your root view controller is
guard let navigationController = self.window?.rootViewController as? UINavigationController else {
return false
}
// Navigate to the view controller that handles the URL within the navigation stack
navigationController.popToRootViewController(animated: false)
// Handle the URL
let vc = navigationController.topViewController as! ViewController
vc.handleDeepLink(url: url)
return true
}
Store if notification is not handled and load it when viewDidLoad.
var pendingNotificationInfo: PushNotificationInfo?

Why is an old view model responding to a notification?

I’m creating a weightlifting calculator application (Swift 4) using MVVM and have been trying for 2 days to figure out why a view model that should have died is still responding to a UserDefaults.defaultsDidChange event notification.
I launch the app:
At launch, in the AppDelegate, I create a new lift event object and use it to initialize a new CalculatorLiftEventViewModelFromLiftEvent for the `CalculatorViewController':
I calculate a lift and save it
I tap the + button to create a new lift:
this causes a new, empty lift event object to be created
this new lift event object is used to initialize a new CalculatorLiftEventViewModelFromLiftEvent object
this new CalculatorLiftEventViewModelFromLiftEvent is then assigned to the CalculatorViewController's viewModel property, replacing the one created when the app launched
the values on the calculator screen are zeroed out, ready for a new lift event to be entered
I tap the Settings button to go to Settings where I change the Formula associated with the current lift event.
The new Formula is saved as the default and the UserDefaults.defaultsDidChange notification is fired
HERE’S THE PART I CAN’T FIGURE OUT: the original view model is still alive and it’s still listening for UserDefault notifications. When I close the Settings screen and go back to the Calculator view, the values from the prior lift event that had been cleared out now reappear.
Here’s what happens when the + (new) button on the Calculator screen is tapped:
#objc fileprivate func onNewButtonTapped(_ sender: UIBarButtonItem) {
let newLiftEvent = dataManager.createNewLiftEvent()
viewModel = CalculatorLiftEventViewModelFromLiftEvent(withLiftEvent: newLiftEvent, dataManager: dataManager)
setupView()
}
Here’s how the CalculatorLiftEventViewModelFromLiftEvent is initialized:
init(withLiftEvent liftEvent: LiftEventRepresentable, dataManager: CoreDataHelper) {
self.modelLiftEvent = liftEvent
self.liftName = Dynamic("\(modelLiftEvent.lift.liftName)")
self.weightLiftedTextField = Dynamic(modelLiftEvent.liftWeight.value)
self.repetitionsTextField = Dynamic("\(modelLiftEvent.repetitions)")
self.oneRepMaxTextField = Dynamic(modelLiftEvent.oneRepMax.value)
self.unitsTextField = Dynamic("\(UserDefaults.weightUnit())")
self.weightPercentages = Dynamic( [ : ] )
self.dataManager = dataManager
super.init()
subscribeToNotifications()
}
UPDATE: Here are the deinit and the addObservers in CalculatorLiftEventViewModelFromLiftEvent. Notice I'm not using block-based observations.
deinit {
print("I got to the deinit method")
unsubscribeFromNotifications()
}
func subscribeToNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(liftNameDidChangeNotification(_:)),
name: NSNotification.Name(rawValue: LiftEventNotifications.LiftNameDidChangeNotification),
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(weightUnitDidChangeNotification(_:)),
name: NSNotification.Name(rawValue: LiftEventNotifications.WeightUnitDidChangeNotification),
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(roundingOptionDidChangeNotification(_:)),
name: NSNotification.Name(rawValue: UserDefaultsNotifications.roundingOptionDidChangeNotification),
object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.defaultsDidChange), name: UserDefaults.didChangeNotification,
object: nil)
}
--- END UPDATE
I pass the modelLiftEvent when segueing to the SettingsViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case a:...
case b:...
case "SettingsSegue":
if let nav = segue.destination as? UINavigationController {
let destinationViewController = nav.topViewController as! SettingsViewController
destinationViewController.dismissalDelegate = self
let settingsViewModel = SettingsViewModelFromLiftEvent(withLiftEvent: self.viewModel.modelLiftEvent)
destinationViewController.settingsViewModel = settingsViewModel
destinationViewController.dataManager = dataManager
settingsViewModel.dataManager = dataManager
}
Finally, in CalculatorLiftEventViewModelFromLiftEvent, I’ve put a break point here because this is called when the view model hears the UserDefaults.defaultsDidChange notification. At this point, I have also verified that this CalculatorLiftEventViewModelFromLiftEvent is the old one, not the new one created when I tapped the + button:
#objc func defaultsDidChange(_ notification: Notification) {
let oneRepMax = modelLiftEvent.calculateOneRepMax()
guard oneRepMax.value != 0.0 else { return }
let weightPercentages = getWeightPercentages(weight: oneRepMax.value)
self.weightPercentages.value = weightPercentages
weightLiftedTextField.value = modelLiftEvent.liftWeight.value
repetitionsTextField.value = "\(modelLiftEvent.repetitions)"
oneRepMaxTextField.value = modelLiftEvent.oneRepMax.value
}
I've read through a bunch of documentation about the life cycle of objects but haven't found anything that helps. I expect that when the new CalculatorLiftEventViewModelFromLiftEvent is created and assigned to the `CalculatorViewController''s viewModel property, it would replace the reference to the old one and it would cease to exist. Evidently, that's not what's happening.
Does anyone have any idea why when I go from the Calculator view (step 3) that has no values (except for 0.0) to the Settings and then come back, the prior lift event values are displayed?
I've fixed the problem of the prior liftEvent being displayed after clearing the calculator, changing the default formula, and coming back to the calculator screen.
On CalculatorViewController, when the + button is tapped, instead of creating a new viewModel and assigning it to the viewModel property, I'm asking my AppDelegate to create both a new CalculatorViewController and CalculatorLiftEventViewModelFromLiftEvent by using the launchCalculatorViewController method which does this when the app launches.
The original code in CalculatorViewController:
#objc fileprivate func onNewButtonTapped(_ sender: UIBarButtonItem) {
let newLiftEvent = dataManager.createNewLiftEvent()
viewModel = CalculatorLiftEventViewModelFromLiftEvent(withLiftEvent: newLiftEvent, dataManager: dataManager)
self.percentagesTableView.reloadData()
setupView()
}
Now the new code in CalculatorViewController:
#objc fileprivate func onNewButtonTapped(_ sender: UIBarButtonItem) {
(UIApplication.shared.delegate as? AppDelegate)?.launchCalculatorViewController()
}
and in AppDelegate:
func launchCalculatorViewController() {
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let initialViewController: CalculatorViewController = mainStoryboard.instantiateInitialViewController() as? CalculatorViewController {
self.window?.rootViewController = initialViewController
let liftEvent = dataManager.createNewLiftEvent()
let viewModel = CalculatorLiftEventViewModelFromLiftEvent(withLiftEvent: liftEvent, dataManager: dataManager)
initialViewController.viewModel = viewModel
initialViewController.dataManager = dataManager
self.window?.makeKeyAndVisible()
}
}
Unfortunately, I determined that CalculatorLiftEventViewModelFromLiftEvent objects are never being deallocated which tells me I've got a strong reference cycle that won't let go:
That will have to be another SO question.

My cells are duplicating themselves

I am new to swift and I am trying to make this note app. I have split view controller that goes in my first view controller and that view controller connects to a table view controller. Everything works perfectly is just that when I launch the app I have all the notes like I want but when I try to go back to my first view controller and come back to my table view controller, all the notes are duplicated every single time I do it. I tried everything I can try, is there anyone who can help me
my MasterViewController is
import UIKit
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
override func viewDidLoad()
{
super.viewDidLoad()
Note.loadNotes() // The problem is here, I think
noteTable = self.tableView
// Do any additional setup after loading the view, typically from a nib.
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
navigationItem.rightBarButtonItem = addButton
if let split = splitViewController
{
let controllers = split.viewControllers
detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
My loadNotes function is
class func loadNotes()
{
let defaults:UserDefaults = UserDefaults.standard
let saveData: [NSDictionary]? = defaults.object(forKey: kAllNotes) as? [NSDictionary]
if let data:[NSDictionary] = saveData
{
for i:Int in 0 ..< data.count
{
let n:Note = Note()
n.setValuesForKeys(data[i] as! [String : Any])
allNotes.append(n)
}
}
}
Your loadNotes method keeps appending. The first line of loadNotes should be:
allNotes = [Note]()
Then it starts with an empty array and fills it up.
And why is loadNotes a static method? That's a bad design. Make Notes a normal class and make loadNotes an instance method.
On an unrelated note (no pun intended), do not use UserDefaults to store app data. Only use it to store little bits of information.

Function Calling After ViewController Is Dismissed

I have a func that calls itself over and over again for a simple little slideshow.
func animateImage(no:Int)
{
println("ANIMATE")
var imgNumber:Int = no
let t:NSTimeInterval = 1;
let t1:NSTimeInterval = 0;
let url = NSURL(string: self.itemss[imgNumber][0])
let data = NSData(contentsOfURL: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check
imgView!.alpha = 0.4
self.imgView?.image = UIImage(data: data!)
//code to animate bg with delay 2 and after completion it recursively calling animateImage method
UIView.animateWithDuration(2.0, delay: 0, options:UIViewAnimationOptions.CurveEaseOut, animations: {() in
self.imgView!.alpha = 1.0;
},
completion: {(Bool) in
imgNumber++;
if imgNumber>self.itemss.count-1 //only for 4 image
{
imgNumber = 0
}
self.animateImage(imgNumber);
})
}
The problem I'm having is that the func seems to continue to call and doesn't seem to be stopping when i dismiss the view controller ? Why is this func continuing to call after the view controller is dismissed.
#IBAction func dismissMe(sender: AnyObject) {
if((self.presentingViewController) != nil){
self.dismissViewControllerAnimated(false, completion: nil)
println("done")
}
}
The reason that your function is continuing to be called after the view controller is dismissed is because there's no logic that should prevent it from discontinuing to call itself once it is dismissed.
A dismissed view controller should cease to have a parent view controller, however, so we could put an end to this loop by checking if there's a parent view controller at the top of the method:
if self.parentViewController == nil {
return;
}
If this is the first thing your method does, then the method should only continue to execute (and line up another call to itself) if the view controller still has a parent.