session-ios/SignalMessaging/Views/ImageEditor/ImageEditorTextViewController.swift
2019-03-01 09:07:03 -05:00

218 lines
6.6 KiB
Swift

//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
public protocol VAlignTextViewDelegate: class {
func textViewDidComplete()
}
// MARK: -
private class VAlignTextView: UITextView {
fileprivate weak var textViewDelegate: VAlignTextViewDelegate?
enum Alignment: String {
case top
case center
case bottom
}
private let alignment: Alignment
@objc public override var bounds: CGRect {
didSet {
if oldValue != bounds {
updateInsets()
}
}
}
@objc public override var frame: CGRect {
didSet {
if oldValue != frame {
updateInsets()
}
}
}
public init(alignment: Alignment) {
self.alignment = alignment
super.init(frame: .zero, textContainer: nil)
self.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
@available(*, unavailable, message: "use other init() instead.")
required public init?(coder aDecoder: NSCoder) {
notImplemented()
}
deinit {
self.removeObserver(self, forKeyPath: "contentSize")
}
private func updateInsets() {
let topOffset: CGFloat
switch alignment {
case .top:
topOffset = 0
case .center:
topOffset = max(0, (self.height() - contentSize.height) * 0.5)
case .bottom:
topOffset = max(0, self.height() - contentSize.height)
}
contentInset = UIEdgeInsets(top: topOffset, leading: 0, bottom: 0, trailing: 0)
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
updateInsets()
}
// MARK: - Key Commands
override var keyCommands: [UIKeyCommand]? {
return [
UIKeyCommand(input: "\r", modifierFlags: .command, action: #selector(self.modifiedReturnPressed(sender:)), discoverabilityTitle: "Send Message"),
UIKeyCommand(input: "\r", modifierFlags: .alternate, action: #selector(self.modifiedReturnPressed(sender:)), discoverabilityTitle: "Send Message")
]
}
@objc
public func modifiedReturnPressed(sender: UIKeyCommand) {
Logger.verbose("")
self.textViewDelegate?.textViewDidComplete()
}
}
// MARK: -
@objc
public protocol ImageEditorTextViewControllerDelegate: class {
func textEditDidComplete(textItem: ImageEditorTextItem, text: String?)
func textEditDidCancel()
}
// MARK: -
// A view for editing text item in image editor.
public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDelegate {
private weak var delegate: ImageEditorTextViewControllerDelegate?
private let textItem: ImageEditorTextItem
private let maxTextWidthPoints: CGFloat
private let textView = VAlignTextView(alignment: .bottom)
init(delegate: ImageEditorTextViewControllerDelegate,
textItem: ImageEditorTextItem,
maxTextWidthPoints: CGFloat) {
self.delegate = delegate
self.textItem = textItem
self.maxTextWidthPoints = maxTextWidthPoints
super.init(nibName: nil, bundle: nil)
self.textView.textViewDelegate = self
}
@available(*, unavailable, message: "use other init() instead.")
required public init?(coder aDecoder: NSCoder) {
notImplemented()
}
// MARK: - View Lifecycle
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
textView.becomeFirstResponder()
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
textView.becomeFirstResponder()
}
public override func loadView() {
self.view = UIView()
self.view.backgroundColor = UIColor(white: 0.5, alpha: 0.5)
configureTextView()
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop,
target: self,
action: #selector(didTapBackButton))
self.view.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20)
self.view.addSubview(textView)
textView.autoPinTopToSuperviewMargin()
textView.autoHCenterInSuperview()
// In order to have text wrapping be as WYSIWYG as possible, we limit the text view
// to the max text width on the image.
// let maxTextWidthPoints = max(textItem.widthPoints, 200)
// textView.autoSetDimension(.width, toSize: maxTextWidthPoints, relation: .lessThanOrEqual)
// textView.autoPinEdge(toSuperviewMargin: .leading, relation: .greaterThanOrEqual)
// textView.autoPinEdge(toSuperviewMargin: .trailing, relation: .greaterThanOrEqual)
textView.autoPinEdge(toSuperviewMargin: .leading)
textView.autoPinEdge(toSuperviewMargin: .trailing)
self.autoPinView(toBottomOfViewControllerOrKeyboard: textView, avoidNotch: true)
}
private func configureTextView() {
textView.text = textItem.text
textView.font = textItem.font
textView.textColor = textItem.color
textView.isEditable = true
textView.backgroundColor = .clear
textView.isOpaque = false
// We use a white cursor since we use a dark background.
textView.tintColor = .white
textView.returnKeyType = .done
// TODO: Limit the size of the text.
// textView.delegate = self
textView.isScrollEnabled = true
textView.scrollsToTop = false
textView.isUserInteractionEnabled = true
textView.textAlignment = .center
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = 0
textView.contentInset = .zero
}
// MARK: - Events
@objc public func didTapBackButton() {
completeAndDismiss()
}
private func completeAndDismiss() {
// Before we take a screenshot, make sure selection state
// auto-complete suggestions, cursor don't affect screenshot.
textView.resignFirstResponder()
if textView.isFirstResponder {
owsFailDebug("Text view is still first responder.")
}
textView.selectedTextRange = nil
self.delegate?.textEditDidComplete(textItem: textItem, text: textView.text)
self.dismiss(animated: false) {
// Do nothing.
}
}
// MARK: - VAlignTextViewDelegate
public func textViewDidComplete() {
completeAndDismiss()
}
}