Calling custom delegate from AppDelegate in Swift - ios

I have this delegate in AppDelegate.swift that fires once another app opens my app with a url scheme.
AppDelegate.swift
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
return true
}
It fires just fine when another app opens my app with the url scheme, but when this function fires, I want to notify a certain ViewController. I thought I could do this with a custom made delegate, and let the AppDelegate notify who ever implements my delegate that someone has opened the app.
MyDelegate.swift
protocol MyDelegate {
func opened(hasBeenOpened: Bool!)
}
Then my ViewController implements this Delegate
LoginViewController.swift
import UIKit
/* Obviously this class has more code and other functions,
but for the illustration of the problem, I removed all the other unrelated things.*/
class LoginViewController: UIViewController, MyDelegate {
func opened(hasBeenOpened: Bool!) {
print(hasBeenOpened)
}
}
So far so good, but let's return to the openURL() function in AppDelegate.swift and try to call the MyDelegate.opened(). This is where I am completely lost.
AppDelegate.swift
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
print("openURL delegate running")
if var delegate = MyDelegate?() {
delegate.opened(true)
}
return true
}
The console prints "openUrl delegate running", so it's running, but my delegate variable becomes nil. Is there some initialization I'm missing?
I can't seem to figure out how I call my own custom Delegate from the AppDelegate. Is there another way to notify ViewControllers that this has happened? Or is this a bad idea overall, is there another way that's considered better?
Thank you all in advance, I really appreciate the help.

Without going into details if this is a good or bad idea, the problem is your delegate property in AppDelegate is never initialized. In the initializer method of your UIViewController you need to get access to the AppDelegate and set the delegate property to self.

You are trying to initialise a protocol (var delegate = MyDelegate?()) which is not possible.
The way you use delegates is by registering conformance on a class, which you are doing in LoginViewController, and calling a method defined in the protocol directly on an instance of that class.
For example:
var loginViewController = // get an instance of LoginViewController
loginViewController.opened(true)
In this case you don't have access to an instance, nor would it be considered good practice to keep a reference to a view controller in the App Delegate. So I think the paradigm you are looking for is notifications. Have a look at the documentation for NSNotificationCenter or the NSHipster article on notifications.

Try NSNotification,I don't think it is a good idea to hold specific view controller in AppDelegate.

Related

Chartboost Delegate not working in swift

Desired : I want to do something when Delegates method call Observed :Delegates method not calling Ad's show on the screen successfully
Error
code:Chartboost.delegate=self
Error: Type 'Chartboost' has no member 'delegate'
AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Chartboost.start(withAppId: "4f21c409cd1cb2fb7000001b", appSignature: "92e2de2fd7070327bdeb54c15a5295309c6fcd2d", delegate: nil)
return true
}
ViewController Code
class ViewController: UIViewController,GADBannerViewDelegate, GADInterstitialDelegate,GADRewardBasedVideoAdDelegate,IMBannerDelegate, IMInterstitialDelegate ,ChartboostDelegate{
#IBAction func Vedio(_ sender: Any) {
Chartboost.showRewardedVideo(CBLocationMainMenu)
}
#IBAction func LoadFullAd(_ sender: Any) {
Chartboost.showInterstitial(CBLocationHomeScreen)
}
private func shouldDisplayRewardedVideo(_ location: CBLocation) -> Bool {
return true
}
private func shouldRequestInterstitial(_ location: CBLocation) -> Bool {
return true
}
}
A had to set the delegate as self with Chartboost.setDelegate(self)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
Chartboost.start(withAppId: "4f21c409cd1cb2fb7000001b", appSignature: "92e2de2fd7070327bdeb54c15a5295309c6fcd2d", delegate:self as ChartboostDelegate)
Chartboost.setDelegate(self as ChartboostDelegate)
return true
}
After looking at how to properly convert Objective-C methods in Swift, I added the underscore (_), which changed the function to:
func shouldDisplayRewardedVideo(_ location: CBLocation) -> Bool
{
return true
}
func shouldRequestInterstitial(_ location: CBLocation) -> Bool {
return true
}
XCode then gave me a hint that I was close to the delegate method, but needed to change the type of location and I ended up with
func shouldDisplayRewardedVideo(_ location: String) -> Bool
{
return true
}
func shouldRequestInterstitial(_ location: String) -> Bool {
return true
}
If the delegate is set to nil, the class that calls the delegate's methods (in this case Chartboost) will not be able to make the delegate's method calls. You should set the delegate to the 'self' of the class where you have implemented the delegate methods expected by Chartboost.
In the example above, you could set the Chartboost delegate to the 'self' of the ViewController.
For example, inside of ViewController, you have already declared the 'ChartboostDelegate' in the class signature. When you want to turn on the Chartboost delegate methods, assign the ViewController's 'self' to the Chartboost delegate using something like:
Chartboost.delegate = self
In the case of Chartboost, it looks like the author made the delegate private, so it can be set in the ViewController using:
Chartboost.start(withAppId: "some uid", appSignature: "some other uid", delegate: self)
or, as later found out:
Chartboost.setDelegate(self)
(It can also be set in the AppDelegate class by locating the ViewController instance in the storyboard. Not a great fit in this case.)
If you're having problems generating the delegate method's call signature stubs (the method calls expected by the delegate), XCode will autogenerate them for you. Just click on the error message found next to your class declaration:
Type '<your class implementing the delegate methods>' does not conform to protocol '<the delegate protocol to implement>'
More detail about the error will appear. Click the 'Fix' button and XCode will autogenerate the method stubs for you.

