How to navigate to view controller after successful verification? - swift3

I'm using Digits - by Twitter API for phone number verification, a quick scenario: user tap on Sign Up buttonex: LoginViewController -> Digits API initialize to verify his phone number -> after successfully verification the app should move to next View Controller ex: HomeViewController but what happens is when the user is successfully verified the app returns to previous which is LoginViewController ! here is my code:
LoginViewController
// MARK: - Sign Up Button
#IBAction func signUpPressed(_ sender: Any) {
let configuration = DGTAuthenticationConfiguration(accountFields: .defaultOptionMask)
configuration?.appearance = DGTAppearance()
configuration?.appearance.backgroundColor = UIColor.white
configuration?.appearance.accentColor = UIColor.red
// Start the Digits authentication flow with the custom appearance.
Digits.sharedInstance().authenticate(with: nil, configuration:configuration!) { (session, error) in
if session != nil {
//Print Data
print(session?.phoneNumber!)
print(session?.userID!)
// Navigate to the main app screen to select a theme.
self.performSegue(withIdentifier: "toSignUpVC", sender: self)
} else {
print("Error")
}
}
}
Example of Storyboard:
NOTE: after the App returns to LoginViewController I can go to SignUpViewController successfully after I tap the SignUp Button again, because Digits registered the device in the first time, so why doesn't Digits move to the next View Controller instead of going back to LoginViewController, any user will never know if he was successfully signed up or not !

You can fix your issue by adding the code below. Closure should be called after the function execution.
let deadline = DispatchTime.now() + .seconds(1)
DispatchQueue.main.asyncAfter(deadline: deadline)
{
self.performSegue(withIdentifier: "toSignUpVC", sender: self)
}

Related

Not Receiving scenePhase Changes

