App Crashing after changing Photo Settings - ios

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.

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:

How do I create a loop checking app connectivity

I'm trying to create a loop on my app to check Connectivity. So, when the app loads for the first time, the user must be connected to the internet, otherwise he cannot proceed on using the app.
However, I'm trying to create a function that while the user has no internet, the UIAlerView persists on the screen until find an internet connection, then it should be dismissed and another method be lunched. What would be the best way for me to do that?
let alert = UIAlertController(title: "Primeiro Uso", message: "Voce precisa estar conectado na internet", preferredStyle: UIAlertControllerStyle.alert)
// add the actions (buttons)
alert.addAction(UIAlertAction(title: "Estou Conectado", style: .default, handler: { action in
if Connectivity.isConnectedToInternet != true {
//How do I create a loop here until find a connection?
//I'd like to persist the UIAlerView until find a connection
}
//Once the user is connected, this code below should be
//lunched
getApiData { (cerveja) in
arrCerveja = cerveja
//Backup
do{
let data = try JSONEncoder().encode(arrCerveja)
UserDefaults.standard.set(data, forKey: "backupSaved")
//
self.tableView.reloadData()
}catch{print(error)
}
}}))
alert.addAction(UIAlertAction(title: "Cancelar", style: UIAlertActionStyle.cancel, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
}
you can use this answer
https://stackoverflow.com/a/3597085/2621857
in the appDelegate level of you application.
i think creating loop for checking internet, somehow will pressure the processor to memory leak so i suggest not using a kind of loop to check the internet.
i hope this will work for you .

Subsequent presentations of UIAlertController stutter

In a very loaded ViewController in an in-house app I've developed (iPad app written in Swift) for my company, I present alert style UIAlertControllers in a few circumstances. The first time an alert is presented, it always displays very smoothly. Any subsequent displays of an alert however end up being very stuttery, even for alerts that don't have much code surrounding their presentation and dismissal.
For example, if the user tries to dismiss the vc without saving after making changes (simple conditional of "if bool_Saved == false { create and display alert }") an alert is presented asking if they're sure they want to exit without saving. If they choose Yes, the vc is dismissed, otherwise, the alert is dismissed. The first creation and presentation of the alert animates smoothly, but anytime this conditional or any other code needs to present an alert, the presentation stutters its way through the animation.
The UI on this vc is pretty loaded. The whole screen is a scrollView with a contentView that contains 10 sub UIViews which in turn each have 3 UITextFields, ~9 UILabels, and ~8 UIButtons (the company wanted an exact digital replica of a paper form they've been using). Dismissing the vc and reloading it causes the next alert to again display smoothly, but again, subsequent alerts stutter through their present animation.
I've begun profiling in Instruments, but am a bit inexperienced in it and intend to use most of today getting more familiar with the various tools to hopefully find a source for this issue. What I'd like to know is if anyone has any suggestions on what might be causing this stuttering problem.
Thanks, and please let me know if there's any additional information I can provide.
Editing with code snippet described above:
func cancelTapped() {
if savedOnce == false {
let alert_Exit = UIAlertController(title: "Exit Inspection?", message: "Unsaved changes to the sheet will be lost upon exit. Are you sure you want to exit without saving?", preferredStyle: .alert)
let action_No = UIAlertAction(title: "No", style: .cancel, handler: nil )
let action_Yes = UIAlertAction(title: "Yes", style: .default, handler: { [unowned self]
(action) in
self.exitSheet()
})
alert_Exit.addAction(action_No)
alert_Exit.addAction(action_Yes)
present(alert_Exit, animated: true, completion: nil)
} else {
exitSheet()
}
}
You are not dismissing the alert. Insert that line in your Yes button.
let action_Yes = UIAlertAction(title: "Yes", style: .default, handler: { [unowned self]
(action) in
alert_Exit.dismiss(animated: true)
self.exitSheet()
})
When you create the alert a second time, it probably results in a conflict with previous alert hidden somewhere in your view that wasn't dismissed. How loaded the VC is, it probably doesn't make any difference.
Style .cancel will remove itself anyway, whilst .default will require you to remove the alert.

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)

safariViewController debugging - URL not loading

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.

Resources