SwiftUI 2 accessing AppDelegate - ios

I did a small prototype which uses Firebase Cloud Messaging and the new SwiftUI 2 app life cycle. I added a custom AppDelegate via
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate and disabled method swizzeling for FCM to work. Everything is working as expected.
Today a colleague asked me, if one can get that delegate object via UIApplication.shared.delegate. So I gave it a shot and noticed that there seems to be two different AppDelegate objects:
po delegate prints:
<MyProject.AppDelegate: 0x6000009183c0>
where as po UIApplication.shared.delegate prints:
▿ Optional<UIApplicationDelegate>
▿ some : <SwiftUI.AppDelegate: 0x600000b58ca0>
Now I'm wondering what is the correct way of accessing the AppDelegate? Should one get it via an #EnvironmentalObject and pass it along all views? Or use the old fashioned way via UIApplication?
Additionally I would like to understand why I end up with two AppDelegates.

Your MyProject.AppDelegate is not direct UIApplicationDelegate, it is transferred via adapter to internal private SwiftUI.AppDelegate, which is real UIApplicationDelegate and which propagates some delegate callback to your instance.
So the solution might be:
Use #EnvironmentalObject if you need access to your MyProject.AppDelegate only in SwiftUI view hierarchy (for this AppDelegate must be confirmed to ObservableObject).
Add and use MyProject.AppDelegate static property which is initialized with object created via adapter, like
class AppDelegate: NSObject, UIApplicationDelegate {
static private(set) var instance: AppDelegate! = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
AppDelegate.instance = self // << here !!
return true
}
}
now everywhere in your code you can access your delegate via AppDelegate.instance.

Related

Routing Viper architecture

How to create good routing in project on Viper architecture? I start create file for routing but i don't understand what i must do next.
I create a file wireframe first controller and wireframe protocol:
// ChooseLanguageWireframeProtocol.swift
import UIKit
#objc protocol ChooseLanguageWireframeProtocol {
func presentChooseLanguageViewControllerWindow()
func presentAuthScreenViewController()
}
in file wireframe i add:
// ChooseLanguageWireframe.swift
import UIKit
class ChooseLanguageWireframe: NSObject , ChooseLanguageWireframeProtocol{
var chooseLanguageScreenViewController: ChooseLanguageViewController?
var window: UIWindow?
func presentChooseLanguageViewControllerWindow() {
let chooseLanguageViewController = UIStoryboard.init(name: "ChooseLanguage", bundle: nil).instantiateViewController(withIdentifier: "ChooseLanguage") as? ChooseLanguageViewController
self.chooseLanguageScreenViewController = chooseLanguageViewController
self.window!.rootViewController = chooseLanguageScreenViewController
self.window!.makeKeyAndVisible()
}
func presentAuthScreenViewController() {
}
}
After i create RootWireframe
// RootWireframe.swift
import UIKit
class RootWireframe: NSObject {
let chooseLanguageScreenWireframe : ChooseLanguageWireframe?
override init() {
//What i must init??
}
func application(didFinishLaunchingWithOptions launchOptions: [AnyHashable: Any]?, window: UIWindow) -> Bool {
self.chooseLanguageScreenWireframe?.window = window
return true
}
}
In file AppDelegate i change only
var window: UIWindow?
let rootWireframe = RootWireframe()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.rootWireframe.application(didFinishLaunchingWithOptions: launchOptions as [NSObject : AnyObject]?, window: self.window!)
}
What i must add or change for correctly work?
One way to do it is to inherit from UINavigationController and do the routing logic in there. You may end up with multiple classes doing routing in different parts of your application depending on your task.
But like with any cromulent buzzword you firstly have to ask yourself
if the "solutuon" solves more issues than it creates.
It starts to make sense when you get two dozen screens and start to get lost in
the very same issue that got resolved ages ago by adding storyboards to
plain old xibs.
So you really have to step back and ask yourself if you really want to follow
this cromulent architecture creating lots and lots of classes in the process,
a maze that will be hardly more readable than a standard MVC project.
If you can not use storyboards and want to use VIPER do so,
if you can use storyboards do VIPE :-)
But in most projects the datamodel is so pathetically simple and the
presentation is so tightly coupled that you have zero need in
UIViewController defattening.
And I suspect that the cure applied by the most viper affictionados is
worse than the decease itself.
2 possibilities:
Change the init of your rootViewframe to accept your dependency and initialize it.
And btw your variable type should not be the real type but the protocol, so you can mock it easily in tests
let chooseLanguageScreenWireframe : ChooseLanguageWireframeProtocol?
override init(chooseLanguage: ChooseLanguageWireframeProtocol) {
self.chooseLanguageScreenWireframe = chooseLanguage
}
And then create your chooseLanguageWireframe implementation in the appdelegate and pass it in the constructor.
This makes the dependency clear and visible. You can also remove the optionality, since you always initialize it.
Or solution 2
Create the chooseLanguageWireframe in the appdelegate and inject it there outside constructor
lazy var rootWireframe = {
let r = RootWireframe()
r.chooseLanguageScreenWireframe = self.chooseLanguageScreenWireframe
return r
}
In any case, you need to instantiate your dependencies somewhere, they cannot get created automatically.
You usually do that with factories and with the help of a dependency injection framework (check SwiftInject or Typhoon)
Also, declare all your dependencies with protocols type, it is the purpose of the VIPER architecture to simplify testing and mocking with isolations between actors

Cannot find connected accessory if EAAccessoryManager.shared() is called in AppDelegate constructor

If I call EAAccessoryManager.shared() inside the AppDelegate constructor e.g.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var accessoryManager = EAAccessoryManager.shared()
...
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
return true
}
...
}
Then later I access the accessory manager to get connected accessories (with an accessory connected) EAAccessoryManager.shared().connectedAccessories.count returns 0, which is incorrect.
If I don't call EAAccessoryManager.shared() in the AppDelegate constructor, then EAAccessoryManager.shared().connectedAccessories.count returns 1, which is correct.
I can workaround this problem, but I really wanted to understand why this might be happening, as I could have misunderstood something about how the EAAccessoryManager is meant to work (or perhaps something more fundamental about how an app is initialised - I am new to iOS programming).
Has anyone hit this problem before, or maybe have an idea why this could be happening?

performSelector error with global function and AppDelegate class

I'm following this apple document and I'm trying to translate some of its parts in Swift language. I have this global function, with performSelector:
func RunLoopSourceScheduleRoutine(info:UnsafeMutableRawPointer? ,rl:CFRunLoop? , mode:CFRunLoopMode?) {
let obj : RunLoopSource = Unmanaged<RunLoopSource>.fromOpaque(info!).takeUnretainedValue()
let del = UIApplication.shared
let theContext = RunLoopContext(withSource: obj, andLoop: rl!)
del.performSelector(onMainThread:#selector(AppDelegate.registerSource) , with: theContext, waitUntilDone: false)
}
And AppDelegate class, in this class there are: methods that automatically adds Xcode in the normal routine of project creation (didFinishLaunchingWithOptions, applicationWillResignActive, etc) I added the sourcesToPing parameter and the registerSource() method:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var sourcesToPing : [RunLoopContext] = Array()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func registerSource(sourceInfo:RunLoopContext) {
sourcesToPing.append(sourceInfo)
}
}
but the compiler get the following error , in RunLoopSourceScheduleRoutine() function:
argument '#selector' refers to instance method 'registerSources(source Info:)' that is not exposed to Objective-C
what is the problem ? and how does it solve?
PerformSelector is an Objective-C method that predates GCD (Grand Central Dispatch). It should be possible to do it that way, but selectors are not type-safe and are awkward to use.
I'm not sure what's wrong with your current code. As Martin points out in his comment, the error you're reporting is complaining about a method called registerSources() but you show code for a method called registerSource() (with no final "e".) If you want to get that code working you need to get to the bottom of that discrepency.
Instead, why not use GCD code like this:
dispatchQueue.main.async() {
registerSource(theContext)
}
That will accomplish the same goal but using the more modern GCD

Could not cast value of type 'LLAppDelegateProxy'

I have integrated the Localtyics iOS SDK. After this I am getting the error like below:
Could not cast value of type 'LLAppDelegateProxy'
It means I am unable to get a reference of the App delegate object. I am in trouble now because I want Localytics & want a reference object of App delegate as well.
Does any body know a solution of this?
Localytics replaces your AppDelegate behind-the-scenes with their proxy class
(LLAppDelegateProxy). Localytics suggests creating a static reference to your original AppDelegate for access like so:
class AppDelegate: UIResponder, UIApplicationDelegate {
static var originalAppDelegate: AppDelegate!
// ...
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
AppDelegate.originalAppDelegate = self
// ...
}
Access using:
AppDelegate.originalAppDelegate.someMethod()

Declaring UIWindow as let instead of optional var compiles fine. Any pitfalls?

I have seen numerous examples of UIWindow been declared as an Optional variable, like so,
var window: UIWindow?
My app only has one window and it will remain the same throughout its lifecycle. I'd argue it makes more sense to declare it as a constant.
And so I did. It doesn't raise any compiler errors (as of iOS 8.2) and seems to work just fine.
Why is nobody else doing this? Are there any pitfalls to doing this?
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
let window: UIWindow = UIWindow(frame: UIScreen.mainScreen().bounds)
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let viewController = ViewController()
window.rootViewController = viewController
window.makeKeyAndVisible()
return true
}
There are two parts to your question: let vs var, and optional vs non-optional.
For the first part, declaring an object property as let just means you expect the property to reference the same object throughout its lifecycle. Makes sense in your AppDelegate for a single-window application.
For the second part, UIWindow inherits its initializer from UIView; the documentation for UIView says that the initializer can return nil, although the Swift version isn't declared as being failable.
So if you're dealing with UIViews in other contexts, it probably makes sense to declare the variables as optional, or at least unwrapped, and be able to cope with the results.
That said, when your program is starting up, if the UIWindow fails to initialize, crashing immediately is probably a reasonable thing to do, as you won't really be able to do anything else. :)
I don't see any problem with how you've done it, but I'm open to hearing from others too.

Resources