45

I'm testing out my UI and I find the search bar a little bit too narrow for my liking. I also want to make sure people with poorer vision or poorer manual dexterity have no issues bringing up the interface they want.

So, what I would like to do is Adjust the height of the UITextfield inside UISearchbar.

What I have tried: 1. In Storyboard, add UISearchbar Height constraint - Results: the searchbar size increases, but the UITextField inside stays the same.

  1. Access the UITextField inside UISearchbar and modify its height - Results: The console output shows that the parameter is modified, but on screen, the UITextField height remains the same.

Note - I can modify other parameters of the UITextField using method 2 and the change is reflected on screen so I know I am getting to the UITextField

Code for Method 2, put in viewDidLoad() of ViewController where UISearchbar is located:

for tempView in (self.roomInfoSearchBar.subviews[0] as! UIView).subviews as! [UIView]
{
  if let tV = tempView as? UITextField
  {
    var currentFrameRect = tV.frame
    currentFrameRect.size.height = 80
    //tV.layer.backgroundColor = UIColor.blueColor().CGColor  //This works fine
    //tV.layer.cornerRadius = 5  //This works fine
    //tV.font = UIFont(name: "Courier", size: 40)  //This works fine
    println(tV.frame.height)  //console output:0.0
    tV.frame = currentFrameRect
    println(tV.frame) //console output:(0.0, 0.0, 0.0, 80.0)
    println(currentFrameRect) //console output:(0.0, 0.0, 0.0, 80.0) - redundant, just checking
    println(tV.frame.height) //console output:80.0 - it says 80, but screen shows searchbar definitely not 80.
  }
}

I think it has something to do with autolayout somehow being able to disregard parameters regardless of where it is set. I would like to continue to use autolayout since it does a lot of work for me, but I wish it would behave like it does in Storyboard where when a user sets a setting it will use those parameters in its autolayout and complain when it can't as opposed to just ignoring the setting without feedback.

Edit:

In response to Mahesh. I'm don't know much objective C++ but I tried my best to convert what you wrote into swift. This is what I have:

(Inside my viewcontroller.swift)

func viewDIdLayoutSubviews()
{
  super.viewDidLayoutSubviews()
  self.roomInfoSearchBar.layoutSubviews()

  var topPadding: Float = 5.0
  for view in (self.roomInfoSearchBar.subviews[0] as! UIView).subviews as! [UIView]
  {
    if let tfView = view as? UITextField
    {
      var calcheight = self.roomInfoSearchBar.frame.size.height - 10
      tfView.frame = CGRectMake(tfView.frame.origin.x, CGFloat(topPadding), tfView.frame.size.width, self.roomInfoSearchBar.frame.size.height - CGFloat(topPadding * 2))
    }
  }
}

I've kept my code for getting to the textfield since I had some issues converting your code to swift. For the CGRectMake - swift complained about various types including that topPadding wasn't a CGFloat and for the last variable (Y size), again it did not like mixing CGFloat with Float so I had to change that too.

Unfortunately, it does not appear to work. I changed the UISearchbar height to 80 in storyboard and I just got a very tall searchbar with the textfield covering about 1/3 of the total height.

Edit #2: Incorporating Yuyutsu and corrected version of Mahesh code. Still not perfect, but closer. Yuyutsu's code works but as mentioned in my comments, the field does not center, the resize is also visible (jump from height A to height B) when the view is first loaded. One additional deficiency is that because the resize is done at viewDidAppear once the orientation changes, the field returns to the intrinsic size.

My code based on Yuyutsu's:

  override func viewDidAppear(animated: Bool)
  {
    super.viewDidAppear(animated)
    var roomInfoSearchBarFrame = roomInfoSearchBar.frame
    var newHeight: CGFloat = 60
    for subView in roomInfoSearchBar.subviews
    {
      for subsubView in subView.subviews
      {
        if let textField = subsubView as? UITextField
        {
          var currentTextFieldFrame = textField.frame
          var recenteredY = (roomInfoSearchBarFrame.height - newHeight)/2
          var newTextFieldFrame = CGRectMake(textField.frame.minX, recenteredY, textField.frame.width, newHeight)

          textField.frame = newTextFieldFrame
          textField.borderStyle = UITextBorderStyle.RoundedRect
          textField.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
          //textField.backgroundColor = UIColor.redColor()
          //                    textField.font = UIFont.systemFontOfSize(20)
        }
      }
    }
  }

