TLDR When I hard code phone numbers into a URL it opens in watch messages correctly, but when I use a variable string with the numbers typed in exactly the same way inside of it, it doesn't.
Example:
NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=Test")
Above code works but below code doesn't:
let hardCode = "8888888888,9999999999,3333333333"
NSURL(string: "sms:/open?addresses=\(hardCode)&body=Test")
FULL DETAILS:
I am making a URL from variables to open messages on the Apple Watch with pre-filled contents. I am getting the phone numbers from the contact book and storing them in an array. They are provided in this format:
(###) ###-#### but need to be ##########
I tested the code by hard-coding phone numbers into the URL and it works properly with all contacts and completed body:
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
But when I build the phone number values programmatically it does not work:
//holds phone numbers without special chars
var tempArray: [String] = []
//if I can access the unformatted numbers
if let recips = saveData["recips"] as? [String] {
//for each number provided
recips.forEach { (person: String) in
//remove all non-numerical digits
//person is now (###) ###-####
let newPerson = person.digitsOnly()
//newPerson is ##########
print(person)
print("->\(newPerson)")
//add formatted number to tempArray
tempArray.append(newPerson)
}
}
//combine all numbers with "," between as a string
let recipString = tempArray.joinWithSeparator(",")
//recipString contains ##########,##########,##########...
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
I then add the "recipString" variable to the NSURL in the below code:
let messageBody = "test"
let urlSafeBody = messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(recipString)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
The FINAL URL print shows the correct string, but the messages app does not open properly, and shows quick reply menu instead of composed message window. It matches the functioning hard coded number version exactly, but behaves differently.
Totally lost, hope someone can help!
UPDATE 1
Here are the debug prints for both versions of the URL:
Manually declared (not created from recipString but actually declared in the URL string explicitly):
This version works
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
Variable created (using recipString):
This version doesn't
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
I have also tried applying url encoding to the "recipString" variable by using the below if let:
if let urlSafeRecip = recipString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(urlSafeRecip)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
}
UPDATE 2
I tested to see if the hardcode version of numbers matches the recipString exactly via this code:
let hardCode = "0000000000,1111111111,2222222222,3333333333,4444444444"
let isEqual = (hardCode == recipString)
if isEqual {
print("hardCode matches recipString")
}
else {
print("hardCode does not match recipString")
}
Debug prints:
hardCode matches recipString
UPDATE 3
I have confirmed that:
When a URL is made with hard coded numbers vs. numbers that I make from variables, checking == between them returns true.
In every test I can do between the two version of the url, it matches.
NOTES AFTER CORRECT ANSWER FOUND:
This type of URL formatting will ONLY work with multiple addresses in the URL. If you do not have multiple addresses you will need to do the following, which is undocumented but none-the-less works. I found this by bashing my face on the keyboard for hours, so if it helps you an upvote is deserved :)
follow the answer marked below, and then use this type of logic check before making the URL in the doItButton() function he mentioned:
func setupAndSendMsg(saveData: NSDictionary) {
if let urlSafeBody = createBody(saveData) {
let theNumbers = createNumbers(saveData).componentsSeparatedByString(",")
print(theNumbers.count-1)
if theNumbers.count-1 > 0 {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers(saveData))&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
} else {
if let url = NSURL(string: "sms:/open?address=\(createNumbers(saveData)),&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
}
My guess is that it is not the acctual openSystemUrl call that is the problem. I believe there must be something with the code that is building the number string programmatically.
The code bellow is a simplified version of all the code you have posted. I have confirmed that it is working on my Apple Watch. It opens the Messages app with pre-populated numbers & body text.
Take one more look at your code and see if there is something your missing. If you can't find anything, just delete the code and re-write it, probably will be faster then spotting the weird issue.
Once again the code bellow is confirmed working as expected, so you should be able to get it to work. (or just copy & paste my code) :)
class InterfaceController: WKInterfaceController {
#IBAction func doItButton() {
if let urlSafeBody = createBody() {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers())&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
private func createBody() -> String? {
let messageBody = "hello test message"
return messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
}
private func createNumbers() -> String {
let numbers = ["(111) 222-3333", "(444) 555-6666"]
var tempArray: [String] = []
numbers.forEach { (number: String) in
tempArray.append(number.digitsOnly())
}
return tempArray.joinWithSeparator(",")
}
}
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
With above said I would recommend against using undocumented Apple features for anything you plan on putting on the App Store for the reasons already mentioned in comments.
Related
I have a string that looks like this:
https://product/000000/product-name-type-color
I used a split to separate this strings, but Im having problems because the link can come without the description or the id
guard let separateLink = deeplink?.split(separator: "/") else { return }
let linkWithoutProductDetails = "\(separateLink[0] ?? "")//\(separateLink[1] ?? "")/\(separateLink[2] ?? "")"
When the link comes only https://product/ Im getting Fatal error: Index out of range even using the Optionals and String Interpolation, how can i guarantee that independently of the quantity of informations in my link the code wont break
You should check the number of path components. However, ideally you should use the URL functions instead of manipulating the link as a String:
if var url = URL(string: "https://product/000000/product-name-type-color") {
let pathComponents = url.pathComponents
// "product" is not a path component, it's the host.
// Path components are "/", "000000" and "product-name-type-color"
if pathComponents.count > 2 {
url = url.deletingLastPathComponent()
}
print(url)
}
The URL I am making is valid and will return the desired JSON when testing on Chrome, but fails in my project.
func createWikipediaURL(place: String) -> URL? {
let _place = place.replacingOccurrences(of: " ", with: "%20")
let urlStr =
"https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|pageimages&exintro&explaintext&generator=search&gsrsearch=intitle:\(_place)&gsrlimit=1&redirects=1"
if let url = URL(string:urlStr) {
return url
} else {
return nil
}
}
With the parameter "Malibu Beach" the function will create the proper URL, https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|pageimages&exintro&explaintext&generator=search&gsrsearch=intitle:Malibu%20Beach&gsrlimit=1&redirects=1, but will also result in no URL being returned because this string cannot be casted into a URL. Any suggestions on how to make the string into a URL?
The issue is with the | character in urlStr. I would suggest using Strings addingPercentEncoding method to make the string url safe. Doing so will mean that you won't need to manually replace spaces for place too.
func createWikipediaURL(place: String) -> URL? {
let urlStr = "https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|pageimages&exintro&explaintext&generator=search&gsrsearch=intitle:\(place)&gsrlimit=1&redirects=1"
// Replaces special characters with their percent encoded counter-parts.
guard let escapedUrlStr = urlStr.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil }
// There's no need for an if statement; URL can be returned as-is.
return URL(string:escapedUrlStr)
}
I am attempting to glyphs from the SF Symbol front using the code sample below provided by an extract https://github.com/davedelong/sfsymbols. I have isolated the code that I can't get to work with values extracted from a run time.
I have condensed the code down to only the statements required to produce the problem.
This issue seems to be the name I am providing to the final statement in CTFontGetGlyphWithName, Every combination of values I have attempted returns 0.
Please note that you have to have the SF Symbols application installed for it to acquire the initial bundle.
Please excuse the code quality, this is just to create the shortest possible example of code to reproduce the problem.
thanks.
I have followed quite a few snipped and tutorials but there doesn't seem to be a great deal of practical examples of these more advanced functions available.
public class StackOverflow {
public func Test() {
let maybeCFURLs = LSCopyApplicationURLsForBundleIdentifier("com.apple.SFSymbols" as CFString, nil)?.takeRetainedValue()
let cfURLs = maybeCFURLs as? Array<URL>
let fontFile = cfURLs!.compactMap { url -> URL? in
guard let appBundle = Bundle(url: url) else { return nil }
return appBundle.url(forResource: "SFSymbolsFallback", withExtension: "ttf")
}
let attributes = [
kCTFontTraitsAttribute: [
kCTFontWeightTrait: NSFont.Weight.regular.rawValue
]
]
let provider = CGDataProvider(url: fontFile.first! as CFURL)
let cgFont = CGFont(provider!)
let ctAttributes = CTFontDescriptorCreateWithAttributes(attributes as CFDictionary)
let font = CTFontCreateWithGraphicsFont(cgFont!, 32, nil, ctAttributes)
// let name = "uni.plus.square.fill.medium" as CFString - Does not work
//var name = "square.fill.medium" as CFString - Does not work
let glyphName = "plus.square.fill.medium" as CFString
var glyph = CTFontGetGlyphWithName(font, glyphName )
}
}
For one week I have been trying to get a string returned from dataTask().
I already read a lot here on StackOverFlow and also from serval sites where they tackle this topic. For example, this one. So I already understand that it's that the dataTask doesn't directly return values, cause it happens on different threads and so on. I also read about closures and completion handlers. I really got the feeling that I actually already got a little clue what this is about. But I can't get it to work.
So this is my code. I just post the whole code so no-one needs to worry that the problem sticks in a part which I don't show. Everything is working fine until I try to return a value and save it for example in a variable:
func requestOGD(code gtin: String, completion: #escaping (_ result: String) -> String) {
// MARK: Properties
var answerList: [String.SubSequence] = []
var answerDic: [String:String] = [:]
var product_name = String()
var producer = String()
// Set up the URL request
let ogdAPI = String("http://opengtindb.org/?ean=\(gtin)&cmd=query&queryid=400000000")
guard let url = URL(string: ogdAPI) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result, which is String. It willbecome split and placed in a dictionary
do {
let answer = (String(decoding: responseData, as: UTF8.self))
answerList = answer.split(separator: "\n")
for entry in answerList {
let entry1 = entry.split(separator: "=")
if entry1.count > 1 {
let foo = String(entry1[0])
let bar = String(entry1[1])
answerDic[foo] = "\(bar)"
}
}
if answerDic["error"] == "0" {
product_name = answerDic["detailname"]!
producer = answerDic["vendor"]!
completion(product_name)
} else {
print("Error-Code der Seite lautet: \(String(describing: answerDic["error"]))")
return
}
}
}
task.resume()
Here I call my function, and no worries, I also tried to directly return it to the var foo, also doesn't work The value only exists within the closure:
// Configure the cell...
var foo:String = ""
requestOGD(code: listOfCodes[indexPath.row]) { (result: String) in
print(result)
foo = result
return result
}
print("Foo:", foo)
cell.textLabel?.text = self.listOfCodes[indexPath.row] + ""
return cell
}
So my problem is, I have the feeling, that I'm not able to get a value out of a http-request.
You used a completion handler in your call to requestOGD:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
// result comes back here
}
But then you tried to capture and return that result:
foo = result
return result
So you're making the same mistake here that you tried to avoid making by having the completion handler in the first place. The call to that completion handler is itself asynchronous. So you face the same issue again. If you want to extract result at this point, you would need another completion handler.
To put it in simple terms, this is the order of operations:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
foo = result // 2
}
print("Foo:", foo) // 1
You are printing foo before the asynchronous code runs and has a chance to set foo in the first place.
In the larger context: You cannot use any asynchronously gathered material in cellForRowAt. The cell is returned before the information is gathered. That's what asynchronous means. You can't work around that by piling on further levels of asynchronicity. You have to change your entire strategy.
I have a desktop app that receives e-mail URLs ("message://" scheme) from the drag&drop pasteboard and I want to get the Subject from the relevant message. The only clue I have, so far, is that the QuickLook library might give me an information object where I can retrieve this info from.
Since the QuickLook API seems to be rather in flux at the moment and most examples show how to use it in iOS, I simply cannot find a way to set up my "Preview" object using a URL and get the information from there.
I would like to avoid setting up my project as a QuickLook plugin, or setting up the whole preview pane / view scaffolding; at the moment I just want to get out what QuickLook loads before it starts displaying, but I can't comprehend what paradigm Apple wants me to implement here.
XCode 7.3.1.
It turns out I misinterpreted the contents of draggingInfo.draggingPasteboard().types as a hierarchical list containing only one type of info (URL in this case).
Had to subscribe to dragged event type kUTTypeMessage as String and retrieve the e-mail subject from the pasteboard with stringForType("public.url-name")
EDIT: Note that the current Mail.app will sometimes create a stack of mails when you drag an e-mail thread. Although the method above still works to get the subject of the stack, there is no URL in the dragging info then and since there's no list of Message-IDs available either, I had to resort to scraping the user's mbox directory:
// See if we can resolve e-mail message meta data
if let mboxPath = pboard.stringForType("com.apple.mail.PasteboardTypeMessageTransfer") {
if let automatorPlist = pboard.propertyListForType("com.apple.mail.PasteboardTypeAutomator") {
// Get the latest e-mail in the thread
if let maxID = (automatorPlist.allObjects.flatMap({ $0["id"]! }) as AnyObject).valueForKeyPath("#max.self") as? Int {
// Read its meta data in the background
let emailItem = draggingEmailItem
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// Find the e-mail file
if let path = Util.findEmlById(searchPath: mboxPath, id: maxID) {
// Read its contents
emailItem.properties = Util.metaDataFromEml(path)
dispatch_async(dispatch_get_main_queue(), {
// Update UI
});
}
}
}
}
}
Util funcs:
/* Searches the given path for <id>.eml[x] and returns its URL if found
*/
static func findEmlById(searchPath searchPath: String, id: Int)-> NSURL? {
let enumerator = NSFileManager.defaultManager().enumeratorAtPath(searchPath)
while let element = enumerator?.nextObject() as? NSString {
switch (element.lastPathComponent, element.pathExtension) {
case (let lpc, "emlx") where lpc.hasPrefix("\(id)"):
return NSURL(fileURLWithPath: searchPath).URLByAppendingPathComponent(element as String)!
case (let lpc, "eml") where lpc.hasPrefix("\(id)"):
return NSURL(fileURLWithPath: searchPath).URLByAppendingPathComponent(element as String)!
default: ()
}
}
return nil
}
/* Reads an eml[x] file and parses it, looking for e-mail meta data
*/
static func metaDataFromEml(path: NSURL)-> Dictionary<String, AnyObject> {
// TODO Support more fields
var properties: Dictionary<String, AnyObject> = [:]
do {
let emlxContent = try String(contentsOfURL: path, encoding: NSUTF8StringEncoding)
// Parse message ID from "...\nMessage-ID: <...>"
let messageIdStrMatches = emlxContent.regexMatches("[\\n\\r].*Message-ID:\\s*<([^\n\r]*)>")
if !messageIdStrMatches.isEmpty {
properties["messageId"] = messageIdStrMatches[0] as String
}
}
catch {
print("ERROR: Failed to open emlx file")
}
return properties
}
Note: If your app is sandboxed you will need the com.apple.security.temporary-exception.files.home-relative-path.read-only entitlement set to an array with one string in it: /Library/