Unexpectedly found nil while unwrapping an Optional value, when trying to change a label's text, triggered by a notification

Currently I have a function in my AppDelegate.swift file which fires when a notification is received. I am using Firebase to send these notifications.
When the function runs, it checks for any extra attached data, with the key 'url', and if so, it runs a function in my ViewController.swift file.
The issue is that the function that is run in the ViewController tries to change the text of a label to "The function has ran", but when this line is run, it throws the error "Unexpectedly found nil while unwrapping an Optional value".
I can't figure out what this Optional is, so if anyone could suggest a possible issue with my code that would be great.
Things I have tried:
Creating and using a new label
Renaming the label
Checked that the Outlet is properly connected
Checking classes are properly set
Snippet of the AppDelegate.swift code:
import UIKit
import Firebase
import FirebaseInstanceID
import FirebaseMessaging
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var vc = ViewController()
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print(userInfo)
let url = userInfo["url"]
print("url is: \(String(describing: url))")
if url != nil {
hasRun = true
vc.changeLabel()
print("Func done")
}
completionHandler(UIBackgroundFetchResult.newData)
}
}
Snippet of the ViewController.swift code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var theLabel: UILabel!
func changeLabel() {
theLabel.text = "The change label code has run here"
}
}
The optional part is your label property. It is marked as force unwrap with having "!" at the end #IBOutlet weak var theLabel: UILabel!. This is a default when connecting outlets and to generally avoid such issue I suggest you to practice #IBOutlet private weak var <#name#>: <#type#>?.
As already mentioned in comment by #Larme you are creating an instance of your view controller through empty constructor which will not load the controller from your storyboard or xib by default (you might have overridden it though). Still even if overridden the view is probably not yet loaded at that point and your label is still nil.
From the code you posted it seems you want to push some data to a view controller which should exist unless your app is opened by triggering a notification. There are many ways to handle this but one you might consider is to have a static method changeLabel which can be called without creating an instance of view controller. The method should check if a view controller is loaded and call your method on it if it does. If it is not yet loaded then it should be called in view did load once it does load (assuming it loads during a default UI pipeline).
To achieve such a system you may do the following:
Add a private static variable of your view controller within itself like private var currentInstance: ViewController?. On view did load call ViewController.currentInstance = self. Also add static variable for data you need (like your url) static var myURL: Any?. Then:
static func setURL(url: Any?) {
if currentInstance != nil {
currentInstance.changeLabel() // Will be called immediately
} else {
myURL = url
}
}
And view did load:
override func viewDidLoad(animated: Bool) {
...
if let url = ViewController.myUrl {
ViewController.myUrl = nil
changeLabel()
}
...
}
This way all you need to do in your app delegate is call ViewController.setURL(userInfo["url"]).
I hope you get a basic idea...

Firebase observer during all the app life

I'm trying to create a firebase observer that remains alive during all the app life. What I want is to change a property of my tabBarController when some data change in firebase. Here's my code:
self.ref.child("mySubRef").observe(.value , with: {snapshot in
self.tabBarController?.tabBar.items?[3].badgeValue = "!"
})
So, I've tried creating it in the viewDidLoad of my first viewController and also in the viewDidAppear. I don't remove it since I want it to be there always. In the viewDidAppear it works only if I'm in that viewController at the moment of the change. If I want that change to happen no matter where I am (always inside the tabBar) where do I have to put that code?
Thanks for the help!
I have found the answer. The problem was that when I changed between viewControllers the reference to the observer was deallocated. So, to fix it, I have created a class like this:
class NotificationListener: NSObject {
let ref:FIRDatabaseReference = FIRDatabase.database().reference()
var user:User?
func setUpListener(tabBarController:UITabBarController){
self.user = User()
self.ref.child("users/" + self.user!.uid + "/notifications").observe(.value , with: {snapshot in
tabBarController.tabBar.items?[3].badgeValue = "!"
})
}
}
Now I have a property of that class in every viewController and every one has a reference to the same object. When I change between VC it will not deallocate the object because it will still be referenced.
I think, you can use Appdelegate didFinishLaunchingWithOptions method but I'm not sure.
Like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FIRApp.configure()
FIRDatabase.database().reference().child("mySubRef").observe(.value, with: { (snapshot) in
//I'm not sure for this part.
UITabBarController.init().tabBar.items?[3].badgeValue = "!"
})
return true
}