Looking at Yuyutsu's code I wanted to know where else I could put this adjustment and I came across link via another stackoverflow post. Based on what I read, I saw that Mahesh's answer should work. This is where I realized why it wasn't working - I needed to override func viewWillLayoutSubviews().

Current code: Keeps size with orientation change. But size jump still visible (it shouldn't be visible)

  override func viewWillLayoutSubviews()
  {
    super.viewWillLayoutSubviews()
    var roomInfoSearchBarFrame = roomInfoSearchBar.frame
    var newHeight: CGFloat = 60
    for subView in roomInfoSearchBar.subviews
    {
      for subsubView in subView.subviews
      {
        if let textField = subsubView as? UITextField
        {
          var currentTextFieldFrame = textField.frame
          var recenteredY = (roomInfoSearchBarFrame.height - newHeight)/2
          var newTextFieldFrame = CGRectMake(textField.frame.minX, recenteredY, textField.frame.width, newHeight)

          textField.frame = newTextFieldFrame
          textField.borderStyle = UITextBorderStyle.RoundedRect
          textField.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
          //textField.backgroundColor = UIColor.redColor()
          //                    textField.font = UIFont.systemFontOfSize(20)
        }
      }
    }
  }

So right now, the only thing left is to try and make it so the textfield is the set size prior to the view being visible so there is no size shift that is visible to the user.

Final Edit: Fully working code With Yuyutsu's additional help, code below does everything I want - starts off at the set size, field is centered, deals with rotation fine.

  override func viewDidLayoutSubviews()
  {
    super.viewDidLayoutSubviews()
    self.roomInfoSearchBar.layoutIfNeeded()
    self.roomInfoSearchBar.layoutSubviews()

    var roomInfoSearchBarFrame = roomInfoSearchBar.frame
    var newHeight: CGFloat = 60 //desired textField Height.
    for subView in roomInfoSearchBar.subviews
    {
      for subsubView in subView.subviews
      {
        if let textField = subsubView as? UITextField
        {
          var currentTextFieldBounds = textField.bounds
          currentTextFieldBounds.size.height = newHeight
          textField.bounds = currentTextFieldBounds
          textField.borderStyle = UITextBorderStyle.RoundedRect
          //textField.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
        }
      }
    }
  }

Thanks Yuyutsu. Thanks Mahesh for pretty much coming up with the final solution right from the start, just missing a few critical bits.

janson
  • 683
  • 1
  • 5
  • 14
  • The Final can be even more convenient by using Mahesh's idea of padding. Essentially, you calculate `newHeight` based on `roomInfoSearchBar.frame` subtract 2 * somePadding (somePadding being the space you want between searchField and the textField). Once you have set that, you can then just adjust the searchBar size in Storyboard and the textfield will adjust as well. You only need to modify the `viewDidLayoutSubviews()` if you want to change the padding. – janson Jun 17 '15 at 09:17
  • 1
    The really best solution is to use `UITextField` and just add the magnifier image to its `leftView`. Really, you will solve a lot of problems caused by hacking into `UISearchBar`. – Sulthan May 28 '16 at 17:28
  • Thanks to all I have fixed using your final code. – Ilesh P May 18 '18 at 07:41
  • Did not work on iOS11/swift 4. – Bogdan Razvan May 30 '18 at 10:35
  • Check my answer. Still working... – WR-BikAir Dec 30 '18 at 19:33
  • roomInfoSearchBarFrame variable in the last solution does not seem to ever get used. – scaly Jun 28 '22 at 00:20

9 Answers9

18
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        for subView in searchBars.subviews  {
            for subsubView in subView.subviews  {
                if let textField = subsubView as? UITextField {
                     var bounds: CGRect
                bounds = textField.frame
                bounds.size.height = 35 //(set height whatever you want)
                    textField.bounds = bounds
                    textField.borderStyle = UITextBorderStyle.RoundedRect
//                    textField.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
                    textField.backgroundColor = UIColor.redColor()
//                    textField.font = UIFont.systemFontOfSize(20)
                }
            }
        }

    }

