How to place a VC in a navigation controller programmatically? - swift

I am currently getting an error whenever I try to present this VC. The error is Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value I believe it is because the VC is not in a navigation controller. How do I put this VC in a navigation controller(programmatically).
import UIKit
import AWSAuthCore
import AWSAuthUI
import AWSMobileClient
class SignInViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
AWSMobileClient.default()
.showSignIn(navigationController: self.navigationController!, // this is where i get the error.
signInUIOptions: SignInUIOptions(
canCancel: true,
logoImage: UIImage(named: "MyCustomLogo"),
backgroundColor: UIColor.red)) { (result, err) in
AWSMobileClient.default().initialize { (userState, error) in
if let userState = userState {
switch(userState){
case .signedIn:
DispatchQueue.main.async {
}
case .signedOut:
AWSMobileClient.default().showSignIn(navigationController: self.navigationController!, { (userState, error) in
if(error == nil){ //Successful signin
DispatchQueue.main.async {
}
}
})
default:
AWSMobileClient.default().signOut()
}
} else if let error = error {
print(error.localizedDescription)
}
}
}
}
}
Here is the code from AppDelegate
import UIKit
import AWSAppSync
import AWSMobileClient
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var appSyncClient: AWSAppSyncClient?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let nav1 = UINavigationController()
let mainView = SignInViewController(nibName: nil, bundle: nil)
nav1.viewControllers = [mainView]
self.window!.rootViewController = nav1
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UINavigationController(rootViewController: TabBarController())
window?.makeKeyAndVisible()
AWSMobileClient.default().addUserStateListener(self) { (userState, info) in
switch (userState) {
case .guest:
print("user is in guest mode.")
case .signedOut:
self.window?.rootViewController?.present(SignInViewController(), animated: true, completion: nil)
print("user signed out.")
case .signedIn:
print("user is signed in.")
case .signedOutUserPoolsTokenInvalid:
print("need to login again.")
case .signedOutFederatedTokensInvalid:
print("user logged in via federation, but currently needs new tokens")
default:
print("unsupported")
}
}
AWSMobileClient.default().initialize { (userState, error) in
if let userState = userState {
print("UserState: \(userState.rawValue)")
} else if let error = error {
print("error: \(error.localizedDescription)")
}
}
do {
// You can choose the directory in which AppSync stores its persistent cache databases
let cacheConfiguration = try AWSAppSyncCacheConfiguration()
// AppSync configuration & client initialization
let appSyncServiceConfig = try AWSAppSyncServiceConfig()
let appSyncConfig = try AWSAppSyncClientConfiguration(appSyncServiceConfig: appSyncServiceConfig,
cacheConfiguration: cacheConfiguration)
appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncConfig)
print("Initialized appsync client.")
} catch {
print("Error initializing appsync client. \(error)")
}
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}

Embed the SignInViewController in a UINavigationController.
To do that, drag-and-drop a UINavigationController to the Storyboard and CTRL + drag from that UINavigationController to the SignInViewController, choose the relationship rootViewController
Programatically:
var nav1 = UINavigationController()
var mainView = SignInViewController(nibName: nil, bundle: nil)
nav1.viewControllers = [mainView]
self.window!.rootViewController = nav1

Related

Check if app is launched/openend by a SharePlay session

