safariViewController debugging - URL not loading - ios

I'm using SFSafariViewController in an iOS 9 app I'm building using Swift 2.
I am trying to open a URL which fails for some reason. EVery other URL I've tried works, except for this one:
http://www.ctvnews.ca/sci-tech/33-engineers-to-be-recognized-at-sci-tech-oscars-1.2730223
The URL is fine in regular mobile Safari in the simulator, on my iPhone, my iPad, and in any desktop browser. It is just when I try accessing it via Swift in this app that it fails.
Code is as follows:
func openInSafari(URLtoOpen: String) {
print("Trying to openURL: " + URLtoOpen)
let safariVC = SFSafariViewController(URL:NSURL(string: URLtoOpen)!, entersReaderIfAvailable: false)
safariVC.delegate = self
self.presentViewController(safariVC, animated: true, completion: nil)
}
func safariViewController(controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
// SFSafariViewController will present an error message in the browser,
// but in this case we will dismiss the view controller and present our
// own error alert.
if didLoadSuccessfully == false {
controller.dismissViewControllerAnimated(true, completion: { [unowned self] () -> Void in
let alert = UIAlertController(title: "Could Not Load", message: "The URL could not be loaded.", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Okay", style: .Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
})
}
}
This code works fine, and, as I said, other URLs load just fine. What I really need is just a way to more verbosely debug what safariViewController is encountering that is causing it to fail.
The didLoadSuccessfully == false line doesn't really appear to offer much more debugging options to get a sense of what went wrong.
In other words, how do I debug this? I can't seem to find anything in Apple's docs that would explain what to check in case of a loading error.
Please help!
Thanks.

The SFSafariViewController is voluntarily shielded from the app (I wouldn't be surprised if it's actually a different process), and there's very little information shared with the app for privacy and security reasons.
What you could try is loading the same based in an UIWebView or WKWebView (which gives you a lot more feedback, but doesn't share cookies or passwords with Safari) to see what happens then.

Related

How to open the default mail app on iOS 14 without a compose view?

I want to open the default Mail application chosen by the user on iOS 14 - but without showing a compose view.
After signing up for an account, the user should confirm their email address, so I want to direct the user there.
There seem to be two known approaches based on the other questions I found:
UIApplication.shared.open(URL(string: "mailto://")!)
and
UIApplication.shared.open(URL(string: "message://")!)
The problem with the first option is that it will open an empty compose mail view in the app that comes up asking the user to type in a new email. That's not what I want. It would confuse users and they make think they have to send us an email. Putting in some text through parameters of the mailto URL syntax where I basically prepopulate the mail compose view with some text instructing to discard that new email draft and asking to check their email instead would work as a workaround but is not very nice.
The problem with the second option is that always opens the Mail.app, even if that is not the default mail app, and presumably it will ask the user to install the Mail.app if they deleted it from their phones because they have chosen e.g. Protonmail as their default mail app instead. Also not a very nice option for anyone who does not use Mail.app mainly.
So neither of the two approaches that have been proposed by other people solve my issue very nicely.
What is the best way to approach this?
Is there maybe some app to query iOS for the default mail app so at least I can try and launch that app if I know that app's custom URL scheme (e.g. googlegmail://)?
I ended up half-solving it by asking the user with an alert view about their preference, because I did not find a way to query iOS about it directly.
So first am showing an alert view like this:
func askUserForTheirPreference(in presentingViewController: UIViewController) {
let alertController = UIAlertController(title: nil, message: "pleaseConfirmWithApp", preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Apple Mail", style: .default) { action in
self.open(presentingViewController, .applemail)
})
alertController.addAction(UIAlertAction(title: "Google Mail", style: .default) { action in
self.open(presentingViewController, .googlemail)
})
alertController.addAction(UIAlertAction(title: "Microsoft Outlook", style: .default) { action in
self.open(presentingViewController, .outlook)
})
alertController.addAction(UIAlertAction(title: "Protonmail", style: .default) { action in
self.open(presentingViewController, .protonmail)
})
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in
os_log("Cancelling", log: Self.log, type: .debug)
})
presentingViewController.present(alertController, animated: true)
}
Then, I am responding to the user's choice like this:
func open(_ presentingViewController: UIViewController, _ appType: AppType) {
switch appType {
case .applemail: UIApplication.shared.open(URL(string: "message:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
case .googlemail: UIApplication.shared.open(URL(string: "googlegmail:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
case .outlook: UIApplication.shared.open(URL(string: "ms-outlook:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
case .protonmail: UIApplication.shared.open(URL(string: "protonmail:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
}
}
private func handleAppOpenCompletion(_ presentingViewController: UIViewController, _ isSuccess: Bool) {
guard isSuccess else {
let alertController = UIAlertController(title: nil, message: "thisAppIsNotInstalled", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: .cancel))
presentingViewController.present(alertController, animated: true)
return
}
}
enum AppType {
case applemail, googlemail, outlook, protonmail
}
A clear limitation of this approach is of course that I am limiting the user to very specific apps (in this case Google Mail, iOS "default" Mail, Microsoft Outlook and ProtonMail).
So this approach does not really scale well.
But at least, you can cover a few favorite ones and go from there based on your users' feedback.
The main reason for jumping through these hoops of asking the first is that, at least at the moment, it seems impossible to get that information from iOS directly.
I also could not find a URL scheme that would always open the chosen default Mail app without showing the compose new email view.
I believe it can be done with a button with link: href=“message://“
Visual example from Revolut app:

push notification in ios using firebase

How can I use push notification capabilities in my project? I don't have developer account, I tried this code
#IBAction func PhoneSignIn(_ sender: Any) {
let alert = UIAlertController(title: "Phone number", message: "Is this your phone number? \n \(PhoneOu.text!)", preferredStyle: .alert)
let action = UIAlertAction(title: "Yes", style: .default) { (UIAlertAction) in
PhoneAuthProvider.provider().verifyPhoneNumber(self.PhoneOu.text!, uiDelegate: nil) { (verificationID, error) in
if error != nil {
print("eror: \(String(describing: error?.localizedDescription))")
} else {
let defaults = UserDefaults.standard
defaults.set(verificationID, forKey: "authVID")
self.performSegue(withIdentifier: "code", sender: Any?.self)
}
}
}
let cancel = UIAlertAction(title: "No", style: .cancel, handler: nil)
alert.addAction(action)
alert.addAction(cancel)
self.present(alert, animated: true, completion: nil)
}
When you call verifyPhoneNumber:UIDelegate:completion:, Firebase sends a silent push notification to your app, or issues a reCAPTCHA challenge to the user. After your app receives the notification or the user completes the reCAPTCHA challenge, Firebase sends an SMS message containing an authentication code to the specified phone number and passes a verification ID to your completion function. You will need both the verification code and the verification ID to sign in the user.
If you don't have a developer account, this is currently not possible using push notifications, better use reCAPTCHA:
Set up reCAPTCHA verification:
To enable the Firebase SDK to use reCAPTCHA verification:
Add custom URL schemes to your Xcode project:
a. Open your project configuration: double-click the project name in the left tree view. Select your app from the TARGETS section, then select the Info tab, and expand the URL Types section.
b. Click the + button, and add a URL scheme for your reversed client ID. To find this value, open the [![GoogleService-Info.plist][1]][1] configuration file, and look for the REVERSED_CLIENT_ID key. Copy the value of that key, and paste it into the URL Schemes box on the configuration page. Leave the other fields blank.
When completed, your config should look something similar to the following (but with your application-specific values):
Optional: If you want to customize the way your app presents the SFSafariViewController or UIWebView when displaying the reCAPTCHA to the user, create a custom class that conforms to the FIRAuthUIDelegate protocol, and pass it to verifyPhoneNumber:UIDelegate:completion:.
Read more:
Authenticate with Firebase on iOS using a Phone Number

Load video in share extension with Swift 4

I'm working on an iOS app that provides a share extension supposed to upload a video from Photo Library to a server.
I cannot find any example on how to handle the video.
Here is my code:
if itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
print ("A movie has been chosen")
itemProvider.loadItem(forTypeIdentifier: "public.movie", options: nil, completionHandler: { (data, error) in
print ("Loaded the video")
if let video = data as? Data {
// Do stuff with the movie now.
print ("The movie should be loaded now")
}
self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
})
}
The first print is printed, so I'm actually in the case of a public.movie item. But not the second and third print.
Can someone, please, tell me how the movie is passed and how I can handle it?
Thanks
With the help of app groups you can do work around : http://www.atomicbird.com/blog/sharing-with-app-extensions
Get video from library and save in Data in userdefaults.
Transfer the USerDefault key to via appschems and intercept it open URL method in app delgate of app.
With the same key load that video and forward it to server.
For references of app groups and Share extension : https://medium.com/#abhishekthaplithapliyal/ios-share-extension-930ba1ad3c3d
loadItem runs asynchronously, and when you're working with a video file, the file size is much larger than images, so loadItem does not have time to complete itself before self.extensionContext?.completeRequest runs and closes the share extension.
The solution I found involved the following:
(1) Create a boolean variable that measures whether the loadItem function is complete, or not.
var loadItemDone: Bool = false
(2) Write a recursive function that periodically checks to see whether the loadItem function has completed. If so, then run the completeRequest call. If not, continue recursion. In my case, I check for completion once per second, which is why it says "after: 1".
func waitForLoadItemToComplete(after seconds: Int) {
let deadline = DispatchTime.now() + .seconds(seconds)
DispatchQueue.main.asyncAfter(deadline: deadline) {
if self.loadItemDone == false{
self.waitForLoadItemToComplete(after: 1)
}else{
let alert = UIAlertController(title: "Success!", message: "Your file is uploaded!", preferredStyle: .alert)
let action1 = UIAlertAction(title: "Yeah!", style: .cancel) { (action) in
print("User acknowledged file uploaded.")
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
alert.addAction(action1)
self.present(alert, animated: true, completion: nil)
}
}
}
(3) Set the loadItemDone variable to true, when loadItem completes:
if attachment.hasItemConformingToTypeIdentifier("com.apple.quicktime-movie") {
// Do stuff with the movie now.
self.loadItemDone = true
}
'public.movie' didn't work for me. I had to use 'com.apple.quicktime-movie'
Also, DispatchQueue did not work for me, which is why I had to hack a bit... I would love to hear a better solution from anyone more familiar with threads in share extensions...

Set rating right in the App (Swift 3, iOS 10.3)

I have a menu-button in my app. If user clicks this button he sees UIAlertView which include app-link to the App Store.
Here is the code:
#IBAction func navButton(_ sender: AnyObject) {
let alertController = UIAlertController(title: "Menu", message: "Thanks for using our app!", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Rate Us on the App Store", style: .default, handler: { (action: UIAlertAction) in
print("Send user to the App Store App Page")
let url = URL(string: "itms-apps://itunes.apple.com/app/id")
if UIApplication.shared.canOpenURL(url!) == true {
UIApplication.shared.openURL(url!)
}
}))
I know that in iOS 10.3 there was an opportunity to set a rating right in the application. What should I change, so that when a user clicks on a link in UIAlertView, he could set a rating right in the application?
I found some information on Apple Developer website (https://developer.apple.com/reference/storekit/skstorereviewcontroller) but I don't know how to do this in my app.
It's one class function based on looking at the docs.
SKStore​Review​Controller.requestReview()
It also states you shouldn't call this function dependent on a user pressing a button or any other type of action because it is not guaranteed to be called. It would be a bad user experience if you indicate they are about to be shown a review modal and then nothing appears.
If you use this new option in your app it seems the best option is to just place it somewhere that won't interrupt any important actions being conducted by the user and let the framework do the work.
You can use criteria the user isn't aware of to choose when to call the function, i.e. launched the app x amount of times, used x number of days in a row, etc.
Edit: alternative
If you want to keep more control over the ability to request reviews you can continue the old way and append the following to your store URL to bring them directly to the review page.
action=write-review
guard let url = URL(string: "appstoreURLString&action=write-review") else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)

App Crashing after changing Photo Settings

I've got an app that needs access to the Photos on your device. I check to see the device status, and if they deny access I trigger a modal which will warn them that they did not provide the necessary access, and then gives them the option to go to their settings and correct the choice.
When this happens, my app crashes with the following error:
This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.
Here is my code for triggering the redirect. Any ideas what could be causing this or suggestions on how I should do this better?
let title = "You didn't allow us to view your photos!"
let message = "Without access, we cannot help you add photos from your device."
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "I know!", style: UIAlertActionStyle.Cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Settings (Required)", style: UIAlertActionStyle.Default, handler: { (action: UIAlertAction) -> Void in
let settingsURL = NSURL(string: UIApplicationOpenSettingsURLString)
UIApplication.sharedApplication().openURL(settingsURL!)
}))
self.presentViewController(alertController, animated: true, completion: nil)
This code is called within a method that I call from the following spot:
PHPhotoLibrary.requestAuthorization({ (status: PHAuthorizationStatus) -> Void in
if(status == .Authorized){
self.getPhonePhotos()
}else{
self.showDeniedPhotosPopup()
}
})
Thanks in advance.
UPDATE
I just realized something I didn't earlier. The code only crashes if I activate the "Photos" switch in the settings. The navigation itself doesn't cause the app to crash, its changing the photo settings configuration while the app is still running. To test my theory, I never triggered the popup, and simply went to the settings, and activated the photos switch and the app crashed. So the crash is definitely sourcing from the change in photo settings.
From the error it appears you are presenting your view controller on a thread which is not main thread leading to implementation of UI stuff (auto layout etc.) on your new view controller on a background thread.
Try encapsulating your view controller presentation code on main queue. Something like this:
weak var aBlockSelf = self
dispatch_async(dispatch_get_main_queue(), {
aBlockSelf.presentViewController(alertController, animated: true, completion: nil)
})
If it still crashes, profile your application to find out the exact culprit.

Resources