This might helps you :)

Yuyutsu
  • 2,509
  • 22
  • 38
  • 1
    Yes, your code does change the size of the textfield itself which to me is great progress! =D. I guess Autolayout does its thing after `viewDidLoad` but before `viewDidAppear`. Only problem with this method is that I see the resizing occur - i.e. The view loads with the Autolayout intrinsic size, and then suddenly changes to the size indicated in `frame.size.height`. I tried putting it in `viewWillAppear` just to see, but then the field is at intrinsic size. Other minor issue that I should be able to fix is that the field is not centered in the searchbar. – janson Jun 17 '15 at 03:31
  • hey, Change `textfield.bounds` instead of `frame`. I update the answer. let me know it works fine for me – Yuyutsu Jun 17 '15 at 05:37
  • Changing to bounds does help by removing the need for the centering code. Only problem left is that the resize is visible to the user. Unfortunately viewDidAppear is too late in the screen draw process. Even in `willLayoutSubviews` the redraw is still visible. – janson Jun 17 '15 at 06:11
  • hey, Add my code to `viewDidLayoutSubviews` and add these `self.searchBars.layoutIfNeeded() self.searchBars.layoutSubviews() ` – Yuyutsu Jun 17 '15 at 06:29
  • http://stackoverflow.com/questions/26629240/ios-autlayout-frame-size-not-set-in-viewdidlayoutsubviews?answertab=oldest#tab-top – Yuyutsu Jun 17 '15 at 06:30
  • 1
    Your final modifications did the trick. In the end it is in `viewDidLayoutSubviews` as Mahesh originally suggested but I guess those two lines are needed also (`layoutIfNeeded` and `layoutSubviews`). For completeness I'll edit my question to cover this final working one. Thank you again. – janson Jun 17 '15 at 07:10
  • it seems it could work for `UITextField` by itself but NOT `UISearchController`/`UISearchBar` – Gargo Feb 28 '23 at 06:59
15

If you use :

func setSearchFieldBackgroundImage(backgroundImage: UIImage?, forState state: UIControlState)   

The height of UISearchBar's UITextfield would be the same as the height of the Image.

You can drop in an Image,use it to customize UISearchBar's UITextfield height and backgroundImage.

Or you can use the function below to create a round corner UIImage:

func getImageWithColor(color: UIColor, size: CGSize) -> UIImage {
    let rect = CGRectMake(0, 0, size.width, size.height)
    let path = UIBezierPath(roundedRect: rect, cornerRadius: 5.0)
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    color.setFill()
    path.fill()
    let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image
}

The Width of the UIImage is ignored.But to have a corner radius ,it's better bigger than 10.0.

The code would be look like this:

let image = self.getImageWithColor(UIColor.lightGrayColor(), size: CGSizeMake(20, 20))
searchBar.setSearchFieldBackgroundImage(image, forState: .Normal)
wj2061
  • 6,778
  • 3
  • 36
  • 62
  • Works as a charm. Search bar height "flicks" when editing text on previous solutions. – djserva Nov 18 '16 at 15:48
  • this is truly the cleanest and best way of all answers I've found on the web. – zero3nna Mar 20 '17 at 16:42
  • Unfortunately, you will get this error now using this method: `UIGraphicsBeginImageContext Usage Violation: Should use UIGraphicsImageRenderer instead`. So I think this method needs to be refreshed. I'll mess around to see if I can get it to work. – johnny Jul 23 '20 at 23:03
8

This is the correct Swift 4.0 answer on how to change the height of a UISearchBar:

  let image = getImageWithColor(color: UIColor.clear, size: CGSize(width: 1, height: 40))
  searchBar.setSearchFieldBackgroundImage(image, for: .normal)

