difference between function with #objc in front and function doesn't have #objc - swift

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)
}
}

Related

Swift calling a ViewController function from the AppDelegate [duplicate]

I am building an iOS app using the new language Swift. Now it is an HTML5 app, that displays HTML content using the UIWebView. The app has local notifications, and what i want to do is trigger a specific javascript method in the UIWebView when the app enters foreground by clicking (touching) the local notification.
I have had a look at this question, but it does not seem to solve my problem. I have also come across this question which tells me about using UIApplicationState, which is good as that would help me know the the app enters foreground from a notification. But when the app resumes and how do i invoke a method in the viewController of the view that gets displayed when the app resumes?
What i would like to do is get an instance of my ViewController and set a property in it to true. Something as follows
class FirstViewController: UIViewController,UIWebViewDelegate {
var execute:Bool = false;
#IBOutlet var tasksView: UIWebView!
}
And in my AppDelegate i have the method
func applicationWillEnterForeground(application: UIApplication!) {
let viewController = self.window!.rootViewController;
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("FirstView") as FirstViewController
setViewController.execute = true;
}
so what i would like to do is when the app enters foreground again, i want to look at the execute variable and run the method as follows,
if execute{
tasksView.stringByEvaluatingJavaScriptFromString("document.getElementById('sample').click()");
}
Where should i put the code for the logic to trigger the javascript from the webview? would it be on viewDidLoad method, or one of the webView delegate methods? i have tried to put that code in the viewDidLoad method but the value of the boolean execute is set to its initial value and not the value set in the delegate when the app enters foreground.
If I want a view controller to be notified when the app is brought back to the foreground, I might just register for the UIApplication.willEnterForegroundNotification notification (bypassing the app delegate method entirely):
class ViewController: UIViewController {
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Note, in the completion closure, I include [unowned self] to avoid strong reference cycle that prevents the view controller from being deallocated if you happen to reference self inside the block (which you presumably will need to do if you're going to be updating a class variable or do practically anything interesting).
Also note that I remove the observer even though a casual reading of the removeObserver documentation might lead one to conclude is unnecessary:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method.
But, when using this block-based rendition, you really do need to remove the notification center observer. As the documentation for addObserver(forName:object:queue:using:) says:
To unregister observations, you pass the object returned by this method to removeObserver(_:). You must invoke removeObserver(_:) or removeObserver(_:name:object:) before any object specified by addObserver(forName:object:queue:using:) is deallocated.
I like to use the Publisher initializer of NotificationCenter. Using that you can subscribe to any NSNotification using Combine.
import UIKit
import Combine
class MyFunkyViewController: UIViewController {
/// The cancel bag containing all the subscriptions.
private var cancelBag: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
addSubscribers()
}
/// Adds all the subscribers.
private func addSubscribers() {
NotificationCenter
.Publisher(center: .default,
name: UIApplication.willEnterForegroundNotification)
.sink { [weak self] _ in
self?.doSomething()
}
.store(in: &cancelBag)
}
/// Called when entering foreground.
private func doSomething() {
print("Hello foreground!")
}
}
Add Below Code in ViewController
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector:#selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appMovedToForeground() {
print("App moved to foreground!")
}
In Swift 3, it replaces and generates the following.
override func viewDidLoad() {
super.viewDidLoad()
foregroundNotification = NotificationCenter.default.addObserver(forName:
NSNotification.Name.UIApplicationWillEnterForeground, object: nil, queue: OperationQueue.main) {
[unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}

Listening for NSWorkspace Notifications in Swift 4

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.

Way to check data modification across many viewcontroller without using global variables?

I have an app which contains several viewControllers. On the viewDidAppear() of the first VC I call a set of functions which populate some arrays with information pulled from a database and then reload table data for a tableView. The functions all work perfectly fine and the desired result is achieved every time. What I am concerned about is how often viewDidAppear() is called. I do not think (unless I am wrong) it is a good idea for the refreshing functions to be automatically called and reload all of the data every time the view appears. I cannot put it into the viewDidLoad() because the tableView is part of a tab bar and if there are some modifications done to the data in any of the other tabs, the viewDidLoad() will not be called when tabbing back over and it would need to reload at this point (as modifications were made). I thought to use a set of variables to check if any modifications were done to the data from any of the other viewControllers to then conditionally tell the VDA to run or not. Generally:
override func viewDidAppear(_ animated: Bool) {
if condition {
//run functions
} else{
//don't run functions
}
}
The issue with this is that the data can be modified from many different viewControllers which may not segue back to the one of interest for the viewDidAppear() (so using a prepareForSegue wouldn't work necessarily). What is the best way to 'check' if the data has been modified. Again, I figured a set of bool variables would work well, but I want to stay away from using too many global variables. Any ideas?
Notification Center
struct NotificationName {
static let MyNotificationName = "kMyNotificationName"
}
class First {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived), name: NotificationName.MyNotificationName, object: nil)
}
func notificationReceived() {
// Refresh table view here
}
}
class Second {
func postNotification() {
NotificationCenter.default.post(name: NotificationName.MyNotificationName, object: nil)
}
}
Once postNotification is called, the function notificationReceived in class First will be called.
Create a common global data store and let all the view controllers get their data from there. This is essentially a global singleton with some accompanying functions. I know you wanted to do this without global variables but I think you should consider this.
Create a class to contain the data. Also let it be able to reload the data.
class MyData {
static let shared = MyData()
var data : SomeDataType
func loadData() {
// Load the data
}
}
Register to receive the notification as follows:
static let dataChangedNotification = Notification.Name("DataChanged")
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Establish a way for call activity to notify this class so it can update accordingly
NotificationCenter.default.addObserver(self, selector: #selector(handleDataChangedNotification(notification:)), name: "DataChanged", object: nil)
}
func handleDataChangedNotification(notification: NSNotification) {
// This ViewController was notified that data was changed
// Do something
}
func getDataToDisplay() {
let currentData = MyData.shared.data
// do something
}
// Any view controller would call this function if it changes the data
func sendDataChangeNotification() {
let obj = [String]() // make some obj to send. Pass whatever custom data you need to send
NotificationCenter.default.post(name: type(of: self).dataChangedNotification, object: obj)
}

