How to make a phone call with Swift 3? - ios

I'm building an app for a business and I've run into a dead end. I need a button that will start a phone call to a client's mobile number.
I have this, which is how it should work in Swift 2:
#IBAction func clientMobile(_ sender: AnyObject) {
UIApplication.shared.openURL(self.mobile!)
}
However openURL is deprecated and I don't see any alternative in the intellisense. What is the Swift 3 equivalent of the above line of code?
One other thing, when I run this code I have thee following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I know the error is related to self.mobile but I'm not sure how to fix it.
Declarations and extra information:
self.mobileis declared and initialised like this:
var mobile : URL?
//inside view will appear
self.mobile = URL(string: "telprompt://" + (self.dog?.client?.mobile)!)

you much check iOS version
guard let number = URL(string: "telprompt://123456789") else { return }
if #available(iOS 10.0, *) {
UIApplication.shared.open(number)
} else {
// Fallback on earlier versions
UIApplication.shared.openURL(number)
}

From the looks of it, you should be checking dog.client.mobile for some invalid characters in the phone number, or even nil (I can't tell if mobile is really optional there, if so you should check for nil before even attempting to launch the call).
Looks like your URL is coming back as nil, and you're trying to pass that to your openURL.
Your phone number should be free of symbols such as ()+-. You can easily remove those using stringByReplacingCharactersInSet (can't remember the exact name in Swift 3 right now.

openURL(:) is deprecated in iOS 10.
The new method is:
- (void)openURL:(NSURL*)url options:(NSDictionary<NSString *, id> *)options
completionHandler:(void (^ __nullable)(BOOL success))completion
Example usage to support both iOS 10 and earlier versions:
// iOS10 check
if (UIApplication.shared.respondsToSelector(#selector(UIApplication.shared.openURL(_:options:completionHandler:))) {
UIApplication.shared.openURL(self.mobile!, options: [:], completionHandler:nil)
} else {
UIApplication.shared.openURL(self.mobile!)
}
(hope this helps, sorry for any errors im on mobile atm)

Related

Compile-time test for availability of UIApplication.shared?

I have some shared code that needs to work in both iOS apps and app extensions, and needs to set UIApplication.shared.isNetworkActivityIndicatorVisible — but only if the code is used in an app.
In an app extension, UIApplication.shared gives this compile error:
'shared' is unavailable: Use view controller based solutions where appropriate instead
That’s fine; I don’t want to use it in the app extension. However, I’m unable to find a way to disable that code at compile time. Sadly, if #available doesn’t seem to do the trick; it shuts off the code path, but the compiler still doesn’t like it:
if #available(iOSApplicationExtension 0, *) {
print("This is an extension")
} else {
print("This is an app")
print(UIApplication.shared) // Unreachable in extension, but still doesn’t compile
}
I don’t see any #if check that handles this.
Is there any way in Swift to conditionally compile the code that requires UIApplication.shared?
A possible solution is to avoid explicit usage of UIApplication.shared and use Objective-C selector wrap instead.
Here is an extension that might help (based on https://github.com/ephread/Instructions/issues/21)
extension UIApplication {
static var safeShared: UIApplication? {
guard UIApplication.responds(to: Selector(("sharedApplication"))) else {
return nil
}
guard let unmanagedSharedApplication = UIApplication.perform(Selector(("sharedApplication"))) else {
return nil
}
return unmanagedSharedApplication.takeRetainedValue() as? UIApplication
}
}
Usage
if let app = UIApplication.safeShared {
result = app.applicationState == .active
}
Happy Coding 👨‍💻

blank page after recaptcha verification Authenticate with Firebase on iOS using a Phone Number Swift

After recaptcha verification, page only returned blank. It did nothing to do next step.
Screen Shot
In your app delegate's application(_:open:options:) method, call Auth.auth().canHandle(url).
For the blank re-captcha page issue I was able to resolve it by doing these 3 things:
1st thing-
Inside the GoogleSerivce-Info.plist file make sure the REVERSED_CLIENT_ID is added to your project via the URL types using this. Follow the first part of the second step there: Add custom URL schemes to your Xcode project (look at the screenshot).
2nd thing-
In the project navigator select the blue project icon
Select Capabilities
Open Background Modes
Select Background fetch
3rd thing-
Before verifying the phone number call PhoneAuthProvider.provider(auth: Auth.auth())
#IBAction func phoneButton(sender: UIButton) {
// ***step 5***
PhoneAuthProvider.provider(auth: Auth.auth())
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumberTextField.text!, uiDelegate: nil) {
(verificationID, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let verificationId = verificationID else { return }
// do something with verificationID
}
}
On iOS, the appVerificationDisabledForTesting setting has to be set to TRUE before calling verifyPhoneNumber. This is processed without requiring any APNs token or sending silent push notifications in the background, making it easier to test in a simulator. This also disables the reCAPTCHA fallback flow.
Firebase Docs
I face this issue and fix it by adding this code into my AppDelegate.m
- (BOOL) application: (UIApplication *) app
openURL: (NSURL *) url
options: (NSDictionary <UIApplicationOpenURLOptionsKey, id> *)
options {
if ([[FIRAuth auth] canHandleURL: url]) {
return YES;
} else {
// URL not auth related, developer should handle it.
return NO;
}
}

Call number in iOS

I am a student learning Swift and am having trouble calling a number from a button call. I researched and found this code below in a couple of different places but there is always an error saying "This app is not allowed to query for scheme tel". Is there something in the Info.plist file that I'm missing for this code to work? Or is there another issue?
#IBAction func call(_ sender: Any) {
let number = URL(string: "tel://" + restaurant.number)
if UIApplication.shared.canOpenURL(number!) {
if #available(iOS 10, *) {
UIApplication.shared.open(number!)
} else {
UIApplication.shared.openURL(number!)
}
}
}
Based on your description, seems you're trying to access a url starting with tel:// which your application is denied access. If you do have access to that domain, access it via your credentials.

