UNNotificationRequest only triggers once after device reboot - xcode

Local Notification only triggers once after device reboot. when i close and reopen the app it won't trigger even though i see it enters the function send() every time i run the app.
i have the trigger set to nil so it will trigger Imm . any ideas what am i getting wrong ?
Thank you so much
O.K
import SwiftUI
import UserNotifications
#main
struct ozApp: App {
var body: some Scene {
WindowGroup {
ContentView().onAppear { send() }
}
}
func send(){
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("All set!")
} else if let error = error {
print(error.localizedDescription)
}
}
let content = UNMutableNotificationContent()
content.title = "Message"
content.subtitle = "999"
content.sound = UNNotificationSound.default
let uuid = UUID().uuidString
let request = UNNotificationRequest(identifier: uuid , content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
}
}

Related

SwiftUI: Sharing NSSharingService on macOS not receiving share

I have a simple test application that attempts to share a CoreData record with another user using NSSharingService. I can create my share and that works,
but when I try to receive the share it opens up the application but doesn't do anything.
I have added CKSharingSupported to my plist.
I have also followed this link to no avail:
CloudKit CKShare userDidAcceptCloudKitShareWith Never Fires on Mac App
Here is my code:
SharingServiceApp:
final class AppDelegate: NSObject, NSApplicationDelegate
{
func application(_ application: NSApplication, userDidAcceptCloudKitShareWith metadata: CKShare.Metadata)
{
print ("userDidAcceptCloudKitShareWith")
let shareStore = persistenceController.sharedPersistentStore!
persistenceController.container.acceptShareInvitations(from: [metadata], into: shareStore)
{ _, error in
if let error = error
{
print("acceptShareInvitation error :\(error)")
}
}
}
}
ContentView:
import SwiftUI
import CoreData
import CloudKit
let persistenceController = PersistenceController()
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
VStack
{
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
Button("Share")
{
shareRecord(item: item)
}
}
} label: {
Text(item.timestamp!, formatter: itemFormatter)
}
}
}
.toolbar {
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
func shareRecord(item: Item)
{
Task
{
if let share = await createShare(item: item)
{
let container = CKContainer(identifier: "iCloud.com.xxxxxxxx.sharingservice")
let item = NSItemProvider()
item.registerCloudKitShare(share, container: container)
DispatchQueue.main.async
{
let sharingService = NSSharingService(named: .cloudSharing)
sharingService!.perform(withItems: [item])
}
}
}
}
private func createShare(item: Item) async -> CKShare?
{
do
{
let (_, share, _) = try await persistenceController.container.share([item], to: nil)
share[CKShare.SystemFieldKey.title] = "MyApp"
return share
}
catch
{
print("Failed to create share")
return nil
}
}
OK I have finally managed to get userDidAcceptCloudKitShareWith to be called.
You need to create the app delegate for your SwiftUI app using #NSApplicationDelegateAdaptor:
#main
struct Sharing_ServiceApp: App
{
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene
{
WindowGroup
{
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
I put that line in and my code instantly started receiving the share requests.

How do I wait for a Firebase function to complete before continuing my code flow in SwiftUI?

I have a Firebase function in a class (listenToUser) that works fine but I noticed that the next code (the IF ELSE) does not wait for it to complete before continuing. How can I wait for my function to be completed before continuing my code?
Portion of code of my main view :
...
#EnvironmentObject var firebaseSession: FirebaseSession_VM
...
.onAppear {
firebaseSession.listenToUser()
if firebaseSession.firebaseUser == nil {
showSignInView = true
} else {
showSignInStep1View = true
}
}
My function :
import SwiftUI
import Combine
import FirebaseAuth
class FirebaseSession_VM: ObservableObject {
static let instance = FirebaseSession_VM()
var didChange = PassthroughSubject<FirebaseSession_VM, Never>()
#Published var firebaseUser: FirebaseUser_M? {
didSet {
self.didChange.send(self)
}
}
var handle: AuthStateDidChangeListenerHandle?
func listenToUser () {
// monitor authentication changes using firebase
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.firebaseUser = FirebaseUser_M(
id: user.uid,
email: user.email
)
} else {
self.firebaseUser = nil
}
}
}
}
Most of Firebase's API calls are asynchronous, which is why you need to either register a state listener or use callbacks.
Two side notes:
You should not implement ObservableObjects as singletons. Use #StateObject instead, to make sure SwiftUI can properly manage its state.
You no longer need to use PassthroughSubject directly. It's easier to use the #Published property wrapper instead.
That being said, here are a couple of code snippets that show how you can implement Email/Password Authentication with SwiftUI:
Main View
The main view shows if you're sign in. If you're not signed in, it will display a button that will open a separate sign in screen.
import SwiftUI
struct ContentView: View {
#StateObject var viewModel = ContentViewModel()
var body: some View {
VStack {
Text("👋🏻 Hello!")
.font(.title3)
switch viewModel.isSignedIn {
case true:
VStack {
Text("You're signed in.")
Button("Tap here to sign out") {
viewModel.signOut()
}
}
default:
VStack {
Text("It looks like you're not signed in.")
Button("Tap here to sign in") {
viewModel.signIn()
}
}
}
}
.sheet(isPresented: $viewModel.isShowingLogInView) {
SignInView()
}
}
}
The main view's view model listens for any auth state changes and updates the isSignedIn property accordingly. This drives the ContentView and what it displays.
import Foundation
import Firebase
class ContentViewModel: ObservableObject {
#Published var isSignedIn = false
#Published var isShowingLogInView = false
init() {
// listen for auth state change and set isSignedIn property accordingly
Auth.auth().addStateDidChangeListener { auth, user in
if let user = user {
print("Signed in as user \(user.uid).")
self.isSignedIn = true
}
else {
self.isSignedIn = false
}
}
}
/// Show the sign in screen
func signIn() {
isShowingLogInView = true
}
/// Sign the user out
func signOut() {
do {
try Auth.auth().signOut()
}
catch {
print("Error while trying to sign out: \(error)")
}
}
}
SignIn View
The SignInView shows a simple email/password form with a button. The interesting thing to note here is that it listens for any changes to the viewModel.isSignedIn property, and calls the dismiss action (which it pulls from the environment). Another option would be to implement a callback as a trailing closure on the view model's signIn() method.
struct SignInView: View {
#Environment(\.dismiss) var dismiss
#StateObject var viewModel = SignInViewModel()
var body: some View {
VStack {
Text("Hi!")
.font(.largeTitle)
Text("Please sign in.")
.font(.title3)
Group {
TextField("Email", text: $viewModel.email)
.disableAutocorrection(true)
.autocapitalization(.none)
SecureField("Password", text: $viewModel.password)
}
.padding()
.background(Color(UIColor.systemFill))
.cornerRadius(8.0)
.padding(.bottom, 8)
Button("Sign in") {
viewModel.signIn()
}
.foregroundColor(Color(UIColor.systemGray6))
.padding(.vertical, 16)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.accentColor)
.cornerRadius(8)
}
.padding()
.onChange(of: viewModel.isSignedIn) { signedIn in
dismiss()
}
}
}
The SignInViewModel has a method signIn that performs the actual sign in process by calling Auth.auth().signIn(withEmail:password:). As you can see, it will change the view model's isSignedIn property to true if the user was authenticated.
import Foundation
import FirebaseAuth
class SignInViewModel: ObservableObject {
#Published var email: String = ""
#Published var password: String = ""
#Published var isSignedIn: Bool = false
func signIn() {
Auth.auth().signIn(withEmail: email, password: password) { authDataResult, error in
if let error = error {
print("There was an issue when trying to sign in: \(error)")
return
}
guard let user = authDataResult?.user else {
print("No user")
return
}
print("Signed in as user \(user.uid), with email: \(user.email ?? "")")
self.isSignedIn = true
}
}
}
Alternative: Using Combine
import Foundation
import FirebaseAuth
import FirebaseAuthCombineSwift
class SignInViewModel: ObservableObject {
#Published var email: String = ""
#Published var password: String = ""
#Published var isSignedIn: Bool = false
// ...
func signIn() {
Auth.auth().signIn(withEmail: email, password: password)
.map { $0.user }
.replaceError(with: nil)
.print("User signed in")
.map { $0 != nil }
.assign(to: &$isSignedIn)
}
}
Alternative: Using async/await
import Foundation
import FirebaseAuth
class SignInViewModel: ObservableObject {
#Published var email: String = ""
#Published var password: String = ""
#Published var isSignedIn: Bool = false
#MainActor
func signIn() async {
do {
let authDataResult = try 3 await 1 Auth.auth().signIn(withEmail: email, password: password)
let user = authDataResult.user
print("Signed in as user \(user.uid), with email: \(user.email ?? "")")
self.isSignedIn = true
}
catch {
print("There was an issue when trying to sign in: \(error)")
self.errorMessage = error.localizedDescription
}
}
}
More details
I wrote an article about this in which I explain the individual techniques in more detail: Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await. If you'd rather watch a video, I've got you covered as well: 3 easy tips for calling async APIs

toggle to get notified in swiftui

I want to be able to notify the user of my app every day at a specific time. in this example, the time is noon
import SwiftUI
import UserNotifications
struct Alert: View {
#State var noon = false
func noonNotify() {
let content = UNMutableNotificationContent()
content.title = "Meds"
content.subtitle = "Take your meds"
content.sound = UNNotificationSound.default
var dateComponents = DateComponents()
dateComponents.hour = 14
dateComponents.minute = 38
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
// choose a random identifier
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
// add our notification request
UNUserNotificationCenter.current().add(request)
}
var body: some View {
VStack {
Toggle(isOn: $noon) {
Text("ThirdHour")
}
if noon {
noonNotify()
}
Button("Request Permission") {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("All set!")
} else if let error = error {
print(error.localizedDescription)
}
}
}
}
}
}
I created a func and when the toggle is true, the func will execute, but when it's false, then it won't. however, when I create an if statement, I get an error
Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols
can someone explain what I'm doing wrong?
You can't call a function like that. Everything inside var body: some View { has to be a View, and noonNotify() doesn't return a View.
Instead, add an onChange block, which will get triggered whenever noon changes.
Toggle(isOn: $noon) {
Text("ThirdHour")
}
.onChange(of: noon) { newValue in
if newValue {
noonNotify()
}
}

