May I create an instance of AppDelegate? - swift

I'm using Appdelegate with some timers to do background checks. How can I trigger to do a check right now.
I would have something like this in my Appdelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
NSTimer.scheduledTimerWithTimeInterval(10.0, target: self, selector: "test", userInfo: nil, repeats: true)
return true
}
func test() -> Bool {
print("true")
return true
}
is it admissable to do something like the following from any other class inside the app?
AppDelegate().test()

If you need access to some method or property of your app delegate, you could do something like this:
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
// Use your app delegate instance:
appDelegate.test()
}
Also, the code you wrote:
AppDelegate.test()
...is wrong; here test() is an instance method, so it should be called on an instance (not the class - notice the capital initial).

You can, but not by making another instance of AppDelegate but use the shared instance of it.
if let delegate = UIApplication.sharedApplication().delegate as? YOURAPPDELEGATE
Try above code.

In model unit tests it's often nice not to have UIApplication like framework classes, as they interfere with your tests in unforeseeable ways. But it's no problem at all to instantiate as many instances of your UIApplicationDelegate as you want:
let sut = AppDelegate()
XCTAssertTrue(sut.test(), "test failed")
It's a delegate class and as such, does not interfere with UIKit by just instantiating.
Also in normal app code you could do the same without harming the rest of your app—it's just less common to do these kind of tests within the actual app target.

Related

Error with appDelegate while testing firebase based app

Im trying to do some snapshot tests on my firebase based app.
I made some snapshot tests with 'LogInVC' it worked well because it has nothing in common with firebase but then, after LoginTests I've tried to test tableView that should be shown after successful login:
First I made "TestingAppDelegate" in test target:
import UIKit
#objc(TestingAppDelegate)
class TestingAppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print(">> Launching with testing app delegate")
return true
}
To avoid the regular app delegate during testing I made a code in a file called "main" that tries to find a class named TestingAppDelegate made in "Testing" target and also deleted #main in regular AppDelegate.
import UIKit
let appDelegateClass: AnyClass =
NSClassFromString("TestingAppDelegate") ?? AppDelegate.self
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil,
NSStringFromClass(appDelegateClass))
This is how my test looks:
class UsersTests: XCTestCase {
var sut: AllUsersViewController!
override func setUp() {
sut = AllUsersViewController()
sut.loadViewIfNeeded()
}
override func tearDown() {
sut = nil
}
func testInitialLoginScreen(){
setUsers()
assertSnapshot(matching: sut, as: .image, record: true)
}
func setUsers(){
for number in 0...5{
let user = UserInfo(firstName: "Nikola", lastName: "Andr", username: "lol", pictureURL: nil, training: ["lol","mma"], uid: "\(number)", admin: false)
sut.users.append(user)
}
}
When I run tests this is error I get in "main" file that switches app delegates.
"The default FirebaseApp instance must be configured before the
default Authinstance can be initialized. One way to ensure this is to
call FirebaseApp.configure() in the App Delegate's
application(_:didFinishLaunchingWithOptions:) (or the #main
struct's initializer in SwiftUI)."
I've never tested firebase app before so I don't know that to do now. Any reference where to learn about how to test firebase apps will be helpful too.

How to share properties between AppDelegate and ViewController, and save before App is terminated

I have a class with properties updated in viewController. I wanted to save the properties when the app goes into background or quit using AppDelegate. I used the following codes but it appears that the properties were not passed to the AppDelegate. Furthermore the applicationWillTerminate codes did not seem to get executed.
// testClass is defined and the properties are updated in viewController, e.g
testClass.status = true // default is false
// I want to save testClass.status when the app goes into background or being terminated using the following:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var vc = ViewController()
func applicationDidEnterBackground(_ application: UIApplication) {
print(vc.testClass.status) // prints false
//codes to save
}
// save before App is terminated
func applicationWillTerminate(_ application: UIApplication) {
print(vc.testClass.status) // this code did not get executed?
//codes to save
}
}
applicationWillTerminate is called only when a user terminates the app without switching it to background mode.
When the app is active, double press on Home button and terminate the app.
But if you switch the app to the background, and then try to terminate the app, applicationWillTerminate will not be called.
And you are creating an instance of ViewController in AppDelegate
var vc = ViewController()
If you change the testClass property in another ViewController class instance, you won't get that value here. So create a singleton class like this
class TestClass: NSObject {
static let shared = TestClass()
private override init() {
super.init()
}
var status = false
}
Now update the value in any view controller from the singleton class
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
TestClass.shared.status = true
}
}
In AppDelegate save and retrieve the value from UserDefaults
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
TestClass.shared.status = UserDefaults.standard.bool(forKey: "Status")
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
UserDefaults.standard.set(TestClass.shared.status, forKey: "Status")
}
func applicationWillTerminate(_ application: UIApplication) {
UserDefaults.standard.set(TestClass.shared.status, forKey: "Status")
}
}
Or create a computed property to save the value in UserDefaults whenever it is changed.
class TestClass: NSObject {
static let shared = TestClass()
private override init() {
super.init()
}
var status: Bool {
get {
return UserDefaults.standard.bool(forKey: "Status")
}
set {
UserDefaults.standard.set(newValue, forKey: "Status")
}
}
}
As already mentioned by others you can ignore applicationWillTerminate.
To get notified when the app goes into the background just add an observer in the view controller.
However rather than didEnterBackground I'd recommend to observe willResignActive.
Add the observer in viewDidLoad once
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { notification in
// save the properties
}
}
Or if you are using multiple view controllers you can add the observer in viewWillAppear and remove it in viewDidDisappear
Side note:
Never create a view controller with the default initializer ViewController() if you are using storyboard. You'll get a brand new instance which is not the storyboard instance.

