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
Related
I'm following the latest tutorial from Stream Chat, which looks great.
Looks like I followed it to the letter except for I replaced the apiKey with one created for me in the dashboard. This was provided when I registered my free trial.
Unfortunately, I'm unable to connect.
Here's my code
SwiftUIChatDemo
import SwiftUI
// 1 Add imports
import StreamChat
import StreamChatSwiftUI
#main
struct SwiftUIChatDemoApp: App {
// 2 Add Adapter
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ChatChannelListView()
}
}
}
App Delegate
import StreamChat
import StreamChatSwiftUI
import UIKit
import SwiftUI
class AppDelegate: NSObject, UIApplicationDelegate {
// Add context provider
var streamChat: StreamChat?
var chatClient: ChatClient = {
// Low-level client variable with a hard-coded apikey
var config = ChatClientConfig(apiKey: .init("[key I created in dashboard]"))
// Set to use the chat in offline mode
config.isLocalStorageEnabled = true
// Pass the low-level client variable as a parameter of the ChatClient
let client = ChatClient(config: config)
return client
}()
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// Initialize the stream chat client
streamChat = StreamChat(chatClient: chatClient)
connectUser()
return true
}
// The `connectUser` function we need to add.
private func connectUser() {
// This is a hardcoded token valid on Stream's tutorial environment.
let token = try! Token(rawValue: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibHVrZV9za3l3YWxrZXIifQ.kFSLHRB5X62t0Zlc7nwczWUfsQMwfkpylC6jCUZ6Mc0")
// Call `connectUser` on our SDK to get started.
chatClient.connectUser(
userInfo: .init(id: "luke_skywalker",
name: "Luke Skywalker",
imageURL: URL(string: "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg")!),
token: token
) { error in
if let error = error {
// Some very basic error handling only logging the error.
log.error("connecting the user failed \(error)")
return
}
}
}
}
SwiftUIChatDemo.app
import SwiftUI
// 1 Add imports
import StreamChat
import StreamChatSwiftUI
#main
struct SwiftUIChatDemoApp: App {
// 2 Add Adapter
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ChatChannelListView()
}
}
}
When you change the apiKey, you will need to generate new tokens with the secret that's provided to you in the dashboard. For development, you can generate tokens with the tool here: https://getstream.io/chat/docs/ios-swift/tokens_and_authentication/?language=swift#manually-generating-tokens. The user id also needs to match to your user. Note that for production usage, you should generate this tokens on a backend.
In case of additional questions, you can also reach our support channel: https://getstream.io/contact/support/
UPDATE
So, I got it working with the apiKey and token values from the tutorial. Well done. How I do this with my own values probably requires my own users.
I am building custom long-look notifications in an Apple Watch app, but for some reason the title defined in the notification's UNMutableNotificationContent is not showing on the long-look notification, and the custom sashColor I’m defining is not used.
In the “Transition to the Long-Look Interface” section of Presenting Notifications on Apple Watch on Apple’s website, there is a screenshot that shows what I would expect to see: a title for the notification in the sash, and a custom sash color.
I built an example app (code below) to isolate the issue.
Here is a screenshot of the notification in my app:
I expect to see the title (“Take Action!”) where the line is, and the sashColor as the background color for the circled region, based on my code.
The short-look of the notification does show the title briefly before it transitions to the long-look (it was hard to get a good screenshot, but here is one as it was animating into the long-look):
Showing or hiding the notification title is not mentioned anywhere that I can find in the documentation, so I expect that to show up automatically since it’s part of the notification.
For the sashColor override, I referred to Customizing Your Long-Look Interface on Apple’s website.
Is there something else specific I need to do to show the title on my customized long-look notification, and get sashColor to work?
Example App
To recreate the issue, create a watchOS app with companion iOS app in Xcode. I called it CustomWatchNotifications.
I updated the main iOS app file to this, with a simple class to request notification permission and send a test notification, which gets passed into the view:
import SwiftUI
import UserNotifications
#main
struct CustomWatchNotificationsApp: App
{
let notifications = NotificationController()
var body: some Scene
{
WindowGroup
{
ContentView(notifications: notifications)
}
}
}
class NotificationController
{
func requestPermissions()
{
Task {
try await UNUserNotificationCenter.current()
.requestAuthorization(
options: [.alert, .sound])
}
}
func scheduleNotification()
{
let content = UNMutableNotificationContent()
content.title = "Take Action!"
content.categoryIdentifier = "takeActionCategory"
content.sound = .default
// Schedule a new notification 5 seconds from now,
// so there is enough time to lock the phone screen
// to deliver notification to Apple Watch.
let trigger = UNTimeIntervalNotificationTrigger(
timeInterval: 5,
repeats: false)
let request = UNNotificationRequest(
identifier: "takeAction",
content: content,
trigger: trigger)
UNUserNotificationCenter.current()
.add(request)
}
}
This is the ContentView for the iOS app, which just includes the two buttons:
import SwiftUI
struct ContentView: View
{
let notifications: NotificationController
var body: some View
{
NavigationView
{
Form
{
// Request notification permissions
Button
{
notifications.requestPermissions()
} label: {
Text("Request Notification Permissions")
}
// Schedule notification
Button
{
notifications.scheduleNotification()
} label: {
Text("Schedule Notification")
}
}
}
}
}
On the watchOS side, I updated the main app file to include a custom View for the notification, inside a WKUserNotificationHostingController for this specific notification category:
import SwiftUI
import UserNotifications
#main
struct CustomWatchNotifications_Watch_AppApp: App
{
var body: some Scene
{
WindowGroup
{
ContentView()
}
WKNotificationScene(
controller: TakeActionNotificationController.self,
category: "takeActionCategory"
)
}
}
struct TakeActionNotificationView: View
{
var body: some View
{
Text("This is a test.")
}
}
class TakeActionNotificationController:
WKUserNotificationHostingController<TakeActionNotificationView>
{
// This does not seem to have an effect on sashColor.
override class var sashColor: Color?
{
return .red
}
override var body: TakeActionNotificationView
{
return TakeActionNotificationView()
}
// This has to be here for custom notification to show up.
override func didReceive(_ notification: UNNotification)
{}
}
When you build and run on real devices, make sure the watchOS app is installed before schedule the test notification. Once you schedule the test notification, lock the iPhone screen immediately so the notification gets delivered to Apple Watch.
I have two applications current on Apple Store, both share part of code, but are correctly configure to work as two different applications.
Facebook login was working correctly on version FBSDKCoreKit (4.39.1) and FBSDKLoginKit (4.39.1), but when I runned pod update and these pods where updated to version 5.0.0 login with facebook stops working correctly, even after I make the necessary changes on my login flow.
The problem is that if I hit button Login with Facebook of the first app, if I choose to use Facebook App it redirects to my second App, not the first one. But, if I login on web modal, it works fine.
I have a different Facebook App Id for both apps, and both implement Login with Facebook.
My AppDelegate.swift is configured like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FBSDKCoreKit.ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let handle = FBSDKCoreKit.ApplicationDelegate.shared.application(app, open: url, options: options)
return handle
}
(Both apps share same AppDelegate.swift)
And my LoginViewController is configured:
#IBAction func loginWithFacebookClicked() {
let fbLoginManager: LoginManager = LoginManager()
if AccessToken.current != nil {
fbLoginManager.logOut()
}
fbLoginManager.loginBehavior = .browser
fbLoginManager.logIn(permissions: ["email"], from: self) { (result, error) in
if (error == nil && !result!.isCancelled) {
if let token = AccessToken.current?.tokenString {
self.facebookLogin(token)
}
}
}
}
Apps has different LoginViewController, but code is the same.
I do not got any error, and both logins are working, just the callback to app that is not. Please, if anyone could help me, I would be very grateful.
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.
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)
}