UISearchBar bookmark icon is not hiding on scroll - ios

I have a UISearchBar integrated in my navigation bar with this code:
// not needed because it's default
navigationItem.hidesSearchBarWhenScrolling = true
self.navigationItem.searchController = searchController
Also I add a custom bookmark icon like this:
searchController.searchBar.setImage(icon, for: .bookmark, state: .normal)
searchController.searchBar.showsBookmarkButton = true
searchController.searchBar.layoutIfNeeded()
This produces this weird look in iOS 11.4 and 12.1.4
It seems that the text field doesn't clip the icon and also doesn't apply the fade animation like for the placeholder and the search icon.
Do you guys see some error on my side?
If not, can someone reproduce this?
Then it is a bug and I will file a radar.

Okay I worked around the issue by doing this:
searchController.searchBar.allSubviews.forEach { $0.clipsToBounds = true }
Using this extension to get all nested subviews:
extension UIView {
var allSubviews: [UIView] {
return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
}
}
But this is a bit hacky so other solutions are appreciated :)

Try to do use clipToBounds instead of layoutIfNeeded()
searchController.searchBar.setImage(icon, for: .bookmark, state: .normal)
searchController.searchBar.showsBookmarkButton = true
searchController.searchBar.clipsToBounds = true

Related

ios14 - Button Title and Image Not getting Displayed

Button title and image not geting rendered. Works fine in iOS13 and below, but after updating to Xcode12.0.1 and iOS14, it is not working.
let viewAllButton: RoundedButton = {
let button = RoundedButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .red
button.setTitle("View all", for: .normal)
button.setTitleColor(.white, for: .normal)
return button
}()
class RoundedButton: UIButton {
override func draw(_ rect: CGRect) {
super.draw(rect)
self.layer.cornerRadius = self.bounds.height / 2
self.layer.masksToBounds = true
}
}
Try to set the title color before setting setTitle. (I had some issues with something like this too).
Xcode 12.0.1, iOS 14, Swift 5.3
Actually the problem was in the redundant code which was lying around in my codebase.
extension UIButton {
open override func layoutSubviews() {
super.layoutSubviews()
}
}
I just had this sitting in my extensions. This didnt cause any issues in iOS 13 and below.
Steps to reproduce:
Add above code in your project.
Run the project(Release or Debug Mode) the project in your simulator(Button renders properly) and Kill the app.
Now, Launch the app by clicking App icon and you will see Button contents disappear.
Strange bug, i hope it save someones time.

UISearchController UITextField background not showing white after update to iOS 13

after the new update, I've noticed that my UISearchController isn't acting like it did before.
First, the UITextField no longer has a white background. I was trying to figure out why this is happening, but have had no luck. This is how I'm creating it.
var resultsSearchController = UISearchController(searchResultsController: nil)
self.resultsSearchController.delegate = self
let searchBar = self.resultsSearchController.searchBar
self.resultsSearchController.searchResultsUpdater = self
self.resultsSearchController.obscuresBackgroundDuringPresentation = false
self.resultsSearchController.extendedLayoutIncludesOpaqueBars = true
searchBar.sizeToFit()
self.tableView.tableHeaderView = searchBar
searchBar.placeholder = "Catalog Search"
searchBar.barTintColor = UIColor.darkAqua
As best I can tell, the default UITextField default appearance seems to have changed is my guess.
Just wondering how to change it back, if possible.
---EDIT---
I attempted to do as suggested and added this code to viewDidLoad()
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = UIUserInterfaceStyle.light
} else {
// Fallback on earlier versions
}
as well as adding this to the UISearchController code
if #available(iOS 13.0, *) {
self.resultsSearchController.overrideUserInterfaceStyle = UIUserInterfaceStyle.light
} else {
// Fallback on earlier versions
}
No combination of both of those codes was able to change it so that that UITextField presented as expected.
Well, turns out I'm dumb. Its much easier that I was trying to make it for myself.
All I ended up doing was accessing the UITextField in the searchBar for the UISearchController
Adding this:
searchBar.searchTextField.backgroundColor = UIColor.white
With the code I already had seems to make it look normal again.

Change the button titles on SLComposeServiceViewController?

Is there a way to change the button titles on the SLComposeServiceViewController? I tried to change the bar button items on the navigation item, but those aren't the right buttons.
Simply accessing from navigationController!.navigationBar does the charm. The following should help.
self.navigationController!.navigationBar.topItem!.rightBarButtonItem!.title = "Save"
I just found a way to do it:
class CustomServiceViewController: SLComposeServiceViewController {
override func viewDidLoad() {
let navigationBar = view.subviews.first?.subviews?.last? as? UINavigationBar
let postButton = navigationBar?.subviews.last? as? UIButton
let cancelButton = navigationBar?.subviews.last? as? UIButton
postButton?.setTitle("Done", forState: .Normal)
}
}
Be warned - it's a fragile solution, based on undocumented internals of SLComposeServiceViewController
The answer by Kasztan no longer works with the latest iOS; here is the latest fragile solution..
class CustomServiceViewController: SLComposeServiceViewController {
override func viewDidLoad() {
let navigationBar = view.subviews.last?.subviews?.last? as? UINavigationBar
let postButton = navigationBar?.subviews[3] as? UIButton
postButton?.setTitle("Done", forState: .Normal)
}
}
EDIT #3: Solution working on iOS 9 and iOS 10 beta
The previous approach stopped working with iOS 9, but the following seems to work again (tested on iOS 9 and 10 beta 2):
1) First, you need to add a UIFont class extension to check if a button font is bold (this, because the Post button is always bold); here's how.
2) Then, in viewDidAppear:, we need the following code (an updated version of the code I wrote in Edit 2):
if let navigationBar = self.navigationController?.navigationBar {
// First, let's set backgroundColor and tintColor for our share extension bar buttons
navigationBar.backgroundColor = UIColor.darkGrayColor()
navigationBar.tintColor = UIColor.whiteColor()
if let navBarSubviews = navigationBar.subviews as? [UIView] {
for eachView in navBarSubviews {
if let navBarButton = eachView as? UIButton {
// Second, let's set our custom titles for both buttons (Cancel and Post); checking for the title wouldn't work for localized devices, so we check if the button is bold (Post) or not (Cancel) via the UIFont class extension above.
let buttonFont : UIFont? = navBarButton.titleLabel?.font
if buttonFont?.isBold == true {
navBarButton.setTitle("Save", forState: .Normal)
} else {
navBarButton.setTitle("Cancel", forState: .Normal)
}
}
}
}
}
Of course, this works now, but it will probably break again in the future...
EDIT #2: I made it work on a device with iOS 8.4 :)
Turns out I was wrong, after spending an unreasonable amount of time on this I've been able to both change the color of the buttons and their text.
Here's my code, that needs to be put inside ViedDidAppear() (if you place it in viewDidLoad() it won't work!):
if let navigationBar = self.navigationController?.navigationBar {
// First, let's set backgroundColor and tintColor for our share extension bar buttons
navigationBar.backgroundColor = UIColor.darkGrayColor()
navigationBar.tintColor = UIColor.whiteColor()
if let navBarSubviews = navigationBar.subviews as? [UIView] {
for eachView in navBarSubviews {
if let navBarButton = eachView as? UIButton {
// Second, let's set our custom titles for both buttons (Cancel and Post); checking for the title wouldn't work on localized devices, so we check if the current button is emphasized (Post) or not (Cancel) via an UIFontDescriptor.
let fontDescriptor : UIFontDescriptor? = navBarButton.titleLabel?.font.fontDescriptor()
if let descriptor = fontDescriptor {
let fontAttributes : NSDictionary = descriptor.fontAttributes()
var buttonFontIsEmphasized : Bool? = fontAttributes["NSCTFontUIUsageAttribute"]?.isEqualToString("CTFontEmphasizedUsage")
if buttonFontIsEmphasized == true {
navBarButton.setTitle("Save", forState: .Normal)
} else {
navBarButton.setTitle("Cancel", forState: .Normal)
}
}
}
}
}
}
Still, I'm not sure this should be done on a shipping app nor it would pass App Review (it should, though, because it doesn't mess with private APIs).
Also, it should be noted that this could break anytime, even though it shouldn't be as easily breakable as the previous solutions (it iterates through the subviews and attempts downcasting them, so a small change in the view hierarchy shouldn't render it useless); my expectations is that, even if in the future it stops working, it shouldn't crash the Share Extension.
Original answer
I believe what you (and I) want to do is not possible anymore, possibly by design. Here's why:
Inspired by #Kasztan and #Paito answers, I tried this in viewDidLoad() of my ShareViewController:
for eachView in view.subviews {
println("1")
for eachSubView in eachView.subviews {
println("2")
if let navigationBarView = eachSubView as? UINavigationBar {
println("3")
for eachNavBarSubView in navigationBarView.subviews {
println("4")
if let navBarButton = eachNavBarSubView as? UIButton {
println("5")
println(navBarButton.titleForState(.Normal))
navBarButton.setTitleColor(UIColor.redColor(), forState: .Normal)
navBarButton.setTitle("My text", forState: .Normal)
navBarButton.tintColor = UIColor.redColor()
navBarButton.setNeedsLayout()
navBarButton.layoutIfNeeded()
}
}
}
}
}
Not that I believe something like this should ship in an app, but as a proof of concept this should have worked and, in theory, should be a bit less breakable with future releases of the OS.
Except, it didn't work for me on iOS 8.4: I see all the checkpoint messages logged, from 1 to 5, some of them multiple times (as it should be, since the code tries every possible subview).
The "5" message is logged twice, which makes sense since it means that it successfully downcast both the buttons, Cancel and Post, but not the text nor the color is changed from the default.
My conclusion is that something in Apple's code prevents us to change the appearance of those buttons.
Of course, if anyone finds a solution, I'd be glad to downvote my own answer (if it can be done, I'm note sure) ;)
EDIT #1: One last check, I logged the button title too, after the buttons downcast (5), and yes, I got Optional("Cancel") and Optional("Post") in the console, so this solution gets the right buttons, but they can't be edited.