Instagram Tutorial SwiftUI Firebase not loading Timeline

I'm new to coding and have been trying to go through instagram tutorials to understand some concepts. Since updating to Xcode 12, my Firebase has seemed to not work anymore and is not showing on the home feed.
I placed a rectangle in to see if it was the if !homeViewModel.isLoading was the cause it appears to be so.
Here is my current code:
import SwiftUI
import URLImage
import Firebase
struct HomeView: View {
#ObservedObject var homeViewModel = HomeViewModel()
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
Story()
Rectangle().frame(width: 200, height: 200).foregroundColor(.red)
if !homeViewModel.isLoading {
ForEach(self.homeViewModel.posts, id: \.postId) { post in
VStack(alignment: .center) {
HeaderCell(post: post)
FooterCell(post: post)
}.background(Color.white).cornerRadius(10)
.padding(.leading, 10).padding(.trailing, 10)
}
}
}.background(Color.gray)
HomeViewModel:
import Foundation
import SwiftUI
import Firebase
class HomeViewModel: ObservableObject {
#Published var posts: [Post] = []
#Published var isLoading = false
var listener: ListenerRegistration!
// init() {
// loadTimeline()
// }
func loadTimeline() {
self.posts = []
isLoading = true
Api.Post.loadTimeline(onSuccess: { (posts) in
self.isLoading = false
if self.posts.isEmpty {
self.posts = posts
}
}, newPost: { (post) in
if !self.posts.isEmpty {
self.posts.insert(post, at: 0)
}
}) { (listener) in
self.listener = listener
}
}
}
LoadTimeline func:
func loadTimeline(onSuccess: #escaping(_ posts: [Post]) -> Void, newPost: #escaping(Post) -> Void, listener: #escaping(_ listenerHandle: ListenerRegistration) -> Void) {
guard let userId = Auth.auth().currentUser?.uid else {
return
}
let listenerFirestore = Ref.FIRESTORE_TIMELINE_DOCUMENT_USERID(userId: userId).collection("timelinePosts").order(by: "date", descending: true).addSnapshotListener({ (querySnapshot, error) in
guard let snapshot = querySnapshot else {
return
}
var posts = [Post]()
snapshot.documentChanges.forEach { (documentChange) in
switch documentChange.type {
case .added:
print("type: added")
let dict = documentChange.document.data()
guard let decoderPost = try? Post.init(fromDictionary: dict) else {return}
newPost(decoderPost)
posts.append(decoderPost)
case .modified:
print("type: modified")
case .removed:
print("type: removed")
}
}
onSuccess(posts)
})
listener(listenerFirestore)
}
For some reason it seems as though the function isn't being triggered and timeline isn't loading. unsure why though... Prior to the update it was working fine?
Any help would be much appreciated!

