iOS canOpenURL fails for phone dialing - ios

Try as I might I cannot get a simple app to dial a number.
I hooked a button action to this function:
#IBAction func wackAMole(sender: AnyObject) {
var phoneNumber = "tel://555-555-5555"
var url = NSURL(fileURLWithPath: phoneNumber)
var sharedApplication = UIApplication.sharedApplication()
if (sharedApplication.canOpenURL(url)) {
sharedApplication.openURL(url)
}
}
I have also added "telephony" to Info.plist as a "Required device capabilities" Still to no avail. When running under the debugger on an iPhone 5 canOpenURL() returns false.
Even if I change phoneNumber to "https://google.com", canOpenURL() will return false.
Any ideas where to look next?

You aren't creating a URL for a local file system entry, so you shouldn't use fileURLwithPath. You should use URLWithString -
var url = NSURL.URLWithString(phoneNumber)

Related

iOS swift how to know if any contact is updated even when app is killed

I have seen many SO question curious about this case but still I am posting this as many of developers out there may also want to know this another reason is that no solution is working for me .
I have used following code but it only works when My app is in background. but I am not notified when my app is killed and meanwhile user has updated the info of any contact. So in this case I am not sure how to do it.
What I am doing: here is a code snippet what I am trying to do
From iOS 9 you can register your class to observe CNContactStoreDidChangeNotification
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(addressBookDidChange),
name: NSNotification.Name.CNContactStoreDidChange,
object: nil)
And then:
#objc func addressBookDidChange(notification: NSNotification){
//Handle event here...
}
I found this solution over here:
Whats Happening: Through this way I am able to get my app notified once the user has updated his contact while app is in background.
What I want: I just want to know that if the user has updated any contact even though my app was killed then How to get my app notified with updated contacts?
Please let me know if you have solution of this issue in advance.
UPDATE: I have seen Whatsapp doing this. Is there anyone who can tell me how Whatsapp is doing this?
To check if a contact has changed you can use a custom hash function because the native one only checks for the identifier:
extension CNContact {
var customHash : Int {
var hasher = Hasher()
hasher.combine(identifier)
hasher.combine(contactType)
hasher.combine(namePrefix)
hasher.combine(givenName)
hasher.combine(middleName)
hasher.combine(familyName)
hasher.combine(previousFamilyName)
hasher.combine(nameSuffix)
hasher.combine(nickname)
hasher.combine(organizationName)
hasher.combine(departmentName)
hasher.combine(jobTitle)
hasher.combine(phoneticGivenName)
hasher.combine(phoneticMiddleName)
hasher.combine(phoneticFamilyName)
if #available(iOS 10.0, *) {
hasher.combine(phoneticOrganizationName)
}
hasher.combine(note)
hasher.combine(imageData)
hasher.combine(thumbnailImageData)
if #available(iOS 9.0, *) {
hasher.combine(imageDataAvailable)
}
hasher.combine(phoneNumbers)
hasher.combine(emailAddresses)
hasher.combine(postalAddresses)
hasher.combine(urlAddresses)
hasher.combine(contactRelations)
hasher.combine(socialProfiles)
hasher.combine(instantMessageAddresses)
hasher.combine(birthday)
hasher.combine(nonGregorianBirthday)
hasher.combine(dates)
return hasher.finalize()
}
}
(You can remove fields you don't care)
Then you have to keep a dictionary inside your app to store the hash values of all the contacts, to build it just do:
let hashedContacts = [String:Int]()
for contact in allContacts {
hashedContacts[contact.identifier] = contact.customHash
}
You have to store it on the file system.
Whenever a contact is updated, you update it:
hashedContacts[updatedContact.identifier] = updatedContact.customHash
Then at every launch, you load the saved dictionary, and you check for differences:
for contact in allContacts {
if contact.customHash != savedHashedValues[contact.identifier] {
// This contact has changed since last launch
...
}
}
And voilĂ !
EDIT:
How to save the hash map on disk...
var hashedContacts = ...
guard let name = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("hashedContacts")
else { return }
try? (hashedContacts as NSDictionary).write(to: name)
How to load the hash map from disk...
guard
let name = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("hashedContacts"),
let loadedContacts = (try? NSDictionary(contentsOf: name, error: ())) as? [String:Int]
else { return }
// Do whatever you want with loaded contacts...
Whenever you open your app you need to get all the contacts from the contact list and can compare to previous one which is saved inside of your app. After that you can push your contact list to server.
What you can do is send an update notification to your application on launch screen. This might have an illusion to your user that you have done the changes while in background.

Cannot play audio file without app crashing on device running ios 13

I have an app being used by people to receive orders with it needing to make a continuous sound until staff attend to it. It was working for two months then just started crashing a lot. For whatever reason, it runs fine on an iPad but not on iPhones running a recent operating system.
When this bit of code gets called it crashes:
guard let path = Bundle.main.path(forResource: "alert.mp3", ofType: nil) else { return }
let url = URL(fileURLWithPath: path)
do {
self.alertSoundEffect = try AVAudioPlayer(contentsOf: url)
} catch let err {
print("err: \(err)")
}
DispatchQueue.main.async {
self.alertSoundEffect.numberOfLoops = -1
self.alertSoundEffect.prepareToPlay()
self.alertSoundEffect.play()
}
The fix online to declare the alertSoundEffect variable like this:
private var alertSoundEffect : AVAudioPlayer!
has not worked at all.
I tried moving everything but the line:
self.alertSoundEffect.play()
to viewDidLoad as I thought maybe that code couldn't get called more than once, but it didn't help.
Specifically, the compiler highlights this line when it crashes:
self.alertSoundEffect = try AVAudioPlayer(contentsOf: url)
I tried using try AVAudioPlayer where it takes a Data object as a parameter or with including the type of audio file to be played, but that did not change anything.
When I try the AVAudioPlayer's delegate and declare it like this:
self.alertSoundEffect.delegate = self
right before the first lines of code I shared above Xcode highlights this line instead when it reliably crashes.
What else should I try?
I suppose your path is wrong.
Try this:
guard let path = Bundle.main.path(forResource: "alert", ofType: "mp3") else { return }
Also, if your audio file is short, like less than 30s, then try not to call self.alertSoundEffect.prepareToPlay(). Just call self.alertSoundEffect.play() right away.
Since iOS 13, this was causing a bug in my app, since I have notification sounds which are 3-10 seconds long.
If you initialise your AVAudioPlayer like var wrongMusicPlayer: AVAudioPlayer = AVAudioPlayer() OR wrongMusicPlayer = AVAudioPlayer() in any method then please remove it and just Declare like var wrongMusicPlayer: AVAudioPlayer!.
iOS 13.1 Crash in AVAudio Player

Programmatically get the URL Scheme from plist, from within a today extension

Ultimately I'm trying to launch the Main app from the Widget by doing
UIApplication.SharedApplication.OpenUrl(new NSUrl("MyURLScheme//"));
So I have an iOS application with a today extension. I would like to access my main application's URL Scheme. Is that possible? My URL Scheme is inside my Plist file of my main application and not my today extension. Can I just reference the one from the main app? I tried getting it from CFBundleURLTypes and CFBundleURLSchemes but they both turn up as "null".
Probably because it's looking in the widget's Plist and not the main app's Plist.
var asdf2 = NSBundle.MainBundle.InfoDictionary["CFBundleURLTypes"];
var asdf3 = NSBundle.MainBundle.InfoDictionary["CFBundleURLSchemes"];
Here is a screenshot of where the URL Scheme is located in the main app's Plist file. I'd prefer to not have to store duplicate information in the today extension's Plist file. And I'd also prefer to not have to hardcode the URL Scheme into the today extension's code.
would be something (ugly) like this :
var arr = (NSBundle.MainBundle.InfoDictionary["CFBundleURLTypes"] as NSMutableArray);
var urlTypes = arr.GetItem<NSDictionary>(0);
var schemes = urlTypes.ObjectForKey(new NSString("CFBundleURLSchemes")) as NSMutableArray;
var scheme = schemes.GetItem<NSString>(0).ToString();
You might need to create an App Group for your app and your extension which will allow you to place a plist or any other file for that matter that you want to share between the 2 into a shared location on disk.
Here's an article that explains how this is done:
https://learn.microsoft.com/en-us/xamarin/ios/app-fundamentals/file-system#configure-an-app-group
You can refer below URL
stackoverflow
extension Bundle {
static let externalURLSchemes: [String] = {
guard let urlTypes = main.infoDictionary?["CFBundleURLTypes"] as? [[String: Any]] else {
return []
}
var result: [String] = []
for urlTypeDictionary in urlTypes {
guard let urlSchemes = urlTypeDictionary["CFBundleURLSchemes"] as? [String] else { continue }
guard let externalURLScheme = urlSchemes.first else { continue }
result.append(externalURLScheme)
}
return result
}()
}

Zattoo App url scheme

I need to open Zattoo app from my app (on a button click event)
what I have done is
let url:NSURL? = NSURL(string: "zattoo://")
if UIApplication.sharedApplication().canOpenURL(url!) {
UIApplication.sharedApplication().openURL(url!)
} else {
print("App not installed")
//redirect to safari because the user doesn't have Zattoo App installed
UIApplication.sharedApplication().openURL(NSURL(string: "https://itunes.apple.com/de/app/zattoo-tv-app-sports-news/id423779936?l=en")!)
}
canOpenURL() always returns me the false (even Zattoo app is installed on my device) hence the code in else is executed always.
but if removing the check and just executing the
let url:NSURL? = NSURL(string: "zattoo://")
UIApplication.sharedApplication().openURL(url!)
It's opening the the Zattoo app perfectly. Strange!
What I am doing wrong?
In iOS9 you should register custom schemes you want to use to open other apps. It should be stored as array of strings (custom schemes) with LSApplicationQueriesSchemes key in Info.plist file. From official documentation ( https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW14 ) :
LSApplicationQueriesSchemes (Array - iOS) Specifies the URL schemes
you want the app to be able to use with the canOpenURL: method of the
UIApplication class. For each URL scheme you want your app to use with
the canOpenURL: method, add it as a string in this array. Read the
canOpenURL: method description for important information about
declaring supported schemes and using that method
There is an example how it can be used: https://github.com/gatzsche/LSApplicationQueriesSchemes-Working-Example
Try this :-
if let url = NSURL(string: "zattoo://") {
let canOpen = UIApplication.sharedApplication().canOpenURL(url)
}
Hope it helps.

Escape Character in URL - iOS Swift

I'm pulling a URL from a string and turning that into a button to a WebView of the link.
This is the error I'm getting...
2015-11-10 18:58:05.159 MPSTApp[520:169178] -canOpenURL: failed for URL: "https:/www.facebook.com/prontosantateresa -- file:///" - error: "This app is not allowed to query for scheme file"
For this instance the string is https://www.facebook.com/prontosantateresa but I believe it's using the double // as an escape character.
The code calling the url link is such -
var anchorLink: String?
func loadWebPage(){
let requestURL = NSURL(string: anchorLink!)
let request = NSURLRequest(URL: requestURL!)
webView.loadRequest(request)
}
It's exactly what the error message says: It tries to open a file:// url. So, your algorithm for retrieving the https:// url seems to do something wrong and turn "//" into "/". It could also come handy to add the NSAllowArbitaryLoads key to your Info.plist.
I investigated issue. Since I was using an app browser, it didn't need to use UIApplication.canOpenUrl().
So, to resolve this I replaced the event method with below code in
#IBAction func website1ButtonPressed(sender: UIButton) {
if self.anchorLink != nil{
self.performSegueWithIdentifier("categoryDetailToWebSegue", sender: nil)
}
}

Resources