mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'mkirk/photocapture-layout-fixups' into release/2.39.0
This commit is contained in:
commit
942b1ed649
|
@ -2950,13 +2950,6 @@ typedef enum : NSUInteger {
|
|||
didApproveAttachments:(NSArray<SignalAttachment *> *)attachments
|
||||
messageText:(nullable NSString *)messageText
|
||||
{
|
||||
OWSAssertDebug(self.isFirstResponder);
|
||||
if (@available(iOS 10, *)) {
|
||||
// do nothing
|
||||
} else {
|
||||
[self reloadInputViews];
|
||||
}
|
||||
|
||||
[self tryToSendAttachments:attachments messageText:messageText];
|
||||
[self.inputToolbar clearTextMessageAnimated:NO];
|
||||
|
||||
|
@ -2964,7 +2957,15 @@ typedef enum : NSUInteger {
|
|||
// the new message scroll into view.
|
||||
[self scrollToBottomAnimated:NO];
|
||||
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
[self dismissViewControllerAnimated:YES
|
||||
completion:^{
|
||||
OWSAssertDebug(self.isFirstResponder);
|
||||
if (@available(iOS 10, *)) {
|
||||
// do nothing
|
||||
} else {
|
||||
[self reloadInputViews];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (nullable NSString *)sendMediaNavInitialMessageText:(SendMediaNavigationController *)sendMediaNavigationController
|
||||
|
|
|
@ -58,9 +58,14 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat
|
|||
|
||||
view.backgroundColor = .ows_gray95
|
||||
|
||||
let cancelButton = UIBarButtonItem(barButtonSystemItem: .stop,
|
||||
target: self,
|
||||
action: #selector(didPressCancel))
|
||||
// The PhotoCaptureVC needs a shadow behind it's cancel button, so we use a custom icon.
|
||||
// This VC has a visible navbar so doesn't need the shadow, but because the user can
|
||||
// quickly toggle between the Capture and the Picker VC's, we use the same custom "X"
|
||||
// icon here rather than the system "stop" icon so that the spacing matches exactly.
|
||||
// Otherwise there's a noticable shift in the icon placement.
|
||||
let cancelImage = UIImage(imageLiteralResourceName: "ic_x_with_shadow")
|
||||
let cancelButton = UIBarButtonItem(image: cancelImage, style: .plain, target: self, action: #selector(didPressCancel))
|
||||
|
||||
cancelButton.tintColor = .ows_gray05
|
||||
navigationItem.leftBarButtonItem = cancelButton
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import Foundation
|
|||
import AVFoundation
|
||||
import PromiseKit
|
||||
|
||||
@objc(OWSPhotoCaptureViewControllerDelegate)
|
||||
protocol PhotoCaptureViewControllerDelegate: AnyObject {
|
||||
func photoCaptureViewController(_ photoCaptureViewController: PhotoCaptureViewController, didFinishProcessingAttachment attachment: SignalAttachment)
|
||||
func photoCaptureViewControllerDidCancel(_ photoCaptureViewController: PhotoCaptureViewController)
|
||||
|
@ -31,10 +30,8 @@ extension PhotoCaptureError: LocalizedError {
|
|||
}
|
||||
}
|
||||
|
||||
@objc(OWSPhotoCaptureViewController)
|
||||
class PhotoCaptureViewController: OWSViewController {
|
||||
|
||||
@objc
|
||||
weak var delegate: PhotoCaptureViewControllerDelegate?
|
||||
|
||||
private var photoCapture: PhotoCapture!
|
||||
|
@ -88,7 +85,7 @@ class PhotoCaptureViewController: OWSViewController {
|
|||
return true
|
||||
}
|
||||
|
||||
// MARK -
|
||||
// MARK: -
|
||||
var isRecordingMovie: Bool = false
|
||||
let recordingTimerView = RecordingTimerView()
|
||||
|
||||
|
@ -325,8 +322,7 @@ class PhotoCaptureViewController: OWSViewController {
|
|||
|
||||
view.addSubview(captureButton)
|
||||
captureButton.autoHCenterInSuperview()
|
||||
|
||||
captureButton.autoPinEdge(toSuperviewMargin: .bottom, withInset: 10)
|
||||
captureButton.centerYAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: SendMediaNavigationController.bottomButtonsCenterOffset).isActive = true
|
||||
}
|
||||
|
||||
private func showFailureUI(error: Error) {
|
||||
|
@ -374,8 +370,9 @@ extension PhotoCaptureViewController: PhotoCaptureDelegate {
|
|||
}
|
||||
|
||||
func photoCaptureDidCompleteVideo(_ photoCapture: PhotoCapture) {
|
||||
// Stop counting, but keep visible
|
||||
isRecordingMovie = false
|
||||
recordingTimerView.stopCounting()
|
||||
updateNavigationItems()
|
||||
}
|
||||
|
||||
func photoCaptureDidCancelVideo(_ photoCapture: PhotoCapture) {
|
||||
|
@ -425,7 +422,7 @@ class CaptureButton: UIView {
|
|||
weak var delegate: CaptureButtonDelegate?
|
||||
|
||||
let defaultDiameter: CGFloat = ScaleFromIPhone5To7Plus(60, 80)
|
||||
let recordingDiameter: CGFloat = ScaleFromIPhone5To7Plus(90, 120)
|
||||
let recordingDiameter: CGFloat = ScaleFromIPhone5To7Plus(68, 120)
|
||||
var innerButtonSizeConstraints: [NSLayoutConstraint]!
|
||||
var zoomIndicatorSizeConstraints: [NSLayoutConstraint]!
|
||||
|
||||
|
|
|
@ -18,6 +18,10 @@ protocol SendMediaNavDelegate: AnyObject {
|
|||
@objc
|
||||
class SendMediaNavigationController: OWSNavigationController {
|
||||
|
||||
// This is a sensitive constant, if you change it make sure to check
|
||||
// on iPhone5, 6, 6+, X, layouts.
|
||||
static let bottomButtonsCenterOffset: CGFloat = -50
|
||||
|
||||
// MARK: - Overrides
|
||||
|
||||
override var prefersStatusBarHidden: Bool { return true }
|
||||
|
@ -27,24 +31,26 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
|
||||
self.delegate = self
|
||||
|
||||
let bottomButtonsCenterOffset = SendMediaNavigationController.bottomButtonsCenterOffset
|
||||
|
||||
view.addSubview(batchModeButton)
|
||||
batchModeButton.setCompressionResistanceHigh()
|
||||
batchModeButton.autoPinEdge(toSuperviewMargin: .bottom)
|
||||
batchModeButton.centerYAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: bottomButtonsCenterOffset).isActive = true
|
||||
batchModeButton.autoPinEdge(toSuperviewMargin: .trailing)
|
||||
|
||||
view.addSubview(doneButton)
|
||||
doneButton.setCompressionResistanceHigh()
|
||||
doneButton.autoPinEdge(toSuperviewMargin: .bottom)
|
||||
doneButton.centerYAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: bottomButtonsCenterOffset).isActive = true
|
||||
doneButton.autoPinEdge(toSuperviewMargin: .trailing)
|
||||
|
||||
view.addSubview(cameraModeButton)
|
||||
cameraModeButton.setCompressionResistanceHigh()
|
||||
cameraModeButton.autoPinEdge(toSuperviewMargin: .bottom)
|
||||
cameraModeButton.centerYAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: bottomButtonsCenterOffset).isActive = true
|
||||
cameraModeButton.autoPinEdge(toSuperviewMargin: .leading)
|
||||
|
||||
view.addSubview(mediaLibraryModeButton)
|
||||
mediaLibraryModeButton.setCompressionResistanceHigh()
|
||||
mediaLibraryModeButton.autoPinEdge(toSuperviewMargin: .bottom)
|
||||
mediaLibraryModeButton.centerYAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: bottomButtonsCenterOffset).isActive = true
|
||||
mediaLibraryModeButton.autoPinEdge(toSuperviewMargin: .leading)
|
||||
}
|
||||
|
||||
|
@ -57,7 +63,6 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
public class func showingCameraFirst() -> SendMediaNavigationController {
|
||||
let navController = SendMediaNavigationController()
|
||||
navController.setViewControllers([navController.captureViewController], animated: false)
|
||||
navController.updateButtons()
|
||||
|
||||
return navController
|
||||
}
|
||||
|
@ -66,7 +71,6 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
public class func showingMediaLibraryFirst() -> SendMediaNavigationController {
|
||||
let navController = SendMediaNavigationController()
|
||||
navController.setViewControllers([navController.mediaLibraryViewController], animated: false)
|
||||
navController.updateButtons()
|
||||
|
||||
return navController
|
||||
}
|
||||
|
@ -74,17 +78,16 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
var isInBatchSelectMode = false {
|
||||
didSet {
|
||||
if oldValue != isInBatchSelectMode {
|
||||
updateButtons()
|
||||
mediaLibraryViewController.batchSelectModeDidChange()
|
||||
guard let topViewController = viewControllers.last else {
|
||||
return
|
||||
}
|
||||
updateButtons(topViewController: topViewController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateButtons() {
|
||||
guard let topViewController = viewControllers.last else {
|
||||
return
|
||||
}
|
||||
|
||||
func updateButtons(topViewController: UIViewController) {
|
||||
switch topViewController {
|
||||
case is AttachmentApprovalViewController:
|
||||
batchModeButton.isHidden = true
|
||||
|
@ -125,12 +128,10 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
|
||||
private func didTapCameraModeButton() {
|
||||
fadeTo(viewControllers: [captureViewController])
|
||||
updateButtons()
|
||||
}
|
||||
|
||||
private func didTapMediaLibraryModeButton() {
|
||||
fadeTo(viewControllers: [mediaLibraryViewController])
|
||||
updateButtons()
|
||||
}
|
||||
|
||||
// MARK: Views
|
||||
|
@ -138,6 +139,7 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
private lazy var doneButton: DoneButton = {
|
||||
let button = DoneButton()
|
||||
button.delegate = self
|
||||
button.setShadow()
|
||||
|
||||
return button
|
||||
}()
|
||||
|
@ -152,6 +154,7 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
button.layer.cornerRadius = width / 2
|
||||
button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
||||
button.backgroundColor = .ows_white
|
||||
button.setShadow()
|
||||
|
||||
return button
|
||||
}()
|
||||
|
@ -166,6 +169,7 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
button.layer.cornerRadius = width / 2
|
||||
button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
||||
button.backgroundColor = .ows_white
|
||||
button.setShadow()
|
||||
|
||||
return button
|
||||
}()
|
||||
|
@ -180,6 +184,7 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
button.layer.cornerRadius = width / 2
|
||||
button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
||||
button.backgroundColor = .ows_white
|
||||
button.setShadow()
|
||||
|
||||
return button
|
||||
}()
|
||||
|
@ -221,7 +226,6 @@ class SendMediaNavigationController: OWSNavigationController {
|
|||
approvalViewController.messageText = sendMediaNavDelegate.sendMediaNavInitialMessageText(self)
|
||||
|
||||
pushViewController(approvalViewController, animated: true)
|
||||
updateButtons()
|
||||
}
|
||||
|
||||
private func didRequestExit(dontAbandonText: String) {
|
||||
|
@ -259,6 +263,14 @@ extension SendMediaNavigationController: UINavigationControllerDelegate {
|
|||
owsFailDebug("unexpected navigationBar: \(navigationBar)")
|
||||
}
|
||||
}
|
||||
|
||||
if viewController is PhotoCaptureViewController && !isInBatchSelectMode {
|
||||
// We're either showing the captureView for the first time or the user is navigating "back"
|
||||
// indicating they want to "retake". We should discard any current image.
|
||||
discardCameraDraft()
|
||||
}
|
||||
|
||||
self.updateButtons(topViewController: viewController)
|
||||
}
|
||||
|
||||
// In case back navigation was canceled, we re-apply whatever is showing.
|
||||
|
@ -270,6 +282,7 @@ extension SendMediaNavigationController: UINavigationControllerDelegate {
|
|||
owsFailDebug("unexpected navigationBar: \(navigationBar)")
|
||||
}
|
||||
}
|
||||
self.updateButtons(topViewController: viewController)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
@ -293,7 +306,7 @@ extension SendMediaNavigationController: PhotoCaptureViewControllerDelegate {
|
|||
func photoCaptureViewController(_ photoCaptureViewController: PhotoCaptureViewController, didFinishProcessingAttachment attachment: SignalAttachment) {
|
||||
attachmentDraftCollection.append(.camera(attachment: attachment))
|
||||
if isInBatchSelectMode {
|
||||
updateButtons()
|
||||
updateButtons(topViewController: photoCaptureViewController)
|
||||
} else {
|
||||
pushApprovalViewController()
|
||||
}
|
||||
|
@ -303,6 +316,14 @@ extension SendMediaNavigationController: PhotoCaptureViewControllerDelegate {
|
|||
let dontAbandonText = NSLocalizedString("SEND_MEDIA_RETURN_TO_CAMERA", comment: "alert action when the user decides not to cancel the media flow after all.")
|
||||
didRequestExit(dontAbandonText: dontAbandonText)
|
||||
}
|
||||
|
||||
func discardCameraDraft() {
|
||||
assert(attachmentDraftCollection.cameraAttachments.count <= 1)
|
||||
if let lastCameraAttachment = attachmentDraftCollection.cameraAttachments.last {
|
||||
attachmentDraftCollection.remove(attachment: lastCameraAttachment)
|
||||
}
|
||||
assert(attachmentDraftCollection.cameraAttachments.count == 0)
|
||||
}
|
||||
}
|
||||
|
||||
extension SendMediaNavigationController: ImagePickerGridControllerDelegate {
|
||||
|
@ -353,15 +374,16 @@ extension SendMediaNavigationController: ImagePickerGridControllerDelegate {
|
|||
let libraryMedia = MediaLibrarySelection(asset: asset, signalAttachmentPromise: attachmentPromise)
|
||||
mediaLibrarySelections.append(key: asset, value: libraryMedia)
|
||||
|
||||
updateButtons()
|
||||
updateButtons(topViewController: imagePicker)
|
||||
}
|
||||
|
||||
func imagePicker(_ imagePicker: ImagePickerGridController, didDeselectAsset asset: PHAsset) {
|
||||
if mediaLibrarySelections.hasValue(forKey: asset) {
|
||||
mediaLibrarySelections.remove(key: asset)
|
||||
|
||||
updateButtons()
|
||||
guard mediaLibrarySelections.hasValue(forKey: asset) else {
|
||||
return
|
||||
}
|
||||
mediaLibrarySelections.remove(key: asset)
|
||||
|
||||
updateButtons(topViewController: imagePicker)
|
||||
}
|
||||
|
||||
func imagePickerCanSelectAdditionalItems(_ imagePicker: ImagePickerGridController) -> Bool {
|
||||
|
@ -406,9 +428,7 @@ extension SendMediaNavigationController: AttachmentApprovalViewControllerDelegat
|
|||
isInBatchSelectMode = true
|
||||
mediaLibraryViewController.batchSelectModeDidChange()
|
||||
|
||||
popViewController(animated: true) {
|
||||
self.updateButtons()
|
||||
}
|
||||
popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -439,7 +459,7 @@ private struct AttachmentDraftCollection {
|
|||
return AttachmentDraftCollection(attachmentDrafts: [])
|
||||
}
|
||||
|
||||
// MARK -
|
||||
// MARK: -
|
||||
|
||||
var count: Int {
|
||||
return attachmentDrafts.count
|
||||
|
@ -456,6 +476,17 @@ private struct AttachmentDraftCollection {
|
|||
}
|
||||
}
|
||||
|
||||
var cameraAttachments: [SignalAttachment] {
|
||||
return attachmentDrafts.compactMap { attachmentDraft in
|
||||
switch attachmentDraft.source {
|
||||
case .picker:
|
||||
return nil
|
||||
case .camera(let cameraAttachment):
|
||||
return cameraAttachment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func append(_ element: AttachmentDraft) {
|
||||
attachmentDrafts.append(element)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import Foundation
|
||||
|
||||
public extension UIEdgeInsets {
|
||||
public init(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) {
|
||||
init(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) {
|
||||
self.init(top: top,
|
||||
left: CurrentAppContext().isRTL ? trailing : leading,
|
||||
bottom: bottom,
|
||||
|
@ -17,8 +17,7 @@ public extension UIEdgeInsets {
|
|||
|
||||
@objc
|
||||
public extension UINavigationController {
|
||||
@objc
|
||||
public func pushViewController(_ viewController: UIViewController,
|
||||
func pushViewController(_ viewController: UIViewController,
|
||||
animated: Bool,
|
||||
completion: (() -> Void)?) {
|
||||
CATransaction.begin()
|
||||
|
@ -27,8 +26,7 @@ public extension UINavigationController {
|
|||
CATransaction.commit()
|
||||
}
|
||||
|
||||
@objc
|
||||
public func popViewController(animated: Bool,
|
||||
func popViewController(animated: Bool,
|
||||
completion: (() -> Void)?) {
|
||||
CATransaction.begin()
|
||||
CATransaction.setCompletionBlock(completion)
|
||||
|
@ -36,8 +34,7 @@ public extension UINavigationController {
|
|||
CATransaction.commit()
|
||||
}
|
||||
|
||||
@objc
|
||||
public func popToViewController(_ viewController: UIViewController,
|
||||
func popToViewController(_ viewController: UIViewController,
|
||||
animated: Bool,
|
||||
completion: (() -> Void)?) {
|
||||
CATransaction.begin()
|
||||
|
@ -49,12 +46,13 @@ public extension UINavigationController {
|
|||
|
||||
// MARK: -
|
||||
|
||||
extension UIView {
|
||||
public func renderAsImage() -> UIImage? {
|
||||
@objc
|
||||
public extension UIView {
|
||||
func renderAsImage() -> UIImage? {
|
||||
return renderAsImage(opaque: false, scale: UIScreen.main.scale)
|
||||
}
|
||||
|
||||
public func renderAsImage(opaque: Bool, scale: CGFloat) -> UIImage? {
|
||||
func renderAsImage(opaque: Bool, scale: CGFloat) -> UIImage? {
|
||||
if #available(iOS 10, *) {
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = scale
|
||||
|
@ -77,38 +75,33 @@ extension UIView {
|
|||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func spacer(withWidth width: CGFloat) -> UIView {
|
||||
class func spacer(withWidth width: CGFloat) -> UIView {
|
||||
let view = UIView()
|
||||
view.autoSetDimension(.width, toSize: width)
|
||||
return view
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func spacer(withHeight height: CGFloat) -> UIView {
|
||||
class func spacer(withHeight height: CGFloat) -> UIView {
|
||||
let view = UIView()
|
||||
view.autoSetDimension(.height, toSize: height)
|
||||
return view
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func hStretchingSpacer() -> UIView {
|
||||
class func hStretchingSpacer() -> UIView {
|
||||
let view = UIView()
|
||||
view.setContentHuggingHorizontalLow()
|
||||
view.setCompressionResistanceHorizontalLow()
|
||||
return view
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func vStretchingSpacer() -> UIView {
|
||||
class func vStretchingSpacer() -> UIView {
|
||||
let view = UIView()
|
||||
view.setContentHuggingVerticalLow()
|
||||
view.setCompressionResistanceVerticalLow()
|
||||
return view
|
||||
}
|
||||
|
||||
@objc
|
||||
public func applyScaleAspectFitLayout(subview: UIView, aspectRatio: CGFloat) -> [NSLayoutConstraint] {
|
||||
func applyScaleAspectFitLayout(subview: UIView, aspectRatio: CGFloat) -> [NSLayoutConstraint] {
|
||||
guard subviews.contains(subview) else {
|
||||
owsFailDebug("Not a subview.")
|
||||
return []
|
||||
|
@ -126,18 +119,24 @@ extension UIView {
|
|||
constraints.append(subview.autoMatch(.height, to: .height, of: self, withMultiplier: 1.0, relation: .lessThanOrEqual))
|
||||
return constraints
|
||||
}
|
||||
|
||||
func setShadow(radius: CGFloat = 2.0, opacity: CGFloat = 0.66, offset: CGPoint = .zero, color: CGColor = UIColor.black.cgColor) {
|
||||
layer.shadowColor = UIColor.black.cgColor
|
||||
layer.shadowRadius = 2.0
|
||||
layer.shadowOpacity = 0.66
|
||||
layer.shadowOffset = .zero
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public extension UIViewController {
|
||||
@objc
|
||||
public func presentAlert(_ alert: UIAlertController) {
|
||||
func presentAlert(_ alert: UIAlertController) {
|
||||
self.presentAlert(alert, animated: true)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func presentAlert(_ alert: UIAlertController, animated: Bool) {
|
||||
func presentAlert(_ alert: UIAlertController, animated: Bool) {
|
||||
self.present(alert,
|
||||
animated: animated,
|
||||
completion: {
|
||||
|
@ -145,8 +144,7 @@ public extension UIViewController {
|
|||
})
|
||||
}
|
||||
|
||||
@objc
|
||||
public func presentAlert(_ alert: UIAlertController, completion: @escaping (() -> Void)) {
|
||||
func presentAlert(_ alert: UIAlertController, completion: @escaping (() -> Void)) {
|
||||
self.present(alert,
|
||||
animated: true,
|
||||
completion: {
|
||||
|
@ -160,32 +158,32 @@ public extension UIViewController {
|
|||
// MARK: -
|
||||
|
||||
public extension CGFloat {
|
||||
public func clamp(_ minValue: CGFloat, _ maxValue: CGFloat) -> CGFloat {
|
||||
func clamp(_ minValue: CGFloat, _ maxValue: CGFloat) -> CGFloat {
|
||||
return CGFloatClamp(self, minValue, maxValue)
|
||||
}
|
||||
|
||||
public func clamp01() -> CGFloat {
|
||||
func clamp01() -> CGFloat {
|
||||
return CGFloatClamp01(self)
|
||||
}
|
||||
|
||||
// Linear interpolation
|
||||
public func lerp(_ minValue: CGFloat, _ maxValue: CGFloat) -> CGFloat {
|
||||
func lerp(_ minValue: CGFloat, _ maxValue: CGFloat) -> CGFloat {
|
||||
return CGFloatLerp(minValue, maxValue, self)
|
||||
}
|
||||
|
||||
// Inverse linear interpolation
|
||||
public func inverseLerp(_ minValue: CGFloat, _ maxValue: CGFloat, shouldClamp: Bool = false) -> CGFloat {
|
||||
func inverseLerp(_ minValue: CGFloat, _ maxValue: CGFloat, shouldClamp: Bool = false) -> CGFloat {
|
||||
let value = CGFloatInverseLerp(self, minValue, maxValue)
|
||||
return (shouldClamp ? CGFloatClamp01(value) : value)
|
||||
}
|
||||
|
||||
public static let halfPi: CGFloat = CGFloat.pi * 0.5
|
||||
static let halfPi: CGFloat = CGFloat.pi * 0.5
|
||||
|
||||
public func fuzzyEquals(_ other: CGFloat, tolerance: CGFloat = 0.001) -> Bool {
|
||||
func fuzzyEquals(_ other: CGFloat, tolerance: CGFloat = 0.001) -> Bool {
|
||||
return abs(self - other) < tolerance
|
||||
}
|
||||
|
||||
public var square: CGFloat {
|
||||
var square: CGFloat {
|
||||
return self * self
|
||||
}
|
||||
}
|
||||
|
@ -193,7 +191,7 @@ public extension CGFloat {
|
|||
// MARK: -
|
||||
|
||||
public extension Int {
|
||||
public func clamp(_ minValue: Int, _ maxValue: Int) -> Int {
|
||||
func clamp(_ minValue: Int, _ maxValue: Int) -> Int {
|
||||
assert(minValue <= maxValue)
|
||||
|
||||
return Swift.max(minValue, Swift.min(maxValue, self))
|
||||
|
@ -203,75 +201,75 @@ public extension Int {
|
|||
// MARK: -
|
||||
|
||||
public extension CGPoint {
|
||||
public func toUnitCoordinates(viewBounds: CGRect, shouldClamp: Bool) -> CGPoint {
|
||||
func toUnitCoordinates(viewBounds: CGRect, shouldClamp: Bool) -> CGPoint {
|
||||
return CGPoint(x: (x - viewBounds.origin.x).inverseLerp(0, viewBounds.width, shouldClamp: shouldClamp),
|
||||
y: (y - viewBounds.origin.y).inverseLerp(0, viewBounds.height, shouldClamp: shouldClamp))
|
||||
}
|
||||
|
||||
public func toUnitCoordinates(viewSize: CGSize, shouldClamp: Bool) -> CGPoint {
|
||||
func toUnitCoordinates(viewSize: CGSize, shouldClamp: Bool) -> CGPoint {
|
||||
return toUnitCoordinates(viewBounds: CGRect(origin: .zero, size: viewSize), shouldClamp: shouldClamp)
|
||||
}
|
||||
|
||||
public func fromUnitCoordinates(viewBounds: CGRect) -> CGPoint {
|
||||
func fromUnitCoordinates(viewBounds: CGRect) -> CGPoint {
|
||||
return CGPoint(x: viewBounds.origin.x + x.lerp(0, viewBounds.size.width),
|
||||
y: viewBounds.origin.y + y.lerp(0, viewBounds.size.height))
|
||||
}
|
||||
|
||||
public func fromUnitCoordinates(viewSize: CGSize) -> CGPoint {
|
||||
func fromUnitCoordinates(viewSize: CGSize) -> CGPoint {
|
||||
return fromUnitCoordinates(viewBounds: CGRect(origin: .zero, size: viewSize))
|
||||
}
|
||||
|
||||
public func inverse() -> CGPoint {
|
||||
func inverse() -> CGPoint {
|
||||
return CGPoint(x: -x, y: -y)
|
||||
}
|
||||
|
||||
public func plus(_ value: CGPoint) -> CGPoint {
|
||||
func plus(_ value: CGPoint) -> CGPoint {
|
||||
return CGPointAdd(self, value)
|
||||
}
|
||||
|
||||
public func minus(_ value: CGPoint) -> CGPoint {
|
||||
func minus(_ value: CGPoint) -> CGPoint {
|
||||
return CGPointSubtract(self, value)
|
||||
}
|
||||
|
||||
public func times(_ value: CGFloat) -> CGPoint {
|
||||
func times(_ value: CGFloat) -> CGPoint {
|
||||
return CGPoint(x: x * value, y: y * value)
|
||||
}
|
||||
|
||||
public func min(_ value: CGPoint) -> CGPoint {
|
||||
func min(_ value: CGPoint) -> CGPoint {
|
||||
// We use "Swift" to disambiguate the global function min() from this method.
|
||||
return CGPoint(x: Swift.min(x, value.x),
|
||||
y: Swift.min(y, value.y))
|
||||
}
|
||||
|
||||
public func max(_ value: CGPoint) -> CGPoint {
|
||||
func max(_ value: CGPoint) -> CGPoint {
|
||||
// We use "Swift" to disambiguate the global function max() from this method.
|
||||
return CGPoint(x: Swift.max(x, value.x),
|
||||
y: Swift.max(y, value.y))
|
||||
}
|
||||
|
||||
public var length: CGFloat {
|
||||
var length: CGFloat {
|
||||
return sqrt(x * x + y * y)
|
||||
}
|
||||
|
||||
public static let unit: CGPoint = CGPoint(x: 1.0, y: 1.0)
|
||||
static let unit: CGPoint = CGPoint(x: 1.0, y: 1.0)
|
||||
|
||||
public static let unitMidpoint: CGPoint = CGPoint(x: 0.5, y: 0.5)
|
||||
static let unitMidpoint: CGPoint = CGPoint(x: 0.5, y: 0.5)
|
||||
|
||||
public func applyingInverse(_ transform: CGAffineTransform) -> CGPoint {
|
||||
func applyingInverse(_ transform: CGAffineTransform) -> CGPoint {
|
||||
return applying(transform.inverted())
|
||||
}
|
||||
|
||||
public func fuzzyEquals(_ other: CGPoint, tolerance: CGFloat = 0.001) -> Bool {
|
||||
func fuzzyEquals(_ other: CGPoint, tolerance: CGFloat = 0.001) -> Bool {
|
||||
return (x.fuzzyEquals(other.x, tolerance: tolerance) &&
|
||||
y.fuzzyEquals(other.y, tolerance: tolerance))
|
||||
}
|
||||
|
||||
public static func tan(angle: CGFloat) -> CGPoint {
|
||||
static func tan(angle: CGFloat) -> CGPoint {
|
||||
return CGPoint(x: sin(angle),
|
||||
y: cos(angle))
|
||||
}
|
||||
|
||||
public func clamp(_ rect: CGRect) -> CGPoint {
|
||||
func clamp(_ rect: CGRect) -> CGPoint {
|
||||
return CGPoint(x: x.clamp(rect.minX, rect.maxX),
|
||||
y: y.clamp(rect.minY, rect.maxY))
|
||||
}
|
||||
|
@ -300,23 +298,23 @@ public extension CGSize {
|
|||
// MARK: -
|
||||
|
||||
public extension CGRect {
|
||||
public var center: CGPoint {
|
||||
var center: CGPoint {
|
||||
return CGPoint(x: midX, y: midY)
|
||||
}
|
||||
|
||||
public var topLeft: CGPoint {
|
||||
var topLeft: CGPoint {
|
||||
return origin
|
||||
}
|
||||
|
||||
public var topRight: CGPoint {
|
||||
var topRight: CGPoint {
|
||||
return CGPoint(x: maxX, y: minY)
|
||||
}
|
||||
|
||||
public var bottomLeft: CGPoint {
|
||||
var bottomLeft: CGPoint {
|
||||
return CGPoint(x: minX, y: maxY)
|
||||
}
|
||||
|
||||
public var bottomRight: CGPoint {
|
||||
var bottomRight: CGPoint {
|
||||
return CGPoint(x: maxX, y: maxY)
|
||||
}
|
||||
}
|
||||
|
@ -324,23 +322,23 @@ public extension CGRect {
|
|||
// MARK: -
|
||||
|
||||
public extension CGAffineTransform {
|
||||
public static func translate(_ point: CGPoint) -> CGAffineTransform {
|
||||
static func translate(_ point: CGPoint) -> CGAffineTransform {
|
||||
return CGAffineTransform(translationX: point.x, y: point.y)
|
||||
}
|
||||
|
||||
public static func scale(_ scaling: CGFloat) -> CGAffineTransform {
|
||||
static func scale(_ scaling: CGFloat) -> CGAffineTransform {
|
||||
return CGAffineTransform(scaleX: scaling, y: scaling)
|
||||
}
|
||||
|
||||
public func translate(_ point: CGPoint) -> CGAffineTransform {
|
||||
func translate(_ point: CGPoint) -> CGAffineTransform {
|
||||
return translatedBy(x: point.x, y: point.y)
|
||||
}
|
||||
|
||||
public func scale(_ scaling: CGFloat) -> CGAffineTransform {
|
||||
func scale(_ scaling: CGFloat) -> CGAffineTransform {
|
||||
return scaledBy(x: scaling, y: scaling)
|
||||
}
|
||||
|
||||
public func rotate(_ angleRadians: CGFloat) -> CGAffineTransform {
|
||||
func rotate(_ angleRadians: CGFloat) -> CGAffineTransform {
|
||||
return rotated(by: angleRadians)
|
||||
}
|
||||
}
|
||||
|
@ -348,7 +346,7 @@ public extension CGAffineTransform {
|
|||
// MARK: -
|
||||
|
||||
public extension UIBezierPath {
|
||||
public func addRegion(withPoints points: [CGPoint]) {
|
||||
func addRegion(withPoints points: [CGPoint]) {
|
||||
guard let first = points.first else {
|
||||
owsFailDebug("No points.")
|
||||
return
|
||||
|
@ -363,37 +361,33 @@ public extension UIBezierPath {
|
|||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public extension UIBarButtonItem {
|
||||
@objc
|
||||
public convenience init(image: UIImage?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String) {
|
||||
convenience init(image: UIImage?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String) {
|
||||
self.init(image: image, style: style, target: target, action: action)
|
||||
|
||||
self.accessibilityIdentifier = accessibilityIdentifier
|
||||
}
|
||||
|
||||
@objc
|
||||
public convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String) {
|
||||
convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String) {
|
||||
self.init(image: image, landscapeImagePhone: landscapeImagePhone, style: style, target: target, action: action)
|
||||
|
||||
self.accessibilityIdentifier = accessibilityIdentifier
|
||||
}
|
||||
|
||||
@objc
|
||||
public convenience init(title: String?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String) {
|
||||
convenience init(title: String?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String) {
|
||||
self.init(title: title, style: style, target: target, action: action)
|
||||
|
||||
self.accessibilityIdentifier = accessibilityIdentifier
|
||||
}
|
||||
|
||||
@objc
|
||||
public convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, target: Any?, action: Selector?, accessibilityIdentifier: String) {
|
||||
convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, target: Any?, action: Selector?, accessibilityIdentifier: String) {
|
||||
self.init(barButtonSystemItem: systemItem, target: target, action: action)
|
||||
|
||||
self.accessibilityIdentifier = accessibilityIdentifier
|
||||
}
|
||||
|
||||
@objc
|
||||
public convenience init(customView: UIView, accessibilityIdentifier: String) {
|
||||
convenience init(customView: UIView, accessibilityIdentifier: String) {
|
||||
self.init(customView: customView)
|
||||
|
||||
self.accessibilityIdentifier = accessibilityIdentifier
|
||||
|
|
Loading…
Reference in a new issue