Passing data from Scene Delegate to ViewController when opening app with URL - swift

I need to Show a View on my initial controller and set a UILabel when the app launches through URL Dynamic Link during Firebase Email Verification.
Problem I'm having is the label is not initialised so app crashes during the process. I have tried many approaches but can't get it to show my view and set the label when opening a Dynamic Link, even when the App is already open.
Scene Delegate
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let link = userActivity.webpageURL?.absoluteString
{
if Auth.auth().isSignIn(withEmailLink: link) {
Config().verifyEmail(link: link)
}
}
}
AppDelegate
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var errorText: String?
var errorType: Bool?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
return true
}
ViewController
func loadErrorInfo()
{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if let eText = appDelegate.errorText, let eType = appDelegate.errorType {
errorCall(message: eText, error: eType)
} else {
errorBox.isHidden = true
}
}
func errorCall(message: String, error: Bool)
{
if error {
self.errorLabel.textColor = UIColor.yellow
self.errorLabel.text = message
self.errorBox.isHidden = false
self.errorBox.shake()
} else {
self.errorLabel.textColor = UIColor.white
self.errorLabel.text = message
self.errorBox.isHidden = false
self.errorBox.shakeL()
}
}
Custom Config NSObject Class
import UIKit
import FirebaseAuth
open class Config: NSObject {
public func verifyEmail(link: String)
{
var email = ""; var password = ""
if let x = UserDefaults.standard.object(forKey: "Email") as? String { email = x }
if let y = UserDefaults.standard.object(forKey: "Password-\(email)") as? String { password = y }
if password != "" && email != ""
{
Auth.auth().signIn(withEmail: email, link: link) { (user, error) in
if let use = user, error == nil
{
Auth.auth().currentUser?.updatePassword(to: password) { (error) in
if let error = error
{
print(error)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.errorText = error.localizedDescription
appDelegate.errorType = true
ViewController().loadErrorInfo()
}
else
{
print(use, "Logged In")
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.errorText = "\(use) Logged In"
appDelegate.errorType = false
ViewController().loadErrorInfo()
}
}
}
}
}
}
}
Not sure what other method will work. To summarise, after clicking a Dynamic Link in an Email, I want to Change my UILabel on main ViewController which is an initial controller. At the moment every approach causes it to crash as the UILabel is not Set, even if the controller is already initialised.

