UIAlertController's actionSheet gives constraint error on iOS 12.2 / 12.3 [duplicate] - swift5

This question already has answers here:
Swift default AlertViewController breaking constraints
(12 answers)
Closed 3 years ago.
On iOS 12.2, while using UIAlertController's actionSheet, Xcode gives constraint error. Anyone having this problem?
This same code runs on iOS 12.1 with no error.
I have tested this code on Xcode 10.2 and 10.1.
class ViewController: UIViewController {
let Click : UIButton = {
let button = UIButton(type: UIButton.ButtonType.system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("OK", for: .normal)
button.tintColor = UIColor.blue
button.addTarget(self, action: #selector(click(_:)), for: UIControl.Event.touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(Click)
Click.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
Click.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc func click(_ sender: UIButton) {
let optionMenu = UIAlertController(title: nil, message: "Choose Option", preferredStyle: .actionSheet)
let deleteAction = UIAlertAction(title: "Delete", style: .default)
let saveAction = UIAlertAction(title: "Save", style: .default)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
optionMenu.addAction(deleteAction)
optionMenu.addAction(saveAction)
optionMenu.addAction(cancelAction)
self.present(optionMenu, animated: true, completion: nil)
}
}
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x6000001b6ee0 UIView:0x7fe3b6513020.width == - 16 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000001b6ee0 UIView:0x7fe3b6513020.width == - 16 (active)>
PS:
Just to make sure that the problem is on UIAlertController, I removed everything and updated the code as below, but I received the same error:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let optionMenu = UIAlertController(title: "Test", message: "Choose Option", preferredStyle: .actionSheet)
let deleteAction = UIAlertAction(title: "Delete", style: .default)
let saveAction = UIAlertAction(title: "Save", style: .default)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
optionMenu.addAction(deleteAction)
optionMenu.addAction(saveAction)
optionMenu.addAction(cancelAction)
self.present(optionMenu, animated: true, completion: nil)
}
}

It's a bug in iOS versions:
12.2
12.3
12.4
13.0
13.1
13.2
13.2.3
13.3
13.4
13.4.1
13.5
13.5.1
13.6
14.0
14.1
14.2
14.4
The only thing we can do is to file a bug report to Apple (I just did that and you should too).
I'll try to update answer for a new versions of iOS if the bug still will be present. Help appreciated.

Related

Error/warning when using alert with text field

I get the following warning/error when I create an alert with text field. When I remove the textfield, the message disappears.
Changing the translatesAutoresizingMaskIntoConstraints property of a UICollectionViewCell that is managed by a UICollectionView is not supported, and will result in incorrect self-sizing. View: <_UIAlertControllerTextFieldViewCollectionCell: 0x1529254e0; frame = (0 0; 270 24); gestureRecognizers = <NSArray: 0x600000d56be0>; layer = <CALayer: 0x6000003b5a80>>
I don't have a collection view and also I haven't changed or set to false the translatesAutoresizingMaskIntoConstraints property of a cell, that's why I don't understand the message.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(createAlert))
}
#objc private func createAlert() {
let alert = UIAlertController(title: "Alert Title", message: nil, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
print("Action") }
alert.addAction(okAction)
alert.addAction(cancelAction)
alert.addTextField { textField in
}
present(alert, animated: true)
}
How can I solve this? Unfortunately, I have not found a solution in my research. The warning occurs not only on the simulator, but also on physical devices (iPhone X).

Saving Color into Core Data for the iOS and macOS Apps

I am building an app (SwiftUI) which will be used for iOS and macOS platform. One of the challenges, I am facing is storing Color (SwiftUI) into the database. In other apps I have used NSValueTransformer to use it with UIColor or NSColor but now the problem is that iOS and macOS share the codebase. I am using Core Data so the data model is being shared but the data saved is on separate SQLite files. If I want to sync the data to iCloud then would't I face issues if iOS app stored color as UIColor and macOS app stored color as NSColor?
How can I resolve this issue?
import UIKit
import CoreData
class saveDataScreenViewController: UIViewController {
#IBOutlet weak var txtName: UITextField!
#IBOutlet weak var txtId: UITextField!
#IBOutlet weak var txtUserName: UITextField!
#IBOutlet weak var txtPassword: UITextField!
#IBOutlet weak var btnSave: UIButton!
//MARK: - Create context for save data
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - save button click with validation and saving data to core data
#IBAction func didTapSave(_ sender: UIButton) {
if txtName.text!.isEmpty {
let alert = UIAlertController(title: "Alert", message: "Please enter Name", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
} else if txtId.text!.isEmpty {
let alert = UIAlertController(title: "Alert", message: "Please enter id", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
} else if txtUserName.text!.isEmpty {
let alert = UIAlertController(title: "Alert", message: "Please enter username", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
} else if txtPassword.text!.isEmpty {
let alert = UIAlertController(title: "Alert", message: "Please enter password", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
} else {
let Entity = NSEntityDescription.insertNewObject(forEntityName: "EntityName", into: context)
clinicEntity.setValue(txtName.text!, forKey: "Name")
clinicEntity.setValue(txtId.text!, forKey: "id")
clinicEntity.setValue(txtPassword.text!, forKey: "password")
clinicEntity.setValue(txtUserName.text!, forKey: "username")
do {
try context.save()
self.txtUserName.text = ""
self.txtPassword.text = ""
self.txtName.text = ""
self.txtId.text = ""
self.navigationController?.popViewController(animated: true)
} catch {
print("Error while insert data!")
}
}
}
}

UIAlertController Crash Error [duplicate]

This question already has answers here:
UIAlertController is Crashed (iPad)
(11 answers)
Closed 4 years ago.
Here is my code
let selectRoute = UIAlertController(title: "Select a Route", message: "Please select the route you want to create your trip on", preferredStyle: .actionSheet)
let route1 = UIAlertAction(title: "Route 1", style: .default) { (action) in
self.reminder()
self.routeID = 1
self.tableView.reloadData()
}
let route2 = UIAlertAction(title: "Route 2", style: .default) { (action) in
}
selectRoute.addAction(route1)
selectRoute.addAction(route2)
if let popoverPresentationController = selectRoute.popoverPresentationController {
popoverPresentationController.sourceView = self.view
popoverPresentationController.sourceRect = self.view.bounds
}
self.present(selectRoute, animated: true, completion: nil)
When I run this, the actionSheet just appears just over the Navigation Controller really small, anyone know a fix?
On iPad the alert will be displayed as a popover using the UIPopoverPresentationController, it requires you define an anchor point for the presentation of the popover using either a sourceView and sourceRect or a barButtonItem.
To support iPad, include this code:
alertController.popoverPresentationController?.sourceView = self.view
alertController.popoverPresentationController?.sourceRect = self.myButton.frame
self.presentViewController(alertController, animated: true, completion: nil)
here is an example:
Suppose you have a button and you are going to show alert controller below of the button:
#IBOutlet weak var myBtn: UIButton!
let alertController = UIAlertController(title: "title :)", message:"message...", preferredStyle: .actionSheet)
let defaultAction = UIAlertAction(title: "cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "delete", style: .destructive) { (action: UIAlertAction) in
//Do something
}
let addAction = UIAlertAction(title: "...", style: .default) { (action) in
//do something
}
alertController.addAction(addAction)
alertController.addAction(deleteAction)
alertController.addAction(defaultAction)
alertController.popoverPresentationController?.sourceRect = self.myBtn.frame
alertController.popoverPresentationController?.sourceView = self.view
self.present(alertController, animated: true, completion: nil)
If you are displaying the action sheet after the user makes a selection on a cell within a UITableView. you can use this code:
alertController.popoverPresentationController.sourceView = cell
alertController.popoverPresentationController.sourceRect = cell.bounds

IPv6 Crash Firebase Swift

I have a Swift app using Firebase as backend.
It works perfectly fine on all simulators and my own iOS devices, but when I submitted it to Apple for review, it crashed specifically when the reviewer tapped on a UIButton that presents a UIAlertController containing my content reporting mechanism:
#IBAction func moreActions(_ sender: AnyObject) {
// 1
let optionMenu = UIAlertController(title: nil, message: "Choose Action", preferredStyle: .actionSheet)
// 2
let reportPost = UIAlertAction(title: "Report this post", style: .default, handler: {
(alert: UIAlertAction!) -> Void in
self.report()
})
// 3
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: {
(alert: UIAlertAction!) -> Void in
print("Cancelled")
})
// 4
optionMenu.addAction(reportPost)
optionMenu.addAction(cancelAction)
self.present(optionMenu, animated: true, completion: nil)
}
func report() {
let messageView = MessageView.viewFromNib(layout: .CardView)
var config = SwiftMessages.Config()
config.dimMode = .gray(interactive: true)
messageView.configureTheme(.info)
messageView.button?.isHidden = true
messageView.configureContent(title: "Thank you", body: "We have received your report, and will soon make all necessary actions.")
SwiftMessages.show(config: config, view: messageView)
ref = FIRDatabase.database().reference()
ref.child("reportedPost").observeSingleEvent(of: .value, with: {(snapshot) in
if snapshot.hasChild(self.passedPurchaseKey) {
print("this post has been reported before")
let targetKey = self.passedPurchaseKey
let value = snapshot.childSnapshot(forPath: targetKey!).value as! Int
let newValue = value + 1
self.ref.child("reportedPost").updateChildValues([targetKey!: newValue])
}else{
print("this post has not been reported before")
let targetKey = self.passedPurchaseKey
self.ref.child("reportedPost").updateChildValues([targetKey!: 1])
}
})
}
The reviewer suggested that the crash might due to IPv6 incompatibility. I cannot agree with them since the networking logics above are very similar to the rest of my App, which did not lead to any crashes. Thus I suspect that the crash was due to something else.
Yet I could not be sure because I do not have the required hardwares to test my app under IPv6 environment.
Thanks in advance for your kind help!
//edit 1: crash log added
crashlog_1
crashlog_2

UIAlertController change font color

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)
}