Skip to content

Instantly share code, notes, and snippets.

@williampower
Created November 14, 2024 00:30
Show Gist options
  • Save williampower/1f9aabfcdd495d78f34c50a71bab9d7a to your computer and use it in GitHub Desktop.
Save williampower/1f9aabfcdd495d78f34c50a71bab9d7a to your computer and use it in GitHub Desktop.
Dynamic Type for UITextView (and ViewController)
//
// ConsentViewController.swift
// TextResizingQuestion
//
// Created by William Power.
//
import UIKit
/**
This code is meant to help the author of:
https://stackoverflow.com/questions/79164046/how-to-dynamically-adjust-font-size-in-the-uitextview-xcode-16
*/
class ConsentViewController: UIViewController {
struct ViewConstants {
static let viewBackgroundColor: UIColor = .systemGray5
//The below font declarations and the configuration in the below Label
//and TextView initializers help the UI scale based on the
//users' preferred font size.
//
//To view the scaling behavior in XCode preview, and turn on
//"Dynamic Type Variants" for the device you've selected.
//
//You can also see the scaling behavior by using the simulator
//and using alt-command-plus or alt-command-minus, which
//changes the simulator's preferred text size.
static let scaledRegularFont: UIFont = UIFontMetrics.default.scaledFont(for: UIFont(name: "AvenirNext-Regular", size: UIFont.labelFontSize)!)
static let scaledMediumFont: UIFont = UIFontMetrics.default.scaledFont(for: UIFont(name: "AvenirNext-Medium", size: UIFont.labelFontSize)!)
static let scaledHeaderFont: UIFont = UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont(name: "AvenirNext-Medium", size: 20.0)!)
static let paragraphAlignment: NSMutableParagraphStyle = {
let retVal = NSMutableParagraphStyle()
retVal.alignment = .justified
return retVal
}()
static let blueColor: UIColor = UIColor.blue
static let headerBlue: UIColor = UIColor.systemBlue
static let consentAttributes:[NSAttributedString.Key: Any] = [
NSAttributedString.Key.font: scaledRegularFont,
NSAttributedString.Key.foregroundColor: UIColor.black,
NSAttributedString.Key.paragraphStyle: paragraphAlignment,
]
}
//MARK: - UI DECLARATIONS
let consentHeader: UILabel = {
let label = UILabel()
label.text = "CONSENT REQUIRED"
//use dynamic type font & set the control to adjust based on preferred text size
label.font = ViewConstants.scaledHeaderFont
label.adjustsFontForContentSizeCategory = true
//enable text wrapping so the label doesn't truncate when size is increased
label.numberOfLines = 0
label.textAlignment = .center
label.textColor = .label
label.backgroundColor = ViewConstants.headerBlue
label.textColor = .white
label.layer.cornerRadius = 10
label.layer.masksToBounds = true
return label
}()
let consentTextView: UITextView = {
let textView = UITextView()
textView.font = ViewConstants.scaledRegularFont
textView.adjustsFontForContentSizeCategory = true
textView.attributedText = {
let originalString =
"""
The content of the app is intended for healthcare professionals only. The information contained within the app is not intended for the general public.
I confirm that I am a professional within the meaning of §2a of Act No. 40/1995 Coll., on advertising regulation, as amended, i.e., a person authorized to prescribe medicinal products or a person authorized to dispense medicinal products.
I acknowledge that the information contained herein is not intended for the general public, but for healthcare professionals, with all the risks and consequences arising therefrom for the general public.
After confirmation, you can continue using the app. We appreciate your understanding - enjoy NeoTools!
"""
let returnString:NSMutableAttributedString = NSMutableAttributedString(string: originalString ,attributes: ViewConstants.consentAttributes)
let initialRange:NSRange = NSRange(location: 0, length: 68)
returnString.addAttribute(.font, value: ViewConstants.scaledMediumFont, range: initialRange)
returnString.addAttribute(.foregroundColor, value: ViewConstants.blueColor, range: initialRange)
let finalRange:NSRange = NSRange(location: 597, length: 102)
returnString.addAttribute(.font, value: ViewConstants.scaledMediumFont, range: finalRange)
returnString.addAttribute(.foregroundColor, value: ViewConstants.blueColor, range: finalRange)
return returnString
}()
textView.isEditable = false
return textView
}()
// MARK: - UI label next to toggle, inside box
let consentToggleContainer: UIView = {
let view = UIView()
view.backgroundColor = .systemGray5
view.layer.cornerRadius = 10
return view
}()
let consentToggleLabel: UILabel = {
let label = UILabel()
label.text = "I agree to the above"
label.font = ViewConstants.scaledRegularFont
label.adjustsFontForContentSizeCategory = true
label.numberOfLines = 0
label.textColor = .label
return label
}()
let consentToggle: UISwitch = {
let switchView = UISwitch()
switchView.isOn = false
return switchView
}()
// MARK: - UI CONTINUE BUTTON
let continueButton: UIButton = {
let button = UIButton()
button.setTitle("Continue", for: .normal)
if let titleLabel = button.titleLabel {
titleLabel.font = ViewConstants.scaledHeaderFont
titleLabel.adjustsFontForContentSizeCategory = true
}
button.setTitleColor(.label, for: .normal)
button.backgroundColor = .systemBlue
button.isEnabled = false
return button
}()
// MARK: - UI Configuration
func addConstraints() {
let viewsDict = ["header": consentHeader,
"text": consentTextView,
"toggleContainer": consentToggleContainer,
"toggleLabel": consentToggleLabel,
"toggle": consentToggle,
"button": continueButton]
viewsDict.forEach { _, subview in
subview.translatesAutoresizingMaskIntoConstraints = false
}
//constrain subcontainer
consentToggleContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[toggleLabel][toggle]-|", metrics: nil, views: viewsDict))
consentToggleContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[toggleLabel]-|", metrics: nil, views: viewsDict))
consentToggleContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[toggle]-|", metrics: nil, views: viewsDict))
//constrain top-level views
view.addConstraints( NSLayoutConstraint.constraints(withVisualFormat: "V:|-[header]-[text]-[toggleContainer]-[button]-|", metrics: nil, views: viewsDict))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[header]-|", metrics: nil, views: viewsDict))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[text]-|", metrics: nil, views: viewsDict))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[toggleContainer]-|", metrics: nil, views: viewsDict))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[button]-|", metrics: nil, views: viewsDict))
}
func getScaledLabelFont() -> UIFont {
guard let customFont = UIFont(name: "AvenirNext-Regular", size: UIFont.labelFontSize) else {
fatalError("Could not load font")
}
return UIFontMetrics.default.scaledFont(for: customFont)
}
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(consentHeader)
self.view.addSubview(consentTextView)
self.view.addSubview(consentToggleContainer)
self.consentToggleContainer.addSubview(consentToggleLabel)
self.consentToggleContainer.addSubview(consentToggle)
consentToggle.addTarget(self, action: #selector(switchValueDidChange(sender: )), for: .valueChanged)
self.view.addSubview(continueButton)
self.addConstraints()
}
// MARK: - View Interactions
@objc func switchValueDidChange(sender: UISwitch) {
self.continueButton.isEnabled = sender.isOn
}
}
//enable xcode preview mode
#Preview {
ConsentViewController()
}
@williampower
Copy link
Author

iPhone SE 3rd generation, with x-small user-set preferred text size:
Screenshot 2024-11-13 at 4 47 09 PM

iPhone SE 3rd generation, with AX3 (very large) user-set preferred text size:
Screenshot 2024-11-13 at 4 47 29 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment