SwiftUI trigger function when notification is sent? - xcode

I currently have a system that can send notifications to the phone in my app. I would like to perform a function every time that notification is sent? Any help is appreciated.
My current code for creating a notification.
static func addNotification(sub: SubscriptionItem){
let center = UNUserNotificationCenter.current()
let addRequest = {
let content = UNMutableNotificationContent()
content.title = "\(sub.name)"
content.subtitle = "€\(sub.price) due in \(sub.daysBefore) day(s)"
content.sound = UNNotificationSound.default
var dateComponents = DateComponents()
dateComponents.day = Calendar.current.dateComponents([.day], from: sub.nextpayment).day
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: sub.id, content: content, trigger: trigger)
center.add(request)
}
center.getNotificationSettings { settings in
if settings.authorizationStatus == .authorized{
addRequest()
}
else{
center.requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success{
addRequest()
}
}
}
}
}

Related

repeated alarm notifications in swiftui

I have created a piece of code to send a local notification at 3 pm and the user can toggle it with a toggle switch. I used an AppStorage wrapper to remember the state it was in. The code works until I toggle the switch on and off (simulating whether the user switches it multiple times) and the number of times I switched the toggle, that same number comes as a notification (i.e. I toggle 5 times, I get 5 notifications all at once). this is the code
struct trial: View {
#AppStorage("3") var three = false
func firstNotify() {
let content = UNMutableNotificationContent()
content.title = "It is 3pm"
content.subtitle = "Read the Prime Hour"
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "notifysound.wav"))
// content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "notifysound"))
var dateComponents = DateComponents()
dateComponents.hour = 15
dateComponents.minute = 00
dateComponents.second = 00
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)
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("All set!")
} else if let error = error {
print(error.localizedDescription)
}
}
}
#State private var name: String = "Tim"
var body: some View {
VStack {
Toggle(isOn: $three) {
Text("3 pm")
}
.padding(.horizontal)
.onChange(of: three) { newValue in
if newValue {
firstNotify()
}
}
}
}
}
struct trial_Previews: PreviewProvider {
static var previews: some View {
trial()
}
}
I think what's happening is that the func is not reading whether the toggle is on or off but just adds a notification each time the switch gets turned on. Would anyone know the solution to this???
The toggle is really only scheduling a new notification on every change of the value to true.
This piece of code means that a new notification will be scheduled each time this method is called.
// choose a random identifier
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
// add our notification request
UNUserNotificationCenter.current().add(request)
Not entirely sure what the desired logic is, but you could solve this by either updating the existing notification or clearing out the old one and adding a new one. Rather than creating a new UUID for each notification, it may be best to have an ID convention or otherwise store it so it can be accessed or removed.
You could store the rather than the toggle value:
#AppStorage("notification_id") var notificationID = nil // I don't know if this can be a string or nil, but hopefully it leads you in the right direction.
// Then when creating the request.
let id = notificationID ?? UUID().uuidString
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
notificationID = id
And there needs to be another method when to unschedule when they toggle the other direction. You could simply check to see if the id exists.
if let id = notificationID {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers identifiers: [id])
// OR
UNUserNotificationCenter.current()removeAllPendingNotificationRequests()
}

Not Executing Function at The Right Time, But Executed After Completion Block

Need help in figuring out why my function is not executing when I thought it should but it executed after the completion block in the code. I am fairly new to Xcode so please excuse me if things sound confusing here. Below is my code.
class ImageDownloader{
typealias completionHandler = (Result<Set<ARReferenceImage>, Error>) -> ()
typealias ImageData = (image: UIImage, orientation: CGImagePropertyOrientation, physicalWidth: CGFloat, name: String)
static let URLDic = [ReferenceImagePayload]()
class func getDocumentData(completion:#escaping ([ReferenceImagePayload]) -> ()) {
var documentCollection: [ReferenceImagePayload] = []
db.collection("Users").getDocuments {(snapshot, error) in
if error == nil && snapshot != nil {
var index = 0
for document in snapshot!.documents {
let loadData = document.data()
index += 1
if loadData["Target URL"] != nil {
let url = loadData["Target URL"]
let urlString = URL(string: "\(String(describing: url ?? ""))")
let urlName = loadData["Target Image"]
documentCollection.append(ReferenceImagePayload(name: urlName as! String, url: urlString!))
if snapshot!.documents.count == index {
// After finished, send back the loaded data
completion(documentCollection)
}
}
}
}
}
}
static var receivedImageData = [ImageData]()
class func downloadImagesFromPaths(_ completion: #escaping completionHandler) {
// THE LINE BELOW WHERE I CALL THE FUNCTION IS NOT EXECUTED WHEN THIS CLASS IS INITIALLY CALLED. BUT AS THE CODE RUNS, THIS LINE BELOW IS EXECUTED AFTER THE COMPLETIONOPERATION = BLOCKOPERATION IS COMPLETED.
let loadedDataDic: () = getDocumentData { (URLDic) in
print(URLDic.self, "Got it")
}
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 6
let completionOperation = BlockOperation {
OperationQueue.main.addOperation({
completion(.success(referenceImageFrom(receivedImageData)))
// LINE "let loadedDataDic: () = getDocumentData" ONLY GOT EXECUTED AT THIS POINT
})
}
URLDic.forEach { (loadData) in
let urlstring = loadData.url
let operation = BlockOperation(block: {
do{
let imageData = try Data(contentsOf: loadData.url)
print(imageData, "Image Data")
if let image = UIImage(data: imageData){
receivedImageData.append(ImageData(image, .up, 0.1, loadData.name))
}
}catch{
completion(.failure(error))
}
})
completionOperation.addDependency(operation)
}
operationQueue.addOperations(completionOperation.dependencies, waitUntilFinished: false)
operationQueue.addOperation(completionOperation)
}
}

