Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add dynamic type to a UISearchBar

My question is pretty much stated in the title. I'm trying to add dynamic type (as defined here) to a UISearchBar with no luck. I know this is possible as the system apps seem to be able to handle it just fine as shown here: enter image description here

However, my app doesn't seem to be handling that so well as shown here:

enter image description here

Knowing that UITextField is contained within UISearchBar I naturally tried this solution without success: UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).adjustsFontForContentSizeCategory = true

I've also tried searching online/checking documentation but I can't seem to find a solution anywhere. Is there something I'm missing to get dynamic type working in a UISearchBar.

Update: @matt suggested I do a manual check and update the font that way. However, that is yielding another issue as the search bar itself is too small to fit the text as shown here: enter image description here

@matt suggested to update the height as well using the scaledValue(for:) method, however this doesn't seem to work. Here's the code I'm using:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).font = UIFont.preferredFont(forTextStyle: .body)
        let textFieldFrame = UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).frame
        UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).frame = CGRect(x: textFieldFrame.minX, y: textFieldFrame.minY, width: textFieldFrame.width, height: UIFontMetrics.default.scaledValue(for: textFieldFrame.height))
}

The font seems to now be scaling with this updated code, yet the search bar's height isn't growing:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    searchBar.textField?.font = UIFont.preferredFont(forTextStyle: .body)
    if let textFieldFrame = searchBar.textField?.frame {
        searchBar.textField?.frame = CGRect(x: textFieldFrame.minX, y: textFieldFrame.minY, width: textFieldFrame.width, height: UIFontMetrics.default.scaledValue(for: textFieldFrame.height))
    }
}

Also, here's how I found the textField (just in case other users who get stuck would like to know):

extension UISearchBar {
    var textField: UITextField? {
        var _textField: UITextField? = nil
        subviews.forEach {
            $0.subviews.forEach {
                if let textField = $0 as? UITextField {
                    _textField = textField
                }
            }
        }
        return _textField
    }
}
like image 536
Harish Avatar asked Jul 31 '18 01:07

Harish


1 Answers

I have followed these milestones to reach your goal:

  • Automatically Adjusts Font with the Dynamic Type feature (STEP 1).
  • Adapt the searchbar constraints (STEP 2) AND its textfield constraints (STEP 3) when a new preferred content size category occurs.

    class SearchBarDynamicTypeVC: UIViewController {
    
    @IBOutlet weak var mySearchBar: UISearchBar!
    
    let fontHead = UIFont(name: "Chalkduster", size: 20.0)
    let fontHeadMetrics = UIFontMetrics(forTextStyle: .title1)
    var initialFrameHeight: CGFloat = 0.0
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).font = fontHeadMetrics.scaledFont(for: fontHead!)
    
        mySearchBar.textField?.adjustsFontForContentSizeCategory = true //STEP 1
    }
    
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    
        initialFrameHeight = mySearchBar.intrinsicContentSize.height
    
        if let textField = mySearchBar.textField {
            adaptConstraints(textField) //Initialization according to the first preferred content size category
        }
    }
    
    
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
    
        if let textField = mySearchBar.textField,
            let _ = previousTraitCollection {
    
            adaptConstraints(textField) // STEP 2 & 3
        }
    }
    
    
    private func adaptConstraints(_ textField: UITextField) {
    
    //  Adapt the SEARCHBAR constraints
        mySearchBar.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.deactivate(mySearchBar.constraints)
    
        let heightSB = mySearchBar.heightAnchor.constraint(equalToConstant: fontHeadMetrics.scaledValue(for: initialFrameHeight))
        let widthSB = mySearchBar.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 20)
        let centerVSB = mySearchBar.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
        let centerHSB = mySearchBar.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
    
        NSLayoutConstraint.activate([centerVSB,
                                     centerHSB,
                                     widthSB,
                                     heightSB])
    
    //  Adapt the SEARCHBAR TEXTFIELD constraints
        textField.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.deactivate(textField.constraints)
    
        let centerXTF = textField.centerXAnchor.constraint(equalTo: textField.superview!.centerXAnchor)
        let centerYTF = textField.centerYAnchor.constraint(equalTo: textField.superview!.centerYAnchor)
        let widthTF = textField.widthAnchor.constraint(equalTo: textField.superview!.widthAnchor, constant: -20.0)
        let heightTF = textField.heightAnchor.constraint(equalTo: textField.superview!.heightAnchor, constant: -20.0)
    
        NSLayoutConstraint.activate([centerXTF,
                                     centerYTF,
                                     widthTF,
                                     heightTF])
    }
    }
    

I used the code snippet provided in your post to get the searchbar textfield:

extension UISearchBar {

    var textField: UITextField? {

        var _textField: UITextField? = nil

        subviews.forEach {
            $0.subviews.forEach {
                if let textField = $0 as? UITextField {
                    _textField = textField
                }
            }
        }
        return _textField
    }
}

However, you can also get it using the key searchField as follows:

let textField = searchBar.value(forKey: "searchField") as? UITextField

The snapshots hereunder show the final result:

enter image description here

You can now add dynamic type to a UISearchBar by adapting the code snippet above and customizing the visual personal choices (text style, font, margins...).

like image 97
XLE_22 Avatar answered Oct 13 '22 01:10

XLE_22