I'm trying to execute some code I'd have previously put in my app delegate, such as saving my managed object context when entering the background. I put the call in the .onChange for the scenePhase, but I'm not getting anything. Here's a sample project:
import SwiftUI
#main
struct PhaseApp: App {
#Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
Text("Hello, world.")
}
.onChange(of: scenePhase) { phase in
switch phase {
case .active:
print("Active")
case .background:
print("Background")
case .inactive:
print("Inactive")
#unknown default: break
}
}
}
}
I'd expect to get a print command in the Simulator or on my test device whenever I press Home or tap the app, but nothing happens.
I acknowledge this question is specifically about schenePhase changes, however, on macOS I am not able to receive any .background notifications when a user switches to a different app. The older NotificationCenter strategy works as I expected, on both platforms. I'll add this to the mix for anyone who is just trying to execute some code, onForeground / onBackground on iOS and macOS.
On any view, you can attach:
.onReceive(NotificationCenter.default.publisher(for: .willResignActiveNotification)) { _ in
doBackgroundThing()
}
The events you may care about are:
iOS: willResignActiveNotification & willEnterForegroundNotification
macOS: willResignActiveNotification & willBecomeActiveNotification
You can find all NotificationCenter Names here.
I use will* variants for background because I assume they'll be called early in the process, and I use did* variants for foreground, because they are called regardless of whether the app is launched for the first time, or it's coming out of background.
I use this extension so I don't have to think about the platform differences:
extension View {
#if os(iOS)
func onBackground(_ f: #escaping () -> Void) -> some View {
self.onReceive(
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification),
perform: { _ in f() }
)
}
func onForeground(_ f: #escaping () -> Void) -> some View {
self.onReceive(
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification),
perform: { _ in f() }
)
}
#else
func onBackground(_ f: #escaping () -> Void) -> some View {
self.onReceive(
NotificationCenter.default.publisher(for: NSApplication.willResignActiveNotification),
perform: { _ in f() }
)
}
func onForeground(_ f: #escaping () -> Void) -> some View {
self.onReceive(
NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification),
perform: { _ in f() }
)
}
#endif
}
As expected, I use it as such:
AppView()
.onBackground {
print("my background")
}
.onForeground {
print("my foreground")
}
Use inside scene root view (usually ContentView)
Tested with Xcode 12 / iOS 14 as worked.
struct ContentView: View {
#Environment(\.scenePhase) private var scenePhase
var body: some View {
TestView()
.onChange(of: scenePhase) { phase in
switch phase {
case .active:
print(">> your code is here on scene become active")
case .inactive:
print(">> your code is here on scene become inactive")
case .background:
print(">> your code is here on scene go background")
default:
print(">> do something else in future")
}
}
}
}
I've been testing with Xcode 12 beta 3 and iOS/iPadOS 14 beta 3 and here's what I'm finding. Note that a lot of this involves supporting multiple windows, but the "SwiftUI lifecycle" projects default to turning that on, so I suspect you have it active already. In my original case I was porting an existing SwiftUI app from a SceneDelegate to using the new App struct, so I had multiple window support already active.
Here's the test View I'm using in a new testing app:
struct ContentView: View {
#Environment(\.scenePhase) private var scenePhase
var body: some View {
Text("Hello, world!").padding()
.onChange(of: scenePhase) { phase in
switch phase {
case .background:
print("PHASECHANGE: View entered background")
case .active:
print("PHASECHANGE: View entered active")
case .inactive:
print("PHASECHANGE: View entered inactive")
#unknown default:
print("PHASECHANGE: View entered unknown phase.")
}
}
}
}
(I have identical code in the App & Scene but they never print anything.)
The ScenePhase documentation claims that you can declare onChange inside the App, a Scene or a View. I don't see the App or Scene level versions ever execute, under any circumstance I can engineer, and the View level versions don't seem to execute completely correctly.
On hardware that doesn't support multiple windows (I use a 7th generation iPod touch) the View level closure executes every time. (Full disclosure, this iPod Touch is still running beta 2, but I don't think it's going to matter. Once I update it to b3 I'll mention it here if it matters.) EDIT (It did matter.)
On hardware running beta 2 that doesn't support multiple windows (a 7th generation iPod Touch) I see the app go into the background, back into the foreground, and so forth. On every app launch I'll see "View entered active" print.
On hardware that does support multiple windows (I use an older iPad Pro with the Lightning connector) I don't see the initial scene creation happen. (The first run does not trigger a "View entered active" message.) I do see subsequent background/foreground transitions. If I create a new scene from the iPad multi-tasking UI the second scene will trigger a "View entered active" log. Unfortunately I hadn't run this test on the iPad against beta 2, so I can't say if the behavior changed with b3 or not.
On my iPod Touch running iOS 14 beta 3 I see the same behavior as the iPad: the first launch doesn't print any phase change messages from the view, but does report subsequent background/foreground changes.
On the simulator it always behaves like the iPad hardware, even when I'm simulating an iPod Touch. I suspect this is because the simulator is running under the hood on the Mac and gets multiple window "support" this way. But I do see messages when I put the app in the background while running in the simulator, I'm just missing the initial "View entered active" message that I get from the actual hardware.
One final note: when I return an app from the foreground I first see "View entered inactive" and then I see "View entered active". When I background the app I see "View entered inactive", followed by "View entered background". I think this is expected behavior, but since other parts seem broken I wanted to mention it.
TL;DR:
I think you should be able to see most ScenePhase changes from a View, but you'll miss the initial app launch on iPads or in the simulator. And hopefully they will show up as expected for App and Scene objects in a later beta?
You can use the following extension:
public extension View {
func onScenePhaseChange(phase: ScenePhase, action: #escaping () -> ()) -> some View {
self.modifier(OnScenePhaseChangeModifier(phase: phase, action: action))
}
}
public struct OnScenePhaseChangeModifier: ViewModifier {
#Environment(\.scenePhase) private var scenePhase
public let phase: ScenePhase
public let action: () -> ()
public func body(content: Content) -> some View {
content
.onChange(of: scenePhase) { phase in
if (self.phase == phase) {
action()
}
}
}
}
Final usage:
ContentView()
.onScenePhaseChange(phase: .active) { print("scene activated!") }
.onScenePhaseChange(phase: .background) { print("scene backgrounded!") }
.onScenePhaseChange(phase: .inactive) { print("scene inactive!") }
In my case, I put "#Environment(.scenePhase) private var scenePhase" in ContentView. Then the onChange works in the child views.