SwiftUI Load View from SceneDelegate sceneDidBecomeActive

I'm trying to understand how to load a SwiftUI view from Swift function code. In this
case specifically, I want to load a view when returning from the background state to
cover sensitive data. I have created a biometric login and that works fine - pure
SwiftUI views for the app. When I put the app into the background and return, the
FaceID works as expected, but the underlying screen is visible. This is a generalized
question too - how can you load any SwiftUI view from any Swift function.
func sceneDidBecomeActive(_ scene: UIScene) {
if userDefaultsManager.wentToBackground {
if userDefaultsManager.enableBiometrics {
BiometricsLogin(userDefaultsManager: userDefaultsManager).authenticate()
//what I want is something like:
//BiometricsLogin(userDefaultsManager: userDefaultsManager)
//kinda like you would do in a TabView
//that would run the authentication just like starting the app
userDefaultsManager.wentToBackground = false
}
}
}
And the login code is pretty generic:
struct BiometricsLogin: View {
#ObservedObject var userDefaultsManager: UserDefaultsManager
var body: some View {
NavigationView {
VStack {
Image("CoifMeCrop180")
.onAppear {
self.authenticate()
}
}//vstack
}//nav
}
func authenticate() {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "The app uses Biometrics to unlock you data"
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { (success, authenticationError)
in
DispatchQueue.main.async {
if success {
self.userDefaultsManager.isAuthenticated = true
self.userDefaultsManager.selectedTab = 1
} else {
if self.userDefaultsManager.enableBiometrics {
self.userDefaultsManager.isAuthenticated = false
self.userDefaultsManager.selectedTab = 3
} else {
self.userDefaultsManager.isAuthenticated = false
self.userDefaultsManager.selectedTab = 1
}
}
}
}
} else {
//no biometrics - deal with this elsewhere
//consider an alert here
}
}//authenticate
}
I also tried using a hosting controller like this, but it did not work either. Same
issue, the authentication worked but the data was visible.
//this does not work
let controller = UIHostingController(rootView: BiometricsLogin(userDefaultsManager: userDefaultsManager))
self.window!.addSubview(controller.view)
self.window?.makeKeyAndVisible()
Any guidance would be appreciated. Xcode 11.3.1 (11C504)
Here is possible approach
In sceneDidBecomeActive add presenting new controller with authentication in full screen to hide sensitive content
func sceneDidBecomeActive(_ scene: UIScene) {
let controller = UIHostingController(rootView: BiometricsLogin(userDefaultsManager: userDefaultsManager))
controller.modalPresentationStyle = .fullScreen
self.window?.rootViewController?.present(controller, animated: false)
}
now it needs to dismiss it when authentication will be done, so add notification for that purpose...
extension SceneDelegate {
static let didAuthenticate = Notification.Name(rawValue: "didAuthenticate")
}
... and subscriber for it in SceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private var authenticateObserver: AnyCancellable?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
if let windowScene = scene as? UIWindowScene {
self.authenticateObserver = NotificationCenter.default.publisher(for: SceneDelegate.didAuthenticate)
.sink { _ in
self.window?.rootViewController?.dismiss(animated: true)
}
let window = UIWindow(windowScene: windowScene)
...
when authentication is done just post didAuthenticate notification to dismiss top controller
DispatchQueue.main.async {
if success {
self.userDefaultsManager.isAuthenticated = true
self.userDefaultsManager.selectedTab = 1
} else {
if self.userDefaultsManager.enableBiometrics {
self.userDefaultsManager.isAuthenticated = false
self.userDefaultsManager.selectedTab = 3
} else {
self.userDefaultsManager.isAuthenticated = false
self.userDefaultsManager.selectedTab = 1
}
}
NotificationCenter.default.post(name: SceneDelegate.didAuthenticate, object: nil)
}

Compress video size before attach to an email in swift

Previous, I have ask for how to attach video then send via email. Now it working. Advised by some friend from this website.
I found new problem that video size is very large and larger than send with default email app in iOS for same video file.
Please advice me how to compress video file before attach to an email application.
Thank you everyone.
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
if let myImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
image = info[UIImagePickerControllerOriginalImage] as! UIImage
self.dismissViewControllerAnimated(false, completion: nil)
sendmail()
}
else {
//picker.videoQuality = UIImagePickerControllerQualityTypeLow
videoURL = info[UIImagePickerControllerMediaURL] as! NSURL
self.dismissViewControllerAnimated(true, completion: nil)
sendmailVDO()
}
}
Below is the code for Compress video by half of the actual size
var assetWriter:AVAssetWriter?
var assetReader:AVAssetReader?
let bitrate:NSNumber = NSNumber(value:250000)
func compressFile(urlToCompress: URL, outputURL: URL, completion:#escaping (URL)->Void){
//video file to make the asset
var audioFinished = false
var videoFinished = false
let asset = AVAsset(url: urlToCompress);
let duration = asset.duration
let durationTime = CMTimeGetSeconds(duration)
print("Video Actual Duration -- \(durationTime)")
//create asset reader
do{
assetReader = try AVAssetReader(asset: asset)
} catch{
assetReader = nil
}
guard let reader = assetReader else{
fatalError("Could not initalize asset reader probably failed its try catch")
}
let videoTrack = asset.tracks(withMediaType: AVMediaType.video).first!
let audioTrack = asset.tracks(withMediaType: AVMediaType.audio).first!
let videoReaderSettings: [String:Any] = [(kCVPixelBufferPixelFormatTypeKey as String?)!:kCVPixelFormatType_32ARGB ]
// ADJUST BIT RATE OF VIDEO HERE
let videoSettings:[String:Any] = [
AVVideoCompressionPropertiesKey: [AVVideoAverageBitRateKey:self.bitrate],
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoHeightKey: videoTrack.naturalSize.height,
AVVideoWidthKey: videoTrack.naturalSize.width
]
let assetReaderVideoOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
let assetReaderAudioOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)
if reader.canAdd(assetReaderVideoOutput){
reader.add(assetReaderVideoOutput)
}else{
fatalError("Couldn't add video output reader")
}
if reader.canAdd(assetReaderAudioOutput){
reader.add(assetReaderAudioOutput)
}else{
fatalError("Couldn't add audio output reader")
}
let audioInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: nil)
let videoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
videoInput.transform = videoTrack.preferredTransform
//we need to add samples to the video input
let videoInputQueue = DispatchQueue(label: "videoQueue")
let audioInputQueue = DispatchQueue(label: "audioQueue")
do{
assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.mov)
}catch{
assetWriter = nil
}
guard let writer = assetWriter else{
fatalError("assetWriter was nil")
}
writer.shouldOptimizeForNetworkUse = true
writer.add(videoInput)
writer.add(audioInput)
writer.startWriting()
reader.startReading()
writer.startSession(atSourceTime: kCMTimeZero)
let closeWriter:()->Void = {
if (audioFinished && videoFinished){
self.assetWriter?.finishWriting(completionHandler: {
print("------ Finish Video Compressing")
self.checkFileSize(sizeUrl: (self.assetWriter?.outputURL)!, message: "The file size of the compressed file is: ")
completion((self.assetWriter?.outputURL)!)
})
self.assetReader?.cancelReading()
}
}
audioInput.requestMediaDataWhenReady(on: audioInputQueue) {
while(audioInput.isReadyForMoreMediaData){
let sample = assetReaderAudioOutput.copyNextSampleBuffer()
if (sample != nil){
audioInput.append(sample!)
}else{
audioInput.markAsFinished()
DispatchQueue.main.async {
audioFinished = true
closeWriter()
}
break;
}
}
}
videoInput.requestMediaDataWhenReady(on: videoInputQueue) {
//request data here
while(videoInput.isReadyForMoreMediaData){
let sample = assetReaderVideoOutput.copyNextSampleBuffer()
if (sample != nil){
let timeStamp = CMSampleBufferGetPresentationTimeStamp(sample!)
let timeSecond = CMTimeGetSeconds(timeStamp)
let per = timeSecond / durationTime
print("Duration --- \(per)")
DispatchQueue.main.async {
self.progress.progress = Float(per)
}
videoInput.append(sample!)
}else{
videoInput.markAsFinished()
DispatchQueue.main.async {
videoFinished = true
self.progress.progress = 1.0
closeWriter()
}
break;
}
}
}
}
You can also display the progress of video compression.

