Swift 4 - UIActivityViewController for Outlook - ios

I am trying to use UIActivityViewController for outlook only...I was able to get my UIActivityViewController working like so:
//Define HTML String
var htmlString = ""
//Add the headings to HTML String table
htmlString += "<table border = '1' cellspacing='0' align = 'center'><tr><th>Job #</th><th>Task</th><th>Date</th></tr>"
//For each item in punch list data array
for i in 0..<punchListData.count {
//If the item is selected
if punchListData[i].cellSelected {
//Add data to the HTML String
htmlString += "<tr><td>" + jobList[i % jobList.count] + "</td><td align = 'center'>" + taskData[i / jobList.count] + "</td><td>" + (punchListData[i].stringData)! + "</td></tr>"
}
}
//Close the HTML table in the HTML String
htmlString += "</table><h5>Please contact me if you have any questions <br /> Thank you.</h5>"
let activityViewController = UIActivityViewController(activityItems : [htmlString], applicationActivities: nil)
activityViewController.setValue("Schedule for Community", forKey: "Subject")
activityViewController.popoverPresentationController?.barButtonItem = self.shareButton
self.present(activityViewController, animated: true, completion: nil)
But I have a few issues:
The subject line is not working, I did some research and apparently setValue will not work for Outlook and that the first line can be the subject line, but I have no idea how to do that.
Is there away to exclude all activities except for Outlook?
Previously I was just using MFMailComposeViewController to compose an email, is there away of doing this for Outlook? Without UIActivityViewController?
Thanks,

You can build your own UI and present it as you like, then convert arguments to a deeplink URL and pass it to the outlook app:
// MARK: - Errors
enum MailComposeError: Error {
case emptySubject
case emptyBody
case unexpectedError
}
// MARK: - Helper function
func outlookDeepLink(subject: String, body: String, recipients: [String]) throws -> URL {
guard !subject.isEmpty else { throw MailComposeError.emptySubject }
guard !body.isEmpty else { throw MailComposeError.emptyBody }
let emailTo = recipients.joined(separator: ";")
var components = URLComponents()
components.scheme = "ms-outlook"
components.host = "compose"
components.queryItems = [
URLQueryItem(name: "to", value: emailTo),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
guard let deepURL = components.url else { throw MailComposeError.unexpectedError }
return deepURL
}
Usage
try! UIApplication.shared.open(
outlookDeepLink(
subject: "subject",
body: "body",
recipients: ["example#email.com", "example2#email.com"]
)
)
At last, Note that:
Don't forget to tell iOS you are going to call ms-outlook. (Add it to LSApplicationQueriesSchemes in info.plist, Otherwise, You will get an clear error message in console if you forget it)
Also don't forget to check if the app actually exists before trying to open the url. (canOpenURL is here to help)

Related

Multiple JSON parsing from different URLs in Swift

I want to parse JSON strings from different URLs in my iOS Tab Bar app:
Parsing.swift
FirstViewController.swift (INITIAL Tab Bar View Controller)
SecondViewController.swift
...
In Parsing.swift I have various struct (TopLevel) and enum schemes I have controlled in Playground: they works perfectly. In every ViewController I have a Table View that I want to popolate with results of different JSON parsing. This is my simplified code:
FirstViewController.swift viewDidLoad()
let url = // my first URL to parse
let urlObj = URL(string: url)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlObj!) { (data, response, error) in
do {
let results = try JSONDecoder().decode(TopLevel.self, from: data!)
... for ...
self.table.reloadData()
}
catch {
...
}
}
task.resume()
This code works perfectly: When app first open, Table View in FirstViewController popolates with results of JSON Parsing from url. But now its time to click on second Bar Item to open SecondViewController. The code is obviously:
SecondViewController.swift viewDidLoad()
let url2 = // my second URL to parse
let urlObj2 = URL(string: url2)
let config2 = URLSessionConfiguration.default
let session2 = URLSession(configuration: config2)
let task2 = session.dataTask(with: urlObj2!) { (data2, response2, error2) in
do {
let results2 = try JSONDecoder().decode(TopLevel.self, from: data2!)
... for ...
self.table2.reloadData()
}
catch {
...
}
}
task2.resume()
Well, when I tap on second Tab Bar Item to open the SecondViewController, Table View don't popolate and XCode gives an error: dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}))) But JSON text is valid.
I have tried a lot of solutions: I've changed tasks to URLSession.shared, I have used private struct and enum, I have controlled variables and costants, well, no way to parse second URL correctly. Even if I create a NEW Single View App and I copy the SecondViewController.swift code into the viewDidLoad() func, it works perfectly, so, again, its not a problem of the second URL, the JSON strings are valid. I think there is an interference between the two parsing tasks, it looks like the first corrupted the second one. What can I do? Thanks.
EDIT: this is my JSON (all fields are valid strings, I have deleted it for simplify)
{
"attributes": {
"version": "2.0",
"nodeValue": "\n"
},
"channel": {
"title": " ",
"link": " ",
"description": " ",
"lastBuildDate": " ",
"language": " ",
"copyright": " ",
"item": [
{
"title": " ",
"link": " ",
"guid": {
"attributes": {
"isPermaLink": "false",
"nodeValue": " "
}
},
"pubDate": " ",
"category": " "
},
{
"title": " ",
"link": " ",
"guid": {
"attributes": {
"isPermaLink": "false",
"nodeValue": " "
}
},
"pubDate": " ",
"category": " "
}
]
} }
As I don't have access to JSON response and Model used.
I can assume few possibilities that may cause this issue.
1) You have your model and JSON response. When you are trying to decode, there could any field in JSON response that is null and same property in your model is not made optional.
2) The model may not have same structure (properties) as JSON response.
Well, I resolved the issue changing my second URL from "WWW.myserver.net/string2.json" to "myserver.net/string2.json", simply without WWW. In this way both tasks works and parses respective strings from different URLs.

Is there a way for Gmail to recognize line breaks from text from UIapplication.shared.open url

Gmail client does not recognize line breaks within text from UIApplication.shared.openURL(url)
I have a function that returns a tuple of available email clients (validated by UIApplication.shared.canOpen) and the associated URL. It's for an error reporting feature, so the arguments array contains the text that will autopopulate email fields.
There are no issues with launching any of the three email clients, but gmail is the only one that doesn't process the line breaks. Does gmail use a different method?
enum EmailClient {
case gmail
case outlook
case mail
var title: String {
switch self {
case .gmail: return "Gmail"
case .outlook: return "Outlook"
case .mail: return "Mail"
}
}
//Creates url used by UIapplciation.shared to launch the client and autopopulate the email
func url(error: CustomError?) -> URL? {
guard let username = CredentialManager.username,
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
return nil
}
let arguments = [
username,
UIDevice().modelName,
UIDevice.current.systemVersion,
appVersion,
error?.description ?? "N/A" //When called from the settings page, no error is passed in
]
var urlFormat: String
switch self {
case .gmail: urlFormat = "googlegmail:///co?to=%#&subject=%#&body=%#"
case .outlook: urlFormat = "ms-outlook://compose?to=%#&subject=%#&body=%#"
case .mail: urlFormat = "mailto:%#?subject=%#&body=%#"
}
return URL(string: String(format: urlFormat, arguments: [
EMAIL_RECIPIENT,
EMAIL_SUBJECT.replacingOccurrences(of: " ", with: "%20"),
String(format: EMAIL_BODY_FORMAT, arguments: arguments).replacingOccurrences(of: " ", with: "%20").replacingOccurrences(of: "\n", with: "%0A")
]))
}
}
It seems that using "\r\n" instead of "\n" fixes the problem
1) Add the the scheme to your info.plist
We can do this through the beautiful thing that is the Info.plist file. Add a new key called LSApplicationQueriesSchemes as an array. Then you can enter your apps within the array. The Mail app doesn’t need to go in here, presumably because it is an Apple app. Your entry should look like the below
func openGmail(withFrom: String?, withSubject: String?) {
var gmailUrlString = "googlegmail:///"
if let from = withFrom {
gmailUrlString += "co?to=\(from)"
}
if let subject = withSubject {
gmailUrlString += "&subject=\(subject)"
}
}
One last thing we will need to do is URL encode the subject line before we pass it into the URL. We can do this by calling subjectString?.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed) on the string.

How to create folder and files in google drive using swift

