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:
However, my app doesn't seem to be handling that so well as shown 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:
@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
}
}
I have followed these milestones to reach your goal:
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:
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...).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With