Managed to fix it by initialising the controller again and updating the root controller.
// SceneDelegate
func changeRootViewController(_ vc: UIViewController, animated: Bool = true)
{
guard let window = self.window else {
return
}
window.rootViewController = vc
}
//Reinitialise
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationNavigationController = storyboard.instantiateViewController(withIdentifier: "ViewController2")
(UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.changeRootViewController(destinationNavigationController)

Related

Google Spreadsheets APIS, confused

I had this working before but now it seems to have stopped. I am trying to run various googlesheets APIS such as read/write/create. I have installed the appropriate cocoa pods:
platform :ios, '10.0'
target 'Safety_App-Prototype' do
use_frameworks!
pod 'GoogleAnalytics'
pod 'GoogleAPIClientForREST/Sheets'
pod 'GoogleAPIClientForREST/Drive'
pod 'GoogleSignIn'
pod 'SVProgressHUD'
pod 'Firebase/Core'
pod 'Firebase/Analytics'
pod 'Firebase/Auth'
pod 'Firebase/Firestore'
end
I have created a google developer console project and included all relevant information into my app. (this includes the URL type under projects -> info tab as well as in my app delegate shown below:
//
// AppDelegate.swift
// Created by Michael Szabo on 2021-01-28.
//
import UIKit
import GoogleSignIn
#main
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
GIDSignIn.sharedInstance().clientID = "FORMYEYESONLY.apps.googleusercontent.com"
GIDSignIn.sharedInstance().delegate = self
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance().handle(url as URL?,
sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
annotation: options[UIApplication.OpenURLOptionsKey.annotation])
}
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
print("The user has not signed in before or they have since signed out.")
} else {
print("\(error.localizedDescription)")
}
return
}
// Perform any operations on signed in user here.
let userId = user.userID // For client-side use only!
let idToken = user.authentication.idToken // Safe to send to the server
let fullName = user.profile.name
let givenName = user.profile.givenName
let familyName = user.profile.familyName
let email = user.profile.email
// ...
print(fullName)
}
func sign(_ signIn: GIDSignIn!, didDisconnectWith user: GIDGoogleUser!, withError error: Error!) {
// Perform any operations when the user disconnects from app here.
print("User has disconnected")
}
// 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.
}
}
I have added a google signing button which allows me to sign in and asks for permission to read/write to sheets and added functions in my script to create/read/write to sheets as seen below:
// Created by Michael Szabo on 2021-02-21.
//
import GoogleAPIClientForREST
import GoogleSignIn
import UIKit
class JHA_pg1: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate, GIDSignInUIDelegate, GIDSignInDelegate {
var rowstart:NSNumber = 0
var rowend:NSNumber = 0
var columnstart:NSNumber = 0
var columnend:NSNumber = 0
var red:NSNumber = 1
var blue:NSNumber = 1
var green:NSNumber = 1
var range1 = ""
var text1 = ""
var text2 = ""
var text3 = ""
var bordertype = "SOLID"
var borderthick:NSNumber = 3
var inbortype = "NONE"
var inborthick:NSNumber = 0
var spreadsheetId = ""
#IBOutlet weak var BeginAssessment: UIButton! //SubmitButton
let today = Date()
let formatter1 = DateFormatter()
let formatter = DateFormatter()
var date = String()
var buttontitle = String()
let defaults = UserDefaults.standard
private let scopes = [kGTLRAuthScopeSheetsSpreadsheets]
private let service = GTLRSheetsService()
//=========================================================================================================
//=========================================================================================================
override func viewDidLoad() {
super.viewDidLoad()
//=========================================================================================================
// Configure Google Sign-in.
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().scopes = scopes
GIDSignIn.sharedInstance().signInSilently()
}
//=========================================================================================================
//=========================================================================================================
//Submit Function
#IBAction func BeginAssessment(_ sender: Any) {
if(GIDSignIn.sharedInstance()?.currentUser != nil)
{
print("loggedIn")
}
else
{
print("not loggedIn")
}
spreadsheetId = "SOME ID"
rowstart = 0
rowend = 5
columnstart = 0
columnend = 7
unmergecell()
CreateSpreadSheet()
print(spreadsheetId)
}
//========================================================================================================
//Write To Sheet Function
func write() {
let range = range1
let updateValues = [[text1,text2,text3]]
let valueRange = GTLRSheets_ValueRange() // GTLRSheets_ValueRange holds the updated values and other params
valueRange.majorDimension = "ROWS" // Indicates horizontal row insert
valueRange.range = range
valueRange.values = updateValues
let query = GTLRSheetsQuery_SpreadsheetsValuesAppend.query(withObject: valueRange, spreadsheetId: spreadsheetId, range: range)
query.valueInputOption = "USER_ENTERED"
service.executeQuery(query) { ticket, object, error in}
}
//========================================================================================================
//Unmerge Cell Function
func unmergecell() {
let request = GTLRSheets_Request.init()
let test = GTLRSheets_GridRange.init()
test.startRowIndex = rowstart
test.endRowIndex = rowend
test.startColumnIndex = columnstart
test.endColumnIndex = columnend
request.unmergeCells = GTLRSheets_UnmergeCellsRequest.init()
request.unmergeCells?.range = test
let batchUpdate = GTLRSheets_BatchUpdateSpreadsheetRequest.init()
batchUpdate.requests = [request]
let createQuery = GTLRSheetsQuery_SpreadsheetsBatchUpdate.query(withObject: batchUpdate, spreadsheetId: spreadsheetId)
service.executeQuery(createQuery) { (ticket, result, NSError) in
}
}
}
//========================================================================================================
//Create Spreadsheet Function
func CreateSpreadSheet()
{
print("==============================================")
print("Createsheet Function")
let newSheet = GTLRSheets_Spreadsheet.init()
let properties = GTLRSheets_SpreadsheetProperties.init()
properties.title = "Daily JHA Form - "+date
newSheet.properties = properties
let query = GTLRSheetsQuery_SpreadsheetsCreate.query(withObject:newSheet)
query.fields = "spreadsheetId"
query.completionBlock = { (ticket, result, NSError) in
if let error = NSError {
print("error!!!!!!!!!!!!!!!!!!!!!!!!!!")
print(error)
}
else {
let response = result as! GTLRSheets_Spreadsheet
let identifier = response.spreadsheetId
self.spreadsheetId = identifier!
GlobalVariable1.sheetID = self.spreadsheetId
print(self.spreadsheetId)
}
}
service.executeQuery(query, completionHandler: nil)
}
//=========================================================================================================
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
print("The user has not signed in before or they have since signed out.")
} else {
print("\(error.localizedDescription)")
}
return
}
// Perform any operations on signed in user here.
let userId = user.userID // For client-side use only!
let idToken = user.authentication.idToken // Safe to send to the server
let fullName = user.profile.name
let givenName = user.profile.givenName
let familyName = user.profile.familyName
let email = user.profile.email
// ...
print(fullName)
}
//=========================================================================================================
// Display (in the UITextView) the names and majors of students in a sample
// spreadsheet:
//https://docs.google.com/spreadsheets/d/e/2PACX-1vTxKKu-0BwyOPq9HTYH237jGlMrf3q8kLwe5R2eH2dbkGqNbk3D7L9_MKxpO4b3g9cy09w2davohJzq/pubhtml
func listMajors() {
let range = "A1:Q"
let query = GTLRSheetsQuery_SpreadsheetsValuesGet
.query(withSpreadsheetId: spreadsheetId, range:range)
service.executeQuery(query) { (ticket, result, error) in
if let error = error {
self.showAlert(title: "Error", message: error.localizedDescription)
return
}
guard let result = result as? GTLRSheets_ValueRange else {
return
}
let rows = result.values!
if rows.isEmpty {
// self.output.text = "No data found."
return
}
// self.output.text = "Number of rows in sheet: \(rows.count)"
}
}
// Process the response and display output
func displayResultWithTicket(ticket: GTLRServiceTicket,
finishedWithObject result : GTLRSheets_ValueRange,
error : NSError?) {
if let error = error {
showAlert(title: "Error", message: error.localizedDescription)
return
}
var majorsString = ""
let rows = result.values!
if rows.isEmpty {
// output.text = "No data found."
return
}
majorsString += "Name, Major:\n"
for row in rows {
let name = row[0]
let major = row[4]
majorsString += "\(name), \(major)\n"
}
// output.text = majorsString
}
// Helper for showing an alert
func showAlert(title : String, message: String) {
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: UIAlertController.Style.alert
)
let ok = UIAlertAction(
title: "OK",
style: UIAlertAction.Style.default,
handler: nil
)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
}
struct GlobalVariable1
{
static var sheetID = ""
}
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
HOWEVER I get the following error:
Optional(Error Domain=com.google.GTLRErrorObjectDomain Code=401 "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project." UserInfo={GTLRStructuredError=GTLRErrorObject 0x600001182310: {errors:[1] message:"Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project." code:401 status:"UNAUTHENTICATED"}, NSLocalizedDescription=Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.})"
What am I doing wrong?
Looking to the error generated by your code, and the scopes you try to require, it seems they are not enough. Indeed you set only:
private let scopes = [kGTLRAuthScopeSheetsSpreadsheets]
According to this google official doc about scopes you can find more details about all the scopes.
In the middle part of your code you read profile informations and these info could be readed adding scopes about google sign in:
profile
email
It appears that I was missing an instruction in my functions being called. I also had to update a bit of my code for newer pod file "pod 'GoogleSignIn', '~> 5.0.2'" The instructions for this migration can be found here:
https://developers.google.com/identity/sign-in/ios/quick-migration-guide
The portion of code I needed to add to my functions is a simple line which can be seen here:
service.authorizer = GIDSignIn.sharedInstance().currentUser.authentication.fetcherAuthorizer()
I hope this helps someone else out (pass it on.)
Thank you.