When a user is in a Facetime call and accepted the SharePlay request, I want to push a specific ViewController. So I thought it would be same as to get notification when the user tap "accept".
import UIKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// to perform action when notification is tapped
UNUserNotificationCenter.current().delegate = self
registerForPushNotifications()
return true
}
func registerForPushNotifications() {
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) {
[weak self] granted, error in
print("Permission granted: \(granted)")
guard granted else { return }
self?.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
extension AppDelegate : UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let application = UIApplication.shared
if(application.applicationState == .active){
print("user tapped the notification bar when the app is in foreground")
}
if(application.applicationState == .inactive)
{
print("user tapped the notification bar when the app is in background")
}
guard let rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController else {
return
}
// Do some work
completionHandler()
}
}
But this was not the case. Nothing were printed out in the logs. Is there another way to know when the app (in the background or terminated) is launched or openend by accepting a SharePlay request ?
So if one accepts the SharePlay request, one will join a session. After that you can switch to a specific ViewController. You can call this function for example in
func sceneWillEnterForeground(_ scene: UIScene) {
if #available(iOS 15, *) {
let _ = CoordinationManager.shared
}
}
and the CoordinationManager could look like this
class CoordinationManager {
static let shared = CoordinationManager()
private var subscriptions = Set<AnyCancellable>()
// Published values that the player, and other UI items, observe
#Published var groupSession: GroupSession<MovieWatchingActivity>?
#Published var enquedMovie: Movies?
private init() {
Task {
// await new sessions to watch movies together
for await groupSession in MovieWatchingActivity.sessions() {
//set the app's active group session
self.groupSession = groupSession
//remove previous subscriptions
subscriptions.removeAll()
//observe changes to the session state
groupSession.$state.sink { [weak self] state in
if case .invalidated = state {
// set the groupSession to nil to publish
// the invalidated session state
self?.groupSession = nil
self?.subscriptions.removeAll()
}
}.store(in: &subscriptions)
// join the session to participate in playback coordination
groupSession.join()
// navigate user to correct view controller to show videos
guard let windowScene = await UIApplication.shared.connectedScenes.first as? UIWindowScene,
let sceneDelegate = await windowScene.delegate as? SceneDelegate,
let navigationController = await sceneDelegate.window?.rootViewController as? UINavigationController else {
return
}
DispatchQueue.main.async {
navigationController.pushViewController(GroupMovieController(), animated: true)
}
// observe when the local user or a remote participant starts an activity
groupSession.$activity.sink { [weak self] activity in
// set the movie to enqueue it in the player
self?.enquedMovie = activity.movie
}.store(in: &subscriptions)
}
}
}
...
}

Swift: Passing managedObjectContext from AppDelegate to UINavigationController

I have some iOS project (Project) with an #escaping method in the AppDelegate that is used to create the NSPersistentContainer for this project. This method is then called within the didFinishLaunchingWithOptions method as follows:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var persistentContainer: NSPersistentContainer!
func createProjectContainer(completion: #escaping (NSPersistentContainer) -> ()) {
let container = NSPersistentContainer(name: "Project")
container.loadPersistentStores { (_, error) in
guard error == nil else { fatalError("Failed to load store: \(error!)") }
DispatchQueue.main.async { completion(container) }
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
createProjectContainer { container in
self.persistentContainer = container
let storyboard = self.window?.rootViewController?.storyboard
guard let vc = storyboard?.instantiateViewController(withIdentifier: "NavigationController") as? NavigationController else {
fatalError("Cannot instantiate root view controller")
}
vc.managedObjectContext = container.viewContext
self.window?.rootViewController = vc
}
return true
}
}
The NavigationController class is simply a subclass of UINavigationController, which embeds a UIViewController (ChildViewController) and has an NSManagedObjectContext variable (managedObjectContext), which I want to pass to the first ChildViewController:
class NavigationController: UINavigationController {
var managedObjectContext: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
guard let vc = self.childViewControllers.first as? ChildViewController else { fatalError("") }
vc.managedObjectContext = managedObjectContext
print("NavigationController: viewDidLoad")
print("managedObjectContext ---> \(managedObjectContext)")
}
}
Now the print("NavigationController: viewDidLoad") appears to be called twice. On the first call, managedObjectContext = nil, on the second call it has been assigned. Does this matter that the app is loading this twice?
It seems to be happening during the storyboard?.instantiateViewController(withIdentifier: "NavigationController") which is called after the first time that NavigationController has been loaded due to the #escaping property of the closure. However, if I exclude that line, as just get the reference to the NavigationController the managedObjectContext appears never to be assigned.

