Composer: Rework TextView for faster / smoother editing

This commit is contained in:
Thomas Ricouard 2023-02-07 18:42:56 +01:00
parent 3b5f2e823a
commit 0496727b6f
6 changed files with 21 additions and 193 deletions

View file

@ -40,7 +40,6 @@ public struct StatusEditorView: View {
viewModel.textView = textView
})
.placeholder(String(localized: "status.editor.text.placeholder"))
.font(Font.scaledBodyUIFont)
.setKeyboardType(preferences.isSocialKeyboardEnabled ? .twitter : .default)
.padding(.horizontal, .layoutPadding)
StatusEditorMediaView(viewModel: viewModel)

View file

@ -45,6 +45,7 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
didSet {
processText()
checkEmbed()
textView?.attributedText = statusText
}
}
@ -280,8 +281,9 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
private func processText() {
guard markedTextRange == nil else { return }
statusText.addAttributes([.foregroundColor: UIColor(Color.label),
.backgroundColor: .clear,
.underlineColor: .clear],
.font: Font.scaledBodyUIFont,
.backgroundColor: UIColor.clear,
.underlineColor: UIColor.clear],
range: NSMakeRange(0, statusText.string.utf16.count))
let hashtagPattern = "(#+[a-zA-Z0-9(_)]{1,})"
let mentionPattern = "(@+[a-zA-Z0-9(_).-]{1,})"

View file