Firebase p8: Invalid APN Certificate. Check the certificate in Settings

I have successfully registered with APN and I received the token ID. The problem is that when I send a notification from Firebase console, I get error
Invalid APN Certificate. Check the certificate in Settings
This is the steps I followed in order to assign the .p8 APN certificate to Firebase. I am testing the app on an Iphone device.
What am I doing wrong?
created new key at https://developer.apple.com/account/ios/authkey/
downloaded the p8 file
got the Team ID from https://developer.apple.com/account/#/membership/
uploaded the .p8 file in firebase console under Settings/cloud messaging
in my .xcworspace under Targets/Capabilities/Push Notifications : ON
myproject.entitlements file contains APS Environment String development.
NOTE: In developer.apple.com, under APP IDs, if I click on myApp id and scroll down, Push Notifications Configurable & Development show in yellow color and not green.
Some people on SO suggested I should create a new Key in developer.apple.com. I did it, followed same process as above, and same error.
EDIT
The token generated by APNs on client side looks like: cUEPUvXjRnI:APA91bGrXvRpjXiIj0jtZkefH-wZzdFzkxauwt4Z2WbZWBSOIj-Kf3a4XqTxjTSkRfaTWLQX-Apo7LAe0SPc2spXRlM8TwhI3VsHfSOHGzF_PfMb89qzLBooEJyObGFMtiNdX-6Vv8L7
import UIKit
import Firebase
import FirebaseCore
import FirebaseMessaging
import FirebaseInstanceID
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FIRApp.configure()
//firInstanceIDTokenRefresh - > called when system determines that tokens need to be refreshed
//when firInstanceIDTokenRefresh is called, our method is called too self.tokenRefreshNotification:
NotificationCenter.default.addObserver(self, selector: #selector(self.tokenRefreshNotification(notification:)), name: NSNotification.Name.firInstanceIDTokenRefresh, object: nil)
//obtain the user’s permission to show any kind of notification
registerForPushNotifications()
return true
}//end of didFinishLaunchingWithOptions
//obtain the user’s permission to show any kind of notification
func registerForPushNotifications() {
// iOS 10 support
if #available(iOS 10, *) {
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .alert, .sound]){ (granted, error) in
print("Permission granted: \(granted)")
guard granted else {return}
self.getNotificationSettings()
}
}
// iOS 9 support
else if #available(iOS 9, *) {
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil))
self.getNotificationSettings()
}
// iOS 8 support
else if #available(iOS 8, *) {
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil))
self.getNotificationSettings()
}
// iOS 7 support
else {
UIApplication.shared.registerForRemoteNotifications(matching: [.badge, .sound, .alert])
}
}//end of registerForPushNotifications()
//if user decliens permission when we request authorization to show notification
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
print("Notification settings: \(settings)")
print("settings.authorizationStatus is \(settings.authorizationStatus)")
guard settings.authorizationStatus == .authorized else {return}
UIApplication.shared.registerForRemoteNotifications()
}
}
func application(_ application: UIApplication,didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register: \(error)")
}
func tokenRefreshNotification(notification: NSNotification) {
let refereshToken = FIRInstanceID.instanceID().token()
print("instance ID token is \(refereshToken)")
connectToFcm()
}
func connectToFcm() {
FIRMessaging.messaging().connect { (error) in
if error != nil {
print("unable to connect to FCM")
}else {
print("connected to FCM")
}
}
}
}//end of AppDelegate
I think this may be due to you missing the step of creating a Provisioning profile. Reference here: https://firebase.google.com/docs/cloud-messaging/ios/certs?authuser=0

How to open Specific View controller on Widgets/ Today Extension click