Blank screen first time launching app xcode

I am having a little issue with logging into an app the first time with a blank screen and I am getting the warning "Attempt to present on whose view is not in the window hierarchy!" After I close and relaunch, the views appear fine. I believe it has something to do with the rootViewController but not sure... Thanks in advance for any help or direction!
App delegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var ref:FIRDatabaseReference?
var databaseHandle:FIRDatabaseHandle?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainNavigationController()
FIRApp.configure()
ref = FIRDatabase.database().reference()
return true
}
Navigation controller as rootViewController
class MainNavigationController: UINavigationController {
var segmentedController: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
viewControllers = [homeController]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginController()
present(loginController, animated: true, completion: {
// perhaps do something here later
})
}
}
// log in function called
func finishLoggingIn() {
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
guard let mainNavigationController = rootViewController as? MainNavigationController else { return }
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
mainNavigationController.viewControllers = [HomeController()]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
UserDefaults.standard.setIsLoggedIn(value: true)
dismiss(animated: true, completion: nil)
}
Ok, so my last answer was wrong (i deleted it), the things is that in app there is a keywindow, which is your navcontroller, and you cannot present anything on this one untill it will load its subviews (even if it hasn't any), and that would happend on viewdidappear, so you should put your code from viewdidload there.

FBSDKLoginManager logInWithReadPermissions:fromViewController:handler:]: unrecognized selector sent to instance 0x7fed38d61290

I can't seem to resolve this issue. I went in and deleted all parse and fb SDKs and then downloaded the newest parse and fb SDKs and put into project.
Still getting this error in logs. Don't even get to fb screen. Below is my code. I have researched all instances of this issue and yet no resolve. I am missing something.
AppDelegate.swift:
import UIKit
import Parse
import ParseFacebookUtilsV4
import Bolts
// If you want to use any of the UI components, uncomment this line
// import ParseUI
// If you want to use Crash Reporting - uncomment this line
// import ParseCrashReporting
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//--------------------------------------
// MARK: - UIApplicationDelegate
//--------------------------------------
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Enable storing and querying data from Local Datastore.
// Remove this line if you don't want to use Local Datastore features or want to use cachePolicy.
Parse.enableLocalDatastore()
let parseConfiguration = ParseClientConfiguration(block: { (ParseMutableClientConfiguration) -> Void in
ParseMutableClientConfiguration.applicationId = "xxx"
ParseMutableClientConfiguration.clientKey = "xxx"
ParseMutableClientConfiguration.server = "https://yourapp.herokuapp.com/parse"
})
Parse.initializeWithConfiguration(parseConfiguration)
PFFacebookUtils.initializeFacebookWithApplicationLaunchOptions(launchOptions)
PFUser.enableAutomaticUser()
let defaultACL = PFACL();
// If you would like all objects to be private by default, remove this line.
defaultACL.publicReadAccess = true
PFACL.setDefaultACL(defaultACL, withAccessForCurrentUser:true)
if application.applicationState != UIApplicationState.Background {
// Track an app open here if we launch with a push, unless
// "content_available" was used to trigger a background push (introduced in iOS 7).
// In that case, we skip tracking here to avoid double counting the app-open.
let preBackgroundPush = !application.respondsToSelector("backgroundRefreshStatus")
let oldPushHandlerOnly = !self.respondsToSelector("application:didReceiveRemoteNotification:fetchCompletionHandler:")
var noPushPayload = false;
if let options = launchOptions {
noPushPayload = options[UIApplicationLaunchOptionsRemoteNotificationKey] != nil;
}
if (preBackgroundPush || oldPushHandlerOnly || noPushPayload) {
PFAnalytics.trackAppOpenedWithLaunchOptions(launchOptions)
}
}
if application.respondsToSelector("registerUserNotificationSettings:") {
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
} else {
let types: UIRemoteNotificationType = [UIRemoteNotificationType.Badge, UIRemoteNotificationType.Alert, UIRemoteNotificationType.Sound]
application.registerForRemoteNotificationTypes(types)
}
return FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
}
//--------------------------------------
// MARK: Push Notifications
//--------------------------------------
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let installation = PFInstallation.currentInstallation()
installation.setDeviceTokenFromData(deviceToken)
installation.saveInBackground()
PFPush.subscribeToChannelInBackground("") { (succeeded, error) in
if succeeded {
print("ParseStarterProject successfully subscribed to push notifications on the broadcast channel.");
} else {
print("ParseStarterProject failed to subscribe to push notifications on the broadcast channel with error = %#.", error)
}
}
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
if error.code == 3010 {
print("Push notifications are not supported in the iOS Simulator.")
} else {
print("application:didFailToRegisterForRemoteNotificationsWithError: %#", error)
}
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
PFPush.handlePush(userInfo)
if application.applicationState == UIApplicationState.Inactive {
PFAnalytics.trackAppOpenedWithRemoteNotificationPayload(userInfo)
}
}
func application(application: UIApplication,
openURL url: NSURL,
sourceApplication: String?,
annotation: AnyObject) -> Bool {
return FBSDKApplicationDelegate.sharedInstance().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation)
}
func applicationDidBecomeActive(application: UIApplication) {
FBSDKAppEvents.activateApp()
}
}
ViewController.swift:
import UIKit
import Parse
import FBSDKCoreKit
import FBSDKLoginKit
import ParseFacebookUtilsV4
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// array of what we want to get from the fb user account
let permissions = ["public_profile"]
PFFacebookUtils.logInInBackgroundWithReadPermissions(permissions) {
(user: PFUser?, error: NSError?) -> Void in
if let error = error { // if error exists
print(error)
} else {
if let user = user { // if user exists
print(user)
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Swift project not segue-ing properly after Facebook login

The initial ViewController, LoginViewController.swift:
import UIKit
class LoginViewController: UIViewController, FBSDKLoginButtonDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
if (FBSDKAccessToken.currentAccessToken() != nil)
{
// User is already logged in, do work such as go to next view controller.
performSegueWithIdentifier("loginSegue", sender: nil)
print("segued due to login")
}
else
{
let loginView : FBSDKLoginButton = FBSDKLoginButton()
self.view.addSubview(loginView)
loginView.center = self.view.center
loginView.readPermissions = ["public_profile", "user_friends"]
loginView.delegate = self
}
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
print("User Logged In")
if ((error) != nil)
{
// Process error
}
else if result.isCancelled {
// Handle cancellations
}
else {
// If you ask for multiple permissions at once, you
// should check if specific permissions missing
performSegueWithIdentifier("loginSegue", sender: nil)
/*
if result.grantedPermissions.contains("email")
{
// Do work
}
*/
}
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
print("User Logged Out")
}
}
"segued due to login" is printed to the terminal upon starting up the app every time, so the if-statement is clearly being reached and also the performSegueWithIdentifier() line. However, the segue is not actually performed as the LoginViewController stays on the screen and the next ViewController is not displayed. I have also tried adding the line:
performSegueWithIdentifier("loginSegue", sender: nil)
in several other locations I know the program is reaching, like right after super.viewDidLoad(). So, the problem seems to be specific to the segue and the problem does not seem to be with Facebook's login.
I have included a screenshot of the storyboard with the segue's attributes:
I can include any other files if needed. I also suspect it could be a similar type bug as this stackoverflow problem. I have tried deleting the placeholders in my UITextViews in all of my ViewControllers, but this did not solve the problem.
Ok so here's how your application:didFinishLaunching:withOptions should look like.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
let mainStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
var initialViewController: UIViewController
if(FBSDKAccessToken.currentAccessToken() != nil){
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("someOtherViewController") as! SomeOtherViewController
initialViewController = vc
}else{
initialViewController = mainStoryboard.instantiateViewControllerWithIdentifier("loginViewController")
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}