How to add code to AppDelegate using SwiftUI Life Cycle - swift

I'm following Google's tutorial on how to integrate AdMob ads into my app. In the tutorial, they put some code into the App Delegate. I've only ever used the SwiftUI life cycle for apps, so I'm not even sure what the App Delegate is. However, the issue is that in the tutorial, they instruct to put this code in the App Delegate file:
[[GADMobileAds sharedInstance] startWithCompletionHandler:nil];
I'm trying to put this code into the init() of my app (aka the SwiftUI version of putting it into my App Delegate). However, as this doesn't seem to be Swift code, I can't use that line of code without receiving errors. What should I do to insert this code into my app init()?
Following this tutorial at around 3:10 into the video.

The init might be too early, try in app delegate as follows
import GoogleMobileAds
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
GADMobileAds.sharedInstance().start(completionHandler: nil) // << here !!
return true
}
}
#main
struct YourApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Related

Firebase exception error for macOS app Xcode

When I try to run the simulator for my macOS app, which is using Firebase, it gives this error: "Thread 1: "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 notice this happened after I created an environment object.
Here is my code:
import SwiftUI
import FirebaseCore
#main
struct testagainApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
let viewModel = AppViewModel()
ContentView()
.environmentObject(viewModel)
}
.windowStyle(HiddenTitleBarWindowStyle())
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
FirebaseApp.configure()
}
}
If I get rid of let viewModel = AppViewModel() and .environmentObject(viewModel), the simulator runs just fine. If I put the app delegate first, the simulator runs but nothing appears. I am new to Swift and am unsure how to fix this.
Option 1:
I faced same issue a while ago, then noticed that I have not linked the
GoogleService-Info.plist file to the project, if you see its already added then better remove and add it to the project via Xcode again.
Hope that solves your issue.
Option 2:
Make sure you don't have more two copies of Firebase linked into your app. If so you can remove one. See this doc on the Firebase iOS SDK for more details.
I solved it. Instead of using the AppDelegate I used the App's initializer:
struct testagainApp: App {
init() {
FirebaseApp.configure()
}
// ...
}

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 do i connect the firebase Firestore to my swift app