Force reload watchOS 2 Complications

I have issues getting Complications to work. It would be helpful if I was able to reliably refresh them.
Therefore I linked a force-press menu button to the following method
#IBAction func updateComplication() {
let complicationServer = CLKComplicationServer.sharedInstance()
for complication in complicationServer.activeComplications {
complicationServer.reloadTimelineForComplication(complication)
}
}
Unfortunately this leads to the app crashing. with a fatal error: unexpectedly found nil while unwrapping an Optional value.
I understand that calling reloadTimelineForComplication(complication) is budgeted but that can't be the issue here as it doesn't work from the very beginning.
I am currently using watchOS2 + Xcode 7 GM
I'd appreciate any ideas on making Complications refresh while the app is running?
Trace or use the exception breakpoint and focus on reading the whole error message where it tells you exactly on which line it found the nil unexpectedly (I do suspect the complicationServer). Use 'if let' instead of 'let' to force unwrap the respective variable.
private func reloadComplications() {
if let complications: [CLKComplication] = CLKComplicationServer.sharedInstance().activeComplications {
if complications.count > 0 {
for complication in complications {
CLKComplicationServer.sharedInstance().reloadTimelineForComplication(complication)
NSLog("Reloading complication \(complication.description)...")
}
WKInterfaceDevice.currentDevice().playHaptic(WKHapticType.Click) // haptic only for debugging
}
}
}

Check if a function is available in Swift?

I would like to detect if the user has enabled Reduce Transparency. It's simple you just call the func UIAccessibilityIsReduceMotionEnabled() and it returns a Bool. But my app targets iOS 7 and 8 and this function isn't available on iOS 7.
In Objective-C, this is how I checked to see if that function exists:
if (UIAccessibilityIsReduceMotionEnabled != NULL) { }
In Swift, I can't figure out how to check if it exists or not. According to this answer, you can simply use optional chaining and if it's nil then it doesn't exist, but that is restricted to Obj-C protocols apparently. Xcode 6.1 doesn't like this:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled?()
It wants you to remove the ?. And of course if you do so it will crash on iOS 7 because that function doesn't exist.
What is the proper way to check if these types of functions exist?
A proper check for availability has been added in Swift 2. This is recommended over other options mentioned here.
var shouldApplyMotionEffects = true
if #available(iOS 8.0, *) {
shouldApplyMotionEffects = !UIAccessibilityIsReduceMotionEnabled()
}
If you're okay with being a little bit cheeky, you can always open the UIKit binary using the library loader and see if it can resolve the symbol:
let uikitbundle = NSBundle(forClass: UIView.self)
let uikit = dlopen(uikitbundle.executablePath!, RTLD_LAZY)
let handle = dlsym(uikit, "UIAccessibilityIsReduceMotionEnabled")
if handle == nil {
println("Not available!")
} else {
println("Available!")
}
The dlopen and dlsym calls can be kinda expensive though so I would recommend keeping the dlopen handle open for the life of the application and storing somewhere the result of trying to dlsym. If you don't, make sure you dlclose it.
As far as I know this is AppStore safe, since UIAccessibilityIsReduceMotionEnabled is a public API.
You could check to see if you're running in iOS 8 or higher --
var reduceMotionEnabled = false
if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
I don't think there's another way to tell. So in theory, if you were able to check, trying to access the function name without the () would give you nil in iOS 7 and the () -> Bool function in iOS 8. However, in order for that to happen, UIAccessibilityIsReduceMotionEnabled would need to be defined as (() -> Bool)?, which it isn't. Testing it out yields a function instance in both versions of iOS that crashes if called in iOS 7:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled
// reduceMotionDetectionIsAvailable is now a () -> Bool
reduceMotionDetectionIsAvailable()
// crashes in iOS7, fine in iOS8
The only way I can see to do it without testing the version is simply to define your own C function to check in your bridging header file, and call that:
// ObjC
static inline BOOL reduceMotionDetectionIsAvailable() {
return (UIAccessibilityIsReduceMotionEnabled != NULL);
}
// Swift
var reduceMotionEnabled = false
if reduceMotionDetectionIsAvailable() {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
From the Apple Developer docs (Using Swift with Cocoa and Objective-C (Swift 3) > Interoperability > Adopting Cocoa Design Patterns > API Availability):
Swift code can use the availability of APIs as a condition at
run-time. Availability checks can be used in place of a condition in a
control flow statement, such as an if, guard, or while
statement.
Taking the previous example, you can check availability in an if
statement to call requestWhenInUseAuthorization() only if the method
is available at runtime:
let locationManager = CLLocationManager()
if #available(iOS 8.0, macOS 10.10, *) {
locationManager.requestWhenInUseAuthorization()
}
Alternatively, you can check availability in a guard statement,
which exits out of scope unless the current target satisfies the
specified requirements. This approach simplifies the logic of handling
different platform capabilities.
let locationManager = CLLocationManager()
guard #available(iOS 8.0, macOS 10.10, *) else { return }
locationManager.requestWhenInUseAuthorization()
Each platform argument consists of one of platform names listed below,
followed by corresponding version number. The last argument is an
asterisk (*), which is used to handle potential future platforms.
Platform Names:
iOS
iOSApplicationExtension
macOS
macOSApplicationExtension
watchOS
watchOSApplicationExtension
tvOS
tvOSApplicationExtension

Resources