Swift: transparent background for UIViewAlertController - ios

there are many answers on Stack Overflow about this but none seem to work. how do you make the background color of a UIAlertViewController truly clear?
i have at the moment:
let errorAlert = UIAlertController(title: "Title", message: "Message", preferredStyle: UIAlertControllerStyle.Alert)
let subview = errorAlert.view.subviews.first! as UIView
let alertContentView = subview.subviews.first! as UIView
alertContentView.backgroundColor = UIColor.clearColor()
errorAlert.view.backgroundColor = UIColor.clearColor()
showViewController(errorAlert, sender: self)
but the result is a kind of white tinted, transparent-ish background over the image... is there anyway to remove this tinted background?

In order to achieve this the color of all subviews needs to be set to UIColor.clear. In addition, all UIVisualEffectView child views need to be removed. This can be accomplished by using a recursive function (Swift 4):
func clearBackgroundColor(of view: UIView) {
if let effectsView = view as? UIVisualEffectView {
effectsView.removeFromSuperview()
return
}
view.backgroundColor = .clear
view.subviews.forEach { (subview) in
clearBackground(of: subview)
}
}
Call this right after creating the UIAlertController instance:
let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
clearBackgroundColor(of: alert.view)
If you now want to change the appearance of the alert:
alert.view.layer.backgroundColor = UIColor.red.withAlphaComponent(0.6).cgColor
alert.view.layer.cornerRadius = 5

You need to access the superview of the UIAlertController
alertView.view.superview?.backgroundColor = UIColor.clearColor()

Related

UIAlertController remove blurry layer

With a custom UIAlertController, I am trying to set the background color to solid red. However, I am getting another blurry layer on top. How do I get rid of the blurry layer?
class CustomAlert: UIAlertController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.RED
}
}
let popUpEmailVerification: CustomAlert = {
let alert = CustomAlert(title: nil, message: "A verification email has been sent to your mailbox. Please open the link to finish verification.", preferredStyle: .alert)
return alert
}()
There's no direct API to achieve the effect you want, as far as I'm aware.
However, you can do what you're describing by messing around with the undocumented view hierarchy of the alert, like so:
let bgView = (alert.view.subviews.first?.subviews.first?.subviews.first!)! as UIView
and then set bgView.backgroundColor = .red. This is likely to bite you later.
Alternatively, you could use one of the many custom Alert libaries out here, SDCAlertView is one that I've used with good success.

UIAlertController Action Sheet without Blurry View Effect

I'm using UIAlertController for some actions.
But I'm not a big fan of the Blurry View Effect in the actions group view (see screenshot below).
I'm trying to remove this blurry effect. I made some research online, and I couldn't find any API in UIAlertController that allows to remove this blurry effect. Also, according to their apple doc here :
The UIAlertController 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.
I see that Instagram also removes this blurry view effect :
The only way I could find to remove it is to update the view hierarchy myself via an extension of UIAlertController.
extension UIAlertController {
#discardableResult private func findAndRemoveBlurEffect(currentView: UIView) -> Bool {
for childView in currentView.subviews {
if childView is UIVisualEffectView {
childView.removeFromSuperview()
return true
} else if String(describing: type(of: childView.self)) == "_UIInterfaceActionGroupHeaderScrollView" {
// One background view is broken, we need to make sure it's white.
if let brokenBackgroundView = childView.superview {
// Set broken brackground view to a darker white
brokenBackgroundView.backgroundColor = UIColor.colorRGB(red: 235, green: 235, blue: 235, alpha: 1)
}
}
findAndRemoveBlurEffect(currentView: childView)
}
return false
}
}
let actionSheetController = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet)
actionSheetController.view.tintColor = .lightBlue
actionSheetController.removeBlurryView()
This worked fine, it removed my blurry view effect:
What I'm wondering... Is my solution the only way to accomplish that? Or there is something that I'm missing about the Alert Controller appearance?
Maybe there is a cleaner way to accomplish exactly that result? Any other ideas?
It is easier to subclass UIAlertController.
The idea is to traverse through view hierarchy each time viewDidLayoutSubviews gets called, remove effect for UIVisualEffectView's and update their backgroundColor:
class AlertController: UIAlertController {
/// Buttons background color.
var buttonBackgroundColor: UIColor = .darkGray {
didSet {
// Invalidate current colors on change.
view.setNeedsLayout()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Traverse view hierarchy.
view.allViews.forEach {
// If there was any non-clear background color, update to custom background.
if let color = $0.backgroundColor, color != .clear {
$0.backgroundColor = buttonBackgroundColor
}
// If view is UIVisualEffectView, remove it's effect and customise color.
if let visualEffectView = $0 as? UIVisualEffectView {
visualEffectView.effect = nil
visualEffectView.backgroundColor = buttonBackgroundColor
}
}
// Update background color of popoverPresentationController (for iPads).
popoverPresentationController?.backgroundColor = buttonBackgroundColor
}
}
extension UIView {
/// All child subviews in view hierarchy plus self.
fileprivate var allViews: [UIView] {
var views = [self]
subviews.forEach {
views.append(contentsOf: $0.allViews)
}
return views
}
}
Usage:
Create alert controller.
Set buttons background color:
alertController.buttonBackgroundColor = .darkGray
Customise and present controller.
Result:
Answer by Vadim works really well.
What I missed in it (testing on iOS 14.5) is lack of separators and invisible title and message values.
So I added setting correct textColor for labels and skipping separator visual effect views in order to get correct appearance. Also remember to override traitCollectionDidChange method if your app supports dark mode to update controls backgroundColor accordingly
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for subview in view.allViews {
if let label = subview as? UILabel, label.textColor == .white {
label.textColor = .secondaryLabel
}
if let color = subview.backgroundColor, color != .clear {
subview.backgroundColor = buttonBackgroundColor
}
if let visualEffectView = subview as? UIVisualEffectView,
String(describing: subview).contains("Separator") == false {
visualEffectView.effect = nil
visualEffectView.contentView.backgroundColor = buttonBackgroundColor
}
}
popoverPresentationController?.backgroundColor = buttonBackgroundColor
}

