Signal-iOS/SignalUI/Views/MediaTextView.swift
Igor Solomennikov 37c0447a6e
Update to allow re-use of code related to text overlays in photo editor.
• 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.
2022-08-23 17:30:20 -07:00

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")
}
}