mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Replace old caption view with new caption view.
This commit is contained in:
parent
504416f79e
commit
87646b1798
|
@ -12,6 +12,7 @@
|
|||
34074F61203D0CBE004596AE /* OWSSounds.m in Sources */ = {isa = PBXBuildFile; fileRef = 34074F5F203D0CBD004596AE /* OWSSounds.m */; };
|
||||
34074F62203D0CBE004596AE /* OWSSounds.h in Headers */ = {isa = PBXBuildFile; fileRef = 34074F60203D0CBE004596AE /* OWSSounds.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
34080EFE2225F96D0087E99F /* ImageEditorPaletteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34080EFD2225F96D0087E99F /* ImageEditorPaletteView.swift */; };
|
||||
34080F0022282C880087E99F /* AttachmentCaptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34080EFF22282C880087E99F /* AttachmentCaptionViewController.swift */; };
|
||||
340B02BA1FA0D6C700F9CFEC /* ConversationViewItemTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 340B02B91FA0D6C700F9CFEC /* ConversationViewItemTest.m */; };
|
||||
340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */; };
|
||||
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */; };
|
||||
|
@ -638,6 +639,7 @@
|
|||
34074F5F203D0CBD004596AE /* OWSSounds.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSounds.m; sourceTree = "<group>"; };
|
||||
34074F60203D0CBE004596AE /* OWSSounds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSounds.h; sourceTree = "<group>"; };
|
||||
34080EFD2225F96D0087E99F /* ImageEditorPaletteView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditorPaletteView.swift; sourceTree = "<group>"; };
|
||||
34080EFF22282C880087E99F /* AttachmentCaptionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentCaptionViewController.swift; sourceTree = "<group>"; };
|
||||
340B02B61F9FD31800F9CFEC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = translations/he.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
340B02B91FA0D6C700F9CFEC /* ConversationViewItemTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationViewItemTest.m; sourceTree = "<group>"; };
|
||||
340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsOptionsViewController.m; sourceTree = "<group>"; };
|
||||
|
@ -2106,6 +2108,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
34AC09D2211B39B000997B47 /* AttachmentApprovalViewController.swift */,
|
||||
34080EFF22282C880087E99F /* AttachmentCaptionViewController.swift */,
|
||||
34AC09CF211B39B000997B47 /* ContactFieldView.swift */,
|
||||
34AC09CD211B39B000997B47 /* ContactShareApprovalViewController.swift */,
|
||||
34AC09DB211B39B100997B47 /* CountryCodeViewController.h */,
|
||||
|
@ -3328,6 +3331,7 @@
|
|||
34AC09E1211B39B100997B47 /* SelectThreadViewController.m in Sources */,
|
||||
34AC09EF211B39B100997B47 /* ViewControllerUtils.m in Sources */,
|
||||
346941A2215D2EE400B5BFAD /* OWSConversationColor.m in Sources */,
|
||||
34080F0022282C880087E99F /* AttachmentCaptionViewController.swift in Sources */,
|
||||
34AC0A17211B39EA00997B47 /* VideoPlayerView.swift in Sources */,
|
||||
34BEDB1321C43F6A007B0EAE /* ImageEditorView.swift in Sources */,
|
||||
34AC09EE211B39B100997B47 /* EditContactShareNameViewController.swift in Sources */,
|
||||
|
|
|
@ -226,49 +226,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
|
||||
// layout immediately to avoid animating the layout process during the transition
|
||||
self.currentPageViewController.view.layoutIfNeeded()
|
||||
|
||||
// As a refresher, the _Information Architecture_ here is:
|
||||
//
|
||||
// You are approving an "Album", which has multiple "Attachments"
|
||||
//
|
||||
// The "media message text" and the "media rail" belong to the Album as a whole, whereas
|
||||
// each caption belongs to the individual Attachment.
|
||||
//
|
||||
// The _UI Architecture_ reflects this hierarchy by putting the MediaRail and
|
||||
// MediaMessageText input into the bottomToolView which is then the AttachmentApprovalView's
|
||||
// inputAccessoryView.
|
||||
//
|
||||
// Whereas a CaptionView lives in each page of the PageViewController, per Attachment.
|
||||
//
|
||||
// So as you page, the CaptionViews move out of view with its page, whereas the input
|
||||
// accessory view (rail/media message text) will remain fixed in the viewport.
|
||||
//
|
||||
// However (and here's the kicker), at rest, the media's CaptionView rests just above the
|
||||
// input accessory view. So when things are static, they appear as a single piece of
|
||||
// interface.
|
||||
//
|
||||
// I'm not totally sure if this is what Myles had in mind, but the screenshots left a lot of
|
||||
// behavior ambiguous, and this was my best interpretation.
|
||||
//
|
||||
// Because of this complexity, it is insufficient to observe only the
|
||||
// KeyboardWillChangeFrame, since the keyboard could be changing frame when the CaptionView
|
||||
// became/resigned first responder, when AttachmentApprovalViewController became/resigned
|
||||
// first responder, or when the AttachmentApprovalView's inputAccessoryView.textView
|
||||
// became/resigned first responder, and because these things can happen in immediatre
|
||||
// sequence, getting a single smooth animation requires handling each notification slightly
|
||||
// differently.
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(keyboardWillShow(notification:)),
|
||||
name: .UIKeyboardWillShow,
|
||||
object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(keyboardDidShow(notification:)),
|
||||
name: .UIKeyboardDidShow,
|
||||
object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(keyboardWillHide(notification:)),
|
||||
name: .UIKeyboardWillHide,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
override public func viewWillAppear(_ animated: Bool) {
|
||||
|
@ -282,8 +239,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
return
|
||||
}
|
||||
navigationBar.overrideTheme(type: .clear)
|
||||
|
||||
updateCaptionVisibility()
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
|
@ -310,66 +265,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
return true
|
||||
}
|
||||
|
||||
var lastObservedKeyboardTop: CGFloat = 0
|
||||
var inputAccessorySnapshotView: UIView?
|
||||
|
||||
@objc
|
||||
func keyboardDidShow(notification: Notification) {
|
||||
// If this is a result of the vc becoming first responder, the keyboard isn't actually
|
||||
// showing, rather the inputAccessoryView is now showing, so we want to remove any
|
||||
// previously added toolbar snapshot.
|
||||
if isFirstResponder, inputAccessorySnapshotView != nil {
|
||||
removeToolbarSnapshot()
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func keyboardWillShow(notification: Notification) {
|
||||
guard let userInfo = notification.userInfo else {
|
||||
owsFailDebug("userInfo was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else {
|
||||
owsFailDebug("keyboardEndFrame was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else {
|
||||
owsFailDebug("keyboardEndFrame was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.debug("\(keyboardStartFrame) -> \(keyboardEndFrame)")
|
||||
lastObservedKeyboardTop = keyboardEndFrame.size.height
|
||||
|
||||
let keyboardScenario: KeyboardScenario = bottomToolView.isEditingMediaMessage ? .editingMessage : .editingCaption
|
||||
currentPageViewController.updateCaptionViewBottomInset(keyboardScenario: keyboardScenario)
|
||||
}
|
||||
|
||||
@objc
|
||||
func keyboardWillHide(notification: Notification) {
|
||||
guard let userInfo = notification.userInfo else {
|
||||
owsFailDebug("userInfo was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else {
|
||||
owsFailDebug("keyboardEndFrame was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else {
|
||||
owsFailDebug("keyboardEndFrame was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.debug("\(keyboardStartFrame) -> \(keyboardEndFrame)")
|
||||
|
||||
lastObservedKeyboardTop = UIScreen.main.bounds.height - keyboardEndFrame.size.height
|
||||
currentPageViewController.updateCaptionViewBottomInset(keyboardScenario: .hidden)
|
||||
}
|
||||
|
||||
// MARK: - View Helpers
|
||||
|
||||
func remove(attachmentItem: SignalAttachmentItem) {
|
||||
|
@ -412,12 +307,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
return pagerScrollView
|
||||
}()
|
||||
|
||||
func updateCaptionVisibility() {
|
||||
for pageViewController in pageViewControllers {
|
||||
pageViewController.updateCaptionVisibility(attachmentCount: attachments.count)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIPageViewControllerDelegate
|
||||
|
||||
public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
|
||||
|
@ -433,9 +322,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
// use compact scale when keyboard is popped.
|
||||
let scale: AttachmentPrepViewController.AttachmentViewScale = self.isFirstResponder ? .fullsize : .compact
|
||||
pendingPage.setAttachmentViewScale(scale, animated: false)
|
||||
|
||||
let keyboardScenario: KeyboardScenario = bottomToolView.isEditingMediaMessage ? .editingMessage : .hidden
|
||||
pendingPage.updateCaptionViewBottomInset(keyboardScenario: keyboardScenario)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -524,7 +410,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
Logger.debug("cache miss.")
|
||||
let viewController = AttachmentPrepViewController(attachmentItem: item)
|
||||
viewController.prepDelegate = self
|
||||
viewController.updateCaptionVisibility(attachmentCount: attachments.count)
|
||||
cachedPages[item] = viewController
|
||||
|
||||
return viewController
|
||||
|
@ -537,8 +422,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
|
|||
}
|
||||
|
||||
page.loadViewIfNeeded()
|
||||
let keyboardScenario: KeyboardScenario = bottomToolView.isEditingMediaMessage ? .editingMessage : .hidden
|
||||
page.updateCaptionViewBottomInset(keyboardScenario: keyboardScenario)
|
||||
|
||||
self.setViewControllers([page], direction: direction, animated: isAnimated, completion: nil)
|
||||
updateMediaRail()
|
||||
|
@ -699,74 +582,6 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate
|
|||
func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem) {
|
||||
self.approvalDelegate?.attachmentApproval?(self, changedCaptionOfAttachment: attachmentItem.attachment)
|
||||
}
|
||||
|
||||
func prepViewController(_ prepViewController: AttachmentPrepViewController, willBeginEditingCaptionView captionView: CaptionView) {
|
||||
// When the CaptionView becomes first responder, the AttachmentApprovalViewController will
|
||||
// consequently resignFirstResponder, which means the bottomToolView would disappear from
|
||||
// the screen, so before that happens, we add a snapshot to holds it's place.
|
||||
addInputAccessorySnapshot()
|
||||
}
|
||||
|
||||
func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) {
|
||||
// Disable paging while captions are being edited to avoid a clunky animation.
|
||||
//
|
||||
// Loading the next page causes the CaptionView to resign first responder, which in turn
|
||||
// dismisses the keyboard, which in turn affects the vertical offset of both the CaptionView
|
||||
// from the page we're leaving as well as the page we're entering. Instead we require the
|
||||
// user to dismiss *then* swipe.
|
||||
disablePaging()
|
||||
}
|
||||
|
||||
func addInputAccessorySnapshot() {
|
||||
assert(inputAccessorySnapshotView == nil)
|
||||
// To fix a layout glitch where the snapshot view is 1/2 the width of the screen, it's key
|
||||
// that we use `bottomToolView` and not `inputAccessoryView` which can trigger a layout of
|
||||
// the `bottomToolView`.
|
||||
// Presumably the frame of the inputAccessoryView has just changed because we're in the
|
||||
// middle of switching first responders. We want a snapshot as it *was*, not reflecting any
|
||||
// just-applied superview layout changes.
|
||||
inputAccessorySnapshotView = bottomToolView.snapshotView(afterScreenUpdates: true)
|
||||
guard let inputAccessorySnapshotView = inputAccessorySnapshotView else {
|
||||
owsFailDebug("inputAccessorySnapshotView was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
view.addSubview(inputAccessorySnapshotView)
|
||||
inputAccessorySnapshotView.autoSetDimension(.height, toSize: bottomToolView.bounds.height)
|
||||
inputAccessorySnapshotView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
|
||||
}
|
||||
|
||||
func removeToolbarSnapshot() {
|
||||
guard let inputAccessorySnapshotView = self.inputAccessorySnapshotView else {
|
||||
owsFailDebug("inputAccessorySnapshotView was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
inputAccessorySnapshotView.removeFromSuperview()
|
||||
self.inputAccessorySnapshotView = nil
|
||||
}
|
||||
|
||||
func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) {
|
||||
enablePaging()
|
||||
}
|
||||
|
||||
func desiredCaptionViewBottomInset(keyboardScenario: KeyboardScenario) -> CGFloat {
|
||||
switch keyboardScenario {
|
||||
case .hidden, .editingMessage:
|
||||
return bottomToolView.bounds.height
|
||||
case .editingCaption:
|
||||
return lastObservedKeyboardTop
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
func disablePaging() {
|
||||
pagerScrollView?.panGestureRecognizer.isEnabled = false
|
||||
}
|
||||
|
||||
func enablePaging() {
|
||||
pagerScrollView?.panGestureRecognizer.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: GalleryRail
|
||||
|
@ -818,12 +633,6 @@ enum KeyboardScenario {
|
|||
|
||||
protocol AttachmentPrepViewControllerDelegate: class {
|
||||
func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem)
|
||||
|
||||
func prepViewController(_ prepViewController: AttachmentPrepViewController, willBeginEditingCaptionView captionView: CaptionView)
|
||||
func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView)
|
||||
func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView)
|
||||
|
||||
func desiredCaptionViewBottomInset(keyboardScenario: KeyboardScenario) -> CGFloat
|
||||
}
|
||||
|
||||
public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarDelegate, OWSVideoPlayerDelegate {
|
||||
|
@ -861,30 +670,9 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateCaptionVisibility(attachmentCount: Int) {
|
||||
if attachmentCount > 1 {
|
||||
captionView.isHidden = false
|
||||
return
|
||||
}
|
||||
|
||||
// If we previously had multiple attachments, we'd have shown the caption fields.
|
||||
//
|
||||
// Subsequently, if the user had added caption text, then removed the other attachments
|
||||
// we will continue to show this caption field, so as not to hide any already-entered text.
|
||||
if let captionText = captionView.captionText, captionText.count > 0 {
|
||||
captionView.isHidden = false
|
||||
return
|
||||
}
|
||||
|
||||
captionView.isHidden = true
|
||||
}
|
||||
|
||||
// MARK: - Subviews
|
||||
|
||||
lazy var captionView: CaptionView = {
|
||||
return CaptionView(attachmentItem: attachmentItem)
|
||||
}()
|
||||
|
||||
// TODO: Do we still need this?
|
||||
lazy var touchInterceptorView: UIView = {
|
||||
let touchInterceptorView = UIView()
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTouchInterceptorView(gesture:)))
|
||||
|
@ -1023,12 +811,6 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
|
|||
view.addSubview(touchInterceptorView)
|
||||
touchInterceptorView.autoPinEdgesToSuperviewEdges()
|
||||
touchInterceptorView.isHidden = true
|
||||
|
||||
view.addSubview(captionView)
|
||||
captionView.delegate = self
|
||||
|
||||
captionView.autoPinWidthToSuperview()
|
||||
captionViewBottomConstraint = captionView.autoPinEdge(toSuperviewEdge: .bottom)
|
||||
}
|
||||
|
||||
override public func viewWillLayoutSubviews() {
|
||||
|
@ -1041,40 +823,11 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
|
|||
ensureAttachmentViewScale(animated: false)
|
||||
}
|
||||
|
||||
// MARK: CaptionView lifts with keyboard
|
||||
|
||||
var hasLaidOutCaptionView: Bool = false
|
||||
var captionViewBottomConstraint: NSLayoutConstraint!
|
||||
func updateCaptionViewBottomInset(keyboardScenario: KeyboardScenario) {
|
||||
guard let prepDelegate = self.prepDelegate else {
|
||||
owsFailDebug("prepDelegate was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let changeBlock = {
|
||||
let offset: CGFloat = -1 * prepDelegate.desiredCaptionViewBottomInset(keyboardScenario: keyboardScenario)
|
||||
self.captionViewBottomConstraint.constant = offset
|
||||
self.captionView.superview?.layoutIfNeeded()
|
||||
}
|
||||
|
||||
// To avoid an animation glitch, we apply this update without animation before initial
|
||||
// appearance. But after that, we want to apply the constraint change within the existing
|
||||
// animation context, since we call this while handling a UIKeyboard notification, which
|
||||
// allows us to slide up the CaptionView in lockstep with the keyboard.
|
||||
if hasLaidOutCaptionView {
|
||||
changeBlock()
|
||||
} else {
|
||||
hasLaidOutCaptionView = true
|
||||
UIView.performWithoutAnimation { changeBlock() }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Event Handlers
|
||||
|
||||
@objc
|
||||
func didTapTouchInterceptorView(gesture: UITapGestureRecognizer) {
|
||||
Logger.info("")
|
||||
captionView.endEditing()
|
||||
touchInterceptorView.isHidden = true
|
||||
}
|
||||
|
||||
|
@ -1228,30 +981,12 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
|
|||
}
|
||||
}
|
||||
|
||||
extension AttachmentPrepViewController: CaptionViewDelegate {
|
||||
func captionViewWillBeginEditing(_ captionView: CaptionView) {
|
||||
prepDelegate?.prepViewController(self, willBeginEditingCaptionView: captionView)
|
||||
}
|
||||
|
||||
func captionView(_ captionView: CaptionView, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem) {
|
||||
extension AttachmentPrepViewController: AttachmentCaptionDelegate {
|
||||
func captionView(_ captionView: AttachmentCaptionViewController, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem) {
|
||||
let attachment = attachmentItem.attachment
|
||||
attachment.captionText = captionText
|
||||
prepDelegate?.prepViewController(self, didUpdateCaptionForAttachmentItem: attachmentItem)
|
||||
}
|
||||
|
||||
func captionViewDidBeginEditing(_ captionView: CaptionView) {
|
||||
// Don't allow user to pan until they've dismissed the keyboard.
|
||||
// This avoids a really ugly animation from simultaneously dismissing the keyboard
|
||||
// while loading a new PrepViewController, and it's CaptionView, whose layout depends
|
||||
// on the keyboard's position.
|
||||
touchInterceptorView.isHidden = false
|
||||
prepDelegate?.prepViewController(self, didBeginEditingCaptionView: captionView)
|
||||
}
|
||||
|
||||
func captionViewDidEndEditing(_ captionView: CaptionView) {
|
||||
touchInterceptorView.isHidden = true
|
||||
prepDelegate?.prepViewController(self, didEndEditingCaptionView: captionView)
|
||||
}
|
||||
}
|
||||
|
||||
extension AttachmentPrepViewController: UIScrollViewDelegate {
|
||||
|
@ -1331,15 +1066,27 @@ extension AttachmentPrepViewController: ImageEditorViewDelegate {
|
|||
if withNavigation {
|
||||
let navigationController = OWSNavigationController(rootViewController: viewController)
|
||||
navigationController.modalPresentationStyle = .overFullScreen
|
||||
self.present(navigationController, animated: true) {
|
||||
|
||||
if let navigationBar = navigationController.navigationBar as? OWSNavigationBar {
|
||||
navigationBar.overrideTheme(type: .clear)
|
||||
} else {
|
||||
owsFailDebug("navigationBar was nil or unexpected class")
|
||||
}
|
||||
|
||||
self.present(navigationController, animated: false) {
|
||||
// Do nothing.
|
||||
}
|
||||
} else {
|
||||
self.present(viewController, animated: true) {
|
||||
self.present(viewController, animated: false) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func imageEditorPresentCaptionView() {
|
||||
let view = AttachmentCaptionViewController(delegate: self, attachmentItem: attachmentItem)
|
||||
self.imageEditor(presentFullScreenOverlay: view, withNavigation: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
@ -1393,251 +1140,9 @@ class BottomToolView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
protocol CaptionViewDelegate: class {
|
||||
func captionView(_ captionView: CaptionView, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem)
|
||||
func captionViewWillBeginEditing(_ captionView: CaptionView)
|
||||
func captionViewDidBeginEditing(_ captionView: CaptionView)
|
||||
func captionViewDidEndEditing(_ captionView: CaptionView)
|
||||
}
|
||||
|
||||
class CaptionView: UIView {
|
||||
|
||||
var captionText: String? {
|
||||
get { return textView.text }
|
||||
set {
|
||||
textView.text = newValue
|
||||
updatePlaceholderTextViewVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
let attachmentItem: SignalAttachmentItem
|
||||
var attachment: SignalAttachment {
|
||||
return attachmentItem.attachment
|
||||
}
|
||||
|
||||
weak var delegate: CaptionViewDelegate?
|
||||
|
||||
private let kMinTextViewHeight: CGFloat = 38
|
||||
private var textViewHeightConstraint: NSLayoutConstraint!
|
||||
|
||||
private lazy var lengthLimitLabel: UILabel = {
|
||||
let lengthLimitLabel = UILabel()
|
||||
|
||||
// Length Limit Label shown when the user inputs too long of a message
|
||||
lengthLimitLabel.textColor = .white
|
||||
lengthLimitLabel.text = NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED", comment: "One-line label indicating the user can add no more text to the attachment caption.")
|
||||
lengthLimitLabel.textAlignment = .center
|
||||
|
||||
// Add shadow in case overlayed on white content
|
||||
lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor
|
||||
lengthLimitLabel.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||
lengthLimitLabel.layer.shadowOpacity = 0.8
|
||||
lengthLimitLabel.isHidden = true
|
||||
|
||||
return lengthLimitLabel
|
||||
}()
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
init(attachmentItem: SignalAttachmentItem) {
|
||||
self.attachmentItem = attachmentItem
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
backgroundColor = UIColor.black.withAlphaComponent(0.6)
|
||||
|
||||
self.captionText = attachmentItem.captionText
|
||||
textView.delegate = self
|
||||
|
||||
let textContainer = UIView()
|
||||
textContainer.addSubview(placeholderTextView)
|
||||
placeholderTextView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
textContainer.addSubview(textView)
|
||||
textView.autoPinEdgesToSuperviewEdges()
|
||||
textViewHeightConstraint = textView.autoSetDimension(.height, toSize: kMinTextViewHeight)
|
||||
|
||||
let hStack = UIStackView(arrangedSubviews: [addCaptionButton, textContainer, doneButton])
|
||||
doneButton.isHidden = true
|
||||
|
||||
addSubview(hStack)
|
||||
hStack.autoPinEdgesToSuperviewMargins()
|
||||
|
||||
addSubview(lengthLimitLabel)
|
||||
lengthLimitLabel.autoPinEdge(toSuperviewMargin: .left)
|
||||
lengthLimitLabel.autoPinEdge(toSuperviewMargin: .right)
|
||||
lengthLimitLabel.autoPinEdge(.bottom, to: .top, of: textView, withOffset: -9)
|
||||
lengthLimitLabel.setContentHuggingHigh()
|
||||
lengthLimitLabel.setCompressionResistanceHigh()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK:
|
||||
|
||||
func endEditing() {
|
||||
textView.resignFirstResponder()
|
||||
}
|
||||
|
||||
override var inputAccessoryView: UIView? {
|
||||
// Don't inherit the vc's inputAccessoryView
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: Subviews
|
||||
|
||||
func updatePlaceholderTextViewVisibility() {
|
||||
let isHidden: Bool = {
|
||||
guard !self.textView.isFirstResponder else {
|
||||
return true
|
||||
}
|
||||
|
||||
guard let captionText = self.textView.text else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard captionText.count > 0 else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}()
|
||||
|
||||
placeholderTextView.isHidden = isHidden
|
||||
}
|
||||
|
||||
private lazy var placeholderTextView: UITextView = {
|
||||
let placeholderTextView = UITextView()
|
||||
placeholderTextView.text = NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_PLACEHOLDER", comment: "placeholder text for an empty captioning field")
|
||||
placeholderTextView.isEditable = false
|
||||
|
||||
placeholderTextView.backgroundColor = .clear
|
||||
placeholderTextView.font = UIFont.ows_dynamicTypeBody
|
||||
|
||||
placeholderTextView.textColor = Theme.darkThemePrimaryColor
|
||||
placeholderTextView.tintColor = Theme.darkThemePrimaryColor
|
||||
placeholderTextView.returnKeyType = .done
|
||||
|
||||
return placeholderTextView
|
||||
}()
|
||||
|
||||
private lazy var textView: UITextView = {
|
||||
let textView = UITextView()
|
||||
textView.backgroundColor = .clear
|
||||
textView.keyboardAppearance = Theme.darkThemeKeyboardAppearance
|
||||
textView.font = UIFont.ows_dynamicTypeBody
|
||||
textView.textColor = Theme.darkThemePrimaryColor
|
||||
textView.tintColor = Theme.darkThemePrimaryColor
|
||||
|
||||
return textView
|
||||
}()
|
||||
|
||||
lazy var addCaptionButton: UIButton = {
|
||||
let addCaptionButton = OWSButton { [weak self] in
|
||||
self?.textView.becomeFirstResponder()
|
||||
}
|
||||
|
||||
let icon = #imageLiteral(resourceName: "ic_add_caption").withRenderingMode(.alwaysTemplate)
|
||||
addCaptionButton.setImage(icon, for: .normal)
|
||||
addCaptionButton.tintColor = Theme.darkThemePrimaryColor
|
||||
|
||||
return addCaptionButton
|
||||
}()
|
||||
|
||||
lazy var doneButton: UIButton = {
|
||||
let doneButton = OWSButton { [weak self] in
|
||||
self?.textView.resignFirstResponder()
|
||||
}
|
||||
doneButton.setTitle(CommonStrings.doneButton, for: .normal)
|
||||
doneButton.tintColor = Theme.darkThemePrimaryColor
|
||||
|
||||
return doneButton
|
||||
}()
|
||||
}
|
||||
|
||||
let kMaxCaptionCharacterCount = 240
|
||||
|
||||
// Coincides with Android's max text message length
|
||||
let kMaxMessageBodyCharacterCount = 2000
|
||||
|
||||
extension CaptionView: UITextViewDelegate {
|
||||
|
||||
public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
|
||||
delegate?.captionViewWillBeginEditing(self)
|
||||
return true
|
||||
}
|
||||
|
||||
public func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
updatePlaceholderTextViewVisibility()
|
||||
doneButton.isHidden = false
|
||||
addCaptionButton.isHidden = true
|
||||
|
||||
delegate?.captionViewDidBeginEditing(self)
|
||||
}
|
||||
|
||||
public func textViewDidEndEditing(_ textView: UITextView) {
|
||||
updatePlaceholderTextViewVisibility()
|
||||
doneButton.isHidden = true
|
||||
addCaptionButton.isHidden = false
|
||||
|
||||
delegate?.captionViewDidEndEditing(self)
|
||||
}
|
||||
|
||||
public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
let existingText: String = textView.text ?? ""
|
||||
let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text)
|
||||
|
||||
let kMaxCaptionByteCount = kOversizeTextMessageSizeThreshold / 4
|
||||
guard proposedText.utf8.count <= kMaxCaptionByteCount else {
|
||||
Logger.debug("hit caption byte count limit")
|
||||
self.lengthLimitLabel.isHidden = false
|
||||
|
||||
// `range` represents the section of the existing text we will replace. We can re-use that space.
|
||||
// Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be
|
||||
// represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is
|
||||
// to just measure the utf8 encoded bytes of the replaced substring.
|
||||
let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count
|
||||
|
||||
// Accept as much of the input as we can
|
||||
let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete
|
||||
if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) {
|
||||
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// After verifying the byte-length is sufficiently small, verify the character count is within bounds.
|
||||
// Normally this character count should entail *much* less byte count.
|
||||
guard proposedText.count <= kMaxCaptionCharacterCount else {
|
||||
Logger.debug("hit caption character count limit")
|
||||
|
||||
self.lengthLimitLabel.isHidden = false
|
||||
|
||||
// `range` represents the section of the existing text we will replace. We can re-use that space.
|
||||
let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count
|
||||
|
||||
// Accept as much of the input as we can
|
||||
let charBudget: Int = Int(kMaxCaptionCharacterCount) - charsAfterDelete
|
||||
if charBudget >= 0 {
|
||||
let acceptableNewText = String(text.prefix(charBudget))
|
||||
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
self.lengthLimitLabel.isHidden = true
|
||||
return true
|
||||
}
|
||||
|
||||
public func textViewDidChange(_ textView: UITextView) {
|
||||
self.delegate?.captionView(self, didChangeCaptionText: textView.text, attachmentItem: attachmentItem)
|
||||
}
|
||||
}
|
||||
|
||||
protocol MediaMessageTextToolbarDelegate: class {
|
||||
func mediaMessageTextToolbarDidTapSend(_ mediaMessageTextToolbar: MediaMessageTextToolbar)
|
||||
func mediaMessageTextToolbarDidBeginEditing(_ mediaMessageTextToolbar: MediaMessageTextToolbar)
|
||||
|
@ -1942,11 +1447,11 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate {
|
|||
return true
|
||||
}
|
||||
|
||||
guard let captionText = self.textView.text else {
|
||||
guard let text = self.textView.text else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard captionText.count > 0 else {
|
||||
guard text.count > 0 else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol AttachmentCaptionDelegate: class {
|
||||
func captionView(_ captionView: AttachmentCaptionViewController, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem)
|
||||
}
|
||||
|
||||
class AttachmentCaptionViewController: OWSViewController {
|
||||
|
||||
weak var delegate: AttachmentCaptionDelegate?
|
||||
|
||||
private let attachmentItem: SignalAttachmentItem
|
||||
|
||||
private let originalCaptionText: String?
|
||||
|
||||
private let textView = UITextView()
|
||||
|
||||
private var textViewHeightConstraint: NSLayoutConstraint?
|
||||
|
||||
private let kMaxCaptionCharacterCount = 240
|
||||
|
||||
init(delegate: AttachmentCaptionDelegate,
|
||||
attachmentItem: SignalAttachmentItem) {
|
||||
self.delegate = delegate
|
||||
self.attachmentItem = attachmentItem
|
||||
self.originalCaptionText = attachmentItem.captionText
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
self.addObserver(textView, forKeyPath: "contentSize", options: .new, context: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable, message: "use other init() instead.")
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.removeObserver(textView, forKeyPath: "contentSize")
|
||||
}
|
||||
|
||||
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
|
||||
updateTextView()
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
public override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
textView.becomeFirstResponder()
|
||||
|
||||
updateTextView()
|
||||
}
|
||||
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
textView.becomeFirstResponder()
|
||||
|
||||
updateTextView()
|
||||
}
|
||||
|
||||
public override func loadView() {
|
||||
self.view = UIView()
|
||||
self.view.backgroundColor = UIColor(white: 0, alpha: 0.25)
|
||||
self.view.isOpaque = false
|
||||
|
||||
self.view.isUserInteractionEnabled = true
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(backgroundTapped)))
|
||||
|
||||
configureTextView()
|
||||
|
||||
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel,
|
||||
target: self,
|
||||
action: #selector(didTapCancel))
|
||||
cancelButton.tintColor = .white
|
||||
navigationItem.leftBarButtonItem = cancelButton
|
||||
let doneIcon = UIImage(named: "image_editor_checkmark_full")?.withRenderingMode(.alwaysTemplate)
|
||||
let doneButton = UIBarButtonItem(image: doneIcon, style: .plain,
|
||||
target: self,
|
||||
action: #selector(didTapDone))
|
||||
doneButton.tintColor = .white
|
||||
navigationItem.rightBarButtonItem = doneButton
|
||||
|
||||
self.view.layoutMargins = .zero
|
||||
|
||||
lengthLimitLabel.setContentHuggingHigh()
|
||||
lengthLimitLabel.setCompressionResistanceHigh()
|
||||
|
||||
let stackView = UIStackView(arrangedSubviews: [lengthLimitLabel, textView])
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = 20
|
||||
stackView.alignment = .fill
|
||||
stackView.addBackgroundView(withBackgroundColor: UIColor(white: 0, alpha: 0.5))
|
||||
stackView.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20)
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
self.view.addSubview(stackView)
|
||||
stackView.autoPinEdge(toSuperviewEdge: .leading)
|
||||
stackView.autoPinEdge(toSuperviewEdge: .trailing)
|
||||
self.autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
|
||||
|
||||
let minTextHeight: CGFloat = textView.font?.lineHeight ?? 0
|
||||
textViewHeightConstraint = textView.autoSetDimension(.height, toSize: minTextHeight)
|
||||
|
||||
view.addSubview(placeholderTextView)
|
||||
placeholderTextView.autoAlignAxis(.horizontal, toSameAxisOf: textView)
|
||||
placeholderTextView.autoPinEdge(.leading, to: .leading, of: textView)
|
||||
placeholderTextView.autoPinEdge(.trailing, to: .trailing, of: textView)
|
||||
}
|
||||
|
||||
private func configureTextView() {
|
||||
textView.delegate = self
|
||||
|
||||
textView.text = attachmentItem.captionText
|
||||
textView.font = UIFont.ows_dynamicTypeBody
|
||||
textView.textColor = .white
|
||||
|
||||
textView.isEditable = true
|
||||
textView.backgroundColor = .clear
|
||||
textView.isOpaque = false
|
||||
// We use a white cursor since we use a dark background.
|
||||
textView.tintColor = .white
|
||||
textView.isScrollEnabled = true
|
||||
textView.scrollsToTop = false
|
||||
textView.isUserInteractionEnabled = true
|
||||
textView.textAlignment = .left
|
||||
textView.textContainerInset = .zero
|
||||
textView.textContainer.lineFragmentPadding = 0
|
||||
textView.contentInset = .zero
|
||||
}
|
||||
|
||||
// MARK: - Events
|
||||
|
||||
@objc func backgroundTapped(sender: UIGestureRecognizer) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
completeAndDismiss(didCancel: false)
|
||||
}
|
||||
|
||||
@objc public func didTapCancel() {
|
||||
completeAndDismiss(didCancel: true)
|
||||
}
|
||||
|
||||
@objc public func didTapDone() {
|
||||
completeAndDismiss(didCancel: false)
|
||||
}
|
||||
|
||||
private func completeAndDismiss(didCancel: Bool) {
|
||||
if didCancel {
|
||||
self.delegate?.captionView(self, didChangeCaptionText: originalCaptionText, attachmentItem: attachmentItem)
|
||||
} else {
|
||||
self.delegate?.captionView(self, didChangeCaptionText: self.textView.text, attachmentItem: attachmentItem)
|
||||
}
|
||||
|
||||
self.dismiss(animated: true) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Length Limit
|
||||
|
||||
private lazy var lengthLimitLabel: UILabel = {
|
||||
let lengthLimitLabel = UILabel()
|
||||
|
||||
// Length Limit Label shown when the user inputs too long of a message
|
||||
lengthLimitLabel.textColor = UIColor.ows_destructiveRed
|
||||
lengthLimitLabel.text = NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED", comment: "One-line label indicating the user can add no more text to the attachment caption.")
|
||||
lengthLimitLabel.textAlignment = .center
|
||||
|
||||
// Add shadow in case overlayed on white content
|
||||
lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor
|
||||
lengthLimitLabel.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
|
||||
lengthLimitLabel.layer.shadowOpacity = 0.8
|
||||
lengthLimitLabel.isHidden = true
|
||||
|
||||
return lengthLimitLabel
|
||||
}()
|
||||
|
||||
// MARK: - Text Height
|
||||
|
||||
// TODO: We need to revisit this with Myles.
|
||||
func updatePlaceholderTextViewVisibility() {
|
||||
let isHidden: Bool = {
|
||||
guard !self.textView.isFirstResponder else {
|
||||
return true
|
||||
}
|
||||
|
||||
guard let captionText = self.textView.text else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard captionText.count > 0 else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}()
|
||||
|
||||
placeholderTextView.isHidden = isHidden
|
||||
}
|
||||
|
||||
private lazy var placeholderTextView: UIView = {
|
||||
let placeholderTextView = UITextView()
|
||||
placeholderTextView.text = NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_PLACEHOLDER", comment: "placeholder text for an empty captioning field")
|
||||
placeholderTextView.isEditable = false
|
||||
|
||||
placeholderTextView.backgroundColor = .clear
|
||||
placeholderTextView.font = UIFont.ows_dynamicTypeBody
|
||||
|
||||
placeholderTextView.textColor = Theme.darkThemePrimaryColor
|
||||
placeholderTextView.tintColor = Theme.darkThemePrimaryColor
|
||||
placeholderTextView.returnKeyType = .done
|
||||
|
||||
return placeholderTextView
|
||||
}()
|
||||
|
||||
// MARK: - Text Height
|
||||
|
||||
private func updateTextView() {
|
||||
guard let textViewHeightConstraint = textViewHeightConstraint else {
|
||||
owsFailDebug("Missing textViewHeightConstraint.")
|
||||
return
|
||||
}
|
||||
|
||||
let contentSize = textView.sizeThatFits(CGSize(width: textView.width(), height: CGFloat.greatestFiniteMagnitude))
|
||||
|
||||
// `textView.contentSize` isn't accurate when restoring a multiline draft, so we compute it here.
|
||||
textView.contentSize = contentSize
|
||||
|
||||
let minHeight: CGFloat = textView.font?.lineHeight ?? 0
|
||||
let maxHeight = minHeight * 4
|
||||
let newHeight = contentSize.height.clamp(minHeight, maxHeight)
|
||||
|
||||
textViewHeightConstraint.constant = newHeight
|
||||
textView.invalidateIntrinsicContentSize()
|
||||
textView.superview?.invalidateIntrinsicContentSize()
|
||||
|
||||
textView.isScrollEnabled = contentSize.height > maxHeight
|
||||
|
||||
updatePlaceholderTextViewVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
extension AttachmentCaptionViewController: UITextViewDelegate {
|
||||
|
||||
public func textViewDidChange(_ textView: UITextView) {
|
||||
updateTextView()
|
||||
}
|
||||
|
||||
public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
let existingText: String = textView.text ?? ""
|
||||
let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text)
|
||||
|
||||
let kMaxCaptionByteCount = kOversizeTextMessageSizeThreshold / 4
|
||||
guard proposedText.utf8.count <= kMaxCaptionByteCount else {
|
||||
Logger.debug("hit caption byte count limit")
|
||||
self.lengthLimitLabel.isHidden = false
|
||||
|
||||
// `range` represents the section of the existing text we will replace. We can re-use that space.
|
||||
// Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be
|
||||
// represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is
|
||||
// to just measure the utf8 encoded bytes of the replaced substring.
|
||||
let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count
|
||||
|
||||
// Accept as much of the input as we can
|
||||
let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete
|
||||
if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) {
|
||||
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// After verifying the byte-length is sufficiently small, verify the character count is within bounds.
|
||||
// Normally this character count should entail *much* less byte count.
|
||||
guard proposedText.count <= kMaxCaptionCharacterCount else {
|
||||
Logger.debug("hit caption character count limit")
|
||||
|
||||
self.lengthLimitLabel.isHidden = false
|
||||
|
||||
// `range` represents the section of the existing text we will replace. We can re-use that space.
|
||||
let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count
|
||||
|
||||
// Accept as much of the input as we can
|
||||
let charBudget: Int = Int(kMaxCaptionCharacterCount) - charsAfterDelete
|
||||
if charBudget >= 0 {
|
||||
let acceptableNewText = String(text.prefix(charBudget))
|
||||
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
self.lengthLimitLabel.isHidden = true
|
||||
return true
|
||||
}
|
||||
|
||||
public func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
updatePlaceholderTextViewVisibility()
|
||||
}
|
||||
|
||||
public func textViewDidEndEditing(_ textView: UITextView) {
|
||||
updatePlaceholderTextViewVisibility()
|
||||
}
|
||||
}
|
|
@ -188,8 +188,6 @@ extension UIImage {
|
|||
return nil
|
||||
}
|
||||
|
||||
Logger.verbose("scale: \(self.scale)")
|
||||
|
||||
// Convert the location from points to pixels and clamp to the image bounds.
|
||||
let xPixels: Int = Int(round(locationPoints.x * self.scale)).clamp(0, imageWidth - 1)
|
||||
let yPixels: Int = Int(round(locationPoints.y * self.scale)).clamp(0, imageHeight - 1)
|
||||
|
|
|
@ -8,6 +8,7 @@ import UIKit
|
|||
public protocol ImageEditorViewDelegate: class {
|
||||
func imageEditor(presentFullScreenOverlay viewController: UIViewController,
|
||||
withNavigation: Bool)
|
||||
func imageEditorPresentCaptionView()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
@ -278,6 +279,16 @@ public class ImageEditorView: UIView {
|
|||
@objc func didTapCaption(sender: UIButton) {
|
||||
Logger.verbose("")
|
||||
|
||||
delegate?.imageEditorPresentCaptionView()
|
||||
|
||||
// // TODO:
|
||||
// let maxTextWidthPoints = model.srcImageSizePixels.width * ImageEditorTextItem.kDefaultUnitWidth
|
||||
// // let maxTextWidthPoints = canvasView.imageView.width() * ImageEditorTextItem.kDefaultUnitWidth
|
||||
//
|
||||
// let textEditor = ImageEditorTextViewController(delegate: self, textItem: textItem, maxTextWidthPoints: maxTextWidthPoints)
|
||||
// self.delegate?.imageEditor(presentFullScreenOverlay: textEditor,
|
||||
// withNavigation: true)
|
||||
|
||||
// TODO:
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue