Problem
We have successfully set up push notifications using Apple APN service, that is push notifications work when using a token generated using the JSON Web Token Generator in the Push Notification console. However, we get an "InvalidProviderToken" error when creating using our own token using the following code.
The Key and TeamID is definitely correct (obviously, censored in the below code). When pasting our token in the JSON Web Token Validator in the Push Notification console we get the error „Invalid signing key“. We merely pasted our secret key in our setNewTokenIfNeeded code, separated on four lines using the “““ style.
Does anyone know why this error happens? Given that it works when we upload our .p8 file to the JSON Web Token Generator and we simply paste the text of this file (excluding the lines with "-----BEGIN/END PRIVATE KEY-----") I guess our secret key is correct?
Code to generate token
fileprivate var currentToken: String?
fileprivate var currentTokenCreateTime: Date?
fileprivate func setNewTokenIfNeeded() {
// Ensure, token is at least 20 minutes but at most 60 minutes old
if let currentTokenCreateTime = currentTokenCreateTime {
let ageOfTokenInSeconds = abs(Int(currentTokenCreateTime.timeIntervalSinceNow))
NSLog("Age of token: \(Int(ageOfTokenInSeconds / 60)) minutes.")
if ageOfTokenInSeconds <= 20 * 60 { return }
}
// Generate new token
NSLog("Renewing token.")
let secret = """
ABCABCABCABCABCABCABCABCABCABCABCABC+ABCABC+ABCABCABC+ABCABCAB/+
ABCABCABCABCABCABCABCABCABCABCABCABC+ABCABC+ABCABCABC+ABCABCAB/+
ABCABCABCABCABCABCABCABCABCABCABCABC+ABCABC+ABCABCABC+ABCABCAB/+
ABCABCAB
"""
let privateKey = SymmetricKey(data: Data(secret.utf8))
let headerJSONData = try! JSONEncoder().encode(Header())
let headerBase64String = headerJSONData.urlSafeBase64EncodedString()
let payloadJSONData = try! JSONEncoder().encode(Payload())
let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString()
let toSign = Data((headerBase64String + "." + payloadBase64String).utf8)
let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
let signatureBase64String = Data(signature).urlSafeBase64EncodedString()
let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".")
currentToken = token
currentTokenCreateTime = Date()
}
fileprivate struct Header: Encodable {
let alg = "ES256"
let kid: String = "ABCABCABC" // Key (censored here)
}
fileprivate struct Payload: Encodable {
let iss: String = "ABCABCABC" // Team-ID (censored here)
let iat: Int = Int(Date().timeIntervalSince1970)
}
extension Data {
func urlSafeBase64EncodedString() -> String {
return base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
}
Code to send the push notification
func SendPushNotification(category: ConversationCategory,
conversationID: UUID,
title: String,
subTitle: String?,
body: String,
devicesToSendTo: [String]) {
// Für alle Felder s. https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification
let payload = [
"aps": [
"alert": [
"title": title,
"subtitle" : subTitle ?? "",
"body": body
],
"category" : category.rawValue,
"mutable-content": 1
],
"conversationID": conversationID.uuidString
] as [String : Any]
// Ggf. Token setzen
setNewTokenIfNeeded()
guard let currentToken = currentToken else {
NSLog("Token not initialized.")
return
}
NSLog(currentToken)
// Notification an alle angegebenen Devices schicken
let bundleID = "com.TEAMID.APPNAME"
for curDeviceID in devicesToSendTo {
NSLog("Sending push notification to device with ID \(curDeviceID).")
let apnServerURL = "https://api.sandbox.push.apple.com:443/3/device/\(curDeviceID)"
var request = URLRequest(url: URL(string: apnServerURL)!)
request.httpMethod = "POST"
request.allHTTPHeaderFields = [
"authorization": "bearer " + currentToken,
"apns-id": UUID().uuidString,
"apns-topic": bundleID,
"apns-priority": "10",
"apns-expiration": "0"
]
request.httpBody = try! JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted)
URLSession(configuration: .ephemeral).dataTask(with: request) { data, response, error in
if let error = error {
NSLog(error.localizedDescription)
}
if let data = data {
NSLog(String(data: data, encoding: .utf8)!)
}
}.resume()
}
}
On a similar note, some people seem to encounter this error when using the prettyPrinted option for the JSON serialization (i.e., in request.httpBody = try! JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted). Could this be the culprit, given our secret key contains „/„ and „+“?
Many thanks!
Post
Replies
Boosts
Views
Activity
Hi there
We're using CloudKit in our app which, generally, syncs data perfectly between devices. However, recently the sync has stopped working (some changes will never sync and the sync is delayed for several days even with the app open on all devices). CloudKit's logs show the error „You can't save and delete the same record" and „Already have a mirrored relationship registered for this key", etc. We’ve a hunch that this issue is related to a mirrored relationship of one database entity.
Our scenario:
We've subclassed the database entities.
The database model (which we can't share publicly) contains mirrored relationships.
We store very long texts in the database (similar to a Word document that contains markup data – in case that’s relevant).
Deleting all data and starting with a completely new container and bundle identifier didn’t help (we tried that multiple times).
This issue occurs on macOS (15.2(24C101) as well on iOS (18.2).
Any hints on how to get the sync working again? Should we simply avoid mirrored relationships?
Many thanks
Hi there
Issue
I've implemented a custom DisclosureGroupStyle for a List, which shows hierarchical data. However, it seems that when applying this (or any other) custom DisclosureGroupStyle in macOS the context menu stops working, except on the lowest level. In iOS, on the other hand, the context menu keeps working on all levels.
Is this excepted behaviour?
Example
var body: some View {
List(categories, id: \.value, children: \.children) { tree in
Text(tree.value).font(.subheadline)
.contextMenu{
Button("Test") {}
}
}
.disclosureGroupStyle(SideBarDisclosureGroupStyle())
.padding(50)
}
}
struct SideBarDisclosureGroupStyle: DisclosureGroupStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .firstTextBaseline) {
configuration.label
Spacer()
Image(systemName:"chevron.\(configuration.isExpanded ? "down" : "right").circle")
.imageScale(.large)
.foregroundStyle(.secondary)
.onTapGesture() {
configuration.isExpanded.toggle()
}
}
.contentShape(Rectangle())
if configuration.isExpanded {
configuration.content
.disclosureGroupStyle(self)
}
}
}
// Example Data (Source: https://swiftwithmajid.com/2020/09/02/displaying-recursive-data-using-outlinegroup-in-swiftui/)
struct Tree<Value: Hashable>: Hashable {
let value: Value
var children: [Tree]? = nil
}
let categories: [Tree<String>] = [
.init(
value: "Clothing",
children: [
.init(value: "Hoodies"),
.init(value: "Jackets"),
.init(value: "Joggers"),
.init(value: "Jumpers"),
.init(
value: "Jeans",
children: [
.init(value: "Regular"),
.init(value: "Slim")
]
),
]
),
.init(
value: "Shoes",
children: [
.init(value: "Boots"),
.init(value: "Sliders"),
.init(value: "Sandals"),
.init(value: "Trainers"),
]
)
]
System info
macOS 13.0 Beta(22A5358e)
Xcode 14.0 beta 6 (14A5294g)
Many thanks
Sebastian
Hello
I am trying to display items in a ScrollView which contains several autosized NSViewRepresentables. However, it seems the items overlap each other after scrolling or when the size of its parent ScrollView is changed (for an example see my post on SO) - https://stackoverflow.com/questions/66175613/autosized-nsviewrepresentables-in-scrollview-overlap-each-other-swiftui
If I've understood correctly, the ScrollView needs to know the height of its items to render it correctly. Therefore, I tried to pass the total height of the each item to its parent ScrollView, however, the items still overlap. I've made a simple example:
struct dataItem: Identifiable {
let id = UUID()
let question: String
let answer: String
}
let exampleData: [dataItem] = [dataItem(question: "Lorem Ipsum is simply....", answer: "Lorem Ips ange...ker including versions of Lorem Ipsum.") ,
dataItem(question:"The standard ..ranslation by H. Rackham.", answer:"The standard c.... Rackham."),
dataItem(question:"The standard ch...Rackham.", answer:"The stand...kham."),
dataItem(question:"The stand..Rackham.", answer:"The standa...kham."),
dataItem(question:"The stand...ackham.", answer:"The standard ...ham."),
dataItem(question:"The standa..H. Rackham.", answer:"The standard c...ackham."),
dataItem(question:"The st... H. Rackham.", answer:"The standard ch...m.")
]
struct ContentView: View {
@State var totalHeight: CGFloat = 80
var body: some View {
ScrollView{
ScrollViewReader{ scrollView in
LazyVStack {
ForEach(exampleData, id: \.id) { item in
RowView(item: item, totalHeight: $totalHeight)
// The following line makes the ScrollView jitter
// .frame(height: totalHeight)
}
}
}
}
.id(UUID().uuidString)
}
}
struct RowView: View {
let item: dataItem
@Binding var totalHeight: CGFloat
@State var optimalHeightQuestionValue: CGFloat = .zero
@State var optimalHeightAnswerValue: CGFloat = .zero
var body: some View{
let optimalHeightQuestion = BindingCGFloat(
get: {self.optimalHeightQuestionValue}, set: {
self.optimalHeightQuestionValue = $0
totalHeight = optimalHeightQuestionValue + optimalHeightAnswerValue
})
let optimalHeightAnswer = BindingCGFloat(
get: {self.optimalHeightAnswerValue}, set: {
self.optimalHeightAnswerValue = $0
totalHeight = optimalHeightQuestionValue + optimalHeightAnswerValue
})
VStack{
TextViewExt(attributedText: NSAttributedString(string: item.question), optimalHeight: optimalHeightQuestion)
.frame(height: optimalHeightQuestionValue)
TextViewExt(attributedText: NSAttributedString(string: item.answer), optimalHeight: optimalHeightAnswer)
.frame(height: optimalHeightAnswerValue)
}
}
}
struct TextViewExt: NSViewRepresentable {
typealias NSViewType = NSTextView
var attributedText: NSAttributedString
@Binding var optimalHeight: CGFloat
func makeNSView(context: Context) - NSTextView {
let textView = NSTextView()
textView.backgroundColor = .clear
return textView
}
func updateNSView(_ nsView: NSTextView, context: Context) {
DispatchQueue.main.async {
nsView.textStorage?.setAttributedString(attributedText)
nsView.translatesAutoresizingMaskIntoConstraints = true
nsView.sizeToFit()
self.optimalHeight = nsView.frame.height
}
}
}
Does anybody know why that's happening? Or is there something wrong with the auto-sizing of the NSTextView?
Thanks a lot for any advise!