245 lines
9.2 KiB
Swift
245 lines
9.2 KiB
Swift
//
|
|
// AdaptableTextContainer.swift
|
|
// BonMot
|
|
//
|
|
// Created by Brian King on 7/19/16.
|
|
// Copyright © 2016 Rightpoint. All rights reserved.
|
|
//
|
|
|
|
#if canImport(UIKit) && !os(watchOS)
|
|
import UIKit
|
|
|
|
/// A protocol to update the text style contained by the object. This can be
|
|
/// triggered manually in `traitCollectionDidChange(_:)`. Any `UIViewController`
|
|
/// or `UIView` that conforms to this protocol will be informed of content size
|
|
/// category changes if `UIApplication.enableAdaptiveContentSizeMonitor()` is called.
|
|
/// - Note: We don't conform UI elements to this protocol due to a [bug in Xcode](http://www.openradar.me/30001713).
|
|
/// The protocol still exists as a container for selectors.
|
|
@objc(BONAdaptableTextContainer)
|
|
public protocol AdaptableTextContainer {
|
|
|
|
/// Update the text style contained by the object in response to a trait
|
|
/// collection change.
|
|
///
|
|
/// - parameter traitCollection: The new trait collection.
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
func adaptText(forTraitCollection traitCollection: UITraitCollection)
|
|
|
|
}
|
|
|
|
// MARK: - AdaptableTextContainer for UILabel
|
|
extension UILabel {
|
|
|
|
/// Adapt `attributedText` to the specified trait collection.
|
|
///
|
|
/// - parameter traitCollection: The new trait collection.
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
public func adaptText(forTraitCollection traitCollection: UITraitCollection) {
|
|
|
|
// Update the font, then the attributed string. If the font doesn't keep in sync when
|
|
// not using attributedText, weird things happen so update it first.
|
|
// See UIKitTests.testLabelFontPropertyBehavior for interesting behavior.
|
|
|
|
if let bonMotStyle = bonMotStyle {
|
|
let attributes = NSAttributedString.adapt(attributes: bonMotStyle.attributes, to: traitCollection)
|
|
font = attributes[.font] as? BONFont
|
|
}
|
|
if let attributedText = attributedText {
|
|
self.attributedText = attributedText.adapted(to: traitCollection)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - AdaptableTextContainer for UITextView
|
|
extension UITextView {
|
|
|
|
/// Adapt `attributedText` and `typingAttributes` to the specified trait collection.
|
|
///
|
|
/// - parameter traitCollection: The updated trait collection
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
public func adaptText(forTraitCollection traitCollection: UITraitCollection) {
|
|
if let attributedText = attributedText {
|
|
self.attributedText = attributedText.adapted(to: traitCollection)
|
|
}
|
|
typingAttributes = NSAttributedString.adapt(attributes: typingAttributes, to: traitCollection)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - AdaptableTextContainer for UITextField
|
|
extension UITextField {
|
|
|
|
/// Adapt `attributedText`, `attributedPlaceholder`, and
|
|
/// `defaultTextAttributes` to the specified trait collection.
|
|
///
|
|
/// - note: Do not modify `typingAttributes`, as they are relevant only
|
|
/// while the text field has first responder status, and they are
|
|
/// reset as new text is entered.
|
|
///
|
|
/// - parameter traitCollection: The new trait collection.
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
public func adaptText(forTraitCollection traitCollection: UITraitCollection) {
|
|
if let attributedText = attributedText?.adapted(to: traitCollection) {
|
|
if attributedText.length > 0 {
|
|
font = attributedText.attribute(.font, at: 0, effectiveRange: nil) as? UIFont
|
|
}
|
|
self.attributedText = attributedText
|
|
}
|
|
if let attributedPlaceholder = attributedPlaceholder {
|
|
self.attributedPlaceholder = attributedPlaceholder.adapted(to: traitCollection)
|
|
}
|
|
defaultTextAttributes = NSAttributedString.adapt(attributes: defaultTextAttributes, to: traitCollection)
|
|
// Fix an issue where shrinking or growing text would stay the same width, but add whitespace.
|
|
setNeedsDisplay()
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - AdaptableTextContainer for UIButton
|
|
extension UIButton {
|
|
|
|
/// Adapt `attributedTitle`, for all control states, to the specified trait collection.
|
|
///
|
|
/// - parameter traitCollection: The new trait collection.
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
public func adaptText(forTraitCollection traitCollection: UITraitCollection) {
|
|
for state in UIControl.State.commonStates {
|
|
let attributedText = attributedTitle(for: state)?.adapted(to: traitCollection)
|
|
setAttributedTitle(attributedText, for: state)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - AdaptableTextContainer for UISegmentedControl
|
|
extension UISegmentedControl {
|
|
|
|
// `UISegmentedControl` has terrible generics ([NSObject: AnyObject]? or [AnyHashable: Any]?) on
|
|
/// `titleTextAttributes`, so use a helper in Swift 3+
|
|
@nonobjc final func bon_titleTextAttributes(for state: UIControl.State) -> StyleAttributes? {
|
|
titleTextAttributes(for: state)
|
|
}
|
|
|
|
/// Adapt `attributedTitle`, for all control states, to the specified trait collection.
|
|
///
|
|
/// - parameter traitCollection: The new trait collection.
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
public func adaptText(forTraitCollection traitCollection: UITraitCollection) {
|
|
for state in UIControl.State.commonStates {
|
|
guard let attributes = bon_titleTextAttributes(for: state) else { continue }
|
|
let newAttributes = NSAttributedString.adapt(attributes: attributes, to: traitCollection)
|
|
setTitleTextAttributes(newAttributes, for: state)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - AdaptableTextContainer for UINavigationBar
|
|
extension UINavigationBar {
|
|
|
|
/// Adapt `titleTextAttributes` to the specified trait collection.
|
|
///
|
|
/// - note: This does not update the bar button items. These should be
|
|
/// updated by the containing view controller.
|
|
///
|
|
/// - parameter traitCollection: The new trait collection.
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
public func adaptText(forTraitCollection traitCollection: UITraitCollection) {
|
|
if let titleTextAttributes = titleTextAttributes {
|
|
self.titleTextAttributes = NSAttributedString.adapt(attributes: titleTextAttributes, to: traitCollection)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#if os(tvOS)
|
|
#else
|
|
// MARK: - AdaptableTextContainer for UIToolbar
|
|
extension UIToolbar {
|
|
|
|
/// Adapt all bar items's attributed text to the specified trait collection.
|
|
///
|
|
/// - note: This will update only bar items that are contained on the screen
|
|
/// at the time that it is called.
|
|
///
|
|
/// - parameter traitCollection: The updated trait collection
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
public func adaptText(forTraitCollection traitCollection: UITraitCollection) {
|
|
for item in items ?? [] {
|
|
item.adaptText(forTraitCollection: traitCollection)
|
|
}
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
// MARK: - AdaptableTextContainer for UIViewController
|
|
extension UIViewController {
|
|
|
|
/// Adapt the attributed text of the bar items in the navigation item or in
|
|
/// the toolbar to the specified trait collection.
|
|
///
|
|
/// - parameter traitCollection: The new trait collection.
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
public func adaptText(forTraitCollection traitCollection: UITraitCollection) {
|
|
for item in navigationItem.allBarItems {
|
|
item.adaptText(forTraitCollection: traitCollection)
|
|
}
|
|
#if os(tvOS)
|
|
#else
|
|
for item in toolbarItems ?? [] {
|
|
item.adaptText(forTraitCollection: traitCollection)
|
|
}
|
|
if let backBarButtonItem = navigationItem.backBarButtonItem {
|
|
backBarButtonItem.adaptText(forTraitCollection: traitCollection)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - AdaptableTextContainer for UIBarItem
|
|
extension UIBarItem {
|
|
|
|
/// Adapt `titleTextAttributes` to the specified trait collection.
|
|
///
|
|
/// - note: This extension does not conform to `AdaptableTextContainer`
|
|
/// because `UIBarItem` is not a view or view controller.
|
|
/// - parameter traitCollection: the new trait collection.
|
|
@objc(bon_updateTextForTraitCollection:)
|
|
public func adaptText(forTraitCollection traitCollection: UITraitCollection) {
|
|
for state in UIControl.State.commonStates {
|
|
let attributes = titleTextAttributes(for: state) ?? [:]
|
|
let newAttributes = NSAttributedString.adapt(attributes: attributes, to: traitCollection)
|
|
setTitleTextAttributes(newAttributes, for: state)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension UIControl.State {
|
|
|
|
/// The most common states that are used in apps. Using this defined set of
|
|
/// attributes is far simpler than trying to build a system that will
|
|
/// iterate through only the permutations that are currently configured. If
|
|
/// you use a valid `UIControlState` in your app that is not represented
|
|
/// here, please open a pull request to add it.
|
|
@nonobjc static var commonStates: [UIControl.State] {
|
|
return [.normal, .highlighted, .disabled, .selected, [.highlighted, .selected]]
|
|
}
|
|
|
|
}
|
|
|
|
extension UINavigationItem {
|
|
|
|
/// Convenience getter comprising `leftBarButtonItems` and `rightBarButtonItems`.
|
|
final var allBarItems: [UIBarButtonItem] {
|
|
var allBarItems = leftBarButtonItems ?? []
|
|
allBarItems.append(contentsOf: rightBarButtonItems ?? [])
|
|
return allBarItems
|
|
}
|
|
|
|
}
|
|
#endif
|