Automatically Reload TableViewController On Rewind

I am working on an app where it starts out at a tableViewController which loads and displays data stored in a Realm database. I have it so I can create a new entry in my Realm database in a separate scene and the save button unwind segues back to the initial tableView.
I current have it so the tableViewController will reload the tableView on pull down (something I Learned here, second answer down) but I would be better if the tableView would reload its self automatically upon unwind, displaying all the data in my database, including my new entry. Could someone please direct me to a tutorial that will teach me how this is done.
Additional info: My app is embedded in a navigation controller. The save button is located the bottom tool bar.
You can use NSNotification for that.
First of all in your tableViewController add this in your viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshTable:", name: "refresh", object: nil)
}
And this will call one method from your class:
func refreshTable(notification: NSNotification) {
println("Received Notification")
tableView.reloadData() //reload your tableview here
}
So add this method too.
Now in your next view controller where you add new data into data base add this in you unWindSegue function:
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
Hope it will help
Try reloading your tabledata in viewWillAppear in initial (tableview)controller.
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
Or call again the function through which you are loading your data from Realm like
override func viewWillAppear(animated: Bool) {
getMyData() //or whatever your function name is
}
If you are using storyboard unwind segue, try using
func unwindSegue(segue:UIStoryboardSegue) {
if segue.identifier == "identifier" {
getData()
self.tableView.reloaddata()
}
}

NSNotificationCenter: Removing an Observer in Swift

I have a view controller with a button. When the button is pressed it adds an observer, like so:
func buttonPress(sender:UIButton){
NSNotificationCenter.defaultCenter().addObserverForName("buttonPressEvent", object:nil, queue:nil, usingBlock:{(notif) -> Void in
// code
})
}
When I dismiss this view controller, and then return to it and press the button the //code is executed twice. If I go away and come back again the //code is executed three times, and so on.
What I want to do is to remove the Observer before I add it again, so this code doesn't execute twice. Ive gone through the documentation here and Ive added this line of code just above where I add the Observer:
NSNotificationCenter.defaultCenter().removeObserver(self, name:"buttonPressEvent", object:nil)
But this isnt working.
Can anyone tell me where I'm going wrong?
When you use the 'blocks' based approach to observing notifications then self isn't in fact the observer. The function returns an object which acts as the observer:
func addObserverForName(_ name: String?,
object obj: AnyObject?,
queue queue: NSOperationQueue?,
usingBlock block: (NSNotification!) -> Void) -> NSObjectProtocol
You need to keep a reference to this returned object and pass it in as the observer when you call removeObserver
It's explained well in the Apple Doc here
Implemented it like this, seems to be working fine.
override func viewDidLoad()
{
super.viewDidLoad()
AddScreenShotNotification()
}
func AddScreenShotNotification() {
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(MyViewController.ScreenShotTaken),
name: UIApplicationUserDidTakeScreenshotNotification,
object: nil)
}
func ScreenShotTaken()
{
// do something
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}