Open mail app with a specific mail in the inbox with Swift - swift

I have the UID of a E-Mail message in the inbox.
Now I want to open Apple Mail (or alternatively Outlook) to present that mail to the user.
All examples I have found are how to start the standard Mail program to compose a new E-Mail.
Any Idea, hoe to solve that.
I tried with a Applescript but it does not work.
import SwiftUI
struct ContentView: View {
var body: some View {
Button(action: {
present_Email()
}) {
Text("Show E-Mail")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
func present_Email()
{
let myAppleScript = """
tell application "Mail"
set myMessages to messages 1 through 1 of inbox
repeat with aMesseage in myMessages
open (contents of aMesseage)
end repeat
end tell
"""
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: myAppleScript) {
scriptObject.executeAndReturnError( &error)
}
}

Found a quite easy Solution:
To open the Mail App with a given Message-ID you can simply open it via the message: URL Scheme.
No Need for AppleScript ;-).
Button(action: {
NSWorkspace.shared.open(URL(string: "message:%3C...YOUR-MESSAGE_ID....%3E")!)
}) {
Text("Open E-Mail in Mail App")
}

Related

SwiftUI - only one alert showing in nested if

So I am creating a register view and I have two alerts to show. One is showing if the email or the password don't meet the requirements and the other one is used if the email is already saved in the system. The issue is that the single alert showing up is the one that checks if the email and password meet that set of requirements.
Button ("Sign Up") {
toggle.toggle()
if (isValidEmailAddr(strToValidate: userCredentials.email) && isValidPassword(strToVailidate: userCredentials.password)) {
if !emailDoesExist(emailToCheck: userCredentials.email) {
print("Email and Passowrd are valid")
saveJSON(email: userCredentials.email, password: userCredentials.password, nickname: userCredentials.nickname)
} else {
print("email address already in the system")
self.showAlert1.toggle()
}
}
else {
self.showAlert0.toggle()
}
}
.alert(isPresented: $showAlert1, content: { self.alert1 })
.alert(isPresented: $showAlert0, content: { self.alert0 })
.font(.title)
.buttonStyle(.bordered)
.frame(width:200, height: 100)
The problem is that you cannot attach more than one alert to the same view and now you have two for your button.
There are several ways to approach this to get only one alert and here I use a simple one where I use a second property that holds an error message that can be used in the alert
#State private var showAlert = false
#State private var errorMessage = ""
if (isValidEmailAddr(strToValidate: userCredentials.email) && isValidPassword(strToVailidate: userCredentials.password)) {
if !emailDoesExist(emailToCheck: userCredentials.email) {
saveJSON(email: userCredentials.email, password: userCredentials.password, nickname: userCredentials.nickname)
} else {
errorMessage = "Mail already exists"
self.showAlert.toggle()
}
} else {
errorMessage = "Mail or password is incorrect"
self.showAlert.toggle()
}
Then you would need to adjust the actual .alert modifier but since I don't know what self.alert0/1 are you need to do that yourself (most likely by keeping only one of them and make use of errorMessage).

SwiftUI 4.0 how to know if ShareLink has been shared successfully

I am now working with the new SwiftUI 4.0 and using the new ShareLink feature . How can I know if a link or text has been shared successfully, any suggestions would be great ? I have a simple ShareLink like this
ShareLink(item: InviteMessage) {
Text("Test Share")
.foregroundColor(.black)
}
before in SwiftUI 3.0 I would use this code
.background(SharingViewController(isPresenting: $InviteOthers) {
let message = "Share Me"
let av = UIActivityViewController(activityItems: [message], applicationActivities: nil)
av.completionWithItemsHandler = { (activity, success, items, error) in
if success {
// shared successfully update count
}
}
return av
})
It doesn't seem possible to know if the content has been shared successfully, but you can at least detect if the link has been clicked with the simultaneousGesture modifier (onTapGesture doesn't work on ShareLink):
ShareLink(){}
.simultaneousGesture(TapGesture().onEnded() {
print("clicked")
})
Im not sure its posible, but you definitely can add tokens to the url, and track when people open it. But as far as if it actually sent, i can’t help.

Why is BetterSafariView Authentication not returning to Main Screen?

I am trying to develop a steam authentication through the package BetterSafariView in Swift. Everything works apart from the WebAuthenticationSession which does not initiate a callback once the authentication has been done. I have tried to create a callbackURLScheme which is callbackios, but the callback still does not start until I press cancel from the BetterSafariView view.
I run my separate server at https://callbackios.willyeah.repl.co/steam/auth in order to receive the OpenID token from Steam. Once I have done that, I return the token to the web browser of the BetterSafariView, which then displays it.
My URL identifier is co.repl.willyeah.callbackios and my URL Scheme is defined as callbackios in the info.plist file.
I am not sure which piece of information I am missing. How can I make the callback of WebAuthenticationSession run once the token has been received? I want to return to the main screen once I am authenticated and have received the OpenID token.
import SwiftUI
import AuthenticationServices
import BetterSafariView
struct ContentView: View {
#State private var startingWebAuthenticationSession = false
private var authUrl = "https://steamcommunity.com/openid/login?openid.ns=http://specs.openid.net/auth/2.0&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.return_to=https://callbackios.willyeah.repl.co/steam/auth&openid.realm=https://callbackios.willyeah.repl.co&openid.mode=checkid_setup"
var body: some View {
Button(action: {
self.startingWebAuthenticationSession = true
}) {
Text("Start WebAuthenticationSession")
}
.webAuthenticationSession(isPresented: $startingWebAuthenticationSession) {
WebAuthenticationSession(
url: URL(string: authUrl)!,
callbackURLScheme: "callbackios"
) { callbackURL, error in
print("HELLO")
print(callbackURL, error)
}
.prefersEphemeralWebBrowserSession(false)
}
}
}

How can I display my user custom data in String format?