Transform UIApplicationDelegate methods into RxSwift Observables

In RxSwift / RxCocoa you can create a reactive wrapper for a delegate (e.g. UIScrollViewDelegate or CLLocationManagerDelegate) to enable Rx observable sequences for certain delegate methods.
I am trying to implement this for the UIApplicationDelegate method applicationDidBecomeActive:
What I tried so far is pretty straightforward and similar to the DelegateProxy subclasses that are included in RxCocoa.
I created my DelegateProxy subclass:
class RxUIApplicationDelegateProxy: DelegateProxy, UIApplicationDelegate, DelegateProxyType {
static func currentDelegateFor(object: AnyObject) -> AnyObject? {
let application: UIApplication = object as! UIApplication
return application.delegate
}
static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
let application: UIApplication = object as! UIApplication
application.delegate = delegate as? UIApplicationDelegate
}
}
And an Rx extension for UIApplication:
extension UIApplication {
public var rx_delegate: DelegateProxy {
return proxyForObject(RxUIApplicationDelegateProxy.self, self)
}
public var rx_applicationDidBecomeActive: Observable<Void> {
return rx_delegate.observe("applicationDidBecomeActive:")
.map { _ in
return
}
}
}
In my AppDelegate I subscribe to the observable:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// the usual setup
// and then:
application.rx_applicationDidBecomeActive
.subscribeNext { _ in
print("Active!")
}
.addDisposableTo(disposeBag)
return true
}
When I start my app "Active!" gets printed and then I get the following crash in RxCocoa's _RXDelegateProxy_ class:
Does anybody have an idea what the problem might be? Or has anybody successfully implemented something like rx_applicationDidBecomeActive?
It looks like a really tricky issue with RxSwift and memory management.
The default implementation of DelegateProxyType sets an instance of a delegate proxy (in this case, RxUIApplicationDelegateProxy) to the delegate of UIApplication.
It also stores the original AppDelegate as a property called forwardToDelegate so all the delegate methods can still be passed to it.
The problem is that, when the new app delegate is set:
application.delegate = delegate as? UIApplicationDelegate
the original one is deallocated! You can check it by overriding deinit in AppDelegate. The reasons are explained in this answer. And because the property forwardToDelegate is of type assign, your app crashes as the property points to a deallocated object.
I have found a workaround for that. I'm not really sure if it is a recommended way, so be warned. You can override a method from DelegateProxyType in RxUIApplicationDelegateProxy:
override func setForwardToDelegate(delegate: AnyObject?, retainDelegate: Bool) {
super.setForwardToDelegate(delegate, retainDelegate: true)
}
In normal circumstances, you don't want to retain the delegate as it leads to a retain cycle. But in this special case, this is not a problem: your UIApplication object will exist the entire time while your application is alive anyway.

Protocols and delegate trouble in swift

All,
I have create a swift file and put a protocol in it, like this :
protocol PayButtonProtocol {
func enablePayButton()
func disablePayButton()
}
I have made my viewcontroller conform to the protocol like this :
class ViewController: UIViewController, PayButtonProtocol
I have also created the functions in the ViewController so it conforms like this
func enablePayButton() {
println("Button enabled")
PAYBarButton.enabled = true
}
func disablePayButton() {
PAYBarButton.enabled = false
}
And in another class, I have set the delegate and I want to execute the enablePayButton when something is pressed like this :
var delegate:PayButtonProtocol?
and in the function i want to execute one of the functions by :
delegate?.enablePayButton()
but it doesn't execute, what am I missing please ?
More than likely delegate is nil. Add a breakpoint and check the value of delegate before that line executes. Or change the "?" to a "!" and it will crash if delegate is nil, letting you know what's wrong.
Your code in the other class:
var delegate:PayButtonProtocol?
defines a variable named delegate that is of type PayButtonProtocol?.
The variable delegate will contain nil until you assign something to it:
delegate = <someObjectThatConformsToPayButtonProtocol>

Resources