UITextField placeholder text: adjust to fit - ios

I have UITextField with longer text in it set as placeholder. What I want is for this placeholder text to adjust its font size when the width of field is too small.
I already tried this solution described in other posts (programmatically and in IB)
self.fieldTest.adjustsFontSizeToFitWidth = true
self.fieldTest.minimumFontSize = 10.0
What am I missing here?

You can create a subclass of UITextField:
class AutoSizeTextField: UITextField {
override func layoutSubviews() {
super.layoutSubviews()
for subview in subviews {
if let label = subview as? UILabel {
label.minimumScaleFactor = 0.3
label.adjustsFontSizeToFitWidth = true
}
}
}
}
Or you can just add some code in your view controller's viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
for subview in fieldTest.subviews {
if let label = subview as? UILabel {
label.minimumScaleFactor = 0.3
label.adjustsFontSizeToFitWidth = true
}
}
}

Here you go :
_myTextField.placeholder = #"SomeTextSomeTextSome";
UILabel *label = [_myTextField valueForKey:#"_placeholderLabel"];
label.adjustsFontSizeToFitWidth = YES;
Cheers!!

Based on answer 9: in Storyboard go to the identity inspector tab of the text field element, and under the "User Defined Runtime Attributes" section, add the following:

Here's a solution that depends on the undocumented fact that the UITextField has a child UILabel (actually UITextFieldLabel) to render the placeholder. The advantage of this solution over some others is that it degrades gracefully should Apple's implementation change. It also doesn't make assumptions about the existence of undocumented ivars.
Basically we extend UILabel via a category. If we see ourselves being parented to a UITextField then we turn on adjustFontSizeToFitWidth.
#interface UILabel (TS)
#end
#implementation UILabel (TS)
- (void) didMoveToSuperview
{
[super didMoveToSuperview];
if ( [self.superview isKindOfClass: [UITextField class]] ) {
self.adjustsFontSizeToFitWidth = YES;
}
}
#end

After reviewing the class reference for UITextField's, it seems that adjustsFontSizeToFitWidth only affects the the text property of the UITextField and not the placeholder property. While I don't know off the top of my head a way to get the placeholder to respond to adjustsFontSizeToFitWidth, I can suggest two hacky ideas that may give you the appearance that you want. Just be aware that I'm not near a Mac right now so I haven't tested these ideas:
1:
Since a placeholder is just text with a 70% gray color, you could set the label's text property to be whatever you need it to be, and then implement the UITextFieldDelegate's textFieldShouldBeginEditing method to clear the text and change the color back to normal. You would also have to implement the textFieldShouldClear and textFieldDidEndEditing methods to replace the pseudo-placeholder back in the UITextField and change the text color back to 70% gray.
2:
In viewWillAppear you could set the UITextField's text to what your placeholder should be, create a UIFont object and set it equal to the UITextField's font property, clear the UITextField's text, and set the placeholder to be an NSAttributedString with the font object as a property. Here's an example of what I mean:
-(void)viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
someTextField.adjustsFontSizeToFitWidth = YES;
someTextField.text = #"placeholderText";
UIFont *font = someTextField.font;
someTextField.text = nil;
NSDictionary *attributes = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
NSAttributedString *placeholderString= [[NSAttributedString alloc] initWithString:#"placeholderText" attributes:attributes];
someTextField.placeholder = placeholderString;
}
Edit: Just noticed the swift tag. I wrote my code in Objective-C, but you should be able to easily translate it to Swift.

Try using attributed placeholder instead of normal place holder
Try this
let attributedplaceholder = NSAttributedString(string: "placeholdertext", attributes: [NSFontAttributeName: UIFont(name: "FontName", size: 10)!])
self.fieldTest.attributedPlaceholder = attributedplaceholder
You can add additional attributes to the placeholder like textcolor and other

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
for subView in fieldTest.subviews{
if subView .isKind(of: UILabel.self){
let label = subView as! UILabel
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.2
}
}
}

Swift
Feel free to improve the extension - pretty sure there is a more elegant way to iterate over the subviews.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tfCountryCode.allSubviewsOfClass(UILabel.self).forEach {
$0.adjustsFontSizeToFitWidth = true
$0.minimumScaleFactor = 0.5
}
}
extension UIView {
func allSubviewsOfClass<K: UIView>(_ clazz: K.Type) -> [K] {
var matches = [K]()
if subviews.isEmpty { return matches }
matches.append(contentsOf: subviews.filter { $0 is K } as! [K])
let matchesInSubviews = subviews.flatMap { return $0.allSubviewsOfClass(clazz) }
matches.append(contentsOf: matchesInSubviews.flatMap { $0 })
return matches
}
}