Remove clear button in UISearchBar

I want to remove the clear button (gray x) from the UISearchBar. I tried to do it like described in this answer, but it doesn't work.
I translated the Objective-C code from the answer and the comment below to following Swift code:
for subview in searchBar.subviews {
configureSearchBarView(subview as UIView)
}
func configureSearchBarView(view: UIView) {
for subview in view.subviews {
self.configureSearchBarView(subview as UIView)
}
if subview.conformsToProtocol(UITextInputTraits) {
var clearView = view as UITextField
clearView.clearButtonMode = UITextFieldViewMode.Never
}
}
Unfortunatly this doesn't remove the clear button.
Another answer suggests to work with appearanceWhenContainedIn, but this method doesn't seem to be implemented in Swift.
Any ideas?
Swift
I found a better way that completely removes the button, using clearButtonMode = .never
let searchBarStyle = searchBar.value(forKey: "searchField") as? UITextField
searchBarStyle?.clearButtonMode = .never
Swift 5
Tested on iOS 13
As I pointed out in another answer, this is the one liner working for me to make it disappear completely:
searchBar.searchTextField.clearButtonMode = .never
However you may also set it to .whileEditing if you only want it displayed when the user is typing and then make it disappear when the search bar loses focus.
I found a solution. It is possible to exchange the clear button with a custom image:
UISearchBar.appearance().setImage(UIImage(named: "emptyImg"), forSearchBarIcon: UISearchBarIcon.Clear, state: UIControlState.Normal)
UISearchBar.appearance().setImage(UIImage(named: "emptyImg"), forSearchBarIcon: UISearchBarIcon.Clear, state: UIControlState.Highlighted)
emptyImg is an png that contains one white pixel. Found in this answer

Make UISearchBar background clear

I am not able to clear search bar I have tried to make it clear by setting its background color clear and I have also placed one image under searchbar
I have also made clear background of searchbar
for (UIView *subview in self.searchBarHome.subviews) {
if ([subview isKindOfClass:NSClassFromString(#"UISearchBarBackground")]) {
[subview removeFromSuperview];//please help me to make clear background of uisearchbar
break;
}
}
[self.searchBarHome setBackgroundColor:[UIColor clearColor]];
For iOS7+, all you need to do is:
[self.searchBarHome setBackgroundColor:[UIColor clearColor]];
[self.searchBarHome setBarTintColor:[UIColor clearColor]]; //this is what you want
NOTE: This will not work for iOS6
For iOS6+, the following will take care of it even in iOS7:
[self.searchBarHome setBackgroundColor:[UIColor clearColor]];
[self.searchBarHome setBackgroundImage:[UIImage new]];
[self.searchBarHome setTranslucent:YES];
Here's my solution in Swift 3 (a combo of Scarafone's and Andrew2M's answers):
for view in searchBar.subviews.last!.subviews {
if type(of: view) == NSClassFromString("UISearchBarBackground"){
view.alpha = 0.0
}
}
or alternatively:
searchBar.barTintColor = UIColor.clear
searchBar.backgroundColor = UIColor.clear
searchBar.isTranslucent = true
searchBar.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
UISearchBar includes two subviews,they are 'UISearchBarBackground' and 'UITextField'. In IOS8, you need to remove 'UISearchBarBackground' to clear it.
for (UIView *subview in [[yourSearchBar.subviews lastObject] subviews]) {
if ([subview isKindOfClass:NSClassFromString(#"UISearchBarBackground")]) {
[subview removeFromSuperview];
break;
}
}
Removing the UISearchBarBackground may result in unknown behavior particularly if as you begin to use the element in more advanced ways.
Ruozi's answer was my preferred solution for grabbing the UISearchBarBackground element however I would suggest setting the alpha to 0 rather than removing the view with the following:
for (UIView *subview in [[self.searchBar.subviews lastObject] subviews]) {
if ([subview isKindOfClass:NSClassFromString(#"UISearchBarBackground")]) {
[subview setAlpha:0.0];
break;
}
}
Add the above code to your UIViewController subclass that contains an IBOutlet *searchBar. This code snippet will acheive a transparent background for both iOS8 and iOS9.
In addition it may be better design decision to include this code in your UISearchBar subclass in order to avoid cluttering your viewController.
For anyone trying to find a non hacky solution to this, just set the background image to nil on your Storyboard/Xib file. Literally, simply write nil in the background image field of the UISearchBar.
If you want to change this...
To this...
Use:
self.searchBar.searchTextField.backgroundColor = .clear
Simply use searchBar.searchBarStyle = .minimal works for me.
I had used many workarounds for a long time, until i discover this property accidentally, and works like a charm.
For anyone coming here and not being able to make the remove or alpha solution work for you, make sure you have your searchBar SearchStyle NOT on MINIMALIST. Change it to Default and use any of the codes provided here
For iOS 13+ we can simply use:
searchBar.searchTextField.backgroundColor = .clear
but other solutions like:
searchBar.setSearchFieldBackgroundImage(UIImage(), for: .normal)
searchBar.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
creates a background on the searchBar which hides the placeholder and doesn't allow to interact with the searchBar. The solution for iOS 12 and below is to use an approach similar to #Get Schwifty.
Setting the backgroundColor and barTintColor did not work me iOS 9+, left me with a black background. However Ruozi solution will work. Here is a swift version of the same code.
for view in _searchBar.subviews.last!.subviews {
if view.isKindOfClass(NSClassFromString("UISearchBarBackground")!) {
view.removeFromSuperview()
}
}
My solution working on Swift 4.1 | iOS 11.4 where I change the background and text colors of UISearchBar
The key is to find the UISearchBar's UITextField component and to change its parameters:
let searchTextField = searchBar.value(forKey: "searchField") as? UITextField
searchTextField?.textColor = UIColor.white
searchTextField?.backgroundColor = UIColor.red
searchTextField?.attributedPlaceholder = NSAttributedString(string: "Your Placeholder", attributes: [NSAttributedStringKey.foregroundColor: UIColor.white.withAlphaComponent(0.5)])
Then I set the "search" and the "clear" image
//search image
searchBar.setImage(UIImage(named: "icon_search"), for: UISearchBarIcon.search, state: .normal)
//clear image
searchBar.setImage(UIImage(named: "icon_search_clear"), for: UISearchBarIcon.clear, state: .normal)
extension UISearchBar {
func changeSearchBarColor(fieldColor: UIColor, backColor: UIColor, borderColor: UIColor?) {
UIGraphicsBeginImageContext(bounds.size)
backColor.setFill()
UIBezierPath(rect: bounds).fill()
setBackgroundImage(UIGraphicsGetImageFromCurrentImageContext()!, for: UIBarPosition.any, barMetrics: .default)
let newBounds = bounds.insetBy(dx: 0, dy: 8)
fieldColor.setFill()
let path = UIBezierPath(roundedRect: newBounds, cornerRadius: newBounds.height / 2)
if let borderColor = borderColor {
borderColor.setStroke()
path.lineWidth = 1 / UIScreen.main.scale
path.stroke()
}
path.fill()
setSearchFieldBackgroundImage(UIGraphicsGetImageFromCurrentImageContext()!, for: UIControlState.normal)
UIGraphicsEndImageContext()
}
}
Subclass the uisearchbar and override initWithFrame and initWithCoder method and set the background color as
// For searchbar created programmatically
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self configureSetup];
}
return self;
}
// For search bar created on xib
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self configureSetup];
}
return self;
}
- (void)configureSetup
{
self.backgroundColor = [UIColor clearColor];
}
and use the subclassed searchbar instead in place where you specified UISearchBar
Oh man! I've been looking everywhere for a solution and trying multiple things. As #gmogames had mentoned above you can use any of these answers, but make sure the Search Bar Style is set to default or it won't work. So, to clarify on the solution this is what worked for me:
Set Search Bar Style to default in the Storyboard or code(wherever you created the UISearchBar.
Then add this code to your view controller:
mySearchBar.searchTextField.backgroundColor = .clear
For iOS 12, swift 4.x
A bit too hacky but Debug view hierarchy shows _UISearchBarSearchFieldBackgroundView is responsible to the background color. To get to it, ...
for subview in searchBar.subviews.last!.subviews {
if subview.isKind(of: NSClassFromString("UISearchBarTextField")!) {
for v in subview.subviews {
if v.isKind(of: NSClassFromString("_UISearchBarSearchFieldBackgroundView")!) {
v.removeFromSuperview()
}
}
}
}
This is all too hacky. I prefer asking designer to accept Apple default color.
searchBarStyle = .default
searchTextField.backgroundColor = .yourColor
With this combination, you can remove the gray background of searchTextField and set yours!
I tried #Johnson's answer however it didn't work well for me. (iOS 13, swift 5)
However the logic behind the solution seemed well. So tried to iterate all subviews of the searchBar and it worked!
I hope, it will help :)
Solution also worked for me in iOS 12.4 - swift 5
Remove Background Method
private func removeBackground(from searchBar: UISearchBar) {
guard let BackgroundType = NSClassFromString("_UISearchBarSearchFieldBackgroundView") else { return }
for v in searchBar.allSubViewsOf(type: UIView.self) where v.isKind(of: BackgroundType){
v.removeFromSuperview()
}
}
Helper Methods -> UIViewExtension
extension UIView {
public func allSubViewsOf<T : UIView>(type : T.Type) -> [T]{
var all = [T]()
func getSubview(view: UIView) {
if let wrapped = view as? T, wrapped != self{
all.append(wrapped)
}
guard view.subviews.count>0 else { return }
view.subviews.forEach{ getSubview(view: $0) }
}
getSubview(view: self)
return all
}
}
Swift 5.4, iOS 14.5.
Here is the default UISearchBar, on top of a white background:
And the same setup on top of a grey background:
To make the background of the search bar clear, set the search bar style to minimal. This removes the search bar's background and makes the search field translucent.
searchBar.searchBarStyle = .minimal
Then, set the search text field's background color to whatever you want. In this case, I've set it to white.
searchBar.searchTextField.backgroundColor = .white
You can see the result of this here:
If you set searchBar.searchTextField.backgroundColor = .red:

Resources