How to initiate email on xcode for mac app

i'm developing a mac app that requires email access
here's the code i've tried on ios
-(IBAction)Contact us:(id)sender {
//Email Subject
NSString *emailTitle = #"";
//Email Content
NSString *messageBody = #"";
//Email Address
NSArray *toRecipients =[NSArray arrayWithObject:#"info#mandarin-espeak.com"];
MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init];
mc.mailComposeDelegate = self;
[mc setSubject:emailTitle];
[mc setMessageBody:messageBody isHTML:YES];
[mc setToRecipients:toRecipients];
}
how can i do it on a mac app to open default email client
In MacOS you can easily send e-mails like this:
class SendEmail: NSObject {
static func send() {
let service = NSSharingService(named: NSSharingServiceNameComposeEmail)!
service.recipients = ["abc#dom.com"]
service.subject = "Vea software"
service.performWithItems(["This is an email for auto testing through code."])
}
}
Usage:
SendEmail.send()
I ended up going with an AppleScript solution. This is a one off app, not for the store, so I turned sandboxing off. I also added Privacy - AppleEvents Sending Usage Description to the info.plist and in the Hardened Runtime under Signing and Capabilities checked Apple Events
My email code, shouldn't be too hard to pan out my unique stuff:
func emailUser(_ thisUser:NSManagedObject) {
let dictSIPResponse = checkSIPforAppIdentifier("com.microsoft.Outlook")
if let bIsSIPEnabled = dictSIPResponse["isSIPEnabled"] as? Bool, let strSIPMessage = dictSIPResponse["sipMessage"] as? String{
if bIsSIPEnabled {
if let thisWriter = thisUser as? Writer, let strEmailAddress = thisWriter.email {
let outlookScript = """
if application "Outlook" is running then
tell application "Outlook"
set newMessage to make new outgoing message with properties {subject:"Hooray for automation"}
make new recipient at newMessage with properties {email address:{name:"Steve Suranie", address:"steven.suranie#xandr.com"}}
send newMessage
end tell
end if
"""
var out: NSAppleEventDescriptor?
if let scriptObject = NSAppleScript(source: outlookScript) {
var errorDict: NSDictionary? = nil
out = scriptObject.executeAndReturnError(&errorDict)
if let error = errorDict {
print(error)
}
}
}
} else {
print(strSIPMessage)
}
}
}
Before trying to email with the AppleScript I check to ensure the permissions have been set:
func checkSIPforAppIdentifier(_ sipIdentifier:String) -> Dictionary<String, Any> {
var dictSIPResponse = [String:Any]()
var targetAppEventDescriptor = NSAppleEventDescriptor(bundleIdentifier: sipIdentifier)
var status = AEDeterminePermissionToAutomateTarget(targetAppEventDescriptor.aeDesc, typeWildCard, typeWildCard, true);
switch (status) {
case -600: //procNotFound
dictSIPResponse["isSIPEnabled"] = false
dictSIPResponse["sipMessage"] = "Not running app with id \(sipIdentifier)"
break;
case 0: // noErr
dictSIPResponse["isSIPEnabled"] = true
dictSIPResponse["sipMessage"] = "SIP check successfull for app with id \(sipIdentifier)"
break;
case -1744: // errAEEventWouldRequireUserConsent
// This only appears if you send false for askUserIfNeeded
dictSIPResponse["isSIPEnabled"] = false
dictSIPResponse["sipMessage"] = "User consent required for app with id \(sipIdentifier)"
break;
case -1743: //errAEEventNotPermitted
dictSIPResponse["isSIPEnabled"] = false
dictSIPResponse["sipMessage"] = "User didn't allow usage for app with id \(sipIdentifier)"
// Here you should present a dialog with a tutorial on how to activate it manually
// This can be something like
// Go to system preferences > security > privacy
// Choose automation and active [APPNAME] for [APPNAME]
default:
break;
}
return dictSIPResponse
}

Resources