Run a function N time anywhere in the app in Swift

I trying to make a calling app for my project and I want to add a function that keeps checking if someone if calling. My app uses Firebase where I have a key for each users to check if he made a call or not.
There's two problem I am facing here, the first one is, as I said, that I want my function to keep checking anywhere in the app for an incoming call. The other problem is that i have a viewcontroller that I want to pop up when someone is calling. I have found this code on github but it uses navigationcontroller which I am not using in my app :
extension UIViewController{
func presentViewControllerFromVisibleViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if let navigationController = self as? UINavigationController, let topViewController = navigationController.topViewController {
topViewController.presentViewControllerFromVisibleViewController(viewControllerToPresent: viewControllerToPresent, animated: true, completion: completion)
} else if (presentedViewController != nil) {
presentedViewController!.presentViewControllerFromVisibleViewController(viewControllerToPresent: viewControllerToPresent, animated: true, completion: completion)
} else {
present(viewControllerToPresent, animated: true, completion: completion)
}
}
}
For your question on monitoring when incoming calls occur and to be called as a result, see this answer. It's probably what you need (I've never tried it, however). The example shows creating a CXCallObserver and setting your AppDelegate as delegate.
For your second question, I'd first try this answer which leverages the window.rootViewController so you can do this from your AppDelegate. Generally, the root VC is your friend when trying to do UI your AppDelegate. :)
A better answer based on Alex's added comments:
I'd first look at how to set up an observer to your Firebase model so that you can get a callback. If you don't have a way to do that, I'd use KVO on the Firebase model property. But to do exactly as you're requesting, and to do so lazily from AppDelegate (rather than from a singleton), see this code:
// In AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool
{
self.timerToCheckForCalls = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
}
func timerFired()
{
let didCall = // TODO: query your Firebase model...
guard didCall == true else
{
return
}
self.displayCallerView()
}
func displayCallerView()
{
// See below link.
}
See this answer for how to present your view controller, even when your app might be showing an action sheet, alert, etc... which I think you'd especially value since you need to display the caller regardless of what your app is doing.
Note while user is scrolling a UITextView, the timer won't fire yet. There may be other situations where the timer could be delayed too. So it really would be best to observe your Firebase model or receive a KVO callback than to use a timer.
If you want to make a function that can be called from anywhere, use a singleton pattern. You can also use that to store your special view controller.
Bear in mind that this code SHOULD NOT considered fully functioning code and will need to be customized by you to suit your needs.
class MyClass {
let shared = MyClass()
var viewController: SpecialViewController?
func checkForCall() {
// do function stuff
}
func getSpecialViewController() {
let storyBoard = UIStoryboard.init(name: "main", bundle: nil)
// keep it so we don't have to instantiate it every time
if viewController == nil {
viewController = storyBoard.instantiateViewController(withIdentifier: "SomeViewController")
}
return viewController
}
}
// Make an extension for UIViewController so that they can all
// use this function
extension UIViewController {
func presentSpecialViewController() {
let vc = MyClass.shared.getSpecialViewController()
present(vc, animated: false, completion: nil)
}
}
Somewhere in your code:
// in some function
MyClass.shared.checkForCall()
Somewhere else in code:
presentSpecialViewController()

