Here's my code that creates the UIAlertController
// Create the alert controller
var alertController = UIAlertController(title: "Are you sure you want to call \(self.number)?", message: "", preferredStyle: .Alert)
// Create the actions
var okAction = UIAlertAction(title: "Call", style: UIAlertActionStyle.Default) {
UIAlertAction in
var url:NSURL = NSURL(string: "tel://\(self.number)")!
UIApplication.sharedApplication().openURL(url)
}
var cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) {
UIAlertAction in
}
// Add the actions
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// Present the controller
self.presentViewController(alertController, animated: true, completion: nil)
I can't figure out how to change the text color of the cancel and call actions. The title text is currently black and the cancel and call buttons are white. I'm making to make them all black for better visibility. Any ideas? Thanks!
After some trial and error, I found this worked. Hopefully this will help future swift newcomers!
alertController.view.tintColor = UIColor.blackColor()
Piyush's answer helped me the most, but here are some tweaks for Swift 3 and to change the title and message separately.
Title:
alert.setValue(NSAttributedString(string: alert.message, attributes: [NSFontAttributeName : UIFont.systemFont(ofSize: 29, weight: UIFontWeightMedium), NSForegroundColorAttributeName : UIColor.red]), forKey: "attributedTitle")
Message:
alert.setValue(NSAttributedString(string: alert.message, attributes: [NSFontAttributeName : UIFont.systemFont(ofSize: 29, weight: UIFontWeightMedium), NSForegroundColorAttributeName : UIColor.red]), forKey: "attributedMessage")
The big font size is because I actually needed to do it for tvOS, works great on it and iOS.
Swift 4.2
One way of doing this is to make extension on UIAlertController, with this all of your app alerts whil have the same tint color. But it leaves destructive actions in red color.
extension UIAlertController{
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.view.tintColor = .yourcolor
}
}
Below code is changing the UIAlertView title color.
let alert = UIAlertController(title: messageTitle, message: messageText, preferredStyle: UIAlertControllerStyle.Alert)
alert.setValue(NSAttributedString(string: messageTitle, attributes: [NSFontAttributeName : UIFont.systemFontOfSize(17),NSForegroundColorAttributeName : UIColor.redColor()]), forKey: "attributedTitle")
alert.addAction(UIAlertAction(title: buttonText, style: UIAlertActionStyle.Default, handler: nil))
parent.presentViewController(alert, animated: true, completion: nil)
If you want to change button color then add following code after present View Controller.
alert.view.tintColor = UIColor.redColor()
Here's an update for Swift 4, using Cody's answer as a base:
Setting a colour for the alert title:
alert.setValue(NSAttributedString(string: alert.title!, attributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 17, weight: UIFont.Weight.medium), NSAttributedStringKey.foregroundColor : UIColor.blue]), forKey: "attributedTitle")
Setting a colour for the alert message:
alert.setValue(NSAttributedString(string: alert.message, attributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 17, weight: UIFont.Weight.Medium), NSAttributedStringKey.foregroundColor : UIColor.green]), forKey: "attributedMessage")
As per https://developer.apple.com/documentation/foundation/nsattributedstring/key
In Swift 5 and XCode 11.1 and later, colour for the alert title:
alert.setValue(NSAttributedString(string: alert.title!, attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 25, weight: UIFont.Weight.medium), NSAttributedString.Key.foregroundColor : UIColor.red]), forKey: "attributedTitle")
Colour for the alert message:
alert.setValue(NSAttributedString(string: alert.message!, attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 20,weight: UIFont.Weight.medium),NSAttributedString.Key.foregroundColor :UIColor.black]), forKey: "attributedMessage")
Before iOS 9.0 you could simply change the underlying view's tintColor like this:
alertController.view.tintColor = UIColor.redColor()
However, due to a bug introduced in iOS 9, you can either:
Change the app tintColor in the AppDelegate.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
self.window.tintColor = UIColor.redColor()
return true
}
Reapply the color in the completion block.
self.presentViewController(alert, animated: true, completion: {() -> Void in
alert.tintColor = UIColor.redColor()
})
See my other answer here: https://stackoverflow.com/a/37737212/1781087
I have faced the same issue and spent a lot of time trying to find the best way to change it's color for iOS 9 and iOS 10 + because it's implemented in a different way.
Finally I have made an extension for UIViewController. In extension I have added custom function which is almost equal to default function "present", but performs fix of colours. Here you are my solution. Applicable for swift 3+, for projects with target starting from iOS 9:
extension UIViewController {
/// Function for presenting AlertViewController with fixed colors for iOS 9
func presentAlert(alert: UIAlertController, animated flag: Bool, completion: (() -> Swift.Void)? = nil){
// Temporary change global colors
UIView.appearance().tintColor = UIColor.red // Set here whatever color you want for text
UIApplication.shared.keyWindow?.tintColor = UIColor.red // Set here whatever color you want for text
//Present the controller
self.present(alert, animated: flag, completion: {
// Rollback change global colors
UIView.appearance().tintColor = UIColor.black // Set here your default color for your application.
UIApplication.shared.keyWindow?.tintColor = UIColor.black // Set here your default color for your application.
if completion != nil {
completion!()
}
})
}
}
To use this fixed function, you should just call this function instead of default present function. Example:
self.presentAlert(alert: alert, animated: true)
Same solution, but for UIActivityViewController:
extension UIViewController {
/// Function for presenting UIActivityViewController with fixed colors for iOS 9 and 10+
func presentActivityVC(vc: UIActivityViewController, animated flag: Bool, completion: (() -> Swift.Void)? = nil) {
// Temporary change global colors for changing "Cancel" button color for iOS 9 and 10+
if UIDevice.current.systemVersion.range(of: "9.") != nil {
UIApplication.shared.keyWindow?.tintColor = ColorThemes.alertViewButtonTextColor
} else {
UILabel.appearance().textColor = ColorThemes.alertViewButtonTextColor
}
self.present(vc, animated: flag) {
// Rollback for changing global colors for changing "Cancel" button color for iOS 9 and 10+
if UIDevice.current.systemVersion.range(of: "9.") != nil {
UIApplication.shared.keyWindow?.tintColor = ColorThemes.tintColor
} else {
UILabel.appearance().textColor = ColorThemes.textColorNormal
}
if completion != nil {
completion!()
}
}
}
}
I hope this will help somebody and will save a lot of time. Because my time was not saved by such detailed answer :)
Try this
alert.setValue(NSAttributedString(string: alert.message, attributes: [NSFontAttributeName : UIFont.systemFont(ofSize: 29, weight: UIFontWeightMedium), NSForegroundColorAttributeName : UIColor.red]), forKey: "attributedTitle")
func showAlertOnVC(title: String,btnTitle:String, btnSubTitle:String, message: String, VC: UIViewController? , getcompleted: #escaping((_ confrmation: Bool)-> ())) {
guard let vc = VC else {
return
}
let alert = UIAlertController(title: title , message: message, preferredStyle: (UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad ) ? .alert : .alert)
alert.view.tintColor = UIColor.green
let confirmBtn = UIAlertAction(title: btnTitle, style: .default, handler: {_ in
getcompleted(true)
})
let cancelBtn = UIAlertAction(title: btnSubTitle, style: .cancel, handler: {_ in
getcompleted(false)
})
cancelBtn.setValue(UIColor.red, forKey: "titleTextColor")
alert.addAction(confirmBtn)
alert.addAction(cancelBtn)
vc.present(alert, animated: true, completion: nil)
}
Related
I try to make BarChart using https://github.com/CoreCharts/CoreCharts I have a button, which call UIAlertController with textField.
After I click submit button, new data is added to the array. I need the diagram to reboot.
#IBAction func newWeightButton(_ sender: Any) {
//updateGraph()
let alert = UIAlertController(title: "Enter your new weight", message: nil, preferredStyle: .alert)
alert.addTextField()
let submitAction = UIAlertAction(title: "Add", style: .default) { [unowned alert] _ in
let textField = alert.textFields![0]
textField.keyboardType = UIKeyboardType.numberPad
textField.becomeFirstResponder()
let answer = Double(textField.text!)
self.weightArr.append(answer!)
print(self.weightArr)
self.loadCoreChartData()
}
alert.addAction(submitAction)
present(alert, animated: true)
}
func loadCoreChartData() -> [CoreChartEntry] {
return getWeightList()
}
func getWeightList()->[CoreChartEntry] {
var allResults = [CoreChartEntry]()
//let days = ["Mon","Tue","Wed","Thur","Fri"]
let plateNumber = [80,75,90,88,84]
var count = self.weightArr.count
for index in 0..<count {
let newEntry = CoreChartEntry(id: "\(self.weightArr[index])",
barTitle: "Day",
barHeight: Double(self.weightArr[index]),
barColor: UIColor(red: 0, green: 122/255, blue: 1, alpha: 1))
allResults.append(newEntry)
}
For CoreCharts, call reload() on the chart.
Generally, in iOS, setting properties of the UIView is sufficient to get it to update, but with views that use a datasource of some type (like tables, collection views, and core charts), there is usually some kind of reload...() function to tell the view that the datasource changed.
I am trying to create an UIAlertAction that has black color text for the button and I want to assign image as well:
let alert = UIAlertController(title: nil,
message: nil,
preferredStyle: .actionSheet)
let image = UIImage(named: "actionSheetGallery")
let action = UIAlertAction(title: "Choose Picture From Gallery", style: .default, handler: selectFromGallery)
action.setValue(image, forKey: "image")
let image1 = UIImage(named: "actionSheetCamera")
let action1 = UIAlertAction(title: "Take Picture from Camera", style: .default, handler: takePhoto)
action1.setValue(image1, forKey: "image")
let cancelAction = UIAlertAction(title: "Cancel",
style: .cancel, handler: nil)
alert.addAction(action1)
alert.addAction(action)
alert.addAction(cancelAction)
self.present(alert, animated: true, completion: nil)
I can assign either image or text color, How can we assign both.
and image background you can see is changed as well.
I think that action1.setValue(image1, forKey: "image") could be dangerous: Apple could reject your app as you're using a private api. It could, I used private api sometimes and nothing happened.
Anyway, if you want to change the text color you can set a tint color to the alert controller's view:
alert.view.tintColor = .orange // or whatever
If you really want to put an image, the image itself will inherit the tint color of the view (which by default is that blue). To avoid using tint color, you could get your image changing the render mode:
let image = UIImage(named: "actionSheetGallery")?.withRenderingMode(.alwaysOriginal)
This line is illegal:
action1.setValue(image1, forKey: "image")
A UIAlertAction cannot have an image. If you want to customize the appearance of an action sheet, create your own presented view controller (which is all an alert controller really is, after all).
Try below function
func myAlert(){
let refreshAlert = UIAlertController(title: "Unlike Article", message: "Are you sure you want to unlike this Article?", preferredStyle: UIAlertController.Style.alert)
let cancel = UIAlertAction(title: "CANCEL", style: .default, handler: { (action: UIAlertAction!) in
return
})
let image = resizeImage(image: #imageLiteral(resourceName: "twitter"), targetSize: CGSize(width: 25, height: 25))
let unLike = UIAlertAction(title: "UNLIKE", style: .destructive, handler: { (action: UIAlertAction!) in
return
})
unLike.setValue(image.withRenderingMode(UIImage.RenderingMode.alwaysOriginal), forKey: "image")
refreshAlert.addAction(cancel)
refreshAlert.addAction(unLike)
refreshAlert.view.tintColor = UIColor.black
self.present(refreshAlert, animated: true, completion: nil)
}
I need to change font of buttons in UIAlertController, I've used the code below, but I doesn't work properly, because when the user touches the buttons of UIAlertController, the font will be change to it's default.
extension UIAlertController {
private func changeFont(view:UIView,font:UIFont) {
for item in view.subviews {
if let col = item as? UICollectionView {
for row in col.subviews{
changeFont(view: row, font: font)
}
}
if let label = item as? UILabel {
label.font = font
} else {
changeFont(view: item, font: font)
}
}
}
//To set font for any UILabels in action sheet
open override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let font = MyCustomFont
changeFont(view: self.view, font: font! )
}
}
and here is the usage of this extension:
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let action = UIAlertAction(title: STRING_OF_ACTION, style: .default) { _ in
//some action code
}
optionMenu.addAction(action)
//in UIViewController
self.present(optionMenu, animated: true, completion: nil)
It's hard to change it because the iOS system doesn't provide the API for us to do it. So we'd better use third party libraries like: SCLAlertView-Swift or PopupDialog if we need some customization.
i want to set an UITapGestureRecognizer to permit to user to add an image as profile. I set the code but, when i touch on the image, nothing happens. Here the code:
#IBAction func selezionaFoto(sender: UITapGestureRecognizer) {
fieldNome.resignFirstResponder()
func selezionaLibreria(action : UIAlertAction!) {
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.Default, animated: true)
CameraManager.sharedInstance.newImageFromLibraryForController(self, editing: false)
}
func scattaFoto(action : UIAlertAction!) {
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.Default, animated: true)
var circle = UIImageView(frame: UIScreen.mainScreen().bounds)
circle.image = UIImage(named: "overlay")
CameraManager.sharedInstance.newImageShootForController(self, editing: false, overlay:circle)
}
var myActionSheet = UIAlertController(title: NSLocalizedString("ACTION_IMAGE_TITLE", comment: ""),
message: NSLocalizedString("ACTION_IMAGE_TEXT", comment: ""),
preferredStyle: UIAlertControllerStyle.ActionSheet)
myActionSheet.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_LIBRARY", comment: ""),
style: UIAlertActionStyle.Default,
handler: selezionaLibreria))
myActionSheet.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_SHOOT", comment: ""),
style: UIAlertActionStyle.Default,
handler: scattaFoto))
myActionSheet.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_CANCEL", comment: ""),
style: UIAlertActionStyle.Cancel,
handler: nil))
self.presentViewController(myActionSheet, animated: true, completion: nil)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func incomingImage(image: UIImage) {
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.LightContent, animated: true)
immagine.image = image
immagineSelezionata = image
}
func cancelImageSelection() {
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.LightContent, animated: true)
}
The first element in the storyboard is the image and then there is the textfield.
I recently faced the same issue, I had complicated view hierarchy and the reason why gesture didnt respond to the action, was that one superView was set to isUserInteractionEnabled to false, and as you may know, how gestures received by the app and go to the responder in the chain, is that all UIResponders in this chain should be isUserInteractionEnabled = true, to be delivered to the most top view. I assume that you faced the same situation, so just check all the views in the hierarchy.
I'm not 100% sure I understand your question, but it looks like you're just creating a UITapGestureRecognizer on your storyboard and connecting an action to it.
I think you mean to add a UITapGestureRecognizer to a UIImageView, which you can do like this:
var myImageview = UIImageView()
var myTapGestureRecognizer = UITapGestureRecognizer()
myImageview.addGestureRecognizer(myTapGestureRecognizer)
myTapGestureRecognizer.addTarget(self, action: Selector("methodName:"))
myImageview.userInteractionEnabled = true
func methodName(sender: UITapGestureRecognizer) {
// do stuff
}
I've been looking up a lot of tutorials on UIAlertController. Thus far, the way I found was to activate a UIAlertController by linking it to a button or label and then call a IBAction.
I tried to replicate the code to automatically pop an alert when user enters the app (I wanted to ask the user if they want to go through the tutorial). However, I keep getting the error:
Warning: Attempt to present UIAlertController on MainViewController whose view is not in the window hierarchy!
Then I tried to add the UIAlertController to the MainViewController via addChildViewController and addSubview. However, I get the error:
Application tried to present modally an active controller
I figured that I cannot use the presentViewController function and commented it out.
The UIAlertController is displayed BUT when I tried to click on the cancel or the never button, this error occurs.
Trying to dismiss UIAlertController with unknown presenter.
I am really stumped. Can someone share what I am doing wrong? Thank you so much. Here is the code.
func displayTutorial() {
alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
self.addChildViewController(alertController)
self.view.addSubview(alertController.view)
alertController.didMoveToParentViewController(self)
alertController.view.frame.origin.x = self.view.frame.midX
alertController.view.frame.origin.y = self.view.frame.midY
//alertController.popoverPresentationController?.sourceView = self.view*/
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Destructive) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
//self.presentViewController(alertController, animated: false) {}
}
I found the solution. Apparently, I cannot call the UIAlertController from the func viewDidLoad. I must call the function from viewDidAppear. So my code now is
override func viewDidAppear(animated: Bool) {
if tutorial == 0 {
displayTutorial(self.view)
}
}
func displayTutorial(sender:AnyObject) {
let alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Default) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
self.presentViewController(alertController, animated: true, completion: nil)
if let pop = alertController.popoverPresentationController {
let v = sender as UIView
pop.sourceView = view
pop.sourceRect = v.bounds
}
}
Thanks to this posting: Warning: Attempt to present * on * whose view is not in the window hierarchy - swift
Below UIAlertController with extension would help you show alert with dynamic number of buttons with completion handler for selected index
extension UIViewController {
func displayAlertWith(message:String) {
displayAlertWith(message: message, buttons: ["Dismiss"]) { (index) in
}
}
func displayAlertWith(message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
displayAlertWithTitleFromVC(vc: self, title: Bundle.main.infoDictionary!["CFBundleDisplayName"] as! String, andMessage: message, buttons: buttons, completion: completion)
}
func displayAlertWithTitleFromVC(vc:UIViewController, title:String, andMessage message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
for index in 0..<buttons.count {
let action = UIAlertAction(title: buttons[index], style: .default, handler: {
(alert: UIAlertAction!) in
if(completion != nil){
completion(index)
}
})
alertController.addAction(action)
}
DispatchQueue.main.async {
vc.present(alertController, animated: true, completion: nil)
}
}
}
If you need to auto dismiss the alert you can call dismiss on presented view controller after some delay.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
vc.dismiss(animated: true, completion: nil)
}
Hope this might help you.