I am using google drive SDK for folder creation, but unable to create. I am able to login and get all files and folder but unable to create it.
I am using swift and used this code
let metaData = GTLRDrive_File()
metaData.name = "xyz"
metaData.mimeType = "application/vnd.google-apps.folder"
let querys = GTLRDriveQuery_FilesCreate.query(withObject: metaData, uploadParameters: nil)
querys.fields = "id"
//service.executeQuery(querys, delegate: self, didFinish: nil)
self.service.executeQuery(querys) { (ticket:GTLRServiceTicket, object:Any?, error:Error?) in
// Put your completion code here
}
But unable to create folder. Can anyone help me out. Thanks in advance.
func chilkatTest() {
var success: Bool = true
// It requires the Chilkat API to have been previously unlocked.
// See Global Unlock Sample for sample code.
// This example uses a previously obtained access token having permission for the
// Google Drive scope.
let gAuth = CkoAuthGoogle()
gAuth.AccessToken = "GOOGLE-DRIVE-ACCESS-TOKEN"
let rest = CkoRest()
// Connect using TLS.
var bAutoReconnect: Bool = true
success = rest.Connect("www.googleapis.com", port: 443, tls: true, autoReconnect: bAutoReconnect)
// Provide the authentication credentials (i.e. the access token)
rest.SetAuthGoogle(gAuth)
// -------------------------------------------------------------------------
// A multipart upload to Google Drive needs a multipart/related Content-Type
rest.AddHeader("Content-Type", value: "multipart/related")
// Specify each part of the request.
// The 1st part is JSON with information about the file.
rest.PartSelector = "1"
rest.AddHeader("Content-Type", value: "application/json; charset=UTF-8")
let json = CkoJsonObject()
json.AppendString("name", value: "testHello.txt")
json.AppendString("description", value: "A simple file that says Hello World.")
json.AppendString("mimeType", value: "text/plain")
// To place the file in a folder, we must add a parents[] array to the JSON
// and list the folder id's. It's possible for a file to be in multiple folders at once
// if it has more than one parent. If no parents are specified, then the file is created
// in the My Drive folder.
// Note: We'll assume we already have the id if the folder. It is the id's that are specified here,
// not the folder names.
var parents: CkoJsonArray? = json.AppendArray("parents")
var folderId: String? = "0B53Q6OSTWYolY2tPU1BnYW02T2c"
parents!.AddStringAt(-1, value: folderId)
parents = nil
rest.SetMultipartBodyString(json.Emit())
// The 2nd part is the file content, which will contain "Hello World!"
rest.PartSelector = "2"
rest.AddHeader("Content-Type", value: "text/plain")
var fileContents: String? = "Hello World!"
rest.SetMultipartBodyString(fileContents)
var jsonResponse: String? = rest.FullRequestMultipart("POST", uriPath: "/upload/drive/v3/files?uploadType=multipart")
if rest.LastMethodSuccess != true {
print("\(rest.LastErrorText)")
return
}
// A successful response will have a status code equal to 200.
if rest.ResponseStatusCode.integerValue != 200 {
print("response status code = \(rest.ResponseStatusCode.integerValue)")
print("response status text = \(rest.ResponseStatusText)")
print("response header: \(rest.ResponseHeader)")
print("response JSON: \(jsonResponse!)")
return
}
// Show the JSON response.
json.Load(jsonResponse)
// Show the full JSON response.
json.EmitCompact = false
print("\(json.Emit())")
// A successful response looks like this:
// {
// "kind": "drive#file",
// "id": "0B53Q6OSTWYoldmJ0Z3ZqT2x5MFk",
// "name": "Untitled",
// "mimeType": "text/plain"
// }
// Get the fileId:
print("fileId: \(json.StringOf("id"))")
}
Link for libraries needed:-
Download libraries
Include CkoAuthGoogle, CkoRest and CkoJsonObject header files in your project.
It is basically due to the scope, I have to give kGTLRAuthScopeDriveFile in scope area
private let scopes = [kGTLRAuthScopeDriveReadonly,kGTLRAuthScopeDriveFile]
and rest same in google
func folder(){
let metadata = GTLRDrive_File()
metadata.name = "eBilling"
metadata.mimeType = "application/vnd.google-apps.folder"
let querys = GTLRDriveQuery_FilesCreate.query(withObject: metadata, uploadParameters: nil)
querys.fields = "id"
self.service.executeQuery(querys, completionHandler: {(ticket:GTLRServiceTicket, object:Any?, error:Error?) in
if error == nil {
self.listFiles()
}
else {
print("An error occurred: \(error)")
}
})
}
Swift 5
If you are looking to just create a folder without uploading a file with it,
I was able to create a drive folder using Google's REST endpoint like this.
This function takes the auth token and a filename and parameters to create a URLRequest that can then be sent off within a URLSession.
func createFolderRequest(authToken: String, folderName: String) -> URLRequest {
let headers = [
"Content-Type": "multipart/related; boundary=123456789",
"Authorization": "Bearer " + authToken
]
let body =
"""
--123456789
Content-Type: application/json
{
"name": "\(folderName)",
"mimeType": "application/vnd.google-apps.folder"
}
--123456789--
"""
var request = URLRequest(url: URL(string: "https://www.googleapis.com/upload/drive/v3/files")!)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = body.data(using: .utf8)
request.addValue(String(body.lengthOfBytes(using: .utf8)), forHTTPHeaderField: "Content-Length")
return request
}
I referenced the google docs for multipart file uploads here

