SwiftUI AppDelegate not being ran whenever app is opened - swiftui

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) { }

Related

SwiftUI: How can you make your app come to the front when clicking on the dock icon of the app?

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.

Swiftui continue with Facebook failed

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
}
}

How to fix a Picker navigation works only when I long press a link

I'm currently developing an application using SwiftUI.
When I use a onTapGesture at Form in NavigationView, navigation of Picker the inside doesn't work.
It works but only when I long-press a link like a LongPressGesture.
If I don't use onTapGesture, the navigation of Picker works, as usual, But in that case, I can not close a keyboard when I use TextField choosing numberPad as keyboardType...
How could I solve this problem?
Here is a code:
OnTapGestureTest.swift
import SwiftUI
struct OnTapGestureTest: View{
#State var inputNo:String = ""
#State var selectNo:Int = 0
var body: some View {
NavigationView{
Form{
TextField("InputNo", text: $inputNo)
.keyboardType(.numberPad)
Picker(selection:$selectNo, label: Text("SelectNo")){
Text("0").tag(0)
Text("1").tag(1)
Text("2").tag(2)
Text("3").tag(3)
Text("4").tag(4)
Text("5").tag(5)
}
}
.onTapGesture {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
}
}
UPDATED
I tried to solve my question by referencing here.
*I want to close keyboard only on Tap outside (without handling drags) in iOS 13.0
SceneDelegate.swift
...
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
...
let tapGesture = UITapGestureRecognizer(target: window, action:#selector(UIView.endEditing))
tapGesture.requiresExclusiveTouchType = false
tapGesture.cancelsTouchesInView = false
tapGesture.delegate = self
window?.addGestureRecognizer(tapGesture)
}
...
But it still doesn't work well... Do I need to do some more things? misunderstand the answer?
Xcode: Version 12.0.1
iOS: 13.0
You could add the tap gesture globally, with the help of UIWindow:
extension UIApplication {
func addTapGestureRecognizer() {
guard let window = windows.first else { return }
let tapGesture = UITapGestureRecognizer(target: window, action: #selector(UIView.endEditing))
tapGesture.requiresExclusiveTouchType = false
tapGesture.cancelsTouchesInView = false
tapGesture.delegate = self
window.addGestureRecognizer(tapGesture)
}
}
extension UIApplication: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
In your main: (The idea: as soon as your app launches, this tapGesture is added to the window)
#main
struct YourApp: App {
...
var body: some Scene {
WindowGroup {
//Your initial view
.onAppear(perform: UIApplication.shared.addTapGestureRecognizer)
}
}
}
This is especially convenient since you don't have to take care of hiding the keyboard ever again inside your views and it's not blocking any of your controls.
Note
This code is from Here
Also, I saw iOS13 - for non SwiftUI Cycle, this would be the equivalent: Here

How would I access UserDefaults in (new) Swift 5.3 iOS 14 code?

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()
}
}
}

An error of No ObservableObject of type XXX found happens on some devices in SwiftUI

I encounter the following error.
Fatal error: No ObservableObject of type Fruit found. A View.environmentObject(_:) for Fruit may be missing as an ancestor of this view.: file SwiftUI, line 0
This error happens when moving between ContentView and ListView repeatedly. Moreover, this error only happens on iPhone of iOS 13.5 such as iPhone 11 pro max and iPhone 8 plus. Doesn't happen on iPads. Doesn't happen on iOS 13.4. Xcode version is 11.5 (11E608c).
The following is the minimal source code for reproducing error. Could anyone tell me how this error happen?
import SwiftUI
class Fruit: ObservableObject {
#Published var sweetness: Double
init(sweetness: Double) {
self.sweetness = sweetness
}
}
struct ListView: View {
#EnvironmentObject var fruit: Fruit
var body: some View {
VStack {
Text(String(fruit.sweetness))
}
.navigationBarItems(trailing: Text("Text"))
}
}
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: ListView()) {
Text("Open")
}
}
}
}
}
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
.environmentObject(Fruit(sweetness: 123.4))
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}