Swift: Notification permissions pop up affecting class functionality

This is a hard one to explain as I'm not entirely sure what's going wrong here. In my AppDelegate I add an array of services to the app:
override var services: [ApplicationService] {
if UIApplication.isRunningUnitTests() {
return []
}
return [
SettingsService(),
TrackingApplicationService(),
NotificationApplicationService(),
AudioApplicationService(),
ConnectionService()
]
}
The connection service is the one I am interested in here. It looks like this:
import PluggableAppDelegate
import Connectivity
final class ConnectionService: NSObject, ApplicationService {
// MARK: - Helpers
private struct Constants {
static let containerHeight: CGFloat = 20
static let containerPadding: CGFloat = 20
static let statusCenterAdjustment: CGFloat = -1
static let statusFont: UIFont = .systemFont(ofSize: 15, weight: .semibold)
static let labelLines = 1
static let radiusDenominator: CGFloat = 2.0
static let animationDuration = 0.2
static let defaultAnchorConstant: CGFloat = 0
}
internal enum ConnectionStatus {
case connected
case disconnected
}
// MARK: - Properties
private let connectivity = Connectivity()
private let queue = DispatchQueue.global(qos: .utility)
internal var currentStatus = ConnectionStatus.connected
private var statusTopAnchor: NSLayoutConstraint?
private lazy var statusContainer: UIView = {
let container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
container.backgroundColor = UIColor.red
container.layer.cornerRadius = Constants.containerHeight / Constants.radiusDenominator
container.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
container.isHidden = true
container.addSubview(statusLabel)
NSLayoutConstraint.activate([
statusLabel.centerYAnchor.constraint(equalTo: container.centerYAnchor, constant: Constants.statusCenterAdjustment),
statusLabel.leftAnchor.constraint(equalTo: container.leftAnchor, constant: Constants.containerPadding),
statusLabel.rightAnchor.constraint(equalTo: container.rightAnchor, constant: -Constants.containerPadding)
])
return container
}()
private lazy var statusLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = Constants.labelLines
label.font = Constants.statusFont
label.textAlignment = .center
label.textColor = .white
label.text = Strings.common.user.noInternetConnection.localized
return label
}()
// MARK: - Lifecycle
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
DispatchQueue.main.async { [weak self] in
self?.prepareUI()
}
prepareConnectivityListener()
updateConnectionStatus(connectivity.status)
return true
}
// MARK: - Connectivity
func updateConnectionStatus(_ status: Connectivity.Status) {
switch status {
case .connected, .connectedViaWiFi, .connectedViaCellular:
updateLabelWith(connectionStatus: .connected)
case .notConnected, .connectedViaWiFiWithoutInternet, .connectedViaCellularWithoutInternet:
updateLabelWith(connectionStatus: .disconnected)
}
NotificationCenter.default.post(name: .connectivityStatusChanged, object: self.currentStatus)
}
// MARK: - Configuration
private func prepareUI() {
guard let window = UIApplication.shared.keyWindow else {
assertionFailure("We require a Window")
return // Don't setup in release if we hit this, no point
}
window.addSubview(statusContainer)
statusTopAnchor = statusContainer.topAnchor.constraint(equalTo: window.topAnchor, constant: -Constants.containerHeight)
guard let statusTopAnchor = statusTopAnchor else {
assertionFailure("Top anchor could not be build")
return
}
NSLayoutConstraint.activate([
statusTopAnchor,
statusContainer.centerXAnchor.constraint(equalTo: window.centerXAnchor),
statusContainer.heightAnchor.constraint(equalToConstant: Constants.containerHeight)
])
}
private func prepareConnectivityListener() {
let connectivityChanged: (Connectivity) -> Void = { [weak self] connectivity in
self?.updateConnectionStatus(connectivity.status)
}
connectivity.whenConnected = connectivityChanged
connectivity.whenDisconnected = connectivityChanged
connectivity.startNotifier(queue: queue)
}
private func updateLabelWith(connectionStatus: ConnectionStatus) {
if currentStatus == connectionStatus {
return // No need to update if we are the same state
} else {
currentStatus = connectionStatus
}
let topAnchorConstant: CGFloat = (connectionStatus == .connected) ? -Constants.containerHeight : Constants.defaultAnchorConstant
DispatchQueue.main.async { [weak self] in
DispatchQueue.main.async { [weak self] in
if connectionStatus != .connected {
self?.statusContainer.isHidden = false
}
self?.statusTopAnchor?.constant = topAnchorConstant
UIView.animate(withDuration: Constants.animationDuration,
animations: { [weak self] in
self?.statusContainer.superview?.layoutIfNeeded()
},
completion: { _ in
if connectionStatus == .connected {
self?.statusContainer.isHidden = true
}
})
}
}
}
}
So I'm using this service to check the connection status of the device and on the login view controller I display different warnings depending on the connection status.
However, my problem is is that the connection service is not functioning properly when the permissions pop up appears. As you can see from the ConnectionService class, an alert should be generated when the app is offline. Now because I am adding this connection service before the permissions are requested, the window briefly shows the alert, but as soon as the permissions alert appears, it disappears.
Furthermore, you can see in ConnectionService.swift I have a listener set up:
NotificationCenter.default.post(name: .connectivityStatusChanged, object: self.currentStatus)
I'm listening for this in the subsequent viewController which triggers this method:
#objc private func connectivityChanged(notification: Notification) {
if notification.object as? ConnectionService.ConnectionStatus == .connected {
deviceConnected = true
} else {
deviceConnected = false
}
}
This in turn triggers a UI change:
private var deviceConnected: Bool = true {
willSet {
DispatchQueue.main.async {
if newValue == true {
self.tabBarItem.image = #imageLiteral(resourceName: "retrieve_completed")
self.errorLabel?.isHidden = true
} else {
self.tabBarItem.image = #imageLiteral(resourceName: "retrieve_offline")
self.errorLabel?.isHidden = false
self.errorLabel?.text = Strings.eHandshake.warning.deviceOffline.title.localized
}
}
}
}
When permissions have already been granted and therefore the app launches without requesting permission, this works fine, but when the permissions alert appears, this seems to break this part of the logic.
To be clear the services var is initially declared here:
public protocol ApplicationService: UIApplicationDelegate {}
extension ApplicationService {
public var window: UIWindow? {
return UIApplication.shared.delegate?.window ?? nil
}
}
open class PluggableApplicationDelegate: UIResponder,
UIApplicationDelegate {
public var window: UIWindow?
open var services: [ApplicationService] { return [] }
internal lazy var _services: [ApplicationService] = {
return self.services
}()
#discardableResult
internal func apply<T, S>(_ work: (ApplicationService, #escaping (T) -> Void) -> S?, completionHandler: #escaping ([T]) -> Swift.Void) -> [S] {
let dispatchGroup = DispatchGroup()
var results: [T] = []
var returns: [S] = []
for service in _services {
dispatchGroup.enter()
let returned = work(service, { result in
results.append(result)
dispatchGroup.leave()
})
if let returned = returned {
returns.append(returned)
} else { // delegate doesn't implement method
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
completionHandler(results)
}
return returns
}
}
Any help with this would be much appreciated.

after enabled touch id successfully how to login with touch id while open app next time

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
IQKeyboardManager.shared.enable = true
for family in UIFont.familyNames {
print("\(family)")
for name in UIFont.fontNames(forFamilyName: family) {
print(" \(name)")
}
}
if UserDefaults.standard.value(forKey: "userEmail") != nil {
let RegisterUser = UserDefaults.standard.value(forKey: "RegisterUser") as? NSDictionary ?? [:]
if RegisterUser.object(forKey: "AddAddress") as? String ?? "" == "0" {
initialViewController = Registration_iPohoneVC(nibName:"Registration_iPohoneVC",bundle:nil)
}else if RegisterUser.object(forKey: "JobInfo") as? String ?? "" == "0" {
initialViewController = JobInfo_iPohoneVC(nibName:"JobInfo_iPohoneVC",bundle:nil)
}else if RegisterUser.object(forKey: "UploadResume") as? String ?? "" == "0" {
initialViewController = Uploadresume_iPhoneVC1(nibName:"Uploadresume_iPhoneVC1",bundle:nil)
}else if RegisterUser.object(forKey: "UploadVideo") as? String ?? "" == "0" {
initialViewController = Createvideo_iPhoneVC(nibName:"Createvideo_iPhoneVC",bundle:nil)
}else {
if UserDefaults.standard.value(forKey: "Touchid") as? Bool ?? false == true {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "Touch the home button with your finger to LogIn."
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason, reply:
{(succes, error) in
DispatchQueue.main.async {
if succes {
if UserDefaults.standard.value(forKey: "Touchid") as? Bool ?? false == true {
if UserDefaults.standard.value(forKey: "PlanSelected") as? Int ?? 0 == 90 {
self.initialViewController = Homescreen_iPhoneVC(nibName:"Homescreen_iPhoneVC",bundle:nil)
}else {
self.initialViewController = Homescreen6_iPhoneVC(nibName:"Homescreen6_iPhoneVC",bundle:nil)
}
}
}else {
let alertController = UIAlertController(title: "Touch ID Authentication Failed", message: "", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default) {
UIAlertAction in
UserDefaults.standard.removeObject(forKey: "userEmail")
UserDefaults.standard.removeObject(forKey: "userPassword")
UserDefaults.standard.synchronize()
self.initialViewController = Login_iPhoneVC(nibName:"Login_iPhoneVC",bundle:nil)
let frame = UIScreen.main.bounds
self.window = UIWindow(frame: frame)
self.window!.rootViewController = appdelegate!.initialViewController!
self.window!.makeKeyAndVisible()
}
alertController.addAction(okAction)
self.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
})
}else {
print("touch id not available...")
}
}else {
if UserDefaults.standard.value(forKey: "PlanSelected") as? Int ?? 0 == 90 {
self.initialViewController = Homescreen_iPhoneVC(nibName:"Homescreen_iPhoneVC",bundle:nil)
}else {
self.initialViewController = Homescreen6_iPhoneVC(nibName:"Homescreen6_iPhoneVC",bundle:nil)
}
}
}
let navigationcontroller = UINavigationController()
navigationcontroller.viewControllers = [appdelegate!.initialViewController!]
navigationcontroller.navigationBar.barTintColor = AppBG1Color
navigationcontroller.interactivePopGestureRecognizer?.delegate = self as UIGestureRecognizerDelegate
navigationcontroller.interactivePopGestureRecognizer?.isEnabled = true
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
window!.rootViewController = navigationcontroller
window!.makeKeyAndVisible()
}else {
initialViewController = Login_iPhoneVC(nibName: "Login_iPhoneVC", bundle: nil)
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
window!.rootViewController = initialViewController
window!.makeKeyAndVisible()
}
UINavigationBar.appearance().tintColor = .white
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard(_:)))
tapGesture.cancelsTouchesInView = false
window?.rootViewController?.view.addGestureRecognizer(tapGesture)
return true
}
here is my Appdelegate code, in my else condition i want first touch id popup for user to Ask touch the home button to login while user has Already enabled touch id but it will crash directly, i want while user has Already enabled touch id then he can Access App with touch every time he open App Again. if Anyone can help it would be greatful Thankyou.
If he enabled touch id set bollean flag as UserDefaults, and next time he runs the app read it, if its true then let him use touch id, if not - need to ask him.
P.S.You should probably move all this logic to SceneDelegate :) AppDelegate isn't place for this things anymore