Performing Long-Term Actions in the Background Swift 3

I have an app that uses Corebluetooth and uses its Background services. I need also long-term CoreBluetooth action so I need to implement State preservation and restoration. I followed Apple document the link at the below https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html also watched WWDC 2013 presentation video link below,
https://developer.apple.com/videos/play/wwdc2013/703/
First of all, the sources are in Objective C and I am not familiar with that, I tried to convert them in swift3.
Then, I defined CBCentralManager with restoration identifier shown below,
self.myCentralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentralManager"])
Then, added delegate method something in the below,
func centralManager( willRestoreState dict: [String : AnyObject]) {
let peripherals: [CBPeripheral] = dict[CBCentralManagerRestoredStatePeripheralsKey] as! [CBPeripheral]
for peripheral in peripherals {
self.peripherals.append(peripheral)
peripheral.delegate = self
}
}
Finally did didFinishLaunchingWithOptions method in AppDelegate,
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
if let peripheralManagerIdentifiers: [String] = launchOptions?[UIApplicationLaunchOptionsKey.bluetoothCentrals] as? [String]{
for identifier in peripheralManagerIdentifiers{
if (identifier == "myCentralManager"){
}
}
}
return true
}
When I debug, didFinishLaunchingWithOptions method called, the parameter launchOptions?[UIApplicationLaunchOptionsKey.bluetoothCentrals] is nil I think because of first time I created the app, then got error when defining CBCentralManager with restoration identifier the error is "reason: ' has provided a restore identifier but the delegate doesn't implement the centralManager:willRestoreState: method'"
I'm totally confused, thank you for any help,
Best regards,

XCGLogger: Ambiguous reference to member 'log'

Trying to set up XCGLogger and receiving error:
Ambiguous reference to member 'log'
I see this issue was already raised but I'm not clear on the solution..
Per the install guide added this global constant to AppDelegate.swift:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let log = XCGLogger.defaultInstance()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
log.setup(.Debug, showThreadName: true, showLogLevel: true, showFileNames: true, showLineNumbers: true, writeToFile: nil, fileLogLevel: .Debug)
return true
}
Then in individual source files:
import XCGLogger
log.debug("A debug message")
What is the proper usage?
The issue is rather simple. If you declare log inside AppDelegate, you are making an instance variable. To access it, you will have to access it as instance variable:
(UIApplication.sharedApplication().delegate as! AppDelegate).log.debug("test")
If you want log to be accessible everywhere, you will have to make it a global constant:
In your AppDelegate, declare a global constant to the default XCGLogger instance.
let log = XCGLogger.defaultInstance()
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
(there is no need to declare it in AppDelegate file, you can basically put it anywhere in your code)
or make it static:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
static let log = XCGLogger()
and access it using:
AppDelegate.log.debug(...)
To explain the ambigious reference, there is a mathematical function called log, and there is also a log function in the malloc.h file. Since you are passing a String as the first parameter and neither of the two functions is a match, the compiler warns you that it does not know which of the two functions you want to use.
Also I guess it's better to create global constant file and create something like this (if you've declared log in AppDelegate):
let LOG = (UIApplication.sharedApplication().delegate as! AppDelegate).log
and then simple use LOG.error("error")