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.
Related
I have a strange issue which is affecting all of many UIAlertControllers shown throughout my app - the AlertControllers' white foreground appears dimmed, or less bright, than it should be.
The easiest way to describe it is to illustrate the desired and expected effect I obtain by placing the relevant code in a fresh 'Test' Single View application (Swift 3 - I should note that the actual app is a Single View application using Swift 2.3).
In my 'Test' app, the code below uses a deprecated AlertView to show a sample message, then builds a AlertController and shows that immediately 'after'. There is no need to use the AlertView except to highlight the contrast in foreground 'white' - the problematic behaviour is the same if I comment out the AlertView.
Problematic behaviour:
Image 1: Test app: the AlertView is shown and the AlertController then shows behind it. Note the whiteness/brightness of the AlertView foreground.
Image 2: Test app: when the AlertView is dismissed, the AlertController is then the uppermost viewcontroller and as expected and desired, its foreground brightness is exactly the same as the AlertView's previously was
Image 3: real app: same code, also positioned in viewDidAppear - the AlertView is shown with the expected whiteness/brightness
Image 4: real app: when the AlertView is dismissed, the AlertController is then uppermost, but its foreground is nowhere near as bright / white as the AlertView's was, OR as it was in the Test app example, image 2.
As previously stated, the behaviour is the same even if I eliminate the AlertView step (which is just for contrast).
One further observation - often when I observe the behaviour, just momentarily when an affected AlertController first appears, as in for maybe 1/4 or 1/8 of a second, its foreground is the correct whiteness/brightness, but then an instant later it becomes dimmed, per the stated problem.
One other observation - some AlertControllers in the real app display properly with the correct level of brightness, but many others do not.
One last observation - I have user the 'View UI Hierarchy' in Xcode and there does not appear to be anything 'on top of' the affected AlertController.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
UIAlertView(title: "My AlertView Title", message: "My AlertView Message", delegate: self, cancelButtonTitle: "OK").show()
let alertTitle = "My AlertController Title"
let msg = "\r\r\r\r\r\r\r\rMy AlertController Msg"
let alertController = UIAlertController(title: alertTitle, message: msg, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Don't show this again", style: .cancel) { (action:UIAlertAction!) in
print("Dismiss button pressed")
}
alertController.addAction(cancelAction)
let OKAction = UIAlertAction(title: "Action 1", style: .default) { (action:UIAlertAction!) in
print("I'm action1!!")
}
alertController.addAction(OKAction)
let OK2Action = UIAlertAction(title: "Action2", style: .default) { (action:UIAlertAction!) in
print("I'm action2")
}
alertController.addAction(OK2Action)
present(alertController, animated: false, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Any help as to this puzzle gratefully received, thanks!
EDIT:
thanks to #Richard G for putting me on the right track - I still have the problem but with more clarity.
So, it you look at the two screenshots below side by side, they are both from the real app, both use the code shown above to generate the UIAlertController (I have now delete the UIAlertView line as it isn't needed), the only difference between the one on the left and one on the right is the one on the right has these 3 lines of code added:
let backView = alertController.view.subviews.last?.subviews.last
backView?.layer.cornerRadius = 10.0
backView?.backgroundColor = UIColor.whiteColor()
side by side UIAlertControllers 1 transparent 1 not
According to Apple, "The UIAlert​Controller class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified." OK, so this is hacking into it, and it proves that somehow the alertController has acquired, prior to displaying it, a backgroundColor which was partly transparent.
By setting the backgroundColor to UIColor.whiteColor() the problem is fixed, but the solution should be unnecessary - how on earth did the problem arise in the first place?
How was the backgroundColor somehow set to have a transparency when Apple doesn't even expose that property to me to be able to set?
any answers gratefully received!
After seeing an image that you have uploaded I can mention some point.
The UIAlertController is little bit transparent if you notice.
Since UIAlertController is transparent so the background will affect on appearance of alert controller.
NOTE: In case 1 & 2 background is white and case 3 & 4 background is black.
So what you can do it make background as bright as in case 1 and case 2 (from image).
**EDIT : **
TO change background color of UIAlertController you can do like...
let subView = alertController.view.subviews.first!;
var alertContentView = subView.subviews.first!;
alertContentView.backgroundColor = UIColor.darkGray;
alertContentView.layer.cornerRadius = 5;
UIAlertView and UIAlertController has same background color and alpha value.
But in your case AlertView is displayed above AlertController, ie why it feels like UIAlertView is Whitier than UIAlertController.
I have Xcode 8.2, iOS 10, Swift 3.
In my app, the user clicks a button "Start Processing" which kicks off a time-consuming function. I want there to be an Alert window that contains an Activity Indicator. However, all of the tutorials I see just show me how to start and stop it instead of how to pair it asynchronously with running of a function.
My code is something like this:
func doProcessing() {
for (...) {
timeConsumingFunction()
}
}
// This function displays a setup which allows the user to manually kick off the time consuming processing.
func displaySetupScreen() {
let alertController = UIAlertController(title: "Settings", message: "Please enter some settings.", preferredStyle: .alert)
// ask for certain settings, blah blah.
let actionProcess = UIAlertAction(title: "Process", style: .default) { (action:UIAlertAction) in
//This is called when the user presses the "Process" button.
let textUser = alertController.textFields![0] as UITextField;
self.doProcessing()
// once this function kicks off, I'd like there to be an activity indicator popup which disappears once the function is done running.
}
self.present(alertController, animated: true, completion: nil)
}
// this displays the actual activity indicator ... but doesn't work
func displayActivityIndicator() {
// show the alert window box
let alertController = UIAlertController(title: "Processing", message: "Please wait while the photos are being processed.", preferredStyle: .alert)
let activityIndicator : UIActivityIndicatorView = UIActivityIndicatorView()
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
activityIndicator.startAnimating()
self.present(alertController, animated: true, completion: nil)
}
Basically, I don't know how to start and stop the activity indicator at the right times and how I can display an alert controller during that time.
Thank you for your help.
As the link ebby94 posted in his comment says, you should really avoid running a time-consuming task on the main thread. It freezes the UI, and the system Springboard will eventually terminate your app as hung if you take too long.
You should really run your long-running task on a background task. I can't really explain that in any detail without more information.
If you are determined to run your time-consuming task on the main thread then you need to start the activity indicator spinning, then return and give the event loop time to actually start the animation before your task begins. Something like this:
activityIndicator.startAnimating()
DispatchQueue.main.async {
//Put your long-running code here
activityIndicator.stopAnimating()
}
The code inside Dispatch will still be run on the main thread, but first the run loop will get a chance to start the activity indicator.
I would like to display a pop-up where I'll ask the user about their preferences related to the push notifications and for that one, I want to display a list of options to the user. User can select more than one options.
I think that I'll have to display a tableview inside the UIAlertView, but it is deprecated now. So, how can I display a pop (with some small message + multiple select list ) before the APN system permissions dialog in Swift.
Any help will be appreciated.
You can use this code:
let alert = UIAlertController(title: title, message:message, preferredStyle: .Alert)
let action = UIAlertAction(title: "OK", style: .Default) { _ in
acceptNotification = true //code to execute when the user taps that OK
}
alert.addAction(action)
//you can add more actions
self.presentViewController(alert, animated: true){ // this part if provided, will be invoked after the dismissed controller's viewDidDisappear: callback is invoked.
}
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.
I am using SWIFT in xcode 6 to develope my application AND needs to support the APP for iphone 4 and later versions... So have selected the 'Deploment Target' as 7.1. In simple words needs to support iOS7, iOS8 AND iOS9...
When using Alert View I came across in many places discussing now we have to use newly introduced 'UIAlertController' rather than the old 'UIAlertView'...
By reading got to know UIAlertView is deprecated from ios 8.
But in my case as I have to support for ios 7.1 AND I can NOT only use 'UIAlertController'.
I started using as the following way as many tutorials explains...
if (objc_getClass("UIAlertController") != nil)
{
// Use UIAlertController
}
else
{
// Use UIAlertView
}
But in this way got to write the same code twice and really annoyiong... Either I have to create a custom Alertview combining both or needs to continue coding like this....
but just to test I've used only UIAlertView (ignoring UIAlertController) and the app runs fine even in ios 8 simulators... But the document says UIAlertView is deprecated from iOS 8.0...
So my question is, Like to hear what the best practice to continue my app with these API changes... Is it alright if I ignore 'UIAlertController' and work with old 'UIAlertView' until stop support for iOS7 one day... Will that effect in to my app in any way bad? Thanks
I've written a wrapper around these two classes which uses appropriate classes according to iOS version.
find it here
https://github.com/amosavian/ExtDownloader/blob/master/Utility%2BUI.swift
to show a simple alert use this:
Utility.UI.alertShow("message", withTitle: "title", viewController: self);
to show an action view: use this code:
let buttons = [Utility.UI.ActionButton(title: "Say Hello", buttonType: .Default, buttonHandler: { () -> Void in
print("Hello")
})]
Utility.UI.askAction("message", withTitle: "title", viewController: self, anchor: Utility.UI.AnchorView.BarButtonItem(button: uibarbuttontapped), buttons: buttons)
In case you are presenting action in iPhone, it will add a cancel button automatically. But you can have custom cancel button by defining a button with type of ".Cancel"
If you want show a modal alert view, use this code:
let cancelBtn = Utility.UI.AlertButton(title: "Cancel", buttonType: .Cancel, buttonHandler: nil)
let okBtn = Utility.UI.AlertButton(title: "OK", buttonType: .Default, buttonHandler: { (textInputs) -> Void in
print("OK button pressed")
})
Utility.UI.askAlert("message", withTitle: "title", viewController: self, buttons: [cancelBtn, okBtn], textFields: nil)
As you can see, the text fields is set to nil above. You can set it if you want ask something from user, like this:
let cancelBtn = Utility.UI.AlertButton(title: "Cancel", buttonType: .Cancel, buttonHandler: nil)
let okBtn = Utility.UI.AlertButton(title: "OK", buttonType: .Default, buttonHandler: { (textInputs) -> Void in
print("You entered" + textInputs[0])
}
})
let textFields = [Utility.UI.AlertTextField(placeHolder: "enter secret text here", defaultValue: "", textInputTraits: TextInputTraits.secretInput())]
Utility.UI.askAlert("Enter password", withTitle: "title", viewController: self, buttons: [cancelBtn, okBtn], textFields: textFields)
You can customize almost everything. e.g by defining custom textInputTrait you can customize text inputs easily. also you have not to deal with delegates, instead simple closures are there. Please read code to find out more.