Using following method:

  func getImageWithColor(color: UIColor, size: CGSize) -> UIImage {
        let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        color.setFill()
        UIRectFill(rect)
        let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }

Tested on 4th. June. 2018

thelearner
  • 1,440
  • 3
  • 27
  • 58
8

Based on thelearner's answer here's the code for Swift 5.* :

extension UISearchBar {
    func updateHeight(height: CGFloat, radius: CGFloat = 8.0) {
        let image: UIImage? = UIImage.imageWithColor(color: UIColor.clear, size: CGSize(width: 1, height: height))
        setSearchFieldBackgroundImage(image, for: .normal)
        for subview in self.subviews {
            for subSubViews in subview.subviews {
                if #available(iOS 13.0, *) {
                    for child in subSubViews.subviews {
                        if let textField = child as? UISearchTextField {
                            textField.layer.cornerRadius = radius
                            textField.clipsToBounds = true
                        }
                    }
                    continue
                }
                if let textField = subSubViews as? UITextField {
                    textField.layer.cornerRadius = radius
                    textField.clipsToBounds = true
                }
            }
        }
    }
}

private extension UIImage {
    static func imageWithColor(color: UIColor, size: CGSize) -> UIImage? {
        let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        color.setFill()
        UIRectFill(rect)
        guard let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() else {
            return nil
        }
        UIGraphicsEndImageContext()
        return image
    }
}

and here's how we can apply it:

searchBar?.updateHeight(height: 48)

and here's the result: enter image description here

Jonathan Cabrera
  • 1,656
  • 1
  • 19
  • 20
Bassem Qoulta
  • 341
  • 1
  • 7
  • 17
5

I've found the accepted answer to be buggy at times.
This is how i've solved the issue of having a custom height for the searchBar: I have set a colour image with the size I want and added it to the searchBar's textField in viewDidLoad:

let image = getImageWithColor(UIColor.whiteColor(), size: CGSizeMake(600, 60))
searchBar.setSearchFieldBackgroundImage(image, forState: .Normal)

Followed with this func taken from here

func getImageWithColor(color: UIColor, size: CGSize) -> UIImage {
    var rect = CGRectMake(0, 0, size.width, size.height)
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    color.setFill()
    UIRectFill(rect)
    var image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image
}
Community
  • 1
  • 1
rcat24
  • 548
  • 2
  • 23
2

You can change the height of the inner text field try this code.

step 1) set constraint to increase your UISearchBar height then make outlet of search bar and write down this code in view controller.

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    [self.searchBar layoutSubviews];

    float topPadding = 5.0;
    for(UIView *view in self.searchBar.subviews)
    {
        for(UITextField *textfield in view.subviews)
        {
            if ([textfield isKindOfClass:[UITextField class]]) {
                textfield.frame = CGRectMake(textfield.frame.origin.x, topPadding, textfield.frame.size.width, (self.searchBar.frame.size.height - (topPadding * 2)));
            }
        }
    }
}

also if you want to change anything else in the search bar then you can find the exact base element like UILabel, UIImage and UIView inside the UITextField subviews and can change the frame and image and other properties also. Thanks.

EDIT:

as you asked in swift am writing it in swift. i think you are trying something wrong. please try this.

func viewDIdLayoutSubviews()
{
    super.viewDidLayoutSubviews()
    self.roomInfoSearchBar.layoutSubviews()

    var topPadding: Float = 5.0
    for view in self.roomInfoSearchBar.subviews as [UIView] 
    {
        for viewNew in view.subviews as [UIView] 
        {
            if viewNew.isKindOfClass(UITextField) 
            {
                viewNew.frame = CGRectMake(viewNew.frame.origin.x, CGFloat(topPadding), viewNew.frame.size.width, self.roomInfoSearchBar.frame.size.height - CGFloat(topPadding * 2))
            }
        }
    }
}

you taken the 0 index view as first and last view to be looped. i think thats the problem the code you translated didn't worked. Try once and please reply.

