From c07a74d029aa2b875306408836de914cc4a0fd06 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 26 Feb 2019 12:54:29 -0500 Subject: [PATCH 1/4] Update crop view to reflect design. --- .../ImageEditorCropViewController.swift | 79 +++++++++++++++++-- SignalMessaging/categories/UIView+OWS.swift | 14 ++++ 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift index 3f420ac34..e78a43d92 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift @@ -36,12 +36,12 @@ class ImageEditorCropViewController: OWSViewController { case topLeft, topRight, bottomLeft, bottomRight } - private class CropCornerView: UIView { + private class CropCornerView: OWSLayerView { let cropRegion: CropRegion init(cropRegion: CropRegion) { self.cropRegion = cropRegion - super.init(frame: .zero) + super.init() } @available(*, unavailable, message: "use other init() instead.") @@ -196,12 +196,14 @@ class ImageEditorCropViewController: OWSViewController { footer.isOpaque = false stackView.addArrangedSubview(footer) + setCropViewAppearance() + updateClipViewLayout() configureGestures() } - private static let desiredCornerSize: CGFloat = 30 + private static let desiredCornerSize: CGFloat = 24 private static let minCropSize: CGFloat = desiredCornerSize * 2 private var cornerSize = CGSize.zero @@ -219,6 +221,72 @@ class ImageEditorCropViewController: OWSViewController { private var cropViewConstraints = [NSLayoutConstraint]() + private func setCropViewAppearance() { + + // TODO: Tune the size. + let cornerSize = CGSize(width: min(clipView.width() * 0.5, ImageEditorCropViewController.desiredCornerSize), + height: min(clipView.height() * 0.5, ImageEditorCropViewController.desiredCornerSize)) + self.cornerSize = cornerSize + for cropCornerView in cropCornerViews { + let cornerThickness: CGFloat = 2 + + let shapeLayer = CAShapeLayer() + cropCornerView.layer.addSublayer(shapeLayer) + shapeLayer.fillColor = UIColor.white.cgColor + shapeLayer.strokeColor = nil + cropCornerView.layoutCallback = { (view) in + let shapeFrame = view.bounds.insetBy(dx: -cornerThickness, dy: -cornerThickness) + shapeLayer.frame = shapeFrame + + let bezierPath = UIBezierPath() + + switch cropCornerView.cropRegion { + case .topLeft: + bezierPath.addRegion(withPoints: [ + CGPoint.zero, + CGPoint(x: shapeFrame.width - cornerThickness, y: 0), + CGPoint(x: shapeFrame.width - cornerThickness, y: cornerThickness), + CGPoint(x: cornerThickness, y: cornerThickness), + CGPoint(x: cornerThickness, y: shapeFrame.height - cornerThickness), + CGPoint(x: 0, y: shapeFrame.height - cornerThickness) + ]) + case .topRight: + bezierPath.addRegion(withPoints: [ + CGPoint(x: shapeFrame.width, y: 0), + CGPoint(x: shapeFrame.width, y: shapeFrame.height - cornerThickness), + CGPoint(x: shapeFrame.width - cornerThickness, y: shapeFrame.height - cornerThickness), + CGPoint(x: shapeFrame.width - cornerThickness, y: cornerThickness), + CGPoint(x: cornerThickness, y: cornerThickness), + CGPoint(x: cornerThickness, y: 0) + ]) + case .bottomLeft: + bezierPath.addRegion(withPoints: [ + CGPoint(x: 0, y: shapeFrame.height), + CGPoint(x: 0, y: cornerThickness), + CGPoint(x: cornerThickness, y: cornerThickness), + CGPoint(x: cornerThickness, y: shapeFrame.height - cornerThickness), + CGPoint(x: shapeFrame.width - cornerThickness, y: shapeFrame.height - cornerThickness), + CGPoint(x: shapeFrame.width - cornerThickness, y: shapeFrame.height) + ]) + case .bottomRight: + bezierPath.addRegion(withPoints: [ + CGPoint(x: shapeFrame.width, y: shapeFrame.height), + CGPoint(x: cornerThickness, y: shapeFrame.height), + CGPoint(x: cornerThickness, y: shapeFrame.height - cornerThickness), + CGPoint(x: shapeFrame.width - cornerThickness, y: shapeFrame.height - cornerThickness), + CGPoint(x: shapeFrame.width - cornerThickness, y: cornerThickness), + CGPoint(x: shapeFrame.width, y: cornerThickness) + ]) + default: + owsFailDebug("Invalid crop region: \(cropCornerView.cropRegion)") + } + + shapeLayer.path = bezierPath.cgPath + } + } + cropView.addBorder(with: .white) + } + private func updateCropViewLayout() { NSLayoutConstraint.deactivate(cropViewConstraints) cropViewConstraints.removeAll() @@ -229,9 +297,6 @@ class ImageEditorCropViewController: OWSViewController { self.cornerSize = cornerSize for cropCornerView in cropCornerViews { cropViewConstraints.append(contentsOf: cropCornerView.autoSetDimensions(to: cornerSize)) - - cropCornerView.addRedBorder() - cropView.addRedBorder() } if !isCropGestureActive { @@ -268,8 +333,6 @@ class ImageEditorCropViewController: OWSViewController { } private func applyTransform() { - Logger.verbose("") - let viewSize = contentView.bounds.size contentView.layer.setAffineTransform(transform.affineTransform(viewSize: viewSize)) } diff --git a/SignalMessaging/categories/UIView+OWS.swift b/SignalMessaging/categories/UIView+OWS.swift index cce4638a1..bb774a851 100644 --- a/SignalMessaging/categories/UIView+OWS.swift +++ b/SignalMessaging/categories/UIView+OWS.swift @@ -238,3 +238,17 @@ public extension CGAffineTransform { return rotated(by: angleRadians) } } + +public extension UIBezierPath { + public func addRegion(withPoints points: [CGPoint]) { + guard let first = points.first else { + owsFailDebug("No points.") + return + } + move(to: first) + for point in points.dropFirst() { + addLine(to: point) + } + addLine(to: first) + } +} From 4db09b45b6c66ea7cba641e94d626a7ed8f1c8b2 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 26 Feb 2019 12:55:55 -0500 Subject: [PATCH 2/4] Update crop view to reflect design. --- .../ImageEditor/ImageEditorCropViewController.swift | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift index e78a43d92..59a2d998e 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift @@ -81,17 +81,7 @@ class ImageEditorCropViewController: OWSViewController { override func loadView() { self.view = UIView() - if (UIAccessibilityIsReduceTransparencyEnabled()) { - self.view.backgroundColor = UIColor(white: 0.5, alpha: 0.5) - } else { - let alpha = OWSNavigationBar.backgroundBlurMutingFactor - self.view.backgroundColor = UIColor(white: 0.5, alpha: alpha) - - let blurEffectView = UIVisualEffectView(effect: Theme.barBlurEffect) - blurEffectView.layer.zPosition = -1 - self.view.addSubview(blurEffectView) - blurEffectView.autoPinEdgesToSuperviewEdges() - } + self.view.backgroundColor = .black let stackView = UIStackView() stackView.axis = .vertical From 69635fafacd2ae30083fd04f6028ab04016b2faf Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 26 Feb 2019 13:18:52 -0500 Subject: [PATCH 3/4] Update crop view to reflect design. --- .../AttachmentApprovalViewController.swift | 19 +++- .../ImageEditorCropViewController.swift | 104 ++++++++++-------- .../Views/ImageEditor/ImageEditorView.swift | 9 +- SignalMessaging/Views/OWSButton.swift | 14 ++- 4 files changed, 90 insertions(+), 56 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index cb179cc56..398c3e2c3 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -1324,12 +1324,19 @@ extension AttachmentPrepViewController: UIScrollViewDelegate { // MARK: - extension AttachmentPrepViewController: ImageEditorViewDelegate { - public func imageEditor(presentFullScreenOverlay viewController: UIViewController) { - - let navigationController = OWSNavigationController(rootViewController: viewController) - navigationController.modalPresentationStyle = .overFullScreen - self.present(navigationController, animated: true) { - // Do nothing. + public func imageEditor(presentFullScreenOverlay viewController: UIViewController, + withNavigation: Bool) { + + if withNavigation { + let navigationController = OWSNavigationController(rootViewController: viewController) + navigationController.modalPresentationStyle = .overFullScreen + self.present(navigationController, animated: true) { + // Do nothing. + } + } else { + self.present(viewController, animated: true) { + // Do nothing. + } } } } diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift index 59a2d998e..014981652 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift @@ -83,23 +83,42 @@ class ImageEditorCropViewController: OWSViewController { self.view.backgroundColor = .black - let stackView = UIStackView() - stackView.axis = .vertical - stackView.alignment = .fill - stackView.spacing = 16 - stackView.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) - stackView.isLayoutMarginsRelativeArrangement = true - self.view.addSubview(stackView) - stackView.ows_autoPinToSuperviewEdges() + // MARK: - Buttons - navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, - target: self, - action: #selector(didTapBackButton)) + // TODO: Apply icons. + let doneButton = OWSButton(title: "Done") { [weak self] in + self?.didTapBackButton() + } + let rotate90Button = OWSButton(title: "Rotate 90°") { [weak self] in + self?.rotate90ButtonPressed() + } + let rotate45Button = OWSButton(title: "Rotate 45°") { [weak self] in + self?.rotate45ButtonPressed() + } + let resetButton = OWSButton(title: "Reset") { [weak self] in + self?.resetButtonPressed() + } + let zoom2xButton = OWSButton(title: "Zoom 2x") { [weak self] in + self?.zoom2xButtonPressed() + } + + // MARK: - Header + + let header = UIStackView(arrangedSubviews: [ + UIView.hStretchingSpacer(), + resetButton, + doneButton + ]) + header.axis = .horizontal + header.spacing = 16 + header.backgroundColor = .clear + header.isOpaque = false + + // MARK: - Canvas & Wrapper let wrapperView = UIView.container() wrapperView.backgroundColor = .clear wrapperView.isOpaque = false - stackView.addArrangedSubview(wrapperView) // TODO: We could mask the clipped region with a semi-transparent overlay like WA. clipView.clipsToBounds = true @@ -127,33 +146,35 @@ class ImageEditorCropViewController: OWSViewController { clipView.addSubview(contentView) contentView.ows_autoPinToSuperviewEdges() - let rotate90Button = OWSButton() - rotate90Button.setTitle(NSLocalizedString("IMAGE_EDITOR_ROTATE_90_BUTTON", comment: "Label for button that rotates image 90 degrees."), - for: .normal) - rotate90Button.block = { [weak self] in - self?.rotate90ButtonPressed() - } + // MARK: - Footer - let rotate45Button = OWSButton() - rotate45Button.setTitle(NSLocalizedString("IMAGE_EDITOR_ROTATE_45_BUTTON", comment: "Label for button that rotates image 45 degrees."), - for: .normal) - rotate45Button.block = { [weak self] in - self?.rotate45ButtonPressed() - } + let footer = UIStackView(arrangedSubviews: [ + rotate90Button, + rotate45Button, + UIView.hStretchingSpacer(), + zoom2xButton + ]) + footer.axis = .horizontal + footer.spacing = 16 + footer.backgroundColor = .clear + footer.isOpaque = false - let resetButton = OWSButton() - resetButton.setTitle(NSLocalizedString("IMAGE_EDITOR_RESET_BUTTON", comment: "Label for button that resets crop & rotation state."), - for: .normal) - resetButton.block = { [weak self] in - self?.resetButtonPressed() - } + let stackView = UIStackView(arrangedSubviews: [ + header, + wrapperView, + footer + ]) + stackView.axis = .vertical + stackView.alignment = .fill + stackView.spacing = 24 + stackView.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) + stackView.isLayoutMarginsRelativeArrangement = true + self.view.addSubview(stackView) + stackView.ows_autoPinToSuperviewEdges() - let zoom2xButton = OWSButton() - zoom2xButton.setTitle("Zoom 2x", - for: .normal) - zoom2xButton.block = { [weak self] in - self?.zoom2xButtonPressed() - } + // MARK: - Crop View + + // Add crop view last so that it appears in front of the content. cropView.setContentHuggingLow() cropView.setCompressionResistanceLow() @@ -166,8 +187,8 @@ class ImageEditorCropViewController: OWSViewController { cropCornerView.autoPinEdge(toSuperviewEdge: .left) case .topRight, .bottomRight: cropCornerView.autoPinEdge(toSuperviewEdge: .right) - default: - owsFailDebug("Invalid crop region: \(cropRegion)") + default: + owsFailDebug("Invalid crop region: \(cropRegion)") } switch cropCornerView.cropRegion { case .topLeft, .topRight: @@ -179,13 +200,6 @@ class ImageEditorCropViewController: OWSViewController { } } - let footer = UIStackView(arrangedSubviews: [rotate90Button, rotate45Button, resetButton, zoom2xButton]) - footer.axis = .horizontal - footer.spacing = 16 - footer.backgroundColor = .clear - footer.isOpaque = false - stackView.addArrangedSubview(footer) - setCropViewAppearance() updateClipViewLayout() diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift index 187a55c3b..a91427d4e 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift @@ -6,7 +6,8 @@ import UIKit @objc public protocol ImageEditorViewDelegate: class { - func imageEditor(presentFullScreenOverlay viewController: UIViewController) + func imageEditor(presentFullScreenOverlay viewController: UIViewController, + withNavigation: Bool) } // MARK: - @@ -571,7 +572,8 @@ public class ImageEditorView: UIView { // let maxTextWidthPoints = canvasView.imageView.width() * ImageEditorTextItem.kDefaultUnitWidth let textEditor = ImageEditorTextViewController(delegate: self, textItem: textItem, maxTextWidthPoints: maxTextWidthPoints) - self.delegate?.imageEditor(presentFullScreenOverlay: textEditor) + self.delegate?.imageEditor(presentFullScreenOverlay: textEditor, + withNavigation: true) } // MARK: - Crop Tool @@ -596,7 +598,8 @@ public class ImageEditorView: UIView { } let cropTool = ImageEditorCropViewController(delegate: self, model: model, srcImage: srcImage, previewImage: previewImage) - self.delegate?.imageEditor(presentFullScreenOverlay: cropTool) + self.delegate?.imageEditor(presentFullScreenOverlay: cropTool, + withNavigation: false) }} // MARK: - diff --git a/SignalMessaging/Views/OWSButton.swift b/SignalMessaging/Views/OWSButton.swift index 9d51bf99d..03ffdf0a0 100644 --- a/SignalMessaging/Views/OWSButton.swift +++ b/SignalMessaging/Views/OWSButton.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // import UIKit @@ -15,8 +15,18 @@ public class OWSButton: UIButton { @objc init(block: @escaping () -> Void = { }) { super.init(frame: .zero) + + self.block = block + addTarget(self, action: #selector(didTap), for: .touchUpInside) + } + + @objc + init(title: String, block: @escaping () -> Void = { }) { + super.init(frame: .zero) + self.block = block - self.addTarget(self, action: #selector(didTap), for: .touchUpInside) + addTarget(self, action: #selector(didTap), for: .touchUpInside) + setTitle(title, for: .normal) } public required init?(coder aDecoder: NSCoder) { From 0ce84b7929be3b5837cc5f64f123d07545bd1112 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 26 Feb 2019 17:42:27 -0500 Subject: [PATCH 4/4] Respond to CR. --- SignalMessaging/Views/ImageEditor/ImageEditorCanvasView.swift | 2 +- .../Views/ImageEditor/ImageEditorCropViewController.swift | 4 ++-- SignalMessaging/Views/ImageEditor/ImageEditorView.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorCanvasView.swift b/SignalMessaging/Views/ImageEditor/ImageEditorCanvasView.swift index 754c3ee41..033b0ffec 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorCanvasView.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorCanvasView.swift @@ -91,7 +91,7 @@ public class ImageEditorCanvasView: UIView { strongSelf.updateAllContent() } clipView.addSubview(contentView) - contentView.ows_autoPinToSuperviewEdges() + contentView.autoPinEdgesToSuperviewEdges() updateLayout() diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift index 014981652..3ee502fcb 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorCropViewController.swift @@ -144,7 +144,7 @@ class ImageEditorCropViewController: OWSViewController { strongSelf.updateContent() } clipView.addSubview(contentView) - contentView.ows_autoPinToSuperviewEdges() + contentView.autoPinEdgesToSuperviewEdges() // MARK: - Footer @@ -170,7 +170,7 @@ class ImageEditorCropViewController: OWSViewController { stackView.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) stackView.isLayoutMarginsRelativeArrangement = true self.view.addSubview(stackView) - stackView.ows_autoPinToSuperviewEdges() + stackView.autoPinEdgesToSuperviewEdges() // MARK: - Crop View diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift index a91427d4e..b0c858dff 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift @@ -69,7 +69,7 @@ public class ImageEditorView: UIView { return false } self.addSubview(canvasView) - canvasView.ows_autoPinToSuperviewEdges() + canvasView.autoPinEdgesToSuperviewEdges() self.isUserInteractionEnabled = true