I am trying to open specific view controller on widgets click , but can not able to open it , i am able to open app using url schema but i want to open specific view controller how can i do this, here is code for open app using url schema :
#IBAction func open_app(_ sender: Any)
{ extensionContext?.open(URL(string: "open://")! ,
completionHandler: nil)
}
on button click i am opeing app sucessfully using url schema. but now i want to open specific view controller on that click how can i do this?
According to your requirement, I have created a sample to get this working correctly.
1. First of all in TodayViewController interface, create 3 different UIButtons and give their tag values to uniquely identify them.
Here I have given tags as: 1, 2, 3 to First, Second and Third UIButton.
2. Next you need to write the code to open your Containing App from Today Extension. In TodayViewController create an #IBAction for and connect it to all three UIButtons.
#IBAction func openAppController(_ sender: UIButton)
{
if let url = URL(string: "open://\(sender.tag)")
{
self.extensionContext?.open(url, completionHandler: nil)
}
}
In the above code, tag will be added to the url scheme to identify which UIViewController needs to be opened on UIButton press. So the url will look something like: open://1
3. In the Containing App's URL Types need to make an entry for URL Scheme, i.e
As evident from the above screenshot, there is no need to make entry for each url that you want to open from your extensions. URLs having same url scheme have only a single entry.
4. When the containing app is opened from extension, you can get the handle in AppDelegate’s application(_ : url: sourceApplication: annotation: ) method. Here, you can handle which controller to open i.e.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
{
if url.scheme == "open"
{
switch url.host
{
case "1":
//Open First View Controller
case "2":
//Open Second View Controller
case "3":
//Open Third View Controller
default:
break
}
}
return true
}
url.scheme identifies the scheme of URL i.e. open and url.host identifies the host component in the URL which is currently set to the UIButton's tag value which you can use to uniquely identify which UIButton is pressed and what to de next accordingly.
For more on Today Extensions, you can refer to: https://hackernoon.com/app-extensions-and-today-extensions-widget-in-ios-10-e2d9fd9957a8
Let me know if you still face any issues regarding this.
add a new scheme for your App
enter image description here
as Shown above image...
then, write a code below on IBAction of your Today Extension
#IBAction func btnFirstWidgetAction() {
let url: URL? = URL(string: "schemename://secondViewController")!
if let appurl = url { self.extensionContext!.open(appurl, completionHandler: nil) }
}
#IBAction func btnSecondWidgetAction() {
let url: URL? = URL(string: "schemename://secondViewController")!
if let appurl = url { self.extensionContext!.open(appurl, completionHandler: nil) }
}
#IBAction func btnThirdWidgetAction() {
let url: URL? = URL(string: "schemename://thirdViewController")!
if let appurl = url { self.extensionContext!.open(appurl, completionHandler: nil) }
}
than, add method application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) in AppDelegate file and write code to redirect in specific ViewController in this method.
//call when tap on Extension and get url that is set into a ToadyExtension swift file...
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let urlPath : String = url.absoluteString
print(urlPath)
if self.isContainString(urlPath, subString: "firstViewController") {
//here go to firstViewController view controller
}
else if self.isContainString(urlPath, subString: "firstViewController") {
//here go to secondViewController view controller
}
else {
//here go to thirdViewController view controller
}
return true
}
this method used for check your string is contains as sub string that are given in widget button action. if contain than true otherwise false
func isContainString(_ string: String, subString: String) -> Bool {
if (string as NSString).range(of: subString).location != NSNotFound { return true }
else { return false }
}
In xCode 11 if you are using sceneDelegate, follow the same logic as described by Malik and Mahesh but use the following function in the SceneDelegate instead:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
//Do stuff with the url
}
}
(instead of application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool)
In details:
First:
Add a url scheme in your project -> info -> url Types -> add url Scheme. Here you can get started by filling the 'URL Schemes" field only (with your app name for instance).
Second:
In your extension, use the following function (called by a button for instance):
let urlString = "MyAppName://host/path"
if let url = URL(string: urlString)
{
self?.extensionContext?.open(url, completionHandler: nil)
}
Third:
Implement your logic in Scene Delegate with:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
//Do stuff with the url
}
}
Swift5
Step1: select project>info>url types>add url scheme
step2: go to the button action method and use this code
let tag = 1
if let url = URL(string: "open://\(tag)")
{
self.extensionContext?.open(url, completionHandler: nil)
}
step 3: welcome you get the control of your host app, jus add this in app delegate method
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
{
if url.scheme == "open"
{
switch url.host
{
case "1":
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
default:
break
}
}
return true
}
Congrats! you open the controller.

