Swift: Network-Request at AppStart in AppDelegate - CompletionHandler in ViewController? - swift

my app is based on a TabBarController with 4 ViewControllers. All 4 of them are dependent of the same data. This is why I would like to load the data at the App start in the AppDelegate.
However, how does the ViewController know, that the request is completed? For example, if there is an error (e.g. no internet connection), how do I pass this error to any of these 4 ViewController to present an alert?

Use NotificationCenter to implement (Swift 3 code):
extension Notification.Name {
static var RequestCompleted = Notification.Name(rawValue: "MyRequestIsCompleted")
static var RequestError = Notification.Name(rawValue: "MyRequestError")
}
class DataRequest {
func request() {
// if there is error:
let error = NSError(domain: "Error Sample", code: 0, userInfo: nil)
NotificationCenter.default.post(name: .RequestError, object: error)
// if the request is completed
let data = "your data is here"
NotificationCenter.default.post(name: .RequestCompleted, object: data)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(requestCompleted(_:)), name: .RequestCompleted, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(requestError(_:)), name: .RequestError, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func requestCompleted(_ notification: Notification) {
if let obj = notification.object {
print(obj)
}
}
func requestError(_ notification: Notification) {
if let obj = notification.object {
print(obj)
}
}
}

Related

Swift NSNotification Observers not working

I have 2 view controllers, one with a switch that when toggled should post the following notification. In the other view controller I have Observers which should trigger the following function which just toggles a boolean. I am not able to get the observers to work and make that function call, am I doing something wrong here? I have another Notification (Doesn't trigger with user input) that is being sent in the opposite direction which works fine.
#IBAction func switchAction(_ sender: Any) {
if switchUI.isOn {
print("Collecting Data ")
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "Collect"), object: self)
}
else
{
print("Not Collecting Data")
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "Do Not Collect"), object: self)
}
}
func collectDataObserver () {
//Add an Observer
NotificationCenter.default.addObserver(self, selector: #selector(CentralViewController.toggleData(notification:)), name: Notification.Name(rawValue: "Collect"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(CentralViewController.toggleData(notification:)), name: Notification.Name(rawValue: "Do Not Collect"), object: nil)
}
#objc func toggleData(notification: NSNotification) {
let isCollectData = notification.name.rawValue == "Collect"
if(isCollectData){
IsCollectingData = true
}
else
{
IsCollectingData = false
}
}
You need to call collectDataObserver() in viewDidLoad() of CentralViewController, i.e.
override func viewDidLoad() {
super.viewDidLoad()
collectDataObserver()
}

How to come back to previous scene and reload data?

I have two Scenes on Storyboard : One to show a list of items with plus button on the Bar to go to another Scene that has form to add a new item. I'm saving on Local Storage, in the saving function I want to come back to the previous page with the new data.
This is the saving function:
#IBAction func AddTrack(_ sender: Any) {
let item = TrackItem(context: PersistenceService.context)
item.kms = Int32(kmsField!.text!)!
item.liters = Float(litersField!.text!)!
item.date = textFieldPicker!.text!
PersistenceService.saveContext()
navigationController?.popViewController(animated: true)
self.TrackList.append(item)
self.tableView?.reloadData()
}
Knowing that I'm using this function in viewDidLoad() and viewDidAppear()
func GetData(){
let fetchRequest: NSFetchRequest<TrackItem> = NSFetchRequest<TrackItem>(entityName: "TrackItem")
do{
let TrackList = try PersistenceService.context.fetch(fetchRequest)
self.TrackList = TrackList
self.tableView?.reloadData()
}catch{
}
}
You can use NotificationCenter or Delegation (Depends on what you are trying to achieve)
Example NotificationCenter:
VC1, takes a photo:
buttonTapped / function () {
// Upload a Photo()
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.dismiss(animated: true, completion: nil)
}
}
VC2, shows all photos:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
#objc func loadList(notification: NSNotification) {
//get data, reload data, etc
}

Removing NSNotificationObserver From Different ViewController