Add image on center of Alert ViewController with transparent background

How to display image in center of alert viewController with transparent background of alert viewController.
I wrote following code.
let image = UIImage(named: "ic_no_data")
let imageView = UIImageView(frame: CGRectMake(220, 10, 40, 40)
let alertMessage = UIAlertController(title: "image", message: "", preferredStyle: .Alert)
let backView = alertMessage.view.subviews.last?.subviews.last
backView?.layer.cornerRadius = 10.0
backView?.backgroundColor = UIColor.lightGrayColor()
alertMessage.view.addSubview(imageView)
let action = UIAlertAction(title:"", style: .Cancel, handler: nil)
action.setValue(image, forKey: "image")
alertMessage .addAction(action)
self.presentViewController(alertMessage, animated: true, completion: nil)
It looks like this.
Please help me to solve this problem and thanks to all.
UIAlertController doesn't support such changes in view hierarchy you'd be better using a custom extensible component that mimics the behavior of UIAlertController. The problem with UIAlertController is that you don't know how Apple will change its view hierarchy in the next version of iOS. And each time they change something you'll have to add code for a specific version of iOS which is really bad from the maintainability point of view.
For example CustomIOSAlertView: https://github.com/wimagguc/ios-custom-alertview.
you can store you reference of imageView an alertMessage, and in completion of the present viewController you have the exact size of the alert view, so you can adjust the image according to the current alert rect.
try this code is in swift3 :
self.present(alertMessage, animated: true, completion: {
var frame = self.alertMessage.view.frame
frame.origin = CGPoint(x: 0, y: 0) // add your location
self.imageView.frame = frame
})

UINavigationBar : Add SearchBar as TitleView

I want to add uisearchbar in place of title of controller.
I am facing 2 problems currently.
1)
I do not know from where this gray background is coming. Please check this picture.
2) I need this searchbar in other inner screens also. But when I push another controller this searchbar is removed.
Here is my code :
// Seachbar Container View
let searchBarContainer = ERView(frame: CGRectMake(0,0,280,44))
searchBarContainer.autoresizingMask = [.FlexibleWidth]
searchBarContainer.backgroundColor = UIColor.clearColor()
// Search Bar
let searchBar = ERSearchBar(frame: searchBarContainer.bounds)
searchBarContainer.addSubview(searchBar)
// Add View as Title View in Navigation Bar
self.navigationController!.navigationBar.topItem?.titleView = searchBarContainer
Here is Code of my UISearchBar Class
func commonInit() -> Void {
// Update UI
if let searchField = self.valueForKey("searchField") as? UITextField{
// To change background color
searchField.backgroundColor = UIColor.appNavigationBarDarkRedColor()
// Tint Color
if let leftViewRef = searchField.leftView {
leftViewRef.tintColor = UIColor.whiteColor()
}else if let imgLeftView = searchField.leftView as? UIImageView{
imgLeftView.tintColor = UIColor.whiteColor()
}
// Font Color
searchField.font = UIFont.robotoRegularFont(WithSize: 14.0)
// Text Color
searchField.textColor = UIColor.whiteColor()
// PlaceHolder Attributes
searchField.attributedPlaceholder = NSAttributedString(string: "Search", attributes: [NSForegroundColorAttributeName:UIColor.whiteColor()])
}
self.setImage(UIImage(named: "ic_search_white"), forSearchBarIcon: UISearchBarIcon.Search, state: UIControlState.Normal)
// To change placeholder text color
}
Please someone help me here.
For 1) Set the searchBarStyle property to minimal. This will provides no default background color in UIsearchBar.
2)I'm not quite sure what'd you mean by "inner screen". But if what you want is a Search Bar that can work across different view controllers, UISearchController is the one you're looking for.
You can fine the Apple sample code here