Snapshotting a view that has not been rendered results in an empty snapshot when view presented modally - Swift 3

I have a Table View Controller as a tab. In it, there's a button that takes you to a Facebook profile:
func didTapFacebook() {
let url = URL(string: "http://www.facebook.com/" + myFacebookId)
if UIApplication.shared.canOpenURL(url!) {
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}
Works fine every time you press the button.
From another tab, there's a button that presents modally a navigation controller with Table View Controller as its root:
func segueToTable(_ sender: UIViewController, tvc: MyTableViewController, completion: #escaping ((_ done: Bool) -> Void)) {
let nc = MyNavigationController(rootViewController: tvc)
sender.present(nc, animated: true, completion: {
completion(true)
})
}
…
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboard.instantiateViewController(withIdentifier: "TVC") as! MyTableViewController
segueToTable(self, tvc: tvc, completion: { done in
print(“segue complete”)
})
Now once you tap the same button to go to Facebook profile (or Twitter button, basically anything that causes the app to go to background), this warning occurs (there is no keyboard displayed on the screen at this time):
Cannot snapshot view (>) with afterScreenUpdates:NO, because the view is not in a window. Use afterScreenUpdates:YES.
Returning to the app and pressing the button again causes this warning to appear (and every attempt after that):
Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates.
I realize this is related to snapshotting a view that automatically occurs prior to entering the background. It appears for some reason it doesn't like doing that within a view that was presented modally. I've tried a variety of things based on other posts but cannot get the warnings to go away.
Any help is appreciated.

SWRevealViewController button not work after clicking Back button from another ViewController in Swift

I use SWRevealViewController for my project. My problem is that I can click on the SWRevealView Toggle button at first time and that button does not work after I click on Back button from another view controller and back to that view. Here is the screenshot of my project.
I click on NavigationLeft button from Service View Controller at first and left menu shows up. After that, I click on "Imageview" from that view controller to go to next page.
When I reached to detailed next page, I click on "Back button" and it goes to Service view controller.
At that time , I click on RevealView Toggle button , it does not work. I got error fatal error: unexpectedly found nil while unwrapping an Optional value.
My codes from Service View Controllers are;
override func viewDidLoad() {
super.viewDidLoad()
if self.revealViewController() != nil {
debugPrint("Menu Click")
btnBack.target = self.revealViewController()
btnBack.action = #selector(SWRevealViewController.revealToggle(_:))
self.revealViewController().panGestureRecognizer()
}
else
{
debugPrint("nil")
btnBack.target = self.revealViewController()
btnBack.action = #selector(SWRevealViewController.revealToggle(_:))
// self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
self.revealViewController().panGestureRecognizer()
}
}
When the project runs first time, the code passes to "self.revealViewController() != nil" and I go to detailed view controller and click on Back button. At that time the code passes to " debugPrint("nil")" and it does not show up menu or work.
Codes from Detailed view controller are;
#IBAction func btnBack(_ sender: UIBarButtonItem) {
debugPrint("BtnBack")
self.dismiss(animated: true, completion:nil)
}
Is my code is something wrong or logic wrong ? I have been trying to solve this problem since one week. Please help me ..
On click event of image view in serviceViewController,
let detailVC = self.storyboard?.instantiateViewController(withIdentifier: "Detail") as! DetailVc
self.navigationController?.pushViewController(detailVC, animated: true)
Attach a navigation controller to detail viewcontroller. No need for manual connection between service and detail.
Add a barbutton for goingback event.
self.navigationController?.popViewController(animated: true)
Also add self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer()) instead of ur code in the lastline of adding target for menubutton.
Check whether this works.