implement format menu

This commit is contained in:
Levitating Pineapple
2024-11-18 22:10:52 +02:00
parent 9e1c543c51
commit 7d23ed3086
@@ -11,6 +11,11 @@ import SwiftyGif
import SimpleXChat
import PhotosUI
private let defaultAttributes: [NSAttributedString.Key : Any] = [
.font: UIFont.preferredFont(forTextStyle: .body),
.foregroundColor: UIColor.label
]
struct NativeTextEditor: UIViewRepresentable {
@Binding var text: String
@Binding var disableEditing: Bool
@@ -29,24 +34,31 @@ struct NativeTextEditor: UIViewRepresentable {
func makeUIView(context: Context) -> UITextView {
let field = CustomUITextField(height: _height)
field.backgroundColor = .clear
field.text = text
field.attributedText = NSAttributedString(
string: "",
attributes: [
.font: UIFont.preferredFont(forTextStyle: .body),
.foregroundColor: UIColor.label
]
)
field.textAlignment = alignment(text)
field.autocapitalizationType = .sentences
field.setOnTextChangedListener { newText, images in
if !disableEditing {
text = newText
field.textAlignment = alignment(text)
updateFont(field)
// Speed up the process of updating layout, reduce jumping content on screen
updateHeight(field)
self.height = field.frame.size.height
} else {
field.text = text
}
if !images.isEmpty {
onImagesAdded(images)
}
}
// TODO: Reintegrate with attributed string
// field.setOnTextChangedListener { newText, images in
// if !disableEditing {
// text = newText
// field.textAlignment = alignment(text)
// updateFont(field)
// // Speed up the process of updating layout, reduce jumping content on screen
// updateHeight(field)
// self.height = field.frame.size.height
// } else {
// field.text = text
// }
// if !images.isEmpty {
// onImagesAdded(images)
// }
// }
field.setOnFocusChangedListener { focused = $0 }
field.delegate = field
field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4)
@@ -57,7 +69,8 @@ struct NativeTextEditor: UIViewRepresentable {
func updateUIView(_ field: UITextView, context: Context) {
if field.markedTextRange == nil && field.text != text {
field.text = text
// TODO: Reintegrate with attributed string
// field.text = text
field.textAlignment = alignment(text)
updateFont(field)
updateHeight(field)
@@ -173,33 +186,34 @@ private class CustomUITextField: UITextView, UITextViewDelegate {
}
func textViewDidChange(_ textView: UITextView) {
if textView.markedTextRange == nil {
var images: [UploadContent] = []
var rangeDiff = 0
let newAttributedText = NSMutableAttributedString(attributedString: textView.attributedText)
textView.attributedText.enumerateAttribute(
NSAttributedString.Key.attachment,
in: NSRange(location: 0, length: textView.attributedText.length),
options: [],
using: { value, range, _ in
if let attachment = (value as? NSTextAttachment)?.fileWrapper?.regularFileContents {
do {
images.append(.animatedImage(image: try UIImage(gifData: attachment)))
} catch {
if let img = (value as? NSTextAttachment)?.image {
images.append(.simpleImage(image: img))
}
}
newAttributedText.replaceCharacters(in: NSMakeRange(range.location - rangeDiff, range.length), with: "")
rangeDiff += range.length
}
}
)
if textView.attributedText != newAttributedText {
textView.attributedText = newAttributedText
}
onTextChanged(textView.text, images)
}
// TODO: Reintegrate with attributed string
// if textView.markedTextRange == nil {
// var images: [UploadContent] = []
// var rangeDiff = 0
// let newAttributedText = NSMutableAttributedString(attributedString: textView.attributedText)
// textView.attributedText.enumerateAttribute(
// NSAttributedString.Key.attachment,
// in: NSRange(location: 0, length: textView.attributedText.length),
// options: [],
// using: { value, range, _ in
// if let attachment = (value as? NSTextAttachment)?.fileWrapper?.regularFileContents {
// do {
// images.append(.animatedImage(image: try UIImage(gifData: attachment)))
// } catch {
// if let img = (value as? NSTextAttachment)?.image {
// images.append(.simpleImage(image: img))
// }
// }
// newAttributedText.replaceCharacters(in: NSMakeRange(range.location - rangeDiff, range.length), with: "")
// rangeDiff += range.length
// }
// }
// )
// if textView.attributedText != newAttributedText {
// textView.attributedText = newAttributedText
// }
// onTextChanged(textView.text, images)
// }
}
func textViewDidBeginEditing(_ textView: UITextView) {
@@ -211,12 +225,37 @@ private class CustomUITextField: UITextView, UITextViewDelegate {
}
// MARK: Formatting
/// Toggles an attribute in the currently selected text range
/// - Parameters:
/// - attribute: Value of the attribute to be enabled or disabled
/// - key: Key for which to apply the attribute
/// - detect: Block which detects, if the attribute is already present within the selection
func toggle(
_ attribute: Any,
for key: NSAttributedString.Key,
detect: (Any) -> Bool?
) {
var detected = false
textStorage.enumerateAttribute(key, in: selectedRange) { value, _, stop in
if let value, detect(value) == true {
detected = true
stop.pointee = true
}
}
if key == .backgroundColor {
textStorage.removeAttribute(.backgroundColor, range: selectedRange)
} else {
textStorage.setAttributes(defaultAttributes, range: selectedRange)
}
if !detected {
textStorage.addAttribute(key, value: attribute, range: selectedRange)
}
}
private var formatMenu: UIMenu {
func set(_ attribute: Any, for key: NSAttributedString.Key) {
textStorage.setAttributes([key: attribute], range: selectedRange)
delegate?.textViewDidChange?(self)
}
func systemImage(_ systemName: String, color: UIColor? = nil) -> UIImage? {
if let uiImage = UIImage(systemName: systemName) {
@@ -228,23 +267,53 @@ private class CustomUITextField: UITextView, UITextViewDelegate {
} else { nil }
}
var bodySize: CGFloat { UIFont.preferredFont(forTextStyle: .body).pointSize }
let colors: [UIColor] = [.red, .green, .blue, .yellow, .cyan, .magenta]
return UIMenu(
title: "Format",
children: [
UIAction(image: systemImage("bold")) { _ in /*TODO*/ },
UIAction(image: systemImage("italic")) { _ in /*TODO*/ },
UIAction(image: systemImage("strikethrough")) { _ in /*TODO*/ },
UIAction(title: "Mono") { _ in /*TODO*/ },
UIAction(image: systemImage("bold")) { _ in
self.toggle(UIFont.boldSystemFont(ofSize: bodySize), for: .font) { value in
(value as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitBold)
}
},
UIAction(image: systemImage("italic")) { _ in
self.toggle(UIFont.italicSystemFont(ofSize: bodySize), for: .font) { value in
(value as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitItalic)
}
},
UIAction(image: systemImage("strikethrough")) { _ in
self.toggle(NSUnderlineStyle.single.rawValue, for: .strikethroughStyle) { value in
(value as? Int).map { $0 == NSUnderlineStyle.single.rawValue }
}
},
UIAction(title: "Mono") { _ in
self.toggle(UIFont.monospacedSystemFont(ofSize: bodySize, weight: .regular), for: .font) { value in
(value as? UIFont)?.fontDescriptor.symbolicTraits.contains(.traitMonoSpace)
}
},
UIMenu(
title: "Color",
children: Array<UIColor>([.red, .green, .blue, .yellow, .cyan, .magenta])
children: colors
.map { color in
UIAction(image: systemImage("circle.fill", color: color)) { _ in
set(color, for: .foregroundColor)
self.toggle(color, for: .foregroundColor) { value in
(value as? UIColor).map { $0 == color }
}
}
}
),
UIAction(title: "Secret") { test in /*TODO*/ }
UIAction(title: "Secret") { _ in
self.toggle(UIColor.clear, for: .foregroundColor) { value in
(value as? UIColor).map { $0 == .clear }
}
self.toggle(UIColor.secondarySystemFill, for: .backgroundColor) { value in
(value as? UIColor).map { $0 == .secondarySystemFill }
}
}
]
)
}