Mahesh Agrawal
  • 3,348
  • 20
  • 34
  • I've tried to translate your suggestion to swift. I think I got it translated correctly, but the textfield inside is still the original height. – janson Jun 16 '15 at 07:24
  • see my edited answer and please use the exact code of mine once which i have edited. – Mahesh Agrawal Jun 16 '15 at 09:25
  • you had taken self.roomInfoSearchBar.subviews[0] which is incorrect. the text field can be in any view which will be in any subview of UISearchBar. you cannot assume that its coming inside the view at index 0. – Mahesh Agrawal Jun 16 '15 at 09:28
  • actually am not sure that its coming in which subview thats why i have taken all views in for loop. – Mahesh Agrawal Jun 16 '15 at 09:28
  • But the point of your two loops is locate the UITextField so that its settings can be modified right? Two loops are required because the textfield is a subview of a subview. I use self.roomInfoSearchBar.subviews[0] because from experimentation I know that is where the UITextField is buried in. In essence I think we are both getting to the same place, I then try to use CGRectMake to draw the full frame as you suggest (instead of just setting the height) but the height of the textfield does not change. Just saw your swift version... will try it out. Thanks. – janson Jun 16 '15 at 10:39
  • Just tried your swift version. Had to change "as" to "as!" to force into UIView. Right below `viewNew.frame = CGRectMake...` I added `println("didLayoutRunning")` just to see... unfortunately, I don't see the output on the console which would suggest this function doesn't get run at all. I corrected the typo from `func viewDIdLayoutSubviews()` to `func viewDidLayoutSubviews()` (the I in Did is capitalized) but still nothing. Thank you for your efforts Mahesh. I appreciate it. – janson Jun 16 '15 at 10:49
  • doesn't work at all – Gargo Feb 28 '23 at 06:54
1

For swift 5.0 it works good:

extension UIImage {
    class func image(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
        return UIGraphicsImageRenderer(size: size).image { rendererContext in
            color.setFill()
            rendererContext.fill(CGRect(origin: .zero, size: size))
        }
    }
}

extension UISearchBar {
    func customize() {
        setSearchFieldBackgroundImage(UIImage.image(color: .clear, size: CGSize(width: 1, height: 44)), for: .normal)

        // other settings
        searchBarStyle = .minimal
        searchTextPositionAdjustment = UIOffset(horizontal: 0, vertical: 0)
        tintColor = .black
        backgroundColor = .whiteBackground
        searchTextField.leftViewMode = .never
        searchTextField.textColor = .black
        searchTextField.backgroundColor = UIColor.lightGrayBackground
        searchTextField.layer.borderColor = UIColor.gray.cgColor
        searchTextField.cornerRadius = 22
        searchTextField.borderWidth = 0.5
    }
}

Just use:

let searchBar = UISearchBar()
searchBar.customize()
Nike Kov
  • 12,630
  • 8
  • 75
  • 122
0

Update for Swift 4.2/iOS 12.1:

let newHeight : CGFloat = 45
for subview in searchBar.subviews {
    for subsubview in subview.subviews {
        if let textField = subsubview as? UITextField {
            var currentTextfieldBounds = textField.bounds
            currentTextfieldBounds.size.height = newHeight
            textField.bounds = currentTextfieldBounds
            textField.borderStyle = .roundedRect
            textField.contentVerticalAlignment = .center
        }
    }
}
WR-BikAir
  • 100
  • 11
0

MonoTouch version of wj2061 answer:

UIImage GetImageWithColor(UIColor color, CGSize size)
{
    var rect = new CGRect(0, 0, size.Width, size.Height);
    var path = UIBezierPath.FromRoundedRect(rect, 5);
    UIGraphics.BeginImageContextWithOptions(size, false, 0);
    color.SetFill();
    path.Fill();
    var image = UIGraphics.GetImageFromCurrentImageContext();
    UIGraphics.EndImageContext();
    return image;
}

var image = GetImageWithColor(UIColor.LightGray, new CGSize(20, 20));
searchBar.SetSearchFieldBackgroundImage(image, UIControlState.Normal);
bart
  • 304
  • 1
  • 3
  • 13