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.
Related
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'm trying to integrate Shopify SDK on a SwiftUI project and I'm having some troubles with the authentication flow.
The code provided on the documentation is pretty straight forward:
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
spotifyConnection.sessionManager.application(application, open: url, options: options)
return true
}
with SwiftUI though this delegate method is never called so I'm trying to use openUrl
var body: some Scene {
WindowGroup {
MenuView()
.onOpenURL { (url) in
spotifyConnection.sessionManager.application(??, open: url, options: ??)
}
}
}
Question is how do I access the parameters application and options from here?
I am also having the same issue, for Spotify SDK particularly, I tried passing in UIApplication.shared and an empty options dictionary, which seems to work. I also tried examining the options dictionary when I use a UIKit AppDelegate lifecycle. It shows it's returning openInPlace to be false in the options dictionary in the Spotify url callback.
You need an UIApplicationDelegateAdaptor
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor private var appDelegate: MyAppDelegate
var body: some Scene { ... }
}
class MyAppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
spotifyConnection.sessionManager.application(application, open: url, options: options)
return true
}
}
In the iOS 13 world, I had code like this:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
// do stuff with the metadata, eventually call CKAcceptSharesOperation
}
}
I am migrating my app to the new SwiftUI app lifecycle, and can’t figure out where to put this method. It used to live in AppDelegate pre-iOS13, and I tried going back to that, but the AppDelegate version never gets called.
There doesn’t seem to be a SceneDelegateAdaptor akin to UIApplicationDelegateAdaptor available, which would provide a bridge to the old code.
So, I’m lost. How do I accept CloudKit shares with SwiftUI app lifecycle? 🙈
You can still use AppDelegate with SwiftUI's new life-cycle until Apple releases APIs to handle this natively in SwiftUI’s App Life-cycle.
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
return true
}
}
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Read this for more
Playing around with examples out there. Found a project that had a class that was a bindableobject and it didn't give any errors. Now that Xcode 11 beta 4 is out, I'm getting the error:
Type 'UserSettings' does not conform to protocol 'BindableObject'
It has a fix button on the error which when you click on that, it adds
typealias PublisherType = <#type#>
It expects you to fill in the type.
What would the type be?
class UserSettings: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
var score: Int = 0 {
didSet {
didChange.send()
}
}
}
Beta 4 Release notes say:
The BindableObject protocol’s requirement is now willChange instead of
didChange, and should now be sent before the object changes rather
than after it changes. This change allows for improved coalescing of
change notifications. (51580731)
You need to change your code to:
class UserSettings: BindableObject {
let willChange = PassthroughSubject<Void, Never>()
var score: Int = 0 {
willSet {
willChange.send()
}
}
}
In Beta 5 they change it again. This time they deprecated BindableObject all together!
BindableObject is replaced by the ObservableObject protocol from the
Combine framework. (50800624)
You can manually conform to ObservableObject by defining an
objectWillChange publisher that emits before the object changes.
However, by default, ObservableObject automatically synthesizes
objectWillChange and emits before any #Published properties change.
#ObjectBinding is replaced by #ObservedObject.
class UserSettings: ObservableObject {
#Published var score: Int = 0
}
struct MyView: View {
#ObservedObject var settings: UserSettings
}
in Xcode 11.X, I verify is fine in Xcode 11.2.1, 11.3.
BindableObject is changed to ObservableObject.
ObjectBinding is now ObservedObject.
didChange should be changed to objectWillChange.
List(dataSource.pictures, id: .self) { }
You can also now get rid of the did/willChange publisher and the .send code and just make pictures #Published
The rest will be autogenerated for you.
for example:
import SwiftUI
import Combine
import Foundation
class RoomStore: ObservableObject {
#Published var rooms: [Room]
init(rooms: [Room]) {
self.rooms = rooms
}
}
struct ContentView: View {
#ObservedObject var store = RoomStore(rooms: [])
}
ref: https://www.reddit.com/r/swift/comments/cu8cqk/getting_the_errors_pictured_below_when_try_to/
SwiftUI and Combine are two new frameworks that were announced at WWDC 2019. These two frameworks received a lot of attention at WWDC 2019, as evidenced by the number of sessions in which these technologies were featured.
SwiftUI was introduced as
a revolutionary, new way to build better apps, faster.
Combine is described as
a unified declarative framework for processing values over time
Between the initial release and now (May, 2020, Swift 5.2), there have been some changes. Anyone new to SwiftUI and Combine, who may have watched the WWDC videos, may be left with a few questions as to how the two frameworks work together.
Combine defines two interfaces: Publisher and Subscriber. A publisher sends events to subscribers. See sequence diagram below.
If you start an application in SwiftUI, and then add combine, there will be no mention of a Publisher or a Subscriber, the two main players required to use Combine. Consider this very simple sample application below.
import SwiftUI
import Combine
import SwiftUI
final class ActorViewModel: ObservableObject {
var name : String
private var imageUrl : URL?
//#Published
private (set) var image : Image = Image(systemName: "photo") {
willSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
init(name: String, imageUrl: URL?) {
self.name = name
self.imageUrl = imageUrl
self.fetchImage()
}
private func fetchImage() {
guard nil != self.imageUrl,
String() != self.imageUrl!.absoluteString else { return }
let task = URLSession.shared.dataTask(with: self.imageUrl!) { (data, response, error) in
guard nil == error , nil != response, nil != data,
let uiImage = UIImage(data: data!) else { return }
self.image = Image(uiImage: uiImage)
}
task.resume()
}
}
struct ContentView: View {
#ObservedObject var actor : ActorViewModel
var body: some View {
HStack {
actor.image
.resizable()
.aspectRatio(contentMode: ContentMode.fit)
.frame(width: 60, height: 60)
Text(actor.name)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let actor = ActorViewModel(name: "Mark Hammill",
imageUrl: URL(string: "https://m.media-amazon.com/images/M/MV5BOGY2MjI5MDQtOThmMC00ZGIwLWFmYjgtYWU4MzcxOGEwMGVkXkEyXkFqcGdeQXVyMzM4MjM0Nzg#._V1_.jpg"))
return ContentView(actor: actor)
}
}
The app preview via the canvas will look like this:
The app uses a list view to display names and images of actors. There are just two classes to consider:
ContentView -- the SwiftUI View subclass
ActorViewModel -- the source of the data for the ContentView (called a ViewModel as it performs the role of VM in MVVM)
The view has a reference to the actor object, as per the class diagram below.
Although this example is using Combine, it is not immediately apparent. There is no mention of a Publisher or a Subscriber. What is going on?
Answer: Looking at the class hierarchy fills in the missing gaps. The below class diagram explains the full picture (click on the image to see it in greater detail).
Consulting Apple's documentation provides definitions for these types:
ObservedObject: A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.
ObservableObject: A type of object with a publisher that emits before the object has changed. By default an ObservableObject synthesizes an objectWillChange publisher that emits the changed value before any of its #Published properties changes.
objectWillChange: A publisher that emits before the object has changed.
PassthroughSubject: A subject that broadcasts elements to downstream subscribers. As a concrete implementation of Subject, the PassthroughSubject provides a convenient way to adapt existing imperative code to the Combine model.
First, consider what the #ObservedObject means. This is a property wrapper. A property wrapper reduces code duplication, and allows for a succinct syntax when declaring properties that hides how the property is stored and defined. In this case, the "Observed Object" is a property which observes another object.
In other words, the property is a Subscriber (from the Combine Framework). The actor is (through the use of a property wrapper) is a Subscriber, which subscribes to a Publisher, but what is the Publisher in this scenario?
The "Observable Object" is not itself the publisher, but rather has a publisher. The ActorViewModel conforms to the ObservableObject protocol. By doing so, it is provided with a publisher property called objectWillChange by an extension (which the framework provides on the ObservableObject protocol). This objectWillChange property is of type PassthroughSubject, which is a concrete type of the Publisher protocol. The passthrough subject has a property called send, which is a publisher method used to send data to any subscribers. So the property called "objectWillChange" is the Publisher.
To recap, the Subscriber is the property called actor from the ContentView class, and the Publisher is the property objectWillChange from the ActorViewModel class. What about the need for the Subscriber to Subscribe to the Publisher? The "#ObservedObject" property wrapper is itself a Subscriber, so it must subscribe to the Publisher. But how does the View find out about changes sent to the Subscriber? That is handled by the SwiftUI framework, which we never see.
Take-away: we don't need to worry about subscribing the view to the Publisher. On the other hand, we do need to worry about making sure the publisher tell the subscriber when something is about to change. When the image has been fetched from a remote server, and the data has been transformed into an image object, we call objectWillChange.send() to inform the View. Once the subscriber receives notification from the publisher that something is about to / has changed, it invalidates the view (which results in the view redrawing itself).
Summary
The way in which SwiftUI uses a ObservedObject PropertyWrapper does not on the surface give away the fact that Combine even exists in the equation. But by inspecting ObservedObject and ObservableObject, the underlying Combine framework is revealed, along with the design pattern:
subscriber --> subscribing to a publisher --> which then publishes changes --> that are received by the subscriber
References:
Blog Article
WWDC 2019 Session 204
WWDC 2019 Session 226
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