Merge branch 'charlesmchen/imageEditorDesign5'

pull/2/head
Matthew Chen 6 years ago
commit 575de76a43

@ -2,17 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "add-photo-24@1x.png", "filename" : "create-album-outline-32@1x.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "add-photo-24@2x.png", "filename" : "create-album-outline-32@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "add-photo-24@3x.png", "filename" : "create-album-outline-32@3x.png",
"scale" : "3x" "scale" : "3x"
} }
], ],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -2,17 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "add-caption-32@1x.png", "filename" : "caption-24@1x.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "add-caption-32@2x.png", "filename" : "caption-24@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "add-caption-32@3x.png", "filename" : "caption-24@3x.png",
"scale" : "3x" "scale" : "3x"
} }
], ],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

@ -2836,7 +2836,9 @@ typedef enum : NSUInteger {
OWSImagePickerGridController *picker = [OWSImagePickerGridController new]; OWSImagePickerGridController *picker = [OWSImagePickerGridController new];
picker.delegate = self; picker.delegate = self;
pickerModal = [[OWSNavigationController alloc] initWithRootViewController:picker]; OWSNavigationController *modal = [[OWSNavigationController alloc] initWithRootViewController:picker];
modal.ows_prefersStatusBarHidden = @(YES);
pickerModal = modal;
} else { } else {
UIImagePickerController *picker = [OWSImagePickerController new]; UIImagePickerController *picker = [OWSImagePickerController new];
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;

@ -380,19 +380,32 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
} }
func complete(withAssets assets: [PHAsset]) { func complete(withAssets assets: [PHAsset]) {
let attachmentPromises: [Promise<SignalAttachment>] = assets.map({
return photoCollectionContents.outgoingAttachment(for: $0) ModalActivityIndicatorViewController.present(fromViewController: self,
}) canCancel: false) { (modal) in
let attachmentPromises: [Promise<SignalAttachment>] = assets.map({
firstly { return self.photoCollectionContents.outgoingAttachment(for: $0)
when(fulfilled: attachmentPromises) })
}.map { attachments in
Logger.debug("built all attachments") firstly {
self.didComplete(withAttachments: attachments) when(fulfilled: attachmentPromises)
}.catch { error in }.map { attachments in
Logger.error("failed to prepare attachments. error: \(error)") Logger.debug("built all attachments")
OWSAlerts.showAlert(title: NSLocalizedString("IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS", comment: "alert title"))
}.retainUntilComplete() DispatchQueue.main.async {
modal.dismiss(completion: {
self.didComplete(withAttachments: attachments)
})
}
}.catch { error in
Logger.error("failed to prepare attachments. error: \(error)")
DispatchQueue.main.async {
modal.dismiss(completion: {
OWSAlerts.showAlert(title: NSLocalizedString("IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS", comment: "alert title"))
})
}
}.retainUntilComplete()
}
} }
private func didComplete(withAttachments attachments: [SignalAttachment]) { private func didComplete(withAttachments attachments: [SignalAttachment]) {

@ -169,6 +169,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
let vc = AttachmentApprovalViewController(mode: .modal, attachments: attachments) let vc = AttachmentApprovalViewController(mode: .modal, attachments: attachments)
vc.approvalDelegate = approvalDelegate vc.approvalDelegate = approvalDelegate
let navController = OWSNavigationController(rootViewController: vc) let navController = OWSNavigationController(rootViewController: vc)
navController.ows_prefersStatusBarHidden = true
guard let navigationBar = navController.navigationBar as? OWSNavigationBar else { guard let navigationBar = navController.navigationBar as? OWSNavigationBar else {
owsFailDebug("navigationBar was nil or unexpected class") owsFailDebug("navigationBar was nil or unexpected class")
@ -198,6 +199,10 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
// MARK: - View Lifecycle // MARK: - View Lifecycle
public override var prefersStatusBarHidden: Bool {
return true
}
override public func viewDidLoad() { override public func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -229,8 +234,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
Logger.debug("") Logger.debug("")
super.viewWillAppear(animated) super.viewWillAppear(animated)
CurrentAppContext().setStatusBarHidden(true, animated: animated)
guard let navigationBar = navigationController?.navigationBar as? OWSNavigationBar else { guard let navigationBar = navigationController?.navigationBar as? OWSNavigationBar else {
owsFailDebug("navigationBar was nil or unexpected class") owsFailDebug("navigationBar was nil or unexpected class")
return return
@ -238,6 +241,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
navigationBar.overrideTheme(type: .clear) navigationBar.overrideTheme(type: .clear)
updateNavigationBar() updateNavigationBar()
updateControlVisibility()
} }
override public func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
@ -246,15 +250,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
super.viewDidAppear(animated) super.viewDidAppear(animated)
updateNavigationBar() updateNavigationBar()
updateControlVisibility()
} }
override public func viewWillDisappear(_ animated: Bool) { override public func viewWillDisappear(_ animated: Bool) {
Logger.debug("") Logger.debug("")
super.viewWillDisappear(animated) super.viewWillDisappear(animated)
// Since this VC is being dismissed, the "show status bar" animation would feel like
// it's occuring on the presenting view controller - it's better not to animate at all.
CurrentAppContext().setStatusBarHidden(false, animated: false)
} }
override public var inputAccessoryView: UIView? { override public var inputAccessoryView: UIView? {
@ -263,12 +264,18 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
} }
override public var canBecomeFirstResponder: Bool { override public var canBecomeFirstResponder: Bool {
return true return !shouldHideControls
} }
// MARK: - Navigation Bar // MARK: - Navigation Bar
public func updateNavigationBar() { public func updateNavigationBar() {
guard !shouldHideControls else {
self.navigationItem.leftBarButtonItem = nil
self.navigationItem.rightBarButtonItem = nil
return
}
var navigationBarItems = [UIView]() var navigationBarItems = [UIView]()
var isShowingCaptionView = false var isShowingCaptionView = false
@ -295,7 +302,29 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
cancelButton.tintColor = .white cancelButton.tintColor = .white
self.navigationItem.leftBarButtonItem = cancelButton self.navigationItem.leftBarButtonItem = cancelButton
} else { } else {
self.navigationItem.leftBarButtonItem = nil // Note: using a custom leftBarButtonItem breaks the interactive pop gesture.
self.navigationItem.leftBarButtonItem = self.createOWSBackButton()
}
}
// MARK: - Control Visibility
public var shouldHideControls: Bool {
guard let pageViewController = pageViewControllers.first else {
return false
}
return pageViewController.shouldHideControls
}
private func updateControlVisibility() {
if shouldHideControls {
if isFirstResponder {
resignFirstResponder()
}
} else {
if !isFirstResponder {
becomeFirstResponder()
}
} }
} }
@ -376,6 +405,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
} }
updateNavigationBar() updateNavigationBar()
updateControlVisibility()
} }
// MARK: - UIPageViewControllerDataSource // MARK: - UIPageViewControllerDataSource
@ -622,7 +652,11 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate
} }
func prepViewControllerUpdateNavigationBar() { func prepViewControllerUpdateNavigationBar() {
self.updateNavigationBar() updateNavigationBar()
}
func prepViewControllerUpdateControls() {
updateControlVisibility()
} }
func prepViewControllerAttachmentCount() -> Int { func prepViewControllerAttachmentCount() -> Int {
@ -682,6 +716,8 @@ protocol AttachmentPrepViewControllerDelegate: class {
func prepViewControllerUpdateNavigationBar() func prepViewControllerUpdateNavigationBar()
func prepViewControllerUpdateControls()
func prepViewControllerAttachmentCount() -> Int func prepViewControllerAttachmentCount() -> Int
} }
@ -712,7 +748,15 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
fileprivate var isShowingCaptionView = false { fileprivate var isShowingCaptionView = false {
didSet { didSet {
prepDelegate?.prepViewControllerUpdateNavigationBar() prepDelegate?.prepViewControllerUpdateNavigationBar()
prepDelegate?.prepViewControllerUpdateControls()
}
}
public var shouldHideControls: Bool {
guard let imageEditorView = imageEditorView else {
return false
} }
return imageEditorView.shouldHideControls
} }
// MARK: - Initializers // MARK: - Initializers
@ -794,8 +838,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
view.addSubview(imageEditorView) view.addSubview(imageEditorView)
imageEditorView.autoPinEdgesToSuperviewEdges() imageEditorView.autoPinEdgesToSuperviewEdges()
imageEditorView.addControls(to: imageEditorView, imageEditorUpdateNavigationBar()
viewController: self)
} }
} }
#endif #endif
@ -872,6 +915,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
super.viewWillAppear(animated) super.viewWillAppear(animated)
prepDelegate?.prepViewControllerUpdateNavigationBar() prepDelegate?.prepViewControllerUpdateNavigationBar()
prepDelegate?.prepViewControllerUpdateControls()
} }
override public func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
@ -880,6 +924,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
super.viewDidAppear(animated) super.viewDidAppear(animated)
prepDelegate?.prepViewControllerUpdateNavigationBar() prepDelegate?.prepViewControllerUpdateNavigationBar()
prepDelegate?.prepViewControllerUpdateControls()
} }
override public func viewWillLayoutSubviews() { override public func viewWillLayoutSubviews() {
@ -1208,6 +1253,10 @@ extension AttachmentPrepViewController: ImageEditorViewDelegate {
public func imageEditorUpdateNavigationBar() { public func imageEditorUpdateNavigationBar() {
prepDelegate?.prepViewControllerUpdateNavigationBar() prepDelegate?.prepViewControllerUpdateNavigationBar()
} }
public func imageEditorUpdateControls() {
prepDelegate?.prepViewControllerUpdateControls()
}
} }
// MARK: - // MARK: -
@ -1411,7 +1460,7 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate {
// Add shadow in case overlayed on white content // Add shadow in case overlayed on white content
lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor
lengthLimitLabel.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) lengthLimitLabel.layer.shadowOffset = .zero
lengthLimitLabel.layer.shadowOpacity = 0.8 lengthLimitLabel.layer.shadowOpacity = 0.8
lengthLimitLabel.isHidden = true lengthLimitLabel.isHidden = true
@ -1645,6 +1694,7 @@ public class ApprovalRailCellView: GalleryRailCellView {
imageView.layer.shadowColor = UIColor.black.cgColor imageView.layer.shadowColor = UIColor.black.cgColor
imageView.layer.shadowRadius = 2 imageView.layer.shadowRadius = 2
imageView.layer.shadowOpacity = 0.66 imageView.layer.shadowOpacity = 0.66
imageView.layer.shadowOffset = .zero
return imageView return imageView
}() }()
@ -1676,8 +1726,8 @@ public class ApprovalRailCellView: GalleryRailCellView {
if hasCaption { if hasCaption {
addSubview(captionIndicator) addSubview(captionIndicator)
captionIndicator.autoPinEdge(toSuperviewEdge: .top, withInset: 0) captionIndicator.autoPinEdge(toSuperviewEdge: .top, withInset: 2)
captionIndicator.autoPinEdge(toSuperviewEdge: .leading, withInset: 4) captionIndicator.autoPinEdge(toSuperviewEdge: .leading, withInset: 6)
} else { } else {
captionIndicator.removeFromSuperview() captionIndicator.removeFromSuperview()
} }

@ -75,11 +75,6 @@ class AttachmentCaptionViewController: OWSViewController {
configureTextView() 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 doneIcon = UIImage(named: "image_editor_checkmark_full")?.withRenderingMode(.alwaysTemplate)
let doneButton = UIBarButtonItem(image: doneIcon, style: .plain, let doneButton = UIBarButtonItem(image: doneIcon, style: .plain,
target: self, target: self,
@ -96,7 +91,6 @@ class AttachmentCaptionViewController: OWSViewController {
stackView.axis = .vertical stackView.axis = .vertical
stackView.spacing = 20 stackView.spacing = 20
stackView.alignment = .fill stackView.alignment = .fill
stackView.addBackgroundView(withBackgroundColor: UIColor(white: 0, alpha: 0.5))
stackView.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) stackView.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20)
stackView.isLayoutMarginsRelativeArrangement = true stackView.isLayoutMarginsRelativeArrangement = true
self.view.addSubview(stackView) self.view.addSubview(stackView)
@ -104,6 +98,15 @@ class AttachmentCaptionViewController: OWSViewController {
stackView.autoPinEdge(toSuperviewEdge: .trailing) stackView.autoPinEdge(toSuperviewEdge: .trailing)
self.autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true) self.autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor(white: 0, alpha: 0.5)
view.addSubview(backgroundView)
view.sendSubview(toBack: backgroundView)
backgroundView.autoPinEdge(toSuperviewEdge: .leading)
backgroundView.autoPinEdge(toSuperviewEdge: .trailing)
backgroundView.autoPinEdge(toSuperviewEdge: .bottom)
backgroundView.autoPinEdge(.top, to: .top, of: stackView)
let minTextHeight: CGFloat = textView.font?.lineHeight ?? 0 let minTextHeight: CGFloat = textView.font?.lineHeight ?? 0
textViewHeightConstraint = textView.autoSetDimension(.height, toSize: minTextHeight) textViewHeightConstraint = textView.autoSetDimension(.height, toSize: minTextHeight)
@ -174,7 +177,7 @@ class AttachmentCaptionViewController: OWSViewController {
// Add shadow in case overlayed on white content // Add shadow in case overlayed on white content
lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor
lengthLimitLabel.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) lengthLimitLabel.layer.shadowOffset = .zero
lengthLimitLabel.layer.shadowOpacity = 0.8 lengthLimitLabel.layer.shadowOpacity = 0.8
lengthLimitLabel.isHidden = true lengthLimitLabel.isHidden = true

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@ -23,6 +23,11 @@ NS_ASSUME_NONNULL_BEGIN
// unsaved changes. // unsaved changes.
@interface OWSNavigationController : UINavigationController @interface OWSNavigationController : UINavigationController
// If set, this property lets us override prefersStatusBarHidden behavior.
// This is useful for supressing the status bar while a modal is presented,
// regardless of which view is currently visible.
@property (nonatomic, nullable) NSNumber *ows_prefersStatusBarHidden;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -69,6 +69,14 @@ NS_ASSUME_NONNULL_BEGIN
self.interactivePopGestureRecognizer.delegate = self; self.interactivePopGestureRecognizer.delegate = self;
} }
- (BOOL)prefersStatusBarHidden
{
if (self.ows_prefersStatusBarHidden) {
return self.ows_prefersStatusBarHidden.boolValue;
}
return [super prefersStatusBarHidden];
}
#pragma mark - UINavigationBarDelegate #pragma mark - UINavigationBarDelegate
- (void)setupNavbar - (void)setupNavbar

@ -64,14 +64,17 @@ public class GalleryRailCellView: UIView {
private(set) var isSelected: Bool = false private(set) var isSelected: Bool = false
func setIsSelected(_ isSelected: Bool) { func setIsSelected(_ isSelected: Bool) {
let borderWidth: CGFloat = 2
self.isSelected = isSelected self.isSelected = isSelected
// Reserve space for the selection border whether or not the cell is selected.
layoutMargins = UIEdgeInsets(top: 0, left: borderWidth, bottom: 0, right: borderWidth)
if isSelected { if isSelected {
layoutMargins = UIEdgeInsets(top: 0, left: 2, bottom: 0, right: 2)
imageView.layer.borderColor = Theme.galleryHighlightColor.cgColor imageView.layer.borderColor = Theme.galleryHighlightColor.cgColor
imageView.layer.borderWidth = 2 imageView.layer.borderWidth = borderWidth
imageView.layer.cornerRadius = 2 imageView.layer.cornerRadius = borderWidth
} else { } else {
layoutMargins = .zero
imageView.layer.borderWidth = 0 imageView.layer.borderWidth = 0
imageView.layer.cornerRadius = 0 imageView.layer.cornerRadius = 0
} }

@ -84,11 +84,18 @@ public class ImageEditorBrushViewController: OWSViewController {
self.view.layoutSubviews() self.view.layoutSubviews()
} }
public func updateNavigationBar() { private func updateNavigationBar() {
// Hide controls during stroke.
let hasStroke = currentStroke != nil
guard !hasStroke else {
updateNavigationBar(navigationBarItems: [])
return
}
let undoButton = navigationBarButton(imageName: "image_editor_undo", let undoButton = navigationBarButton(imageName: "image_editor_undo",
selector: #selector(didTapUndo(sender:))) selector: #selector(didTapUndo(sender:)))
let doneButton = navigationBarButton(imageName: "image_editor_checkmark_full", let doneButton = navigationBarButton(imageName: "image_editor_checkmark_full",
selector: #selector(didTapDone(sender:))) selector: #selector(didTapDone(sender:)))
// Prevent users from undo any changes made before entering the view. // Prevent users from undo any changes made before entering the view.
let canUndo = model.canUndo() && firstUndoOperationId != model.currentUndoOperationId() let canUndo = model.canUndo() && firstUndoOperationId != model.currentUndoOperationId()
@ -101,6 +108,12 @@ public class ImageEditorBrushViewController: OWSViewController {
updateNavigationBar(navigationBarItems: navigationBarItems) updateNavigationBar(navigationBarItems: navigationBarItems)
} }
private func updateControls() {
// Hide controls during stroke.
let hasStroke = currentStroke != nil
paletteView.isHidden = hasStroke
}
// MARK: - Actions // MARK: - Actions
@objc func didTapUndo(sender: UIButton) { @objc func didTapUndo(sender: UIButton) {
@ -129,7 +142,12 @@ public class ImageEditorBrushViewController: OWSViewController {
// MARK: - Brush // MARK: - Brush
// These properties are non-empty while drawing a stroke. // These properties are non-empty while drawing a stroke.
private var currentStroke: ImageEditorStrokeItem? private var currentStroke: ImageEditorStrokeItem? {
didSet {
updateControls()
updateNavigationBar()
}
}
private var currentStrokeSamples = [ImageEditorStrokeItem.StrokeSample]() private var currentStrokeSamples = [ImageEditorStrokeItem.StrokeSample]()
@objc @objc

@ -23,11 +23,13 @@ class ImageEditorCropViewController: OWSViewController {
private var transform: ImageEditorTransform private var transform: ImageEditorTransform
public let contentView = OWSLayerView()
public let clipView = OWSLayerView() public let clipView = OWSLayerView()
private var imageLayer = CALayer() public let croppedContentView = OWSLayerView()
public let uncroppedContentView = UIView()
private var croppedImageLayer = CALayer()
private var uncroppedImageLayer = CALayer()
private enum CropRegion { private enum CropRegion {
// The sides of the crop region. // The sides of the crop region.
@ -121,19 +123,32 @@ class ImageEditorCropViewController: OWSViewController {
} }
wrapperView.addSubview(clipView) wrapperView.addSubview(clipView)
imageLayer.contents = previewImage.cgImage croppedImageLayer.contents = previewImage.cgImage
imageLayer.contentsScale = previewImage.scale croppedImageLayer.contentsScale = previewImage.scale
contentView.backgroundColor = .clear croppedContentView.backgroundColor = .clear
contentView.isOpaque = false croppedContentView.isOpaque = false
contentView.layer.addSublayer(imageLayer) croppedContentView.layer.addSublayer(croppedImageLayer)
contentView.layoutCallback = { [weak self] (_) in croppedContentView.layoutCallback = { [weak self] (_) in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.updateContent() strongSelf.updateContent()
} }
clipView.addSubview(contentView) clipView.addSubview(croppedContentView)
contentView.autoPinEdgesToSuperviewEdges() croppedContentView.autoPinEdgesToSuperviewEdges()
uncroppedImageLayer.contents = previewImage.cgImage
uncroppedImageLayer.contentsScale = previewImage.scale
// The "uncropped" view/layer are used to display the
// content that has been cropped out. Its content
// should be semi-transparent to distinguish it from
// the content within the crop bounds.
uncroppedImageLayer.opacity = 0.5
uncroppedContentView.backgroundColor = .clear
uncroppedContentView.isOpaque = false
uncroppedContentView.layer.addSublayer(uncroppedImageLayer)
wrapperView.addSubview(uncroppedContentView)
uncroppedContentView.autoPin(toEdgesOf: croppedContentView)
// MARK: - Footer // MARK: - Footer
@ -329,7 +344,7 @@ class ImageEditorCropViewController: OWSViewController {
Logger.verbose("") Logger.verbose("")
let viewSize = contentView.bounds.size let viewSize = croppedContentView.bounds.size
guard viewSize.width > 0, guard viewSize.width > 0,
viewSize.height > 0 else { viewSize.height > 0 else {
return return
@ -354,13 +369,15 @@ class ImageEditorCropViewController: OWSViewController {
} }
private func applyTransform() { private func applyTransform() {
let viewSize = contentView.bounds.size let viewSize = croppedContentView.bounds.size
contentView.layer.setAffineTransform(transform.affineTransform(viewSize: viewSize)) croppedContentView.layer.setAffineTransform(transform.affineTransform(viewSize: viewSize))
uncroppedContentView.layer.setAffineTransform(transform.affineTransform(viewSize: viewSize))
} }
private func updateImageLayer() { private func updateImageLayer() {
let viewSize = contentView.bounds.size let viewSize = croppedContentView.bounds.size
ImageEditorCanvasView.updateImageLayer(imageLayer: imageLayer, viewSize: viewSize, imageSize: model.srcImageSizePixels, transform: transform) ImageEditorCanvasView.updateImageLayer(imageLayer: croppedImageLayer, viewSize: viewSize, imageSize: model.srcImageSizePixels, transform: transform)
ImageEditorCanvasView.updateImageLayer(imageLayer: uncroppedImageLayer, viewSize: viewSize, imageSize: model.srcImageSizePixels, transform: transform)
} }
private func configureGestures() { private func configureGestures() {
@ -368,14 +385,22 @@ class ImageEditorCropViewController: OWSViewController {
let pinchGestureRecognizer = ImageEditorPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture(_:))) let pinchGestureRecognizer = ImageEditorPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture(_:)))
pinchGestureRecognizer.referenceView = self.clipView pinchGestureRecognizer.referenceView = self.clipView
// Use this VC as a delegate to ensure that pinches only
// receive touches that start inside of the cropped image bounds.
pinchGestureRecognizer.delegate = self pinchGestureRecognizer.delegate = self
view.addGestureRecognizer(pinchGestureRecognizer) view.addGestureRecognizer(pinchGestureRecognizer)
let panGestureRecognizer = ImageEditorPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) let panGestureRecognizer = ImageEditorPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
panGestureRecognizer.maximumNumberOfTouches = 1 panGestureRecognizer.maximumNumberOfTouches = 1
panGestureRecognizer.referenceView = self.clipView panGestureRecognizer.referenceView = self.clipView
panGestureRecognizer.delegate = self // _DO NOT_ use this VC as a delegate to filter touches;
// pan gestures can start outside the cropped image bounds.
// Otherwise the edges of the crop rect are difficult to
// "grab".
view.addGestureRecognizer(panGestureRecognizer) view.addGestureRecognizer(panGestureRecognizer)
// De-conflict the gestures; the pan gesture has priority.
panGestureRecognizer.shouldBeRequiredToFail(by: pinchGestureRecognizer)
} }
override public var canBecomeFirstResponder: Bool { override public var canBecomeFirstResponder: Bool {

@ -100,7 +100,12 @@ public class ImageEditorPaletteView: UIView {
// We use an invisible margin to expand the hot area of this control. // We use an invisible margin to expand the hot area of this control.
let margin: CGFloat = 20 let margin: CGFloat = 20
imageView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)) imageView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin))
imageView.layer.borderColor = UIColor.white.cgColor
imageView.layer.borderWidth = CGHairlineWidth()
imageView.layer.shadowColor = UIColor.black.cgColor
imageView.layer.shadowRadius = 2.0
imageView.layer.shadowOpacity = 0.33
imageView.layer.shadowOffset = .zero
selectionWrapper.layoutCallback = { [weak self] (view) in selectionWrapper.layoutCallback = { [weak self] (view) in
guard let strongSelf = self else { guard let strongSelf = self else {
return return

@ -9,6 +9,7 @@ public protocol ImageEditorViewDelegate: class {
func imageEditor(presentFullScreenView viewController: UIViewController, func imageEditor(presentFullScreenView viewController: UIViewController,
isTransparent: Bool) isTransparent: Bool)
func imageEditorUpdateNavigationBar() func imageEditorUpdateNavigationBar()
func imageEditorUpdateControls()
} }
// MARK: - // MARK: -
@ -81,16 +82,17 @@ public class ImageEditorView: UIView {
return true return true
} }
// TODO: Should this method be private? // MARK: - Navigation Bar
@objc
public func addControls(to containerView: UIView, private func updateNavigationBar() {
viewController: UIViewController) {
delegate?.imageEditorUpdateNavigationBar() delegate?.imageEditorUpdateNavigationBar()
} }
// MARK: - Navigation Bar
public func navigationBarItems() -> [UIView] { public func navigationBarItems() -> [UIView] {
guard !shouldHideControls else {
return []
}
let undoButton = navigationBarButton(imageName: "image_editor_undo", let undoButton = navigationBarButton(imageName: "image_editor_undo",
selector: #selector(didTapUndo(sender:))) selector: #selector(didTapUndo(sender:)))
let brushButton = navigationBarButton(imageName: "image_editor_brush", let brushButton = navigationBarButton(imageName: "image_editor_brush",
@ -110,6 +112,15 @@ public class ImageEditorView: UIView {
return buttons return buttons
} }
private func updateControls() {
delegate?.imageEditorUpdateControls()
}
public var shouldHideControls: Bool {
// Hide controls during "text item move".
return movingTextItem != nil
}
// MARK: - Actions // MARK: - Actions
@objc func didTapUndo(sender: UIButton) { @objc func didTapUndo(sender: UIButton) {
@ -138,6 +149,12 @@ public class ImageEditorView: UIView {
@objc func didTapNewText(sender: UIButton) { @objc func didTapNewText(sender: UIButton) {
Logger.verbose("") Logger.verbose("")
createNewTextItem()
}
private func createNewTextItem() {
Logger.verbose("")
let viewSize = canvasView.gestureReferenceView.bounds.size let viewSize = canvasView.gestureReferenceView.bounds.size
let imageSize = model.srcImageSizePixels let imageSize = model.srcImageSizePixels
let imageFrame = ImageEditorCanvasView.imageFrame(forViewSize: viewSize, imageSize: imageSize, let imageFrame = ImageEditorCanvasView.imageFrame(forViewSize: viewSize, imageSize: imageSize,
@ -178,6 +195,8 @@ public class ImageEditorView: UIView {
let location = gestureRecognizer.location(in: canvasView.gestureReferenceView) let location = gestureRecognizer.location(in: canvasView.gestureReferenceView)
guard let textLayer = self.textLayer(forLocation: location) else { guard let textLayer = self.textLayer(forLocation: location) else {
// If there is no text item under the "tap", start a new one.
createNewTextItem()
return return
} }
@ -263,7 +282,12 @@ public class ImageEditorView: UIView {
// MARK: - Editor Gesture // MARK: - Editor Gesture
// These properties are valid while moving a text item. // These properties are valid while moving a text item.
private var movingTextItem: ImageEditorTextItem? private var movingTextItem: ImageEditorTextItem? {
didSet {
updateNavigationBar()
updateControls()
}
}
private var movingTextStartUnitCenter: CGPoint? private var movingTextStartUnitCenter: CGPoint?
private var movingTextHasMoved = false private var movingTextHasMoved = false
@ -479,11 +503,11 @@ extension ImageEditorView: ImageEditorModelObserver {
public func imageEditorModelDidChange(before: ImageEditorContents, public func imageEditorModelDidChange(before: ImageEditorContents,
after: ImageEditorContents) { after: ImageEditorContents) {
delegate?.imageEditorUpdateNavigationBar() updateNavigationBar()
} }
public func imageEditorModelDidChange(changedItemIds: [String]) { public func imageEditorModelDidChange(changedItemIds: [String]) {
delegate?.imageEditorUpdateNavigationBar() updateNavigationBar()
} }
} }

@ -15,6 +15,7 @@ public extension NSObject {
button.layer.shadowColor = UIColor.black.cgColor button.layer.shadowColor = UIColor.black.cgColor
button.layer.shadowRadius = 2 button.layer.shadowRadius = 2
button.layer.shadowOpacity = 0.66 button.layer.shadowOpacity = 0.66
button.layer.shadowOffset = .zero
return button return button
} }
} }
@ -29,7 +30,7 @@ public extension UIViewController {
return return
} }
let spacing: CGFloat = 8 let spacing: CGFloat = 16
let stackView = UIStackView(arrangedSubviews: navigationBarItems) let stackView = UIStackView(arrangedSubviews: navigationBarItems)
stackView.axis = .horizontal stackView.axis = .horizontal
stackView.spacing = spacing stackView.spacing = spacing

Loading…
Cancel
Save