I have a UIViewcontroller let's say "DialerViewController" which has a UILabel
#IBOutlet weak var statusText: UILabel!
,
which has a default value of "pending", how can I change the value of statusText using an app delegate, let's assume the app delegate downloads a text from the server and needs to update the statusText after completion.
I am new to swift development, what is the best way to go around this?
If the DialerViewController is the only view controller in your app you can address it like this...
(window?.rootViewController as? DialerViewController)?.statusText?.text = "YOURTEXT"
Another option would be to make the DialerViewController instance observe some specific notification and post this notification in the app delegate when the text was downloaded from the server.
// create an extension for your own notification
extension Notification.Name {
static let textWasDownloadedNotification = Notification.Name("textWasDownloadedNotification")
}
class DialerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// make your dialer view controller listen to your new notification
NotificationCenter.default.addObserver(self, selector: #selector(updateLabel), name: .textWasDownloadedNotification, object: nil)
}
// function that gets called when a notification is received
#objc func updateLabel(_ notification: Notification) {
// get the new text from the notification's `userInfo` dictionary
let text = notification.userInfo?["text"] as? String
// update the label
statusText.text = text
}
}
// somewhere in your app delegate...
// prepare the `userInfo` dictionary with the information that is needed
let userInfo = ["text": "This is the new text."]
// post the notification
NotificationCenter.default.post(name: .textWasDownloadedNotification,
object: nil,
userInfo: userInfo)
See https://developer.apple.com/documentation/foundation/notificationcenter.
I think you can't do that because AppDelegate methods are called at specifics state of your application and the one that could be good is this one :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
But when it's called, your viewController isn't yet loaded.
Loading from network it’s not AppDelegate responsibility, add new network service class and inject it to the view controller. Just get knowledges about layers architecture, solid. It’s very powerful for new devs, good luck.
Related
I want to call a web service whenever application coming back in the foreground. I am calling it from didBecomeActive().
What's the best way to handle it and pass data to Root view controller?
Since the data you want to pass is always going to the same view controller you should instead set the observer in that view controller instead of app delegate. This way you won't need to pass any data in the first place.
class YourViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default
.addObserver(self, selector: #selector(activityHandler(_:)),
name: UIApplication.didBecomeActiveNotification, object: nil)
}
#objc func activityHandler(_ notification: Notification) {
//Call your web service here
}
}
You have two choices. Get rootViewController and pass the data, handle it.
func applicationDidBecomeActive(_ application: UIApplication) {
// 1
let rootVC1 = self.window?.rootViewController
// 2
let rooVC2 = application.windows.first?.rootViewController
...
/*
pass data to rootVC1 or rootVC2
*/
}
I know that if I register UIApplicationDelegate in UNUserNotificationCenter and an app in foreground mode I will change TabBarItem.badgeValue easily. I only need to insert a piece of code in func userNotificationCenter(...)
But how to do the same thing when an app is in background mode? (like WhatsApp does)
Simple answer...you don't. UI changes must be on the main thread. Instead, I would suggest you register for the UIApplicationDidBecomeActive notification in NotificationCenter and update the badge value there.
(I use UserDefaults in the code below out of convenience and just for example. I might recommend using another method of storage based on how you're storing state in your app.)
Code while in background:
let badgeValue = 100
UserDefaults.standard.set(badgeValue, forKey: "badgeValue")
In your view controller:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//set it here for when view controller is loaded
let badgeValue = UserDefaults.standard.string(forKey: "badgeValue")
self.tabBarItem.badgeValue = badgeValue
NotificationCenter.default.addObserver(forName: Notification.Name.UIApplicationDidBecomeActive, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
//set it here for when you come back from the background
let badgeValue = UserDefaults.standard.string(forKey: "badgeValue")
self?.tabBarItem.badgeValue = badgeValue
}
}
}
I would like to know is it possible to, let say, change or modify an object like a button of another view controller from AppDelegate.swift.
Here is what I tried to begin with but somehow stucked.
func application(_ application: UIApplication,didReceiveRemoteNotification userInfo: [AnyHashable : Any],fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .active {
if self.window?.rootViewController is homeViewController {
//modify a button exist in homeViewController
}
}
}
Any help is appreciated. Thanks in advance.
You can use the NotificationCenter to send and receive internal notifications (note they are different from local and remote notifications).
First create your notification doing something like this:
extension Notification.Name {
static let remoteNotificationReceived = Notification.Name("uk.co.company.app.remoteNotificationReceived")
}
Then in your view controller that is to respond do something like this:
class TestViewController: UIViewController {
var remoteNotificationReceivedObserver: NSObjectProtocol?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
remoteNotificationReceivedObserver = NotificationCenter.default.addObserver(forName: Notification.Name.remoteNotificationReceived, object: nil, queue: nil, using: { (notification) in
DispatchQueue.main.async { // because the notification won't be received on the main queue and you want to update the UI which must be done on the main queue.
// Put the code to change the button here
}
})
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if let remoteNotificationReceivedObserver = remoteNotificationReceivedObserver {
NotificationCenter.default.removeObserver(remoteNotificationReceivedObserver)
}
}
}
Then elsewhere in you app you post the notification like this:
NotificationCenter.default.post(name: Notification.Name.remoteNotificationReceived, object: nil)
You can use NotificationCenter! add observer in your view controller and remove it while you go back from that view controller(custom notification). and when you receive notification post it!
If you don't have idea that how to deal with NotificationCenter then refer This SO post !
The only place you should really be interacting with another vew controller like that is during segues (if you are using Storyboards). Even then, you should let the view functions for that controller be responsible for changing the state of its buttons and just pass some variable to the controller or perhaps better set up the controller to listen for notifications. Then your app delegate, or other controller can just post notifications that your home controller listens for.
It is possible, but addressing directly members of another ViewController breaks responsibility. It is a good practice to define Interface protocols for internal interactions. In this particular case, it is a good idea to create protcol RemoteNotificationReciverInterface (or kind of RemoteNotificationReciveable according to some modern coding styles advice, although I found it difficult to find appropriate adjective in this case) :
protocol RemoteNotificationReciverInterface: class {
func didReciveNotification(info : [AnyHashable : Any])
}
Then extent your ViewController( and any view controllers that had to react on Notifications when they are topmost)
extension HomeViewController: RemoteNotificationReciverInterface {
func didReciveNotification(info : [AnyHashable : Any]) {
// Chnage you button, ignore para,eters
}
}
You can adopt UINavigationContoroller, UITabBarConroller etc. to forward notifications to their topmost controllers, like:
extension UINavigationController: RemoteNotificationReciverInterface {
func didReciveNotification(info : [AnyHashable : Any]) {
(topViewController as? RemoteNotificationReciverInterface)?.didReciveNotification(info: info)
}
}
And the easily forward it from app delegate.
func application(_ application: UIApplication,didReceiveRemoteNotification userInfo: [AnyHashable : Any],fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .active {
(window?.rootViewController as? RemoteNotificationReciverInterface).didReciveNotification(info: userInfo)
}
}
This is a fragment of my iOS app:
where the Tab Bar Controller is my root controller followed by a Navigation Controller followed by a Table View Controller followed by a final View Controller. When a push notification arrives, I need to pass some data to this final view controller (in order to fill those blank UITextView and a UITextField).
This is the code I have written up to now but I don't know how to go on:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
//let tab :UITabBarController = self.window?.rootViewController as! UITabBarController
//tab.selectedViewController = tab.viewControllers![3]
print("NOTIFICA \n")
print(userInfo)
let tab :UITabBarController = self.window?.rootViewController as! UITabBarController
tab.selectedViewController = tab.viewControllers![2]
}
This code is supposed to let me access to the 3-rd view controller of the tab bar controller but I need to go beyond it to the final view controller and pass userInfoto it. How shall I do? Thanks to all.
UPDATE
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
//let tab :UITabBarController = self.window?.rootViewController as! UITabBarController
//tab.selectedViewController = tab.viewControllers![3]
print("NOTIFICA \n")
print(userInfo)
application.applicationIconBadgeNumber = 0
application.cancelAllLocalNotifications()
NSNotificationCenter.defaultCenter().postNotificationName("MyNotificationReceived", object: userInfo)
}
class DynamicEventsViewController:UIViewController {
#IBOutlet weak var dynamicEventTitle:UITextField!
#IBOutlet weak var dynamicEventDescription:UITextView!
var eventTitle:String!
var eventDescription:String!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.titleView = UIImageView(image: UIImage(named: "icons/bar.png"))
self.navigationController?.navigationBar.barTintColor = UIColor.whiteColor()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DynamicEventsViewController.notificatonArrived(_:)), name: "MyNotificationReceived", object: nil)
self.dynamicEventTitle.text = self.eventTitle.uppercaseString
self.dynamicEventDescription.text = self.eventDescription
}
func notificatonArrived(notification: NSNotification) {
let userInfo:[NSObject : AnyObject] = notification.userInfo!
self.eventTitle = userInfo["aps"]!["alert"] as! String
self.eventDescription = userInfo["aps"]!["description"] as! String
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
I think there are two questions to this post.
How to take the user to the final view controller upon receiving a push notification?
How to pass the payload of the push notification to the final view controller?
For #1, you will have to first determine how the user would normally go to the final view controller. Based on the storyboard he would switch to the third tab, select an item in the table view which would then load the final view controller. So you will have to do this programatically by switching the Tab controller to the third view controller, then getting the navigation controller and then simulating the selection of an item in the table view which will then load the final view controller.
For #2 notifications may not work properly because the final view controller may not be loaded by the time the notification is sent. Its hard to sync this. In this case you can save the push notification payload in user defaults using NSStandardUserDefaults. Then in the final view controller you can check if there is any prefs already stored as a part of this flow and if so load it and populate the text field and text view with the values and delete the prefs so that next time it is loaded the values are not populated.
This kind of decouples the push notification and saving of the data to be preloaded in the final view controller from the actual loading of this data in the final view controller.
Another option is to just load the final view controller as a modal view controller and pass the data to that controller at the time of receiving the notification itself. Of course this is applicable only if your final view controller's data model doesn't require it to always comes from the table view controller.
You can use NSNotificationClass, you can read a tutorial here. Register the notification in (say) FinalViewController
class FinalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(FinalViewController.notificatonArrived(_:)), name: "MyNotificationReceived", object: nil)
}
func notificatonArrived(notification: NSNotification) {
//do you updation of labels here
let userInfo = notification.userInfo
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
And when you receive the notification the do this
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
print("NOTIFICA \n")
print(userInfo)
NSNotificationCenter.defaultCenter().postNotificationName("MyNotificationReceived", object: userInfo)
}
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
}