Set Status bar in AppDelegate?

Previously, I had this code and it worked perfectly before Swift was updated. Now it says:
Setter for 'statusBarStyle' was deprecated in iOS 9.0: Use -[UIViewController preferredStatusBarStyle]
Now, I read that you have to override the statusBarStyle but I don't want to do this manually in each UIViewController but instead, control it via a struct and an extension of the UIApplication. Not sure how to do that though.
The code:
extension UIApplication {
var statusBarView: UIView? {
return value(forKey: "statusBar") as? UIView
}
}
struct StatusBar {
static func setStatusBar() {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
if let currentVC = appDelegate?.window?.rootViewController?.getCurrentlyDisplayedVC() {
if currentVC is LoginVC || currentVC is SignUpVC || currentVC is SignUpVC {
UIApplication.shared.statusBarView?.backgroundColor = .clear
}
else {
UIApplication.shared.statusBarView?.backgroundColor = Colors.mainBlueColor
}
}
UIApplication.shared.statusBarStyle = .lightContent
}
}
extension UIViewController {
func getCurrentlyDisplayedVC() -> UIViewController {
if let presentedVC = presentedViewController {
return presentedVC.getCurrentlyDisplayedVC()
}
else if let split = self as? UISplitViewController, let last = split.viewControllers.last {
return last.getCurrentlyDisplayedVC()
}
else if let nav = self as? UINavigationController, let top = nav.topViewController {
return top.getCurrentlyDisplayedVC()
}
else if let tab = self as? UITabBarController {
if let selected = tab.selectedViewController {
return selected.getCurrentlyDisplayedVC()
}
}
return self
}
}

