Created
November 14, 2024 00:30
-
-
Save williampower/1f9aabfcdd495d78f34c50a71bab9d7a to your computer and use it in GitHub Desktop.
Dynamic Type for UITextView (and ViewController)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
iPhone SE 3rd generation, with x-small user-set preferred text size:

iPhone SE 3rd generation, with AX3 (very large) user-set preferred text size:
