Environment, Swift 5.3 and Xcode 12
Normally we do someObject.delegate = self to set the current class as the delegate for some class instance. However, inside AppDelegate there is no such assignment to make it a delegate of the application.
import Cocoa
#main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {}
func applicationWillTerminate(_ aNotification: Notification) {}
}
So where the application's delegate property get set?
#UIApplicationMain attribute means that this class is the application delegate, https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID589.
It's the same as calling the UIApplicationMain(:,:,:,:). If the attribute is NOT used, supply a main.swift file with code at the top level that calls UIApplicationMain(:,:,:,:) (also applicable to NSApplication).
Adding #main seems to have the same effect as #UIApplicationMain. Replace #main with #UIApplicationMain and the app works just fine, tested.
Related
With the new SwiftUI Lifecycle there normally is no AppDelegate anymore. However, in order to implement Firebase Messaging, it is recommended on the Firebase docs to implement an AppDelegate and attach it using:
#main
struct ExampleApp: SwiftUI.App {
// register app delegate for Firebase setup
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate //<--- HERE
#StateObject var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appState)
}
}
}
Inside this AppDelegate functions one obtains an FCM Token, with which the device is identified to subsequently send it notifications remotely. This token has to get sent to the server. This could be done inside the respective function inside the AppDelegate . However, there is a class AppState (ObservableObject protocol) that handles the user data and writing to the server, so it would make a lot more sense to write pass it to this class (or retrieve in the class from the AppDelegate?) and then push it from there.
How can this be accomplished?
edit: I guess this could be achieved by using a static property in the AppDelegate as described in this answer. Is using statics to access variables globally not bad practice? Is there a other (better) way?
You can do it with the "old way" of accessing the delegate
#MainActor //Required by the app delegate
class AppState: ObservableObject{
lazy var appDelegate: AppDelegate? = {
UIApplication.shared.delegate as? AppDelegate
}()
}
Then you can just use
appDelegate?.yourToken
yourToken referencing a property in the delegate
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.
I have an array inside a class called Anacii and obviously an AppDelegate class. I'm trying to save two arrays, both located inside the Anacii class, to UserDefaults when the application terminates. Everything works fine except getting the two arrays from the Anacii class from the AppDelegate class. Both arrays have multiple values inside them (I tested that with some print statements) and I can access them from my root view controller just fine with all the values inside of them but they return as empty arrays when I get them from the AppDelegate class.
Here are the two arrays defined in the Anacii class:
class Anacii {
// MARK: - Anacs / Rarities
var anacs = [String]()
var rarities = [Int]()
...
}
Here's where I set the actual values inside my root view controller:
class HomeController: UITableViewController, CLLocationManagerDelegate {
private let a = Anacii()
...
override func viewDidLoad() {
super.viewDidLoad()
for _ in 1...10 {
collectAnac()
}
...
}
...
// MARK: - Other functions
func collectAnac() {
let rarity = a.generateRarity()
let anac = a.findAnac(rarity: rarity)
a.anacs.append(anac)
a.rarities.append(rarity)
...
}
}
And finally, here is where I try to access the variables from the AppDelegate class:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
let a = Anacii()
let anacs = a.anacs // Comes out as []
let rarities = a.rarities // Comes out as []
...
}
}
The two values at the bottom of the AppDelegate class (anacs and rarities) both equal [] (tested by using print statements).
Sorry if this is a duplicate question, I looked at a lot of other posts like this but could find no answer that worked for me. Thanks!
TL;DR: You should read the whole thing but... Trying to access two arrays from the Anacii class from the AppDelegate class returns empty list, even though those two arrays are NOT empty (other classes see them with all the values they have). See the code above. Sorry if this is a duplicate post.
Putting it simply, you don't do this from the AppDelegate class. You do it from the class where the data actually is. In HomeController, register for the appropriate notification (e.g. the app is being backgrounded) from UIApplication and respond to it.
By the way, that notification should not be applicationWillTerminate, as it is never called.
(The actual reason for the phenomenon you're seeing is that Anacii() in AppDelegate is the wrong object. But it's best to do this the right way.)
My app needs to perform multiple URL requests via Alamofire but I would like to perform these tasks independently on views or what user does in UI. This basically means "on background" so the handlers which actually performing the tasks would not be deinitialized until they are done.
My idea would be to call these requests from shared AppDelegate via some kind of class methods. I have only one question now:
What would be the best way to implement this scenario?
My actual knowledge would create something like this:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
class func someKindOfClassRequest() {
// ...
}
func someKindOfRequest() {
// ...
}
// ...
}
And I would call the method this way:
AppDelegate.someKindOfClassRequest()
or with not-class func, which of course will not solve the issue:
let sharedDelegate = UIApplication.shared.delegate as! AppDelegate
AppDelegate.someKindOfRequest()
As mentioned in the comments, Alamofire uses closure-based completions so they will not get deinitialized even if the calling object is deinitialized. For the sake of keeping your code well organized, why not just create a class that does these things instead of throwing it into your AppDelegate? For instance, create a class called BankgroundRequestController:
class BackgroundRequestController {
static let sharedInstance = BackgroundRequestController()
class func someKindOfClassRequest() {
// ...
}
}
Then you can call these functions like:
BackgroundRequestController.sharedInstance.someKindOfClassRequest()
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