iOS URL Scheme Microsoft Outlook App

This seems impossible to find, unless perhaps there isn't one for it. But anyone know (if there is one) the iOS URL Scheme for opening the Microsoft Outlook Mobile App right to the Compose Screen with pre-defined TO_EMAIL, SUBJECT and BODY?
Here is a link I found that helped me out with the IOS Outlook URL Scheme.
From that I was able to come up with this code:
// Create an array of recipients for the email.
NSArray* emailRecipients = #[#"example#email.com", #"example2#email.com"];
// Create a mutable string to hold all of the recipient email addresses and add the first one.
NSMutableString* emailTo = [[NSMutableString alloc] initWithString:emailRecipients[0]];
// Loop through all of the email recipients except for the first one.
for (int index = 1; index < emailRecipients.count; index++)
{
// Add a semicolon and then the email address at the current index.
[emailTo appendFormat:#";%#", emailRecipients[index]];
}
// Get the email subject from the subject text field.
NSString* emailSubject = fieldSubject.text;
// Encode the string for URL.
NSString* encodedSubject = [emailSubject stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
// Get the email body from the body text field.
NSString* emailBody = fieldBody.text;
// Encode the string for URL.
NSString* encodedBody = [emailBody stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
// See if the subject or body are empty.
if (![emailSubject length] || ![emailBody length])
{
// Exit.
return;
}
// Create a string with the URL scheme and email properties.
NSString *stringURL = [NSString stringWithFormat:#"ms-outlook://compose?to=%#&subject=%#&body=%#", emailTo, encodedSubject, encodedBody];
// Convert the string to a URL.
NSURL *url = [NSURL URLWithString:stringURL];
// Open the app that responds to the URL scheme (should be Outlook).
[[UIApplication sharedApplication] openURL:url];
The URL scheme for Outlook is: ms-outlook://compose?to=example#email.com&subject=Subject&body=Message
Hope this helps!
Swift
func outlookDeepLink(subject: String, body: String, recipients: [String]) throws -> URL {
enum MailComposeError: Error {
case emptySubject
case emptyBody
case unexpectedError
}
guard !subject.isEmpty else { throw MailComposeError.emptySubject }
guard !body.isEmpty else { throw MailComposeError.emptyBody }
let emailTo = recipients.joined(separator: ";")
var components = URLComponents()
components.scheme = "ms-outlook"
components.host = "compose"
components.queryItems = [
URLQueryItem(name: "to", value: emailTo),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
guard let deepURL = components.url else { throw MailComposeError.unexpectedError }
return deepURL
}
Usage
try! UIApplication.shared.open(
outlookDeepLink(
subject: "subject",
body: "body",
recipients: ["example#email.com", "example2#email.com"]
)
)
Note that:
Don't forget to tell iOS you are going to call ms-outlook. (Add it to LSApplicationQueriesSchemes in info.plist, Otherwise, You will get an clear error message in console if you forget it)
Also don't forget to check if the app actually exists before trying to open the url. (canOpenURL is here to help)

Swift URL query string get parameters

I'm using the OAuthSwift library to authenticate users of my app, but something doesn't seem to be working as it throws an error. After some debugging it seems to be going wrong on this line: parameters = url.query!.parametersFromQueryString()
It does have the url query string (set to url.query) but it somehow fails parsing the parametersFromQueryString(). Does someone else have this problem (with this library) and how did you solve it?
The "parametersFromQueryString" function can be found here: https://github.com/dongri/OAuthSwift/blob/1babc0f465144411c0dd9721271f239685ce83a9/OAuthSwift/String%2BOAuthSwift.swift
Create an extension of URL class and paste this code:
import Foundation
extension URL {
public var parametersFromQueryString : [String: String]? {
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
let queryItems = components.queryItems else { return nil }
return queryItems.reduce(into: [String: String]()) { (result, item) in
result[item.name] = item.value
}
}}
Now you can get the value of this calculated attribute from URL object by simply using:
url.parametersFromQueryString
Have you checked that url.query returns anything? and that it is url decoded before/after calling url.query?
try looking at the answers here:
URL decode in objective-c
If the method is looking for characters such as '?' or '=' and the URL is still encoded, it might not find them
Here is the same code as it should look like in swift:
let URLString = "http://sound17.mp3pk.com/indian/barfi/%5BSongs.PK%5D%20Barfi%20-%2001%20-%20Barfi!.mp3"
let URL = NSURL(string:url)
let filename = URL.path.lastPathComponent.stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
//=> [Songs.PK] Barfi - 01 - Barfi!.mp3

Resources