• rename VAlignTextView to MediaTextView. • rename ImageEditorPaletteView to ColorPickerBarView. • move text style declarations from ImageEditorTextItem to MediaTextView because those would no longer be specific to image editor. • extract TextToolbar from ImageEditorViewController to a standalone public class that is supposed to work in conjunction with MediaTextView.
195 lines
7.2 KiB
Swift
195 lines
7.2 KiB
Swift
//
|
|
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
public class MediaTextView: UITextView {
|
|
|
|
enum DecorationStyle: Int {
|
|
case none = 0
|
|
case inverted
|
|
case underline
|
|
case outline
|
|
}
|
|
|
|
enum TextStyle: Int {
|
|
case regular = 0
|
|
case bold
|
|
case serif
|
|
case script
|
|
case condensed
|
|
}
|
|
|
|
class func font(forTextStyle textStyle: TextStyle, pointSize: CGFloat) -> UIFont {
|
|
// TODO: this is a copy-paste code from TextAttachmentView that needs to be consolidated in one place.
|
|
let attributes: [UIFontDescriptor.AttributeName: Any]
|
|
|
|
switch textStyle {
|
|
case .regular:
|
|
attributes = [.name: "Inter-Regular_Bold"]
|
|
case .bold:
|
|
attributes = [.name: "Inter-Regular_Black"]
|
|
case .serif:
|
|
attributes = [.name: "EBGaramond-Regular"]
|
|
case .script:
|
|
attributes = [.name: "Parisienne-Regular"]
|
|
case .condensed:
|
|
// TODO: Ideally we could set an attribute to make this font
|
|
// all caps, but iOS deprecated that ability and didn't add
|
|
// a new equivalent function.
|
|
attributes = [.name: "BarlowCondensed-Medium"]
|
|
}
|
|
|
|
// TODO: Eventually we'll want to provide a cascadeList here to fallback
|
|
// to different fonts for different scripts rather than just relying on
|
|
// the built in OS fallbacks that don't tend to match the desired style.
|
|
let descriptor = UIFontDescriptor(fontAttributes: attributes)
|
|
|
|
return UIFont(descriptor: descriptor, size: pointSize)
|
|
}
|
|
|
|
private var kvoObservation: NSKeyValueObservation?
|
|
|
|
override init(frame: CGRect, textContainer: NSTextContainer?) {
|
|
super.init(frame: frame, textContainer: textContainer)
|
|
|
|
backgroundColor = .clear
|
|
isOpaque = false
|
|
isScrollEnabled = false
|
|
keyboardAppearance = .dark
|
|
scrollsToTop = false
|
|
textAlignment = .center
|
|
tintColor = .white
|
|
self.textContainer.lineFragmentPadding = 0
|
|
|
|
kvoObservation = observe(\.contentSize, options: [.new]) { [weak self] _, _ in
|
|
guard let self = self else { return }
|
|
self.adjustFontSizeIfNecessary()
|
|
}
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private func adjustFontSizeIfNecessary() {
|
|
// TODO: Figure out correct way to handle long text and implement it.
|
|
}
|
|
|
|
func update(withColor color: UIColor, font: UIFont, decorationStyle: MediaTextView.DecorationStyle) {
|
|
var attributes: [NSAttributedString.Key: Any] = [ .font: font]
|
|
|
|
let textColor: UIColor = {
|
|
switch decorationStyle {
|
|
case .none: return color
|
|
default: return .white
|
|
}
|
|
}()
|
|
attributes[.foregroundColor] = textColor
|
|
|
|
if let paragraphStyle = NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle {
|
|
paragraphStyle.alignment = .center
|
|
attributes[.paragraphStyle] = paragraphStyle
|
|
}
|
|
|
|
switch decorationStyle {
|
|
case .underline:
|
|
attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue
|
|
attributes[.underlineColor] = color
|
|
|
|
case .outline:
|
|
attributes[.strokeWidth] = -3
|
|
attributes[.strokeColor] = color
|
|
|
|
case .inverted:
|
|
attributes[.backgroundColor] = color
|
|
|
|
default:
|
|
break
|
|
}
|
|
|
|
attributedText = NSAttributedString(string: text, attributes: attributes)
|
|
|
|
// This makes UITextView apply text styling to the text that user enters.
|
|
typingAttributes = attributes
|
|
|
|
invalidateIntrinsicContentSize()
|
|
}
|
|
|
|
// MARK: - Key Commands
|
|
|
|
public override var keyCommands: [UIKeyCommand]? {
|
|
return [
|
|
UIKeyCommand(input: "\r", modifierFlags: .command, action: #selector(modifiedReturnPressed(sender:)), discoverabilityTitle: "Add Text"),
|
|
UIKeyCommand(input: "\r", modifierFlags: .alternate, action: #selector(modifiedReturnPressed(sender:)), discoverabilityTitle: "Add Text")
|
|
]
|
|
}
|
|
|
|
@objc
|
|
private func modifiedReturnPressed(sender: UIKeyCommand) {
|
|
Logger.verbose("")
|
|
|
|
acceptAutocorrectSuggestion()
|
|
resignFirstResponder()
|
|
}
|
|
}
|
|
|
|
public class TextStylingToolbar: UIView {
|
|
|
|
let colorPickerView: ColorPickerBarView
|
|
|
|
let textStyleButton = RoundMediaButton(image: #imageLiteral(resourceName: "media-editor-text-font"), backgroundStyle: .blur)
|
|
var textStyle: MediaTextView.TextStyle = .regular
|
|
|
|
let decorationStyleButton = RoundMediaButton(image: #imageLiteral(resourceName: "media-editor-text-style-1"), backgroundStyle: .blur)
|
|
var decorationStyle: MediaTextView.DecorationStyle = .none {
|
|
didSet {
|
|
decorationStyleButton.isSelected = (decorationStyle != .none)
|
|
}
|
|
}
|
|
|
|
init(currentColor: ColorPickerBarColor) {
|
|
self.colorPickerView = ColorPickerBarView(currentColor: currentColor)
|
|
super.init(frame: .zero)
|
|
|
|
decorationStyleButton.setContentCompressionResistancePriority(.required, for: .vertical)
|
|
decorationStyleButton.setImage(#imageLiteral(resourceName: "media-editor-text-style-2"), for: .selected)
|
|
|
|
// A container with width capped at a predefined size,
|
|
// centered in superview and constrained to layout margins.
|
|
let stackViewLayoutGuide = UILayoutGuide()
|
|
addLayoutGuide(stackViewLayoutGuide)
|
|
addConstraints([
|
|
stackViewLayoutGuide.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
stackViewLayoutGuide.leadingAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.leadingAnchor),
|
|
stackViewLayoutGuide.topAnchor.constraint(equalTo: topAnchor),
|
|
stackViewLayoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -2) ])
|
|
addConstraint({
|
|
let constraint = stackViewLayoutGuide.widthAnchor.constraint(equalToConstant: ImageEditorViewController.preferredToolbarContentWidth)
|
|
constraint.priority = .defaultHigh
|
|
return constraint
|
|
}())
|
|
|
|
// I had to use a custom layout guide because stack view isn't centered
|
|
// but instead has slight offset towards the trailing edge.
|
|
let stackView = UIStackView(arrangedSubviews: [ colorPickerView, textStyleButton, decorationStyleButton ])
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
stackView.alignment = .center
|
|
stackView.spacing = 8
|
|
stackView.setCustomSpacing(0, after: textStyleButton)
|
|
addSubview(stackView)
|
|
addConstraints([
|
|
stackView.leadingAnchor.constraint(equalTo: stackViewLayoutGuide.leadingAnchor),
|
|
stackView.trailingAnchor.constraint(equalTo: stackViewLayoutGuide.trailingAnchor,
|
|
constant: decorationStyleButton.layoutMargins.trailing),
|
|
stackView.topAnchor.constraint(equalTo: stackViewLayoutGuide.topAnchor),
|
|
stackView.bottomAnchor.constraint(equalTo: stackViewLayoutGuide.bottomAnchor) ])
|
|
}
|
|
|
|
@available(iOS, unavailable, message: "Use init(currentColor:)")
|
|
required init(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
}
|