NSURL error for broken links in Swift

I coded a function for OSX 10.10 that is willing to open a text file from an URL and display its content.
Everything is working but if the URL cannot be reach then the App will crash. How could I handle this type of Error?
I guess it comes from the completionHandler closure but I am not sure.
here is my code
#IBAction func checkAdminMessage(sender: NSMenuItem) {
let messageURL = NSURL(string: "http://www.xxxxxx.com/text.txt")
// The Network stuff will be handled in a background thread
let sharedSession = NSURLSession.sharedSession()
let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(messageURL!,
completionHandler: {
(location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
var urlContents = NSString(contentsOfURL: location, encoding: NSUTF8StringEncoding, error: nil)
// Check if text.txt has NULL as content
if urlContents! == "NULL" {
// Have to use Grand Central Dispatch to put NSAlert in the main thread
let noMessage = NSLocalizedString("Nothing there", comment: "Text to dislay when the file is empty" )
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.displayAlertNotification(notification: noMessage)
}
} else {
// If the file is not empty then we display the content of this file
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.displayAlertNotification(notification: urlContents!)
}
}
})
downloadTask.resume()
}
Thank you
EDIT: Here is the updated code but the App still crashed
#IBAction func checkAdminMessage(sender: NSMenuItem) {
if let messageURL = NSURL(string: "http://www.xxxxxx.com/text.txt") {
let sharedSession = NSURLSession.sharedSession()
let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(messageURL,
completionHandler: {
(location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
var urlContents = NSString(contentsOfURL: location, encoding: NSUTF8StringEncoding, error: nil )
if urlContents == "NULL" {
println(urlContents)
// Have to use Grand Central Dispatch to put NSAlert in the main thread
let noMessage = NSLocalizedString("Nothing there", comment: "Text to dislay when the file is empty" )
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.displayAlertNotification(notification: noMessage)
}
}
else {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.displayAlertNotification(notification: urlContents!)
}
}
})
downloadTask.resume()
}
else {
println("Error")
}
}
NSURL(string: ...) returns an optional, so the result may be nil due to several reasons.
Wrap your code in a conditional unwrap:
if let messageURL = NSURL(string: "http://www.xxxxxx.com/text.txt") {
// success
...
}
else {
// error
}
I figured it out with the helps of the people that commented my question.
I was getting a nil from 'location' in downloadTaskWithUrl, then the var urlContents was receiving a nil as well.
The solution is to check if 'location' is nil :
let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(messageURL,
completionHandler: {
(location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
**if (location != nil) {**
var urlContents = NSString(contentsOfURL: location, encoding: NSUTF8StringEncoding, error: nil ) ...

Resources