I'm new to Swift UI and MongoDB Realms. I'm trying to display a user's email (from their respective custom user data) in a text box but I'm getting a weird result that I don't know how to fix. I've been semi-blindly trying all sorts of things hoping something would work but no luck so far. I'm using Email/Password authorization so I know that the email/password wouldn't be stored in the user custom data (I think) but its just an example for what I'm trying to do.
My current code so far below.
struct HomeView: View {
let user = app.currentUser!
#State var isLoggingOut = false
var body: some View {
let userEmail = user.customData["email"] ?? "no email"
let userPassword = user.customData["password"] ?? "no password"
let userDisplay = user.customData["display"] ?? "no display"
let userName = user.customData["fullName"] ?? "no name"
ZStack {
Rectangle().foregroundColor(.yellow)
VStack {
Spacer()
Text("Home")
HStack {
Text("Email").frame(width: 100)
Spacer()
Text(String(reflecting: userEmail))
}.padding(.vertical)
HStack {
Text("Password").frame(width: 100)
Spacer()
Text(String(describing: userPassword))
}.padding(.vertical)
HStack {
Text("Display").frame(width: 100)
Spacer()
Text(String(describing: userDisplay))
}.padding(.vertical)
HStack {
Text("Full name").frame(width: 100)
Spacer()
Text(String(describing: userName))
}.padding(.vertical)
Spacer()
Button("Log Out") {tryLogOut()}
}.padding(40)
if isLoggingOut {LoginView()}
}
}
func tryLogOut() {
app.currentUser?.logOut {error in}
self.isLoggingOut = true
}
}
After logging in with a test user, this is what I'm getting in the right HStack text boxes (for example, the top email text box):
Email Optional(RealmSwift.AnyBSON.string("test123#gmail.com"))
Obviously what I'm trying to end up with is:
Email test123#gmail.com
What am I doing wrong? Everything else works as intended but this problem is giving me a headache. Any help would be appreciated.
Also FYI - Everything I am trying to display in the text boxes is stored in the database as Strings according to Atlas so I don't see the problem. However in my NewUserRegistrationView, when I create the new user document, I use the following code, I'm not sure if there is anything conflicting with the AnyBSON types before inserting the document.
struct NewUserRegistrationView: View {
// Email, password, displayName, and fullName obtained from TextFields in the body ...
// createUserDocument() is called after registering and confirming the user
func createUserDocument() {
let credentials = Credentials.emailPassword(
email: self.email,
password: self.password)
app.login(credentials: credentials) {result in
switch result {
case .failure:
self.statustext = "Document creation failed, try again"
case .success(let user):
let client = user.mongoClient("mongodb-atlas")
let database = client.database(named: "AppDatabase")
let collection = database.collection(withName: "userDocuments")
collection.insertOne([
"userID": AnyBSON(user.id),
"email": AnyBSON(self.email),
"password": AnyBSON(self.password),
"display": AnyBSON(self.display),
"fullName": AnyBSON(self.fullName)
]) { result in
switch result {
case .failure:
self.statustext = "Could not add document"
case .success(let newObjectId):
self.statustext = "Inserted document with objID: \(newObjectId)"
self.isDone = true
}
}
}
}
}
First of all conditionally downcast all dictionary values to AnyBSON to get the String value
let userEmail = (user.customData["email"] as? AnyBSON)?.stringValue ?? "no email"
let userPassword = (user.customData["password"] as? AnyBSON)?.stringValue ?? "no password"
let userDisplay = (user.customData["display"] as? AnyBSON)?.stringValue ?? "no display"
let userName = (user.customData["fullName"] as? AnyBSON)?.stringValue ?? "no name"
Without the cast you get Any which prints the weird output using String(reflecting
Then simply write
Text(userEmail)
...
Text(userPassword)
...
Text(userDisplay)
...
Text(userName)
It is because you are using String(...) change it to 'userEmail.description ?? ""`
The best way to display user data is to have a function in your class/struct that converts user data in whatever form to a string and then displays it. This would allow you to just use one function to convert data
I managed to figure it out but I have absolutely no idea why it works or what is happening. Lucky enough, I got it by brute force trial and error. Other answers didn't work unfortunately, but thanks to those for the suggestions.
I changed:
let userEmail = user.customData["email"] ?? "no email"
let userPassword = user.customData["password"] ?? "no password"
let userDisplay = user.customData["display"] ?? "no display"
let userName = user.customData["fullName"] ?? "no name"
To this:
let userEmail = ((user.customData["email"] ?? "email")?.stringValue)!
let userPassword = ((user.customData["password"] ?? "password")?.stringValue)!
let userDisplay = ((user.customData["display"] ?? "display")?.stringValue)!
let userName = ((user.customData["fullName"] ?? "fullName")?.stringValue)!
Text(userEmail)
Text(userPassword)
Text(userDisplay)
Text(userName)
Can anyone breakdown how this works? Like what do the question/exclamation marks do? And what is a simpler way to do this (if any)?
This solves the problem of decoding any AnyBSON. Not, user.customData...which in fact always returns nil!
I'm upset that it took so long to basically guess at the solution. And even this solution needs a refactor as I'm force unwrapping which means its a ticking bomb.
So I used the Realm call gist:
collection.findOneDocument(filter: ["userId": AnyBSON(user.id)])
Using the result:
case .success(let document):
let email = document?["email"] ?? "no email"
print("Email: \(String(describing: email!.stringValue!))")
So this returns the email string by itself. The fact I had to double-unwrap is insane.
Naturally, you want to guard and or if let as necessary. The document in this case is a Realm Document which is: Dictionary <String : AnyBSON?>

skpsmtpmessage sending email function is not working correctly?

So I downloaded the skpsmtpmessage pod and installed it to my project. I have a file called MailSender in the project that looks like:
import Foundation
import skpsmtpmessage
class MailSender: NSObject, SKPSMTPMessageDelegate {
static let shared = MailSender()
func sendEmail() {
let message = SKPSMTPMessage()
message.relayHost = "smtp.gmail.com"
message.login = "myemail#gmail.com"
message.pass = "password"
message.requiresAuth = true
message.wantsSecure = true
message.relayPorts = [587]
message.fromEmail = "myemail#gmail.com"
message.toEmail = "recipientemail#gmail.com"
message.subject = "subject"
let messagePart = [kSKPSMTPPartContentTypeKey: "text/plain; charset=UTF-8", kSKPSMTPPartMessageKey: "body of email"]
message.parts = [messagePart]
message.delegate = self
message.send()
}
func messageSent(_ message: SKPSMTPMessage!) {
print("Successfully sent email!")
}
func messageFailed(_ message: SKPSMTPMessage!, error: Error!) {
print("Sending email failed!")
}
}
In ContentView, I have a button that looks like:
Button("click me") {
MailSender.shared.sendEmail()
}
When I run the simulator and click the button in ContentView, I get a bunch of "*** stopping watchdog ***" messages in the output and then some additional messages that say "S: 250-smtp.gmail.com at your service, [My IP Address]", and then there's a message that says "S: 535-5.7.8 Username and Password not accepted. Learn more at", but I know that the from email and password that I'm providing are correct (it's my personal email information in my version of the project). Any help would be greatly appreciated.
By default support for external clients is off on Gmail, so login in your account and make sure you turned on some, IMAP is preferred.
Then make sure you configure properly email client