妥当なUIインタラクションを可能にするためにisScrollEnabledをtrueに設定する必要がある状況では、これがまったく珍しいことではないことがわかりました。これの単純なケースは、自動拡張テキストビューを許可したいが、UITableViewで最大高さを適切なものに制限したい場合です。
ここに私が思いついたUITextViewのサブクラスがあります。これは自動レイアウトで自動拡張を可能にしますが、最大の高さに制限することができ、高さに応じてビューがスクロール可能かどうかを管理します。制約がそのように設定されている場合、デフォルトではビューは無期限に拡張されます。
import UIKit
class FlexibleTextView: UITextView {
// limit the height of expansion per intrinsicContentSize
var maxHeight: CGFloat = 0.0
private let placeholderTextView: UITextView = {
let tv = UITextView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.backgroundColor = .clear
tv.isScrollEnabled = false
tv.textColor = .disabledTextColor
tv.isUserInteractionEnabled = false
return tv
}()
var placeholder: String? {
get {
return placeholderTextView.text
}
set {
placeholderTextView.text = newValue
}
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
isScrollEnabled = false
autoresizingMask = [.flexibleWidth, .flexibleHeight]
NotificationCenter.default.addObserver(self, selector: #selector(UITextInputDelegate.textDidChange(_:)), name: Notification.Name.UITextViewTextDidChange, object: self)
placeholderTextView.font = font
addSubview(placeholderTextView)
NSLayoutConstraint.activate([
placeholderTextView.leadingAnchor.constraint(equalTo: leadingAnchor),
placeholderTextView.trailingAnchor.constraint(equalTo: trailingAnchor),
placeholderTextView.topAnchor.constraint(equalTo: topAnchor),
placeholderTextView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var text: String! {
didSet {
invalidateIntrinsicContentSize()
placeholderTextView.isHidden = !text.isEmpty
}
}
override var font: UIFont? {
didSet {
placeholderTextView.font = font
invalidateIntrinsicContentSize()
}
}
override var contentInset: UIEdgeInsets {
didSet {
placeholderTextView.contentInset = contentInset
}
}
override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize
if size.height == UIViewNoIntrinsicMetric {
// force layout
layoutManager.glyphRange(for: textContainer)
size.height = layoutManager.usedRect(for: textContainer).height + textContainerInset.top + textContainerInset.bottom
}
if maxHeight > 0.0 && size.height > maxHeight {
size.height = maxHeight
if !isScrollEnabled {
isScrollEnabled = true
}
} else if isScrollEnabled {
isScrollEnabled = false
}
return size
}
@objc private func textDidChange(_ note: Notification) {
// needed incase isScrollEnabled is set to true which stops automatically calling invalidateIntrinsicContentSize()
invalidateIntrinsicContentSize()
placeholderTextView.isHidden = !text.isEmpty
}
}
おまけとして、UILabelと同様のプレースホルダーテキストを含めることができます。