Change background color of title (only) in UIAlertController

I found the following code in How to change the background color of the UIAlertController? to change the background color of a UIAlertView:
let subview = dialog.view.subviews.first! as UIView
let alertContentView = subview.subviews.first! as UIView
subview.subviews.top!
alertContentView.backgroundColor = UIColor.whiteColor()
However, it changes the background color of the entire view. How can I change only the background color of the title?
Try this out:
let alertController = UIAlertController(title: "", message: "myMessage", preferredStyle: .Alert)
let attributedString = NSAttributedString(string: "myTitle", attributes: [
NSFontAttributeName : UIFont.systemFontOfSize(15),
NSForegroundColorAttributeName : UIColor.redColor()
])
alertController.setValue(attributedString, forKey: "attributedTitle")
try with following its works for me.
let confirmAlert = UIAlertController(title: "Application Name", message: "This is demo message.", preferredStyle: .Alert)
let attributedString = NSAttributedString(string: "Application Name", attributes: [
NSFontAttributeName : UIFont.systemFontOfSize(13),
NSForegroundColorAttributeName : UIColor.greenColor()
])
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
confirmAlert.addAction(defaultAction)
confirmAlert.setValue(attributedString, forKey: "attributedTitle")
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(confirmAlert, animated: true, completion: nil)
Sample project -> Contains latest code
Step one:
Find a subview that has the right frame!
This little function will return the frame and address of all nested subviews.
We are looking for a subview that has a height that is smaller than that of the UIAlertController and has an Origin of 0,0.
Height of my alertController was 128
func subViewStack(view:UIView, levels: [Int]) {
var currentLevels = levels
currentLevels.append(0)
for i in 0..<view.subviews.count {
currentLevels[currentLevels.count - 1] = i
let subView = view.subviews[i]
print(subView.frame, "depth:", currentLevels)
subViewStack(subView, levels: currentLevels)
}
}
This prints stuff like this:
(0.0, 0.0, 270.0, 128.0) depth: [0, 0]
//frame - firstsubview - firstsubview
This might be a good candidate:
(0.0, 0.0, 270.0, 84.0) depth: [0, 1, 0, 0]
//frame - firstsubview - secondsubiew - firstsubview - firstsubview
This translates to this:
print(alertController.view.subviews[0].subviews[1].subviews[0].subviews[0].frame)
// prints (0.0, 0.0, 270.0, 84.0) -> Bingo!
Step two:
Color that subview!
alertController.view.subviews[0].subviews[1].subviews[0].subviews[0].backgroundColor = UIColor.redColor()
If you set the colour before presenting the alertController it will crash.
Setting it in the completionHandler works.
presentViewController(alertController, animated: true) { () -> Void in
self.subViewStack(alertController.view, levels: [])
print(alertController.view.subviews[0].subviews[1].subviews[0].subviews[0].frame)
alertController.view.subviews[0].subviews[1].subviews[0].subviews[0].backgroundColor = UIColor.redColor()
}
Step three:
Make it safe!
If they ever change anything to the stack this will prevent your app from crashing. Your view might not be coloured but a crash is worse.
Extend UIView to be able to fetch subviews with one of the addresses we printed earlier. Use guardor if let to prevent accessing non-existent subviews.
extension UIView {
// use the printed addresses to access the views. This is safe even if the UIView changes in an update.
func getSubView(withAddress address:[Int]) -> UIView? {
var currentView : UIView = self
for index in address {
guard currentView.subviews.count > index else {
return nil
}
currentView = currentView.subviews[index]
}
return currentView
}
}
Step four:
Since it is not allowed to subclass UIAlertController we might try to extend it. This won't give you access to viewDidAppear so you can't display the UIAlertController animated.
protocol CustomAlert {
var view : UIView! { get }
}
extension CustomAlert {
func titleBackgroundColor(color:UIColor) {
if view == nil {
return
}
if let titleBackground = view.getSubView(withAddress: [0, 1, 0, 0]) {
titleBackground.backgroundColor = color
}
}
}
extension UIAlertController : CustomAlert {
}
This gives you both the method to find any subviews you might want to alter and a way to add a custom function for it to that class. Without subclassing.
Call titleBackgroundColor in the completionHandler of presentViewController to set the color.

Resources