How to implement private chat in ios using firebase

I have implemented private chat in my ios app. However, it is not so private. When I send a message that I intend to send to one person, everyone in the app can see it. I have three view controllers in play here.
The FirstViewController has a list of users, and when the cell is clicked it is segued to the DetailedViewController. In this viewController, it only lists the details of the user clicked on. Next, when I press the compose button in the DetailedViewController, the goal is to segue to MessageUserController. This is where I am stuck. This is the code to segue to the MessageUserController:
var username: String?
#IBAction func sendMessage(_ sender: Any) {
performSegue(withIdentifier: "sendMessageToUser", sender: self.username)
}
override public func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "sendMessageToUser", let chatVc = segue.destination as? MessageViewController else {
return
}
chatVc.senderId = self.loggedInUser?.uid
chatVc.senderDisplayName = self.username
}
I assume the sender could be the username because it is unique to the user. When I click on a user to chat with, it works fine but when I click on another user, the chat between the first users are already displayed in the new user's chatController
In the firstViewController, username is passed like this:
if segue.identifier == "UsersProfile" {
if let indexPath = sender as? IndexPath{
let vc = segue.destination as! UsersProfileViewController
let post = self.posts[indexPath.row] as! [String: AnyObject]
let username = post["username"] as? String
vc.username = username
}
}
entire view controller:
import UIKit
import Photos
import Firebase
import FirebaseDatabase
import JSQMessagesViewController
class SendMessageViewController: JSQMessagesViewController {
var username: String?
//var receiverData = AnyObject?()
var messages = [JSQMessage]()
private var photoMessageMap = [String: JSQPhotoMediaItem]()
private let imageURLNotSetKey = "NOTSET"
lazy var outgoingBubbleImageView: JSQMessagesBubbleImage = self.setupOutgoingBubble()
lazy var incomingBubbleImageView: JSQMessagesBubbleImage = self.setupIncomingBubble()
var rootRef = FIRDatabase.database().reference()
var messageRef = FIRDatabase.database().reference().child("messages")
private var newMessageRefHandle: FIRDatabaseHandle?
private lazy var usersTypingQuery: FIRDatabaseQuery =
self.rootRef.child("typingIndicator").queryOrderedByValue().queryEqual(toValue: true)
lazy var storageRef: FIRStorageReference = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
private var updatedMessageRefHandle: FIRDatabaseHandle?
private lazy var userIsTypingRef: FIRDatabaseReference =
self.rootRef.child("typingIndicator").child(self.senderId) // 1
private var localTyping = false // 2
var isTyping: Bool {
get {
return localTyping
}
set {
// 3
localTyping = newValue
userIsTypingRef.setValue(newValue)
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.senderId = FIRAuth.auth()?.currentUser?.uid
// Do any additional setup after loading the view.
self.navigationController?.navigationBar.barTintColor = UIColor(red:0.23, green:0.73, blue:1.00, alpha:1.0)
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
self.navigationItem.title = senderDisplayName
self.navigationItem.rightBarButtonItem?.tintColor = UIColor.white
self.navigationItem.leftBarButtonItem?.tintColor = UIColor.white
// No avatars
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
observeMessages()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
observeTyping()
}
deinit {
if let refHandle = newMessageRefHandle {
messageRef.removeObserver(withHandle: refHandle)
}
if let refHandle = updatedMessageRefHandle {
messageRef.removeObserver(withHandle: refHandle)
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
private func setupOutgoingBubble() -> JSQMessagesBubbleImage {
let bubbleImageFactory = JSQMessagesBubbleImageFactory()
return bubbleImageFactory!.outgoingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue())
}
private func setupIncomingBubble() -> JSQMessagesBubbleImage {
let bubbleImageFactory = JSQMessagesBubbleImageFactory()
return bubbleImageFactory!.incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleLightGray())
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item] // 1
if message.senderId == senderId { // 2
return outgoingBubbleImageView
} else { // 3
return incomingBubbleImageView
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAt indexPath: IndexPath!) -> CGFloat {
return 15
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId == senderId {
cell.textView?.textColor = UIColor.white
} else {
cell.textView?.textColor = UIColor.black
}
return cell
}
//ADD A NEW MESSAGE
private func addMessage(withId id: String, name: String, text: String) {
if let message = JSQMessage(senderId: id, displayName: name, text: text) {
messages.append(message)
}
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
let itemRef = rootRef.child("messages").childByAutoId() // 1
let messageItem = [ // 2
"senderId": senderId!,
"ReceiverName": senderDisplayName!,
"text": text!,
]
itemRef.setValue(messageItem) // 3
JSQSystemSoundPlayer.jsq_playMessageSentSound() // 4
finishSendingMessage() // 5
isTyping = false
}
private func observeMessages() {
// 1.
let messageQuery = rootRef.child("messages").queryLimited(toLast: 25)
// 2. We can use the observe method to listen for new
// messages being written to the Firebase DB
newMessageRefHandle = messageQuery.observe(.childAdded, with: { (snapshot) -> Void in
// 3
let messageData = snapshot.value as! Dictionary<String, String>
if let id = messageData["senderId"] as String!, let name = messageData["ReceiverName"] as String!, let text = messageData["text"] as String!, text.characters.count > 0 {
// 4
self.addMessage(withId: id, name: name, text: text)
// 5
self.finishReceivingMessage()
} else if let id = messageData["senderId"] as String!,
let photoURL = messageData["photoURL"] as String! { // 1
// 2
if let mediaItem = JSQPhotoMediaItem(maskAsOutgoing: id == self.senderId) {
// 3
self.addPhotoMessage(withId: id, key: snapshot.key, mediaItem: mediaItem)
// 4
if photoURL.hasPrefix("gs://") {
self.fetchImageDataAtURL(photoURL, forMediaItem: mediaItem, clearsPhotoMessageMapOnSuccessForKey: nil)
}
}
} else {
print("Error! Could not decode message data")
}
})
// We can also use the observer method to listen for
// changes to existing messages.
// We use this to be notified when a photo has been stored
// to the Firebase Storage, so we can update the message data
updatedMessageRefHandle = messageRef.observe(.childChanged, with: { (snapshot) in
let key = snapshot.key
let messageData = snapshot.value as! Dictionary<String, String> // 1
if let photoURL = messageData["photoURL"] as String! { // 2
// The photo has been updated.
if let mediaItem = self.photoMessageMap[key] { // 3
self.fetchImageDataAtURL(photoURL, forMediaItem: mediaItem, clearsPhotoMessageMapOnSuccessForKey: key) // 4
}
}
})
}
override func textViewDidChange(_ textView: UITextView) {
super.textViewDidChange(textView)
// If the text is not empty, the user is typing
isTyping = textView.text != ""
}
private func observeTyping() {
let typingIndicatorRef = rootRef.child("typingIndicator")
userIsTypingRef = typingIndicatorRef.child(senderId)
userIsTypingRef.onDisconnectRemoveValue()
usersTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqual(toValue: true)
// 1
usersTypingQuery.observe(.value) { (data: FIRDataSnapshot) in
// 2 You're the only one typing, don't show the indicator
if data.childrenCount == 1 && self.isTyping {
return
}
// 3 Are there others typing?
self.showTypingIndicator = data.childrenCount > 0
self.scrollToBottom(animated: true)
}
}
func sendPhotoMessage() -> String? {
let itemRef = messageRef.childByAutoId()
let messageItem = [
"photoURL": imageURLNotSetKey,
"senderId": senderId!,
]
itemRef.setValue(messageItem)
JSQSystemSoundPlayer.jsq_playMessageSentSound()
finishSendingMessage()
return itemRef.key
}
func setImageURL(_ url: String, forPhotoMessageWithKey key: String) {
let itemRef = messageRef.child(key)
itemRef.updateChildValues(["photoURL": url])
}
override func didPressAccessoryButton(_ sender: UIButton) {
let picker = UIImagePickerController()
picker.delegate = self as! UIImagePickerControllerDelegate & UINavigationControllerDelegate
if (UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera)) {
picker.sourceType = UIImagePickerControllerSourceType.camera
} else {
picker.sourceType = UIImagePickerControllerSourceType.photoLibrary
}
present(picker, animated: true, completion:nil)
}
private func addPhotoMessage(withId id: String, key: String, mediaItem: JSQPhotoMediaItem) {
if let message = JSQMessage(senderId: id, displayName: "", media: mediaItem) {
messages.append(message)
if (mediaItem.image == nil) {
photoMessageMap[key] = mediaItem
}
collectionView.reloadData()
}
}
private func fetchImageDataAtURL(_ photoURL: String, forMediaItem mediaItem: JSQPhotoMediaItem, clearsPhotoMessageMapOnSuccessForKey key: String?) {
// 1
let storageRef = FIRStorage.storage().reference(forURL: photoURL)
// 2
storageRef.data(withMaxSize: INT64_MAX){ (data, error) in
if let error = error {
print("Error downloading image data: \(error)")
return
}
// 3
storageRef.metadata(completion: { (metadata, metadataErr) in
if let error = metadataErr {
print("Error downloading metadata: \(error)")
return
}
// 4
if (metadata?.contentType == "image") {
mediaItem.image = UIImage.init(data: data!)
} else {
mediaItem.image = UIImage.init(data: data!)
}
self.collectionView.reloadData()
// 5
guard key != nil else {
return
}
self.photoMessageMap.removeValue(forKey: key!)
})
}
}
}
extension SendMessageViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
picker.dismiss(animated: true, completion:nil)
// 1
if let photoReferenceUrl = info[UIImagePickerControllerReferenceURL] as? URL {
// Handle picking a Photo from the Photo Library
// 2
let assets = PHAsset.fetchAssets(withALAssetURLs: [photoReferenceUrl], options: nil)
let asset = assets.firstObject
// 3
if let key = sendPhotoMessage() {
// 4
asset?.requestContentEditingInput(with: nil, completionHandler: { (contentEditingInput, info) in
let imageFileURL = contentEditingInput?.fullSizeImageURL
// 5
let path = "\(FIRAuth.auth()?.currentUser?.uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\(photoReferenceUrl.lastPathComponent)"
// 6
self.storageRef.child(path).putFile(imageFileURL!, metadata: nil) { (metadata, error) in
if let error = error {
print("Error uploading photo: \(error.localizedDescription)")
return
}
// 7
self.setImageURL(self.storageRef.child((metadata?.path)!).description, forPhotoMessageWithKey: key)
}
})
}
} else {
// Handle picking a Photo from the Camera - TODO
// 1
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
// 2
if let key = sendPhotoMessage() {
// 3
let imageData = UIImageJPEGRepresentation(image, 1.0)
// 4
let imagePath = FIRAuth.auth()!.currentUser!.uid + "/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
// 5
let metadata = FIRStorageMetadata()
metadata.contentType = "image/jpeg"
// 6
storageRef.child(imagePath).put(imageData!, metadata: metadata) { (metadata, error) in
if let error = error {
print("Error uploading photo: \(error)")
return
}
// 7
self.setImageURL(self.storageRef.child((metadata?.path)!).description, forPhotoMessageWithKey: key)
}
}
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion:nil)
}
}
Looks to me like this isn't a privacy issue as you state, it's simply that you're not clearing the data on your messages view controller when you load the new conversation.
Ultimately it really depends on how secure you want this to be; if you're happy having the private messages saved in memory, then don't destroy them until the user logs out — you can even keep multiple private conversations saved in a CoreData database. It's still relatively secure this way, and it's convenient for users and performant. If you prefer to destroy messages sooner, clear the data on viewDidDisappear, then checking in your prepareForSegue method that the data is again cleared. You could also destroy the entire messages controller each time you dismiss it, if storing a strong reference isn't what you want to do.
An example of this, as a storyboard:
App loads
User1 is logged in
User1 selects private messages
User1 has conversation with User2
User1 switches to a conversation with User3
[pseudo-code]
userDidChangeRecipient {
// destroy messages view controller
// or destroy Firebase array data and destroy the reference to the message/conversation ID
}
And each time you load the view controller:
prepareForSegue {
if strongRefToMessagesVC == nil {
// instantiate a new instance of vc from nib or scratch
// load the appropriate message/conversation ID
// load messages
}
}
More digging:
There's two possibilities here:
You're not destroying the view controller when you switch messages, and this tutorial expects you to. In that case, you need to look at when the segue ends or the user closes the messages view controller and either destroy it, or empty the array.
You're trying to write all the private messages into the same JSQMessage array. I notice in that view controller you have:
var messageRef = FIRDatabase.database().reference().child("messages")
Is that the database connection you're working with? Each private message conversation should have a unique reference ID so that they do not overlap, otherwise every user will load up the same set of messages from Firebase.