I have an observer that I register in one class as so:
class ViewControllerA: UIViewController {
//create shared class reference
var sharedClassReference_A = SharedClass()
//initialize Notification Observer and store observer reference in sharedClass
override func viewDidLoad() {
super.viewDidLoad()
var observerHandler: Any? = nil
observerHandler = NotificationCenter.default.addObserver(self, selector: #selector(ViewControllerA.appDidTerminate(_:)), name: .UIApplicationWillTerminate, object: nil)
self.sharedClassReference_A.sharedHandler = observerHandler
}
//perform some action when a notification is received
#objc func appDidTerminate(_ notification: NSNotification) {
//perform some action
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueA_X" {
let destinationController = segue.destination as! ViewControllerX
destinationController.sharedClassReference_X = self.sharedClassReference_A
}
}
}
I store a reference to the observer in a shared class:
class SharedClass {
var sharedHandler: Any? = nil
}
I attempt to remove the observer once I reach a different view controller as so:
class ViewControllerX: UIViewController {
var sharedClassReference_X = SharedClass()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//Attempt to remove observer registered in ViewControllerA
if let observerHandler = self.sharedClassReference_X.sharedHandler {
NotificationCenter.default.removeObserver(observerHandler)
}
}
}
I know that removing the observer using this approach is failing, because the observer is getting called after ViewControllerX is deallocated.
My question is: How can I successfully initialize an observer in one class (ViewControllerA) and be able to remove it later in a different class (ViewControllerX)?
I think it's better to follow the general guidelines of setting the observers inside viewDidLoad/viewWillAppear and removing them inside deinit / viewDidDisappear respectively according to your case , as this
NotificationCenter.default.addObserver(self, selector: #selector(ViewControllerA.appDidTerminate(_:)), name: .UIApplicationWillTerminate, object: nil)
returns void
//
class ViewControllerX: UIViewController {
var aRef:ViewControllerA!
}
in prepareForSegue
destinationController.aRef = self
Then use
NotificationCenter.default.removeObserver(aRef, name: NSNotification.Name.UIApplicationWillTerminate, object: nil)

How to unit test if a view controller has subscribed to a notification in swift

I would like to test if MyViewController has subscribed to notifications in the default NSNotificationCenter in swift.
Here is code you can drop right into a Playground. Inline comments explain what's going on.
import UIKit
import XCTest
//this is a useful helper function for delaying an action
func delay(_ delay:Double, closure:#escaping ()->()) {
//credit: Matt Neuberg - http://stackoverflow.com/questions/24034544/dispatch-after-gcd-in-swift/24318861#24318861
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
class MyViewController : UIViewController {
//you just need something you can check to verify the notification was received
var didReceiveMyNotification = false
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "myNotification"), object: nil, queue: nil) {
notification in
//keep track when the notification is received
self.didReceiveMyNotification = true
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
class testMyViewController: XCTestCase {
func testShouldSubscribeToNotification() {
weak var expectation = self.expectation(description: "testShouldSubscribeToNotification")
//if your view controller is in a storyboard, then do this instead
//let viewController = UIStoryboard(name: "MyStoryboard", bundle: Bundle.main).instantiateViewController(withIdentifier: "MyViewControllerIdentifier") as! MyViewController
let viewController = MyViewController()
//force the view to load
let _ = viewController.view
//post the notification
NotificationCenter.default.post(name: Notification.Name(rawValue: "myNotification"), object: nil, userInfo: nil)
//wait however long is needed for the notification to get handled by view controller
delay(0.5) {
print("result=\(viewController.didReceiveMyNotification)")
//if the view controller was properly subscribed, this should be true
XCTAssertTrue(viewController.didReceiveMyNotification)
expectation?.fulfill()
}
//wait for the notification to get handled before allowing the test to exit
waitForExpectations(timeout: 1.0, handler: nil)
}
}
//if you want to run the test in a Playground...
let myTester = testMyViewController()
myTester.testShouldSubscribeToNotification()

Getting the error 'Execution was interrupted, reason: signal SIGABRT' when using NSNotificationCenter

I'm fairly new to swift and have been playing around with NSNotificationCenter in a Playground. I'm getting the error:
Execution was interrupted, reason: signal SIGABRT
when using the postNotificationName function. I cannot figure out exactly why the issue is occurring, any pointers will be helpful!
let notificationKey = "rachel"
class FirstViewController {
var sentNotificationLabel = "Nothing has been sent yet."
func addObserver() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateNotificationLabel", name: notificationKey, object: nil)
}
func notify() {
NSNotificationCenter.defaultCenter().postNotificationName(notificationKey, object: self)
}
func updateNotificationLabel() {
self.sentNotificationLabel = "Notification sent!"
}
}
class SecondViewController {
var notificationLabel = "I have not been notified yet."
func addObserver() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateNotificationLabel", name: notificationKey, object: nil)
}
func updateNotificationLabel() {
self.notificationLabel = "I've been notified!"
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
class ThirdViewController {
var notificationLabel = "I have not been notified yet."
func addObserver() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateNotificationLabel", name: notificationKey, object: nil)
}
func updateNotificationLabel() {
self.notificationLabel = "I've been notified too!"
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
var firstView = FirstViewController()
print(firstView.sentNotificationLabel) // Nothing has been sent yet.
firstView.addObserver()
firstView.updateNotificationLabel()
firstView.notify() // ERROR 'Execution was interrupted, reason: signal SIGABRT'
print(firstView.sentNotificationLabel) // Notification sent!
var secondView = SecondViewController()
print(secondView.notificationLabel)
var thirdView = ThirdViewController()
print(thirdView.notificationLabel)
Try conforming the three classes to NSObject... You can't use NSNotificationCenter with purely swift objects.
IE: class FirstViewController: NSObject {}