I have tried many different resources online but they all seem to use app delegate files and I get TOTALLY confused.
All I want to be able to do is read and write from the cloud firestore to my swift app
I have installed firebase and the firestore via the package manager and it can't seem to find the Firestore Manager
import SwiftUI
import Firebase
#main
struct RecipyApp: App{
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#StateObject var firestoreManager = FirestoreManager()
var body: some Scene{
let viewModel = AppViewModel()
WindowGroup{
ContentView()
.environmentObject(viewModel)
.environmentObject(firestoreManager)
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
MY CODE.
What can I do to fix my problem?
Many thanks
Solution 1:
Delete The Excess Package And Run The App
Solution 2:
Go To Xcode Preference > Location, Delete Derived Data

Can I use the Snapchat SDK (SnapKit) with SwiftUI?

I'm trying to integrate Snapkit with an iOS app but I want to use SwiftUI instead of UIKit. I've already done the required setup with Snapkit and now I'm trying to get the snapchat login button to display in my app. I know the Snapkit SDK is made for UIKit and not SwiftUI, but SwiftUI has a way to wrap UIViews into SwiftUI with the UIViewRepresentable protocol. I've tried implementing this but the login button still doesn't display.
Here's my code:
import SwiftUI
import UIKit
import SCSDKLoginKit
struct ContentView: View {
var body: some View {
SnapchatLoginButtonView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct SnapchatLoginButtonView: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIView(context: Context) -> SCSDKLoginButton {
let s = SCSDKLoginButton()
s.delegate = context.coordinator
return s
}
func updateUIView(_ uiView: SCSDKLoginButton, context: Context) {
}
class Coordinator: NSObject, SCSDKLoginButtonDelegate {
func loginButtonDidTap() {
}
}
}
I have a feeling I'm missing something from within SCSDKLoginButton, but not sure what it is so here's the file SCSDKLoginButton.h for reference. Any help would be greatly appreciated!
//
// SCSDKLoginButton.h
// SCSDKLoginKit
//
// Copyright © 2018 Snap, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
#protocol SCSDKLoginButtonDelegate
- (void)loginButtonDidTap;
#end
#interface SCSDKLoginButton : UIView
#property (nonatomic, weak, nullable) id<SCSDKLoginButtonDelegate> delegate;
- (instancetype)initWithCompletion:(nullable void (^)(BOOL success, NSError *error))completion NS_DESIGNATED_INITIALIZER;
#end
Coincidentally I attempted implementing the SnapKit SDK in an exclusive SwiftUI/ iOS13 project about 3 days after you posted your Issue.
Unfortunately I can't directly resolve your issue as there are a few key issues which Snapchat has to address with their SDK before it is suitable for development with the SceneDelegate & AppDelegate Paradigm introduced in iOS 13. But I hope I can shed light on your question and present my findings to anyone else who is in a similar predicament.
These are the following issues/ observations I made on my quest of Implementing SCSDKLoginKit & SCSDKBitmojiKit in SwiftUI:
The most basic issue is that the SCSDKLoginKit Module is outdated, As you correctly realised. SCSDKLoginClient.login() requires the calling view to conform to the (UIKIT) UIViewController class. So we must use the workaround with a UIViewControllerRepresentable to act as our SwiftUI <-> UIKit intermediary.
However the fundamental issue relates to the fact that the SnapKit SDK documentation has not been updated to give developers a SceneDelegate link between Snapchat Auth and Your app's logic. So even if you implemented your SCSDKLoginButton correctly it is not smooth sailing!
Now to directly answer your question, You are attempting to wrap a SCSDKLoginButton in a UIViewControllerRepresentable which can be done and I'm sure someone with better knowledge of coordinators etc than myself can help you with that. However I just wanted to show that your efforts at the moment may be fruitless until snapchat provides an updated SDK.
Here is my setup:
[ContentView.swift]
import SwiftUI
struct ContentView: View {
#State private var isPresented = false
var body: some View {
Button("Snapchat Login Button") { self.isPresented = true}
.sheet(isPresented: $isPresented) {
LoginCVWrapper()
}
}
}
[LoginCVWrapper.swift]
import SwiftUI
import UIKit
import SCSDKLoginKit
struct LoginCVWrapper: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
return LoginViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
//Unused in demonstration
}
}
[LoginViewController.swift]
import UIKit
import SCSDKLoginKit
class LoginViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
performLogin() //Attempt Snap Login Here
}
//Snapchat Credential Retrieval Fails Here
private func performLogin() {
//SCSDKLoginClient.login() never completes once scene becomes active again after Snapchat redirect back to this app.
SCSDKLoginClient.login(from: self, completion: { success, error in
if let error = error {
print("***ERROR LOC: manualTrigger() \(error.localizedDescription)***")
return
}
if success {
self.fetchSnapUserInfo({ (userEntity, error) in
print("***SUCCESS LOC: manualTrigger()***")
if let userEntity = userEntity {
DispatchQueue.main.async {
print("SUCCESS:\(userEntity)")
}
}
})
}
})
}
private func fetchSnapUserInfo(_ completion: #escaping ((UserEntity?, Error?) -> ())){
let graphQLQuery = "{me{displayName, bitmoji{avatar}}}"
SCSDKLoginClient
.fetchUserData(
withQuery: graphQLQuery,
variables: nil,
success: { userInfo in
if let userInfo = userInfo,
let data = try? JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted),
let userEntity = try? JSONDecoder().decode(UserEntity.self, from: data) {
completion(userEntity, nil)
}
}) { (error, isUserLoggedOut) in
completion(nil, error)
}
}
}
[This Runs as follows]:
GIF: Code Running On Device
More on the SceneDelegate interface link issue:
When you inevitably implement the SCSDKLoginClient.login() call (presumably when your SCSDKLoginButton is pressed), Snapchat will open, it will present the "grant access" sheet correctly assuming your app is linked in the Snapchat Dev Portal.
When you accept these permissions, Snapchat redirects to your application. However this is where the link between your app and retrieving a snapchat username/ bitmoji will breakdown. This is due to the fact that in new iOS 13 apps, the SceneDelegate handles when your application states change not the AppDelegate as in pre iOS13 versions. Therefore Snapchat returns the user data but your app never retrieves it.
[Going Forward]
The SnapKitSDK (currently version 1.4.3) needs to be updated along with the documentation.
I have just submitted a support question to Snapchat asking when this update will come so I will update this if I hear more. Apologies if you were looking for a direct solution to your SCSDKLoginButton() issue, I just wanted you to know what challenges lie beyond it at this current moment in time.
[Further Reading]
Facebook has updated their tools and documentation to incorporate the Scene/App Delegates. See step "5. Connect Your App Delegate and Scene Delegate" here: https://developers.facebook.com/docs/facebook-login/ios/
#Stephen2697 was right in pointing out that the snap sdk isn't built for iOS 13 yet due to SceneDelegate now handling oauth redirects rather than AppDelegate. I figured out a workaround to use the SCSDKLoginClient.application() method (which was made for appdelegate) to be used in scene delegate. Here's the code, add it to your scene delegate and the completion handler passed in to your Snapchat login will run:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
for urlContext in URLContexts {
let url = urlContext.url
var options: [UIApplication.OpenURLOptionsKey : Any] = [:]
options[.openInPlace] = urlContext.options.openInPlace
options[.sourceApplication] = urlContext.options.sourceApplication
options[.annotation] = urlContext.options.annotation
SCSDKLoginClient.application(UIApplication.shared, open: url, options: options)
}
}
As far as the login button not appearing, make sure to add a completion handler to the SCSDKLoginButton instance or else it won't work. Like this:
let s = SCSDKLoginButton { (success, error) in
//handle login
}
To follow up with #Stephen.Alger's answer, you don't have to use UIViewControllerRepresentable just call: SCSDKLoginClient.login directly from SwiftUI's Button callback closure and add this modifier to the view:
.onOpenURL(perform: { url in
let success = SCSDKLoginClient.application(UIApplication.shared, open: url)
if success {
print("Logged in")
}
})