@ -1,4 +1,5 @@
import SwiftUI
import DesignSystem
extension TextView.Representable {
final class Coordinator: NSObject, UITextViewDelegate {
@ -10,7 +11,7 @@ extension TextView.Representable {
private var calculatedHeight: Binding<CGFloat>
var didBecomeFirstResponder = false
var getTextView: ((UITextView) -> Void)?
init(text: Binding<NSMutableAttributedString>,
@ -29,8 +30,20 @@ extension TextView.Representable {
self.getTextView = getTextView
super.init()
textView.delegate = self
textView.font = Font.scaledBodyUIFont
textView.adjustsFontForContentSizeCategory = true
textView.autocapitalizationType = .sentences
textView.autocorrectionType = .yes
textView.isEditable = true
textView.isSelectable = true
textView.dataDetectorTypes = []
textView.allowsEditingTextAttributes = false
textView.returnKeyType = .default
textView.allowsEditingTextAttributes = true
self.getTextView?(textView)
}
@ -58,38 +71,7 @@ extension TextView.Representable {
extension TextView.Representable.Coordinator {
func update(representable: TextView.Representable) {
textView.attributedText = representable.text
textView.font = representable.font
textView.adjustsFontForContentSizeCategory = true
textView.autocapitalizationType = representable.autocapitalization
textView.autocorrectionType = representable.autocorrection
textView.isEditable = representable.isEditable
textView.isSelectable = representable.isSelectable
textView.dataDetectorTypes = representable.autoDetectionTypes
textView.allowsEditingTextAttributes = representable.allowsRichText
textView.keyboardType = representable.keyboard
switch representable.multilineTextAlignment {
case .leading:
textView.textAlignment = textView.traitCollection.layoutDirection ~= .leftToRight ? .left : .right
case .trailing:
textView.textAlignment = textView.traitCollection.layoutDirection ~= .leftToRight ? .right : .left
case .center:
textView.textAlignment = .center
}
if let value = representable.enablesReturnKeyAutomatically {
textView.enablesReturnKeyAutomatically = value
} else {
textView.enablesReturnKeyAutomatically = false
}
if let returnKeyType = representable.returnKeyType {
textView.returnKeyType = returnKeyType
} else {
textView.returnKeyType = .default
}
recalculateHeight()
textView.setNeedsDisplay()
}

View file

@ -2,14 +2,6 @@ import SwiftUI
public extension TextView {
/// Specifies whether or not this view allows rich text
/// - Parameter enabled: If `true`, rich text editing controls will be enabled for the user
func allowsRichText(_ enabled: Bool) -> TextView {
var view = self
view.allowRichText = enabled
return view
}
/// Specify a placeholder text
/// - Parameter placeholder: The placeholder text
func placeholder(_ placeholder: String) -> TextView {
@ -38,116 +30,10 @@ public extension TextView {
return view
}
/// Enables auto detection for the specified types
/// - Parameter types: The types to detect
func autoDetectDataTypes(_ types: UIDataDetectorTypes) -> TextView {
var view = self
view.autoDetectionTypes = types
return view
}
/// Specify the foreground color for the text
/// - Parameter color: The foreground color
func foregroundColor(_ color: UIColor) -> TextView {
var view = self
view.foregroundColor = color
return view
}
/// Specifies the capitalization style to apply to the text
/// - Parameter style: The capitalization style
func autocapitalization(_ style: UITextAutocapitalizationType) -> TextView {
var view = self
view.autocapitalization = style
return view
}
/// Specifies the alignment of multi-line text
/// - Parameter alignment: The text alignment
func multilineTextAlignment(_ alignment: TextAlignment) -> TextView {
var view = self
view.multilineTextAlignment = alignment
return view
}
func setKeyboardType(_ keyboardType: UIKeyboardType) -> TextView {
var view = self
view.keyboard = keyboardType
return view
}
/// Specifies the font to apply to the text
/// - Parameter font: The font to apply
func font(_ font: UIFont) -> TextView {
var view = self
view.font = font
return view
}
/// Specifies if the field should clear its content when editing begins
/// - Parameter value: If true, the field will be cleared when it receives focus
func clearOnInsertion(_ value: Bool) -> TextView {
var view = self
view.clearsOnInsertion = value
return view
}
/// Disables auto-correct
/// - Parameter disable: If true, autocorrection will be disabled
func disableAutocorrection(_ disable: Bool?) -> TextView {
var view = self
if let disable = disable {
view.autocorrection = disable ? .no : .yes
} else {
view.autocorrection = .default
}
return view
}
/// Specifies whether the text can be edited
/// - Parameter isEditable: If true, the text can be edited via the user's keyboard
func isEditable(_ isEditable: Bool) -> TextView {
var view = self
view.isEditable = isEditable
return view
}
/// Specifies whether the text can be selected
/// - Parameter isSelectable: If true, the text can be selected
func isSelectable(_ isSelectable: Bool) -> TextView {
var view = self
view.isSelectable = isSelectable
return view
}
/// Specifies the type of return key to be shown during editing, for the device keyboard
/// - Parameter style: The return key style
func returnKey(_ style: UIReturnKeyType?) -> TextView {
var view = self
view.returnKeyType = style
return view
}
/// Specifies whether the return key should auto enable/disable based on the current text
/// - Parameter value: If true, when the text is empty the return key will be disabled
func automaticallyEnablesReturn(_ value: Bool?) -> TextView {
var view = self
view.enablesReturnKeyAutomatically = value
return view
}
/// Specifies the truncation mode for this field
/// - Parameter mode: The truncation mode
func truncationMode(_ mode: Text.TruncationMode) -> TextView {
var view = self
switch mode {
case .head: view.truncationMode = .byTruncatingHead
case .tail: view.truncationMode = .byTruncatingTail
case .middle: view.truncationMode = .byTruncatingMiddle
@unknown default:
fatalError("Unknown text truncation mode")
}
return view
}
}

View file

@ -6,21 +6,7 @@ extension TextView {
@Binding var text: NSMutableAttributedString
@Binding var calculatedHeight: CGFloat
let foregroundColor: UIColor
let autocapitalization: UITextAutocapitalizationType
var multilineTextAlignment: TextAlignment
let font: UIFont
let returnKeyType: UIReturnKeyType?
let clearsOnInsertion: Bool
let autocorrection: UITextAutocorrectionType
let truncationMode: NSLineBreakMode
let isEditable: Bool
let keyboard: UIKeyboardType
let isSelectable: Bool
let enablesReturnKeyAutomatically: Bool?
var autoDetectionTypes: UIDataDetectorTypes = []
var allowsRichText: Bool
var getTextView: ((UITextView) -> Void)?
func makeUIView(context: Context) -> UIKitTextView {

View file

@ -1,4 +1,5 @@
import SwiftUI
import DesignSystem
/// A SwiftUI TextView implementation that supports both scrolling and auto-sizing layouts
public struct TextView: View {
@ -13,20 +14,7 @@ public struct TextView: View {
private var getTextView: ((UITextView) -> Void)?
var placeholderView: AnyView?
var foregroundColor: UIColor = .label
var autocapitalization: UITextAutocapitalizationType = .sentences
var multilineTextAlignment: TextAlignment = .leading
var font: UIFont = .preferredFont(forTextStyle: .body)
var returnKeyType: UIReturnKeyType?
var clearsOnInsertion: Bool = false
var autocorrection: UITextAutocorrectionType = .default
var truncationMode: NSLineBreakMode = .byTruncatingTail
var keyboard: UIKeyboardType = .default
var isEditable: Bool = true
var isSelectable: Bool = true
var enablesReturnKeyAutomatically: Bool?
var autoDetectionTypes: UIDataDetectorTypes = []
var allowRichText: Bool
/// Makes a new TextView that supports `NSAttributedString`
/// - Parameters:
@ -41,28 +29,13 @@ public struct TextView: View {
)
self.getTextView = getTextView
allowRichText = true
}
public var body: some View {
Representable(
text: $text,
calculatedHeight: $calculatedHeight,
foregroundColor: foregroundColor,
autocapitalization: autocapitalization,
multilineTextAlignment: multilineTextAlignment,
font: font,
returnKeyType: returnKeyType,
clearsOnInsertion: clearsOnInsertion,
autocorrection: autocorrection,
truncationMode: truncationMode,
isEditable: isEditable,
keyboard: keyboard,
isSelectable: isSelectable,
enablesReturnKeyAutomatically: enablesReturnKeyAutomatically,
autoDetectionTypes: autoDetectionTypes,
allowsRichText: allowRichText,
getTextView: getTextView
)
.frame(
@ -72,8 +45,8 @@ public struct TextView: View {
.background(
placeholderView?
.foregroundColor(Color(.placeholderText))
.multilineTextAlignment(multilineTextAlignment)
.font(Font(font))
.multilineTextAlignment(.leading)
.font(.scaledBody)
.padding(.horizontal, 0)
.padding(.vertical, 0)
.opacity(isEmpty ? 1 : 0),