My solution:
if let label = yourTextField.value(forKey: "placeholderLabel") as? UILabel {
label.adjustsFontSizeToFitWidth = true
}
And don't forget this:
yourTextField.minimumFontSize = 10 // 10 is an example, pass your minimumFontSize
Tested and run perfectly.

You can use these two solution:
1.If You have fixed font size if Textfield size is less than placeholder text:
let placeholderString = testTF.placeholder
print(placeholderString!)
let font = UIFont(name: (testTF.font?.fontName)!, size: 16)!
let fontAttributes = [NSFontAttributeName: font]
let size = (placeholderString! as NSString).sizeWithAttributes(fontAttributes)
print(size)
print(testTF.frame.size.width)
if(size.width > testTF.frame.size.width)
{
let font = UIFont(name: (testTF.font?.fontName)!, size: 4)!
let attributes = [
NSForegroundColorAttributeName: UIColor.lightGrayColor(),
NSFontAttributeName : font]
testTF.attributedPlaceholder = NSAttributedString(string: placeholderString!,
attributes:attributes)
}
else
{
let attributes = [
NSForegroundColorAttributeName: UIColor.lightGrayColor(),
NSFontAttributeName : font]
testTF.attributedPlaceholder = NSAttributedString(string: placeholderString!,
attributes:attributes)
}
2) If you want dynamic font size than you just check the above condition for width of textfield and placeholder text size.width. if the placeholder text size is greater than textfield size than create one label inside the textfield and set minimum font on that.
if(size.width > testTF.frame.size.width)
{
placeholder = UILabel(frame: CGRect( x: 0, y: 0, width: testTF.bounds.width, height: testTF.bounds.height))
placeholder.text = placeholderString
placeholder.numberOfLines = 1;
//placeholder.minimumScaleFactor = 8;
placeholder.adjustsFontSizeToFitWidth = true
placeholder.textColor = UIColor.grayColor()
placeholder.hidden = !testTF.text!.isEmpty
placeholder.textAlignment = .Center
testTF.addSubview(placeholder)
}

In Swift
yourTextField.subviews
.filter { $0 is UILabel }
.flatMap { $0 as? UILabel }
.forEach {
$0.adjustsFontSizeToFitWidth = true
$0.minimumScaleFactor = 0.5
}
it works :)

Try this: It's working fine without any issues:
yourTextField.placeholder = "Adjust placeHolder text for textFields iOS"
let label = yourTextField.value(forKey: "_placeholderLabel") as? UILabel
label?.adjustsFontSizeToFitWidth = true

The whole approach or shrinking font size to fit is misguided in the day and age of accessibility.
Firstly you have zero business specifying text size in the first place, let alone shrinking that further: you have to rely on the accessibility API.
Thus if the placeholder is likely to not fit it has to be placed
as a UILabel preceding the UITextField. The placeholders are supposed to be SHORT and fit without clippage.
To determine if it's clipped I guess you could use - (CGRect)placeholderRectForBounds:(CGRect)bounds; but then you are in
murky waters of using an API which Apple says you should only override (but not call yourself even though it's probably meaningful and safe
within the confines of didlayoutsubviews method[s])
If placeholder text is dynamic (server served) dump it into a UILabel.

Related

How to add a small image on right side of text whatever font size it is?

I'm trying to achieve this on iOS using Swift and storyboards:
Notice that the image size on the right never changes, only the text font size changes depending on the user's default font size. I want the image to remain at the end of the label.
Does anyone have an idea how I can do it?
The simplest is to create an extension on UILabel and append whatever image/icon to the end of the attributedText, something along these lines:
extension UILabel {
func appendIcon() {
let iconAttachment = NSTextAttachment()
iconAttachment.image = UIImage(systemName: "info.circle.fill")
let iconString = NSAttributedString(attachment: iconAttachment)
guard let attributedText = self.attributedText else { return }
let currentString = NSMutableAttributedString(attributedString: attributedText)
currentString.append(iconString)
self.attributedText = currentString
}
}
And then call that programmatically.
label.appendIcon() // will need to create outlet

Change font of prompt in UINavigationController

can someone help me out in changing font, size and color in prompt string on my NavigationController?
In the attachment, I want to modify "Consulenze" string.
Thank you everybody
Edit: I already tried the solution found here but no results.
You can try following ways:
1) In viewDidLoad of your ViewController add this lines:
self.navigationController?.navigationBar.tintColor = UIColor.white
let navigationTitleFont = UIFont(name: "Avenir", size: 20)!
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.font: navigationTitleFont]
2) You can create completely custom nav bar, just add UIView to the top your view and add all necessary elements - buttons, labels, etc.
Simply add this code in your ViewController. You can change both the Prompt text and color by using this code -
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
for view in self.navigationController?.navigationBar.subviews ?? [] {
let subviews = view.subviews
if subviews.count > 0, let label = subviews[0] as? UILabel {
label.textColor = UIColor.red
label.font = UIFont.systemFont(ofSize: 30)
}
}
}
}
OUTPUT -
Additional -

