I'm creating a new app using swiftui, I've added "continue with Facebook" login option, however I'm getting the below error when I press the button, how can i fix it? any help?
error message:
Thread 1: "As of v9.0, you must initialize the SDK prior to calling any methods or setting any properties. You can do this by calling FBSDKApplicationDelegate's application:didFinishLaunchingWithOptions: method. Learn more: https://developers.facebook.com/docs/ios/getting-started"
code:
import SwiftUI
import Firebase
import FBSDKCoreKit
#main
struct FirstApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: { url in
ApplicationDelegate.shared.application(
UIApplication.shared,
open: url,
sourceApplication: nil,
annotation: [UIApplication.OpenURLOptionsKey.annotation]
)
})
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool {
FirebaseApp.configure()
return true
}
}
Related
I'm using SwiftUI's new app lifecycle coming in iOS 14.
However, I'm stuck at how to access my AppState (single source of truth) object in the AppDelegate.
I need the AppDelegate to run code on startup and register for notifications (didFinishLaunchingWithOptions, didRegisterForRemoteNotificationsWithDeviceToken, didReceiveRemoteNotification) etc.
I am aware of #UIApplicationDelegateAdaptor but then I can not e.g. pass an object through to the AppDelegate with a constructor. I guess the other way round (creating the AppState in the AppDelegate and then accessing it in MyApp) does not work either.
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#State var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(appState)
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// access appState here...
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// ...and access appState here
}
}
class AppState: ObservableObject {
// Singe source of truth...
#Published var user: User()
}
Any help is appreciated. Maybe there is currently no way to achieve this, and I need to convert my app to use the old UIKit lifecycle?
Use shared instance for AppState
class AppState: ObservableObject {
static let shared = AppState() // << here !!
// Singe source of truth...
#Published var user = User()
}
so you can use it everywhere
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#StateObject var appState = AppState.shared
// ... other code
}
and
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// ...and access appState here
AppState.shared.user = ...
}
Is there a reason why you need to run the code in app delegate?
If you are using new app lifecycle, why not trigger your code from WindowGroup.onChange()
struct MyScene: Scene {
#Environment(\.scenePhase) private var scenePhase
#StateObject private var cache = DataCache()
var body: some Scene {
WindowGroup {
MyRootView()
}
.onChange(of: scenePhase) { newScenePhase in
if newScenePhase == .background {
cache.empty()
}
}
}
}
Apple Documentation Link
Managing scenes in SwiftUI by Majid
When a SwiftUI app is minimized and the dock icon is clicked. The app won't be deminimized and put to the front just like other apps do.
import SwiftUI
#main
struct MyApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
MainView()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
// THIS IS NEVER CALLED!!!
if !flag {
for window: AnyObject in sender.windows {
window.makeKeyAndOrderFront(self)
}
}
return true
}
}
Other delegate methods like applicationDidLaunch do get called so its not a linking issue. Does anyone know how to get this to work?
Comment on Asperi
2022
Will display app in case of it is hidden or minimized both!
func applicationDidBecomeActive(_ notification: Notification) {
NSApp.unhide(self)
if let wnd = NSApp.windows.first {
wnd.makeKeyAndOrderFront(self)
wnd.setIsVisible(true)
}
}
Use did become active callback, tested as worked with Xcode 13.3 / macOS 12.2.1
Like,
func applicationDidBecomeActive(_ notification: Notification) {
if NSApp.windows.compactMap({ $0.isVisible ? Optional(true) : nil }).isEmpty {
NSApp.windows.first?.makeKeyAndOrderFront(self)
}
}
Test module in project is here
Note: in case of active application the only application(Will/Did)Update are called in click Dock icon.
In delegate methods that get called when clicking on the dock icon try calling
NSApp.activate(ignoringOtherApps: true)
This should bring your app to the foreground
This is a temporary work around that works for me
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// disable tabs ...
NSWindow.allowsAutomaticWindowTabbing = false
}
// Quit if the main window is closed
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
BzLogger[Self.self].info("")
return true
}
func applicationDidUpdate(_ notification: Notification) {
if NSApplication.shared.mainWindow == nil,
let event = NSApplication.shared.currentEvent {
if event.type == .systemDefined {
// FIX-ME: kdeda
// This is hack since Apple broke this whole thing on macOS
// BzLogger[Self.self].info("NSApplication.shared.currentEvent: \(event)")
// BzLogger[Self.self].info("NSApplication.shared.currentEvent: \(event.subtype)")
// BzLogger[Self.self].info("NSApplication.shared.pressedMouseButtons: \(NSEvent.pressedMouseButtons)")
// BzLogger[Self.self].info("NSApplication.shared.isActive: \(NSApplication.shared.isActive)")
if NSEvent.pressedMouseButtons == 1 {
// mouse down, maybe there is a better way
if NSApp.windows.compactMap({ $0.isVisible ? Optional(true) : nil }).isEmpty {
NSApp.windows.first?.makeKeyAndOrderFront(self)
}
}
}
}
}
}
I found this solution.
In your #main entry class App add this property:
#NSApplicationDelegateAdaptor var appDelegate: AppDelegate
It now should looks like this:
import SwiftUI
#main
struct TestApp: App {
#NSApplicationDelegateAdaptor var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Now, create the AppDelegate class (you can create a new separate AppDelegate.swift file or just continue typing after the App struct closed:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidChangeOcclusionState(_ notification: Notification) {
if let window = NSApp.windows.first, window.isMiniaturized {
NSApp.hide(self)
}
}
func applicationDidBecomeActive(_ notification: Notification) {
NSApp.windows.first?.makeKeyAndOrderFront(self)
}
}
Now it is minimizing, maximizing, closing and reopening again as normal as other apps (ex. Safari, Mail, etc.).
This works perfectly on Monterey 12.4.
Here is a simple AppDelegate that I have made:
import SwiftUI
class AppDelegate: UIResponder, UIApplicationDelegate {
private func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
print("application")
// first launch
// this method is called only on first launch when app was closed / killed
return true
}
private func applicationWillEnterForeground(application: UIApplication) -> Bool{
print("applicationWillEnterForeground")
// app will enter in foreground
// this method is called on first launch when app was closed / killed and every time app is reopened or change status from background to foreground (ex. mobile call)
return true
}
private func applicationDidBecomeActive(application: UIApplication) -> Bool {
print("applicationDidBecomeActive")
// app becomes active
// this method is called on first launch when app was closed / killed and every time app is reopened or change status from background to foreground (ex. mobile call)
return true
}
}
#main
struct CouponDeckApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
AppContentView()
}
}
}
The AppDelegate doesn't seem to be calling any of its functions, though. It should be running them at 3 places, but none are running. Why is this happening, and how do I fix it?
You've marked your app with #main which makes it app entry point.
Not all methods of app delegate will work with UIApplicationDelegateAdaptor, like here applicationWillEnterForeground applicationDidBecomeActive won't be called.
Instead you're supposed to use publishers inside SwiftUI view, like this:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
print("applicationDidBecomeActive")
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
print("applicationWillEnterForeground")
}
An other problem with your AppDelegate is that it doesn't have needed methods signature. When you're adding new method, don't paste it from somewhere, instead type a couple of letters from method name and let Xcode finish the rest. In your case if you have your signature correct, private would be an error reported by Xcode.
If you still wanna get them inside your delegate for some reason, you need to move #main to AppDelegate, and initialize your SwiftUI view with UIHostingController like it was back done in SwiftUI 1:
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow()
self.window = window
window.rootViewController = UIHostingController(rootView: ContentView())
window.makeKeyAndVisible()
return true
}
func applicationWillEnterForeground(_ application: UIApplication) {
print("applicationWillEnterForeground")
}
func applicationDidBecomeActive(_ application: UIApplication) {
print("applicationDidBecomeActive")
}
}
Also you need to update Info.plist, set enable multiple windows to NO:
Use the SceneDelegate file instead.
Add it in this method:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { }
I am using Big Sur and SwiftUI with the SwiftUI lifecycle. I want to implement an alert, where the user gets asked, if the application can be quit or not. How is this possible with SwiftUI? It should look like this:
It's possible by using this code (this code opens the Alert only in the key window):
import SwiftUI
import AppKit
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
#Published var willTerminate = false
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
// check, if at least one window is open:
if NSApplication.shared.windows.count == 0 {
// if no one is open, close it
return .terminateNow
}
// if one or more are open, set the willTerminate variable, so that the alert can be shown
self.willTerminate = true
// return a .terminateLater (to which we need to reply later!)
return .terminateLater
}
/// This method tells the application, that it should not close
func `continue`() {
NSApplication.shared.reply(toApplicationShouldTerminate: false)
}
/// This method closes the application
func close() {
NSApplication.shared.reply(toApplicationShouldTerminate: true)
}
}
#main
struct WindowShouldCloseApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
And this is the ContentView.swift:
import SwiftUI
struct ContentView: View {
#EnvironmentObject private var appDelegate: AppDelegate
#State private var window: NSWindow?
var body: some View {
Text("Hello, world!")
.padding()
.background(WindowAccessor(window: self.$window)) // access the window
.alert(isPresented: Binding<Bool>(get: { self.appDelegate.willTerminate && self.window?.isKeyWindow ?? false }, set: { self.appDelegate.willTerminate = $0 }), content: {
// show an alert, if the application should be closed
Alert(title: Text("Really close?"),
message: Text("Do you really want to close the application?"),
primaryButton: .default(Text("Continue"), action: { self.appDelegate.continue() }),
secondaryButton: .destructive(Text("Close"), action: { self.appDelegate.close() }))
})
}
}
// thanks to Asperi: https://stackoverflow.com/questions/63432700/how-to-access-nswindow-from-main-app-using-only-swiftui/63439982#63439982
struct WindowAccessor: NSViewRepresentable {
#Binding var window: NSWindow?
func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
self.window = view.window
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
}
In the newest SwiftUI template project there is no AppDelegate (like in the oldern-days :^) so where do I put the code to work with UserDefaults?
Any pointers? Thanks!
Now why didn't I think to do this... (because I'm a newbie).
And coming from a Java world... where the one Class <-> one File rule has some werid influence upon code structure thinking. Got give up the Java.
import SwiftUI
import UIKit
// no changes in your AppDelegate class
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print(">> your code here !!")
return true
}
}
#main
struct Testing_SwiftUI2App: App {
// inject into SwiftUI life-cycle via adaptor !!!
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Of course if you want to use "pure" swiftUI, you could put your code in the init() of #main...
Like:
#main
struct Testing_SwiftUI2App: App {
init() {
// your userdefaults code here...
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}