Swift Admob issue's Thread 1: EXC_BAD_INSTRUCTION

I've been working on the following problem for a couple of days now and I'm getting a little upset with it. So I have a project file https://github.com/PCmex/lift-side-memu-in-swift-3 and I successfully initiated Cocoapod with it. I followed the whole admob tutorial and did all the required steps. When I try to test the app the build is OK, but after the app launches it crashes and gives me the following information:
Screenshot for error message: Thread 1: EXC_BAD_INSTRUCTION
The log gives me the following information:
Google Mobile Ads SDK version:(GADRequest.sdkVersion())
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Here is the app delegate.swift
import UIKit
import GoogleMobileAds
import Firebase
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
//Use Firebase library to configure APIs
FirebaseApp.configure()
GADMobileAds.configure(withApplicationID: "ca-app-pub-***")
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
And this is the ViewController.swift
import UIKit
import GoogleMobileAds
import Firebase
class ViewController: UIViewController {
#IBOutlet weak var btnMenuButton: UIBarButtonItem!
#IBOutlet weak var bannerView: GADBannerView!
override func viewDidLoad() {
super.viewDidLoad()
// Aanroepen print functie voor de Google AdMob banner
print("Google Mobile Ads SDK version:(GADRequest.sdkVersion())")
bannerView.adUnitID = "ca-app-pub-***"
bannerView.rootViewController = self
bannerView.load(GADRequest())
// Do any additional setup after loading the view, typically from a nib.
if revealViewController() != nil {
// revealViewController().rearViewRevealWidth = 62
btnMenuButton.target = revealViewController()
btnMenuButton.action = #selector(SWRevealViewController.revealToggle(_:))
// revealViewController().rightViewRevealWidth = 150
// extraButton.target = revealViewController()
// extraButton.action = "rightRevealToggle:"
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I'm pretty sure I've been installing cocoa-pod / AdMob and all prerequisites correctly. When I do all steps in a new project everything works fine. But I'm trying to understand why it doesn't work in my current project. Hope someone could point me in the right direction, thanks in advance!
The variable bannerView is an implicitly unwrapped optional. That means that it is an optional variable type. Remember that unwrapping optionals will crash if its value is nil, so normally you would do some optional chaining like if let to test before unwrapping to prevent crashing. In your case, bannerView is nil, so your application crashed. An implicitly unwrapped optional is declared by placing a ! after its type (in your case, GADBannerView!).
I suggest you to go to the storyboard (or XIB) of your controller, select your GADBannerView and go to the connections inspector
And check if there is anything in the "Referencing Outlets" section (except for "New Referencing Outlet). If there is, break the connection by clicking the X button.
Then delete the #IBOutlet weak var bannerView line in the Controller and reconnect the GADBannerView to ViewController. If there is nothing in the section, simply delete #IBOutlet weak var bannerView and reconnect the GADBannerView to ViewController