How to change UITableViewRowAction title color?

Am using UITableViewRowAction in "editActionsForRowAtIndexPath" method. I can change the backgroundcolor of UITableViewRowAction, but am not able to change the title color. But I would like to change the color of the UITableViewRowAction. Any inputs on this regard will be appreciable.
There is one way that can achieve what you are looking for. But it is little tricky though.
Example of result:
The idea of this trick is that you can actually modify background color. This means that you can set UIColor's +colorWithPatternImage: and set an bitmap image that match desired styling. This can be achieved either by creating image using graphic editor or by rendering it using for example Core Graphics. The only problem with this solution is, that you have to mimic original title length with you specific text attributes to make it work properly and also you must set title for table view row action as string of white spaces so that table view cell will prepare enough space for you "custom action button". Creating static png assets in photoshop may be inappropriate if you use variable cell rows.
This is category for NSString that creates string of empty spaces that will create space for your custom button and second will generate bitmap image that will be placed as background pattern image. For parameters you must set text attributes for original title, that is basically #{NSFontAttributeName: [UIFont systemFontOfSize:18]}, than your desired text attributes. Maybe there is better way to achieve this :)
#implementation NSString (WhiteSpace)
- (NSString *)whitespaceReplacementWithSystemAttributes:(NSDictionary *)systemAttributes newAttributes:(NSDictionary *)newAttributes
{
NSString *stringTitle = self;
NSMutableString *stringTitleWS = [[NSMutableString alloc] initWithString:#""];
CGFloat diff = 0;
CGSize stringTitleSize = [stringTitle sizeWithAttributes:newAttributes];
CGSize stringTitleWSSize;
NSDictionary *originalAttributes = systemAttributes;
do {
[stringTitleWS appendString:#" "];
stringTitleWSSize = [stringTitleWS sizeWithAttributes:originalAttributes];
diff = (stringTitleSize.width - stringTitleWSSize.width);
if (diff <= 1.5) {
break;
}
}
while (diff > 0);
return [stringTitleWS copy];
}
#end
Second important part is code that renders bitmap that can be used as pattern image for UITableViewRowAction's backgroundColor.
- (UIImage *)imageForTableViewRowActionWithTitle:(NSString *)title textAttributes:(NSDictionary *)attributes backgroundColor:(UIColor *)color cellHeight:(CGFloat)cellHeight
{
NSString *titleString = title;
NSDictionary *originalAttributes = #{NSFontAttributeName: [UIFont systemFontOfSize:18]};
CGSize originalSize = [titleString sizeWithAttributes:originalAttributes];
CGSize newSize = CGSizeMake(originalSize.width + kSystemTextPadding + kSystemTextPadding, originalSize.height);
CGRect drawingRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, cellHeight));
UIGraphicsBeginImageContextWithOptions(drawingRect.size, YES, [UIScreen mainScreen].nativeScale);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(contextRef, color.CGColor);
CGContextFillRect(contextRef, drawingRect);
UILabel *label = [[UILabel alloc] initWithFrame:drawingRect];
label.textAlignment = NSTextAlignmentCenter;
label.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attributes];
[label drawTextInRect:drawingRect];
//This is other way how to render string
// CGSize size = [titleString sizeWithAttributes:attributes];
// CGFloat x = (drawingRect.size.width - size.width)/2;
// CGFloat y = (drawingRect.size.height - size.height)/2;
// drawingRect.origin = CGPointMake(x, y);
// [titleString drawInRect:drawingRect withAttributes:attributes];
UIImage *returningImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return returningImage;
}
And then when you are creating your row action you can do something like this:
NSDictionary *systemAttributes = #{ NSFontAttributeName: [UIFont systemFontOfSize:18] };
NSDictionary *newAttributes = #{NSFontAttributeName: [UIFont fontWithName:#"Any font" size:15]};
NSString *actionTitle = #"Delete";
NSString *titleWhiteSpaced = [actionTitle whitespaceReplacementWithSystemAttributes:systemTextFontAttributes newAttributes:newAttributes];
UITableViewRowAction *rowAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:titleWhiteSpaced handle:NULL];
UIImage *patternImage = [self imageForTableViewRowActionWithTitle:actionTitle textAttributes:newAttributes backgroundColor:[UIColor redColor] cellHeight:50];
rowAction.backgroundColor = [UIColor colorWithPatternImage:patternImage];
This solution is obviously hacky but you can achieve desired results without using private APIs and in future if something breaks, this will not break your app. Only button will look inappropriate.
Example of result:
This code has of course a lot of space for improvements, any suggestions are appreciated.
I'm afraid that there's no way to change the title color of the UITableViewRowAction.
The only things you can change on the action are:
backgroundColor
style (destructive (red backgroundcolor, ...)
title
For more info, please refer to the Apple Doc UITableViewRowAction
Swift
No need to mess with UIButton.appearance...
Put this in your cell's class and change UITableViewCellActionButton according to your needs.
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
for subview2 in subview.subviews {
if (String(subview2).rangeOfString("UITableViewCellActionButton") != nil) {
for view in subview2.subviews {
if (String(view).rangeOfString("UIButtonLabel") != nil) {
if let label = view as? UILabel {
label.textColor = YOUR COLOUR
}
}
}
}
}
}
}
So there still is no public api to change textColor or font to the cells action in the iOS 13 days.
Working solution for Swift 5.3, iOS 14
Hacks from answers in the thread have very unreliable results but I have managed to get my version of the hack working.
1. Getting the label
Here is my simplified way of accessing the actions UILabel:
extension UITableViewCell {
var cellActionButtonLabel: UILabel? {
superview?.subviews
.filter { String(describing: $0).range(of: "UISwipeActionPullView") != nil }
.flatMap { $0.subviews }
.filter { String(describing: $0).range(of: "UISwipeActionStandardButton") != nil }
.flatMap { $0.subviews }
.compactMap { $0 as? UILabel }.first
}
}
2. Updating the label on layout changes
Next, overriding layoutSubviews() in my UITableViewCell subclass wasn't enough so additionally I had to override layoutIfNeeded() for the hack to work.
Note that it's important to override both of them!
override func layoutSubviews() {
super.layoutSubviews()
cellActionButtonLabel?.textColor = .black // you color goes here
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
cellActionButtonLabel?.textColor = .black // you color goes here
}
3. Triggering extra layout refresh
The last piece of the puzzle is to schedule an additional refresh of the color, so we cover all of those cases where for some reason above functions would not get called. The best place for doing so is UITableViewDelegate method ..., willBeginEditingRowAt: ...
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
tableView.cellForRow(at: indexPath)?.layoutIfNeeded()
}
}
A delayed layout refresh trigger does the trick of letting the UIKit configure the cell first, so we can jump right after with the customisation. 0.01 was just enough for me but i can probably vary from case to case.
4. Profit
Now you can customise the label anyway you want! Change the text, its color, font or add a subview!
It goes without saying that this can break -anytime- if Apple will decide to change their private implementation.
There is indeed a way to change the title color of the UITableViewRowAction. It's a button. You can use the UIButton appearance proxy:
[[UIButton appearance] setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];
Lee Andrew's answer in Swift 3 / Swift 4:
class MyCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
for sub in subview.subviews {
if String(describing: sub).range(of: "UITableViewCellActionButton") != nil {
for view in sub.subviews {
if String(describing: view).range(of: "UIButtonLabel") != nil {
if let label = view as? UILabel {
label.textColor = UIColor.black
}
}
}
}
}
}
}
}
iOS 11 solution:
Create UITableViewCell extension like this:
extension UITableViewCell {
/// Returns label of cell action button.
///
/// Use this property to set cell action button label color.
var cellActionButtonLabel: UILabel? {
for subview in self.superview?.subviews ?? [] {
if String(describing: subview).range(of: "UISwipeActionPullView") != nil {
for view in subview.subviews {
if String(describing: view).range(of: "UISwipeActionStandardButton") != nil {
for sub in view.subviews {
if let label = sub as? UILabel {
return label
}
}
}
}
}
}
return nil
}
}
And write this in your UITableViewCell
override func layoutSubviews() {
super.layoutSubviews()
cellActionButtonLabel?.textColor = .red
}
But there is a bug - if you pull the cell fast this code sometimes doesn't change the color.
You can add these two functions in your UITableViewCell subclass and call the setActionButtonsTitleColor function to set action buttons' title color.
func setActionButtonsTitleColor(color: UIColor) {
let actionButtons: [UIButton] = self.getActionButtons()
for actionButton in actionButtons {
actionButton.setTitleColor(color, for: .normal)
}
}
func getActionButtons() -> [UIButton] {
let actionButtons: [UIButton] = self.subviews.map {
(view: UIView) -> [UIView] in
return view.subviews
}
.joined()
.filter {
(view: UIView) -> Bool in
return String(describing: view).contains("UITableViewCellActionButton")
}.flatMap {
(view: UIView) -> UIButton? in
return view as? UIButton
}
return actionButtons
}
Thanks #Witek for sharing.
Your solution works but it's not stable. So, I try to update your code, and now it's working very well.
Put this code below in your UITableViewCell
override func layoutSubviews() {
super.layoutSubviews()
if let button = actionButton {
button.setTitleColor(.black, for: .normal)
}
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
if let button = actionButton {
button.setTitleColor(.black, for: .normal)
}
}
var actionButton: UIButton? {
superview?.subviews
.filter({ String(describing: $0).range(of: "UISwipeActionPullView") != nil })
.flatMap({ $0.subviews })
.filter({ String(describing: $0).range(of: "UISwipeActionStandardButton") != nil })
.compactMap { $0 as? UIButton }.first
}
-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewRowAction *editAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:#"edit" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
{
// Action something here
}];
editAction.backgroundColor = [UIColor whiteColor];
[[UIButton appearance] setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
return #[editAction];
IF someone is still looking for an alternative solution:
You can use the swipecellkit pod
https://github.com/SwipeCellKit/SwipeCellKit
This lets you customize the label color, image color and even the entire background while perserving the actual look and feel of the native implementation.

How to left align UISearchBar placeholder text

I need to make a custom search bar like this. The problem I am having is left aligning the placeholder text, as well as placing the search icon in the right. I have a png of the search icon that I have tried to use in an UIImageView, and set that UIImageView as the rightView of the UISearchBar's UITextField. This solution has not worked, and I ran out of ideas. Does anyone have a solution?
Don't use a UISearchBar if you need to do these kinds of customizations. You'll have to make your own using a UITextField and a UIImageView, and responding to the delegate calls.
Based in Mohittomar answer, as asked by #DevC to add spaces to the end of the place holder, here the code in swift:
I subclass placeholder in the UISearchBar, after set the value, I check if the last character is a space. then get the size of one space and how many spaces I need to add to align left.
class SearchBar: UISearchBar, UISearchBarDelegate {
override var placeholder:String? {
didSet {
if let text = placeholder {
if text.last != " " {
// get the font attribute
let attr = UITextField.s_appearanceWhenContainedIn(SearchBar).defaultTextAttributes
// define a max size
let maxSize = CGSizeMake(UIScreen.mainScreen().bounds.size.width - 60, 40)
// get the size of the text
var widthText = text.boundingRectWithSize( maxSize, options: .UsesLineFragmentOrigin, attributes:attr, context:nil).size.width
// get the size of one space
var widthSpace = " ".boundingRectWithSize( maxSize, options: .UsesLineFragmentOrigin, attributes:attr, context:nil).size.width
let spaces = floor((maxSize.width - widthText) / widthSpace)
// add the spaces
let newText = text + (" " * spaces)
// apply the new text if nescessary
if newText != text {
placeholder = newText
}
}
}
}
}
A working solution for Drix answer
import Foundation
import UIKit
class LeftAlignedSearchBar: UISearchBar, UISearchBarDelegate {
override var placeholder:String? {
didSet {
if #available(iOS 9.0, *) {
if let text = placeholder {
if text.characters.last! != " " {
// get the font attribute
let attr = UITextField.appearanceWhenContainedInInstancesOfClasses([LeftAlignedSearchBar.self]).defaultTextAttributes
// define a max size
let maxSize = CGSizeMake(UIScreen.mainScreen().bounds.size.width - 87, 40)
// let maxSize = CGSizeMake(self.bounds.size.width - 92, 40)
// get the size of the text
let widthText = text.boundingRectWithSize( maxSize, options: .UsesLineFragmentOrigin, attributes:attr, context:nil).size.width
// get the size of one space
let widthSpace = " ".boundingRectWithSize( maxSize, options: .UsesLineFragmentOrigin, attributes:attr, context:nil).size.width
let spaces = floor((maxSize.width - widthText) / widthSpace)
// add the spaces
let newText = text + ((Array(count: Int(spaces), repeatedValue: " ").joinWithSeparator("")))
// apply the new text if nescessary
if newText != text {
placeholder = newText
}
}
}
}
}
}
}
This method appearanceWhenContainedInInstancesOfClasses is not available in iOS 8, there is a workaround for iOS 8 here
SWIFT 3
swift 3 does not allow to override placeholder property. This is the modified version of Drix answer.
func setPlaceHolder(placeholder: String)-> String
{
var text = placeholder
if text.characters.last! != " " {
// define a max size
let maxSize = CGSize(width: UIScreen.main.bounds.size.width - 97, height: 40)
// let maxSize = CGSizeMake(self.bounds.size.width - 92, 40)
// get the size of the text
let widthText = text.boundingRect( with: maxSize, options: .usesLineFragmentOrigin, attributes:nil, context:nil).size.width
// get the size of one space
let widthSpace = " ".boundingRect( with: maxSize, options: .usesLineFragmentOrigin, attributes:nil, context:nil).size.width
let spaces = floor((maxSize.width - widthText) / widthSpace)
// add the spaces
let newText = text + ((Array(repeating: " ", count: Int(spaces)).joined(separator: "")))
// apply the new text if nescessary
if newText != text {
return newText
}
}
return placeholder;
}
and call the function as :
searchBar.placeholder = self.setPlaceHolder(placeholder: "your placeholder text");
A working swift 3 solution for Drix answer:
import Foundation
import UIKit
class LeftAlignedSearchBar: UISearchBar, UISearchBarDelegate {
override var placeholder:String? {
didSet {
if #available(iOS 9.0, *) {
if let text = placeholder {
if text.characters.last! != " " {
// get the font attribute
let attr = UITextField.appearance(whenContainedInInstancesOf: [LeftAlignedSearchBar.self]).defaultTextAttributes
// define a max size
let maxSize = CGSize(width: UIScreen.main.bounds.size.width - 87, height: 40)
// let maxSize = CGSize(width:self.bounds.size.width - 92,height: 40)
// get the size of the text
let widthText = text.boundingRect( with: maxSize, options: .usesLineFragmentOrigin, attributes:attr, context:nil).size.width
// get the size of one space
let widthSpace = " ".boundingRect( with: maxSize, options: .usesLineFragmentOrigin, attributes:attr, context:nil).size.width
let spaces = floor((maxSize.width - widthText) / widthSpace)
// add the spaces
let newText = text + ((Array(repeating: " ", count: Int(spaces)).joined(separator: "")))
// apply the new text if nescessary
if newText != text {
placeholder = newText
}
}
}
}
}
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
If you want your control to respond exactly how you want, you should probably make your own custom control.
This control could be separated in three parts :
a background UIImageView
a UITextField
a UIButton for the the search icon if you want the user to interact with it
The easiest way to do that is probably to create a new class MySearchBar, with the three parts in the private interface :
#interface MySearchBar ()
#property (nonatomic, strong) UISearchBar* searchBar;
#property (nonatomic, strong) UITextField* textField;
#property (nonatomic, strong) UIButton* button;
#end
In your MySearchBar, you can create your component, customize it, add a better look & feel. To get back the search result, your control can have a delegate id<UISearchBarDelegate> (your UIViewController) which will basically simulate having a standard UISearchBar.
What remains is to create your MySearchBar in your controller and set the delegate to your view controller. The messages from the UISearchBarDelegate can either go to your MySearchBar to filter or do pre-treatment before sending to your UIViewController, or go directly to your UIViewController.
No need to any customization just do it...
searchBar.placeholder=#"Search ";
searchbar has centeral text alignment for its place-hoder , so just give some big text. and if you text is small then just use some space after place-holder text.
Version for Xamarin
SearchBar.MovePlaceHolderLeft();
public static void MovePlaceHolderLeft(this UISearchBar searchbar)
{
NSAttributedString text = new NSAttributedString(searchbar.Placeholder ?? "");
// define a max size
var maxSize = new CGSize(width: UIScreen.MainScreen.Bounds.Size.Width - 97, height: 40);
// get the size of the text
var widthText = text.GetBoundingRect(maxSize, NSStringDrawingOptions.UsesLineFragmentOrigin, null).Size.Width;
// get the size of one space
var widthSpace = new NSAttributedString(" ").GetBoundingRect(maxSize, NSStringDrawingOptions.UsesLineFragmentOrigin, null).Size.Width;
var spaces = Math.Floor((maxSize.Width - widthText) / widthSpace);
// add the spaces
string newText = searchbar.Placeholder;
for (double i = 0; i < spaces; i++)
{
newText += " ";
}
searchbar.Placeholder = newText;
}
it could be done from story board there is combo box with name semantic on attribute inspector of search bar if you set it to force right to left its done you have right align search bar and it has a similar thing for aligning from left
It's too late, but if anyone is still wondering the solution, then you can follow this.
UITextField *searchTextField = [searchBarController.searchBar valueForKey:#"_searchField"];
You can get the search field using above code. Now simply use the properties you want to use, like.
searchTextField.layer.cornerRadius = 10.0f;
searchTextField.textAlignment = NSTextAlignmentLeft;
PS. Text alignment property is used for text and placeholder both.
Thanks.

UISearchBar change placeholder color

Has anyone any idea or code sample on how can I change the text color of the placeholder text of a UISearchBar?
for iOS5+ use the appearance proxy
[[UILabel appearanceWhenContainedIn:[UISearchBar class], nil] setTextColor:[UIColor redColor]];
Found the answer from Change UITextField's placeholder text color programmatically
// Get the instance of the UITextField of the search bar
UITextField *searchField = [searchBar valueForKey:#"_searchField"];
// Change search bar text color
searchField.textColor = [UIColor redColor];
// Change the search bar placeholder text color
[searchField setValue:[UIColor blueColor] forKeyPath:#"_placeholderLabel.textColor"];
First solution is OK, but if you use multiple UISearchBar, or create a lot of instances it may fail. The one solution that always work for me is to use also appearance proxy but directly on UITextField
NSDictionary *placeholderAttributes = #{
NSForegroundColorAttributeName: [UIColor darkButtonColor],
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue" size:15],
};
NSAttributedString *attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.searchBar.placeholder
attributes:placeholderAttributes];
[[UITextField appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setAttributedPlaceholder:attributedPlaceholder];
Here is a Solution for Swift:
Swift 2
var textFieldInsideSearchBar = searchBar.valueForKey("searchField") as? UITextField
textFieldInsideSearchBar?.textColor = UIColor.whiteColor()
var textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.valueForKey("placeholderLabel") as? UILabel
textFieldInsideSearchBarLabel?.textColor = UIColor.whiteColor()
Swift 3
let textFieldInsideSearchBar = searchBar.value(forKey: "searchField") as? UITextField
textFieldInsideSearchBar?.textColor = UIColor.white
let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as? UILabel
textFieldInsideSearchBarLabel?.textColor = UIColor.white
Try this and see: (I tested below code with Swift 4.1 - Xcode 9.3-beta4)
#IBOutlet weak var sbSearchBar: UISearchBar!
if let textfield = sbSearchBar.value(forKey: "searchField") as? UITextField {
textfield.backgroundColor = UIColor.yellow
textfield.attributedPlaceholder = NSAttributedString(string: textfield.placeholder ?? "", attributes: [NSAttributedStringKey.foregroundColor : UIColor.red])
textfield.textColor = UIColor.green
if let leftView = textfield.leftView as? UIImageView {
leftView.image = leftView.image?.withRenderingMode(.alwaysTemplate)
leftView.tintColor = UIColor.red
}
}
Here is result:
It is easy from iOS 13.0 onwards, You can simply use searchTextField property of a search bar to update attributed properties of the placeholder.
self.searchController.searchBar.searchTextField.attributedPlaceholder = NSAttributedString.init(string: "Search anything...", attributes: [NSAttributedString.Key.foregroundColor:UIColor.red])
One line solution
if let textFieldInsideSearchBar = searchBar.value(forKey: "searchField") as ? UITextField {
textFieldInsideSearchBar ? .textColor = UIColor.white
if let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as ? UILabel {
textFieldInsideSearchBarLabel ? .textColor = UIColor.white
if let clearButton = textFieldInsideSearchBar ? .value(forKey: "clearButton") as!UIButton {
clearButton.setImage(clearButton.imageView ? .image ? .withRenderingMode(.alwaysTemplate),
for : .normal)
clearButton.tintColor = UIColor.white
}
}
let glassIconView = textFieldInsideSearchBar ? .leftView as ? UIImageView
glassIconView ? .image = glassIconView ? .image ? .withRenderingMode(.alwaysTemplate)
glassIconView ? .tintColor = UIColor.white
}
Swift 5 - ios 13:
Those who are stuck let me tell you it is going to work only in viewDidLayoutSubviews not in viewDidLoad
override func viewDidLayoutSubviews() {
setupSearchBar(searchBar: YourSearchBar)
}
func setupSearchBar(searchBar : UISearchBar) {
searchBar.setPlaceholderTextColorTo(color: UIColor.white)
}
extension UISearchBar
{
func setPlaceholderTextColorTo(color: UIColor)
{
let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
textFieldInsideSearchBar?.textColor = color
let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as? UILabel
textFieldInsideSearchBarLabel?.textColor = color
}
}
Happy coding :)
It looks like Apple does not want us to play with the placeholder colors when it comes to UISearchBar class. So, let's create our own placeholder label!
No subclassing.
Works with iOS 13 SDK.
Just one innocent workaround.
let searchBar = searchController.searchBar
// ...
// Configure `searchBar` if needed
// ...
let searchTextField: UITextField
if #available(iOS 13, *) {
searchTextField = searchBar.searchTextField
} else {
searchTextField = (searchBar.value(forKey: "searchField") as? UITextField) ?? UITextField()
}
// Since iOS 13 SDK, there is no accessor to get the placeholder label.
// This is the only workaround that might cause issued during the review.
if let systemPlaceholderLabel = searchTextField.value(forKey: "placeholderLabel") as? UILabel {
// Empty or `nil` strings cause placeholder label to be hidden by the search bar
searchBar.placeholder = " "
// Create our own placeholder label
let placeholderLabel = UILabel(frame: .zero)
placeholderLabel.text = "Search"
placeholderLabel.font = UIFont.systemFont(ofSize: 17.0, weight: .regular)
placeholderLabel.textColor = UIColor.blue.withAlphaComponent(0.5)
systemPlaceholderLabel.addSubview(placeholderLabel)
// Layout label to be a "new" placeholder
placeholderLabel.leadingAnchor.constraint(equalTo: systemPlaceholderLabel.leadingAnchor).isActive = true
placeholderLabel.topAnchor.constraint(equalTo: systemPlaceholderLabel.topAnchor).isActive = true
placeholderLabel.bottomAnchor.constraint(equalTo: systemPlaceholderLabel.bottomAnchor).isActive = true
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
placeholderLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
} else {
searchBar.placeholder = ""
}
Swift 3
UILabel.appearance(whenContainedInInstancesOf: [UISearchBar.self]).textColor = UIColor.white
iOS 13
Previous solutions may not work on iOS 13 because new searchTextField has been added, and you can set attributed string on it.
I wrapped that into category:
#interface UISearchBar (AttributtedSetter)
- (void)setThemedPlaceholder:(NSString*)localizationKey;
#end
#implementation UISearchBar (AttributtedSetter)
- (void)setThemedPlaceholder:(NSString*)localizationKey {
ThemeObject *currentTheme = [[ThemeManager standardThemeManager] currentTheme];
self.searchTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:NSLocalizedString(localizationKey, #"") attributes:#{NSForegroundColorAttributeName : currentTheme.colorSearchBarText}];
}
#end
iOS 13
Use a custom search bar subclass.
This also works when part of a UISearchController inside a UINavigationItem (with hidesSearchBarWhenScrolling = true).
We want to apply our changes immediately after UIAppearance proxies are being applied since those are the most likely root cause:
class MySearchBar : UISearchBar {
// Appearance proxies are applied when a view is added to a view hierarchy, so apply your tweaks after that:
override func didMoveToSuperview() {
super.didMoveToSuperview() // important! - system colors will not apply correctly on ios 11-12 without this
let placeholderColor = UIColor.white.withAlphaComponent(0.75)
let placeholderAttributes = [NSAttributedString.Key.foregroundColor : placeholderColor]
let attributedPlaceholder = NSAttributedString(string: "My custom placeholder", attributes: placeholderAttributes)
self.searchTextField.attributedPlaceholder = attributedPlaceholder
// Make the magnifying glass the same color
(self.searchTextField.leftView as? UIImageView)?.tintColor = placeholderColor
}
}
// Override `searchBar` as per the documentation
private class MySearchController : UISearchController {
private lazy var customSearchBar = MySearchBar()
override var searchBar: UISearchBar { customSearchBar }
}
That took quite some time to get working properly...
Try this:
[self.searchBar setValue:[UIColor whatever] forKeyPath:#"_searchField._placeholderLabel.textColor"];
You can also set this in storyboard, select search bar, add entry under User Defined Runtime Attributes:
_searchField._placeholderLabel.textColor
of type Color and select the color you need.
worked for me on IOS 13
searchBar.searchTextField.attributedPlaceholder = NSAttributedString(
string: "Search something blabla",
attributes: [.foregroundColor: UIColor.red]
)
After surveyed a couple of answers, I come out this, hope its help
for (UIView *subview in searchBar.subviews) {
for (UIView *sv in subview.subviews) {
if ([NSStringFromClass([sv class]) isEqualToString:#"UISearchBarTextField"]) {
if ([sv respondsToSelector:#selector(setAttributedPlaceholder:)]) {
((UITextField *)sv).attributedPlaceholder = [[NSAttributedString alloc] initWithString:searchBar.placeholder attributes:#{NSForegroundColorAttributeName: [UIColor whiteColor]}];
}
break;
}
}
}
This solution works on Xcode 8.2.1. with Swift 3.0. :
extension UISearchBar
{
func setPlaceholderTextColorTo(color: UIColor)
{
let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
textFieldInsideSearchBar?.textColor = color
let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as? UILabel
textFieldInsideSearchBarLabel?.textColor = color
}
}
Usage example:
searchController.searchBar.setPlaceholderTextColorTo(color: mainColor)
This is an old question, but for anyone stumbling on it nowadays, you can change the search icon on iOS 8.x - 10.3 using the following:
[_searchBar setImage:[UIImage imageNamed:#"your-image-name"] forSearchBarIcon:UISearchBarIconSearch state:UIControlStateNormal];
Regarding the placeholder text color, you may check my other answer, which uses a Category, here: UISearchBar change placeholder color
Try this:
UITextField *searchField = [searchbar valueForKey:#"_searchField"];
field.textColor = [UIColor redColor]; //You can put any color here.

Resources