mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			476 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			476 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Objective-C
		
	
| //
 | |
| //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "OWSMath.h"
 | |
| #import "UIView+OWS.h"
 | |
| #import <SignalServiceKit/AppContext.h>
 | |
| 
 | |
| NS_ASSUME_NONNULL_BEGIN
 | |
| 
 | |
| static inline CGFloat ScreenShortDimension()
 | |
| {
 | |
|     return MIN([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
 | |
| }
 | |
| 
 | |
| static const CGFloat kIPhone5ScreenWidth = 320.f;
 | |
| static const CGFloat kIPhone7PlusScreenWidth = 414.f;
 | |
| 
 | |
| CGFloat ScaleFromIPhone5To7Plus(CGFloat iPhone5Value, CGFloat iPhone7PlusValue)
 | |
| {
 | |
|     CGFloat screenShortDimension = ScreenShortDimension();
 | |
|     return round(CGFloatLerp(iPhone5Value,
 | |
|         iPhone7PlusValue,
 | |
|         CGFloatInverseLerp(screenShortDimension, kIPhone5ScreenWidth, kIPhone7PlusScreenWidth)));
 | |
| }
 | |
| 
 | |
| CGFloat ScaleFromIPhone5(CGFloat iPhone5Value)
 | |
| {
 | |
|     CGFloat screenShortDimension = ScreenShortDimension();
 | |
|     return round(iPhone5Value * screenShortDimension / kIPhone5ScreenWidth);
 | |
| }
 | |
| 
 | |
| #pragma mark -
 | |
| 
 | |
| @implementation UIView (OWS)
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)autoPinWidthToSuperviewWithMargin:(CGFloat)margin
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:margin],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:margin],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)autoPinWidthToSuperview
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeLeft],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeRight],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)autoPinLeadingAndTrailingToSuperview
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinLeadingToSuperview],
 | |
|         [self autoPinTrailingToSuperview],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)autoPinHeightToSuperviewWithMargin:(CGFloat)margin
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:margin],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:margin],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)autoPinHeightToSuperview
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeTop],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeBottom],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)autoPinToSuperviewEdges
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeLeft],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeRight],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeTop],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeBottom],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoHCenterInSuperview
 | |
| {
 | |
|     return [self autoAlignAxis:ALAxisVertical toSameAxisOfView:self.superview];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoVCenterInSuperview
 | |
| {
 | |
|     return [self autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.superview];
 | |
| }
 | |
| 
 | |
| - (void)autoPinWidthToWidthOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     [self autoPinEdge:ALEdgeLeft toEdge:ALEdgeLeft ofView:view];
 | |
|     [self autoPinEdge:ALEdgeRight toEdge:ALEdgeRight ofView:view];
 | |
| }
 | |
| 
 | |
| - (void)autoPinHeightToHeightOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     [self autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:view];
 | |
|     [self autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:view];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinToSquareAspectRatio
 | |
| {
 | |
|     return [self autoPinToAspectRatio:1.0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinToAspectRatio:(CGFloat)ratio
 | |
| {
 | |
|     // Clamp to ensure view has reasonable aspect ratio.
 | |
|     CGFloat clampedRatio = Clamp(ratio, 0.05, 95.0);
 | |
|     if (clampedRatio != ratio) {
 | |
|         OWSFail(@"Invalid aspect ratio: %f for view: %@", ratio, self);
 | |
|     }
 | |
| 
 | |
|     self.translatesAutoresizingMaskIntoConstraints = NO;
 | |
|     NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self
 | |
|                                                                   attribute:NSLayoutAttributeWidth
 | |
|                                                                   relatedBy:NSLayoutRelationEqual
 | |
|                                                                      toItem:self
 | |
|                                                                   attribute:NSLayoutAttributeHeight
 | |
|                                                                  multiplier:clampedRatio
 | |
|                                                                    constant:0.f];
 | |
|     [constraint autoInstall];
 | |
|     return constraint;
 | |
| }
 | |
| 
 | |
| #pragma mark - Content Hugging and Compression Resistance
 | |
| 
 | |
| - (void)setContentHuggingLow
 | |
| {
 | |
|     [self setContentHuggingHorizontalLow];
 | |
|     [self setContentHuggingVerticalLow];
 | |
| }
 | |
| 
 | |
| - (void)setContentHuggingHigh
 | |
| {
 | |
|     [self setContentHuggingHorizontalHigh];
 | |
|     [self setContentHuggingVerticalHigh];
 | |
| }
 | |
| 
 | |
| - (void)setContentHuggingHorizontalLow
 | |
| {
 | |
|     [self setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
 | |
| }
 | |
| 
 | |
| - (void)setContentHuggingHorizontalHigh
 | |
| {
 | |
|     [self setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
 | |
| }
 | |
| 
 | |
| - (void)setContentHuggingVerticalLow
 | |
| {
 | |
|     [self setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisVertical];
 | |
| }
 | |
| 
 | |
| - (void)setContentHuggingVerticalHigh
 | |
| {
 | |
|     [self setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
 | |
| }
 | |
| 
 | |
| - (void)setCompressionResistanceLow
 | |
| {
 | |
|     [self setCompressionResistanceHorizontalLow];
 | |
|     [self setCompressionResistanceVerticalLow];
 | |
| }
 | |
| 
 | |
| - (void)setCompressionResistanceHigh
 | |
| {
 | |
|     [self setCompressionResistanceHorizontalHigh];
 | |
|     [self setCompressionResistanceVerticalHigh];
 | |
| }
 | |
| 
 | |
| - (void)setCompressionResistanceHorizontalLow
 | |
| {
 | |
|     [self setContentCompressionResistancePriority:0 forAxis:UILayoutConstraintAxisHorizontal];
 | |
| }
 | |
| 
 | |
| - (void)setCompressionResistanceHorizontalHigh
 | |
| {
 | |
|     [self setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
 | |
| }
 | |
| 
 | |
| - (void)setCompressionResistanceVerticalLow
 | |
| {
 | |
|     [self setContentCompressionResistancePriority:0 forAxis:UILayoutConstraintAxisVertical];
 | |
| }
 | |
| 
 | |
| - (void)setCompressionResistanceVerticalHigh
 | |
| {
 | |
|     [self setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
 | |
| }
 | |
| 
 | |
| #pragma mark - Manual Layout
 | |
| 
 | |
| - (CGFloat)left
 | |
| {
 | |
|     return self.frame.origin.x;
 | |
| }
 | |
| 
 | |
| - (CGFloat)right
 | |
| {
 | |
|     return self.frame.origin.x + self.frame.size.width;
 | |
| }
 | |
| 
 | |
| - (CGFloat)top
 | |
| {
 | |
|     return self.frame.origin.y;
 | |
| }
 | |
| 
 | |
| - (CGFloat)bottom
 | |
| {
 | |
|     return self.frame.origin.y + self.frame.size.height;
 | |
| }
 | |
| 
 | |
| - (CGFloat)width
 | |
| {
 | |
|     return self.frame.size.width;
 | |
| }
 | |
| 
 | |
| - (CGFloat)height
 | |
| {
 | |
|     return self.frame.size.height;
 | |
| }
 | |
| 
 | |
| - (void)centerOnSuperview
 | |
| {
 | |
|     OWSAssert(self.superview);
 | |
| 
 | |
|     self.frame = CGRectMake(round((self.superview.width - self.width) * 0.5f),
 | |
|         round((self.superview.height - self.height) * 0.5f),
 | |
|         self.width,
 | |
|         self.height);
 | |
| }
 | |
| 
 | |
| #pragma mark - RTL
 | |
| 
 | |
| - (BOOL)isRTL
 | |
| {
 | |
|     if (@available(iOS 9.0, *)) {
 | |
|         return ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute]
 | |
|             == UIUserInterfaceLayoutDirectionRightToLeft);
 | |
|     } else {
 | |
|         return [CurrentAppContext() isRTL];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToSuperview
 | |
| {
 | |
|     return [self autoPinLeadingToSuperviewWithMargin:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToSuperviewWithMargin:(CGFloat)margin
 | |
| {
 | |
|     if (@available(iOS 9.0, *)) {
 | |
|         NSLayoutConstraint *constraint =
 | |
|             [self.leadingAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.leadingAnchor
 | |
|                                                constant:margin];
 | |
|         constraint.active = YES;
 | |
|         return constraint;
 | |
|     } else {
 | |
|         margin += (self.isRTL ? self.superview.layoutMargins.right : self.superview.layoutMargins.left);
 | |
|         return [self autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:margin];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToSuperview
 | |
| {
 | |
|     return [self autoPinTrailingToSuperviewWithMargin:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToSuperviewWithMargin:(CGFloat)margin
 | |
| {
 | |
|     if (@available(iOS 9.0, *)) {
 | |
|         NSLayoutConstraint *constraint =
 | |
|             [self.trailingAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.trailingAnchor
 | |
|                                                 constant:-margin];
 | |
|         constraint.active = YES;
 | |
|         return constraint;
 | |
|     } else {
 | |
|         margin += (self.isRTL ? self.superview.layoutMargins.left : self.superview.layoutMargins.right);
 | |
|         return [self autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:margin];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinBottomToSuperviewWithMargin:(CGFloat)margin
 | |
| {
 | |
|     if (@available(iOS 9.0, *)) {
 | |
|         NSLayoutConstraint *constraint =
 | |
|             [self.bottomAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.bottomAnchor constant:-margin];
 | |
|         constraint.active = YES;
 | |
|         return constraint;
 | |
|     } else {
 | |
|         return [self autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:margin];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTopToSuperviewWithMargin:(CGFloat)margin
 | |
| {
 | |
|     if (@available(iOS 9.0, *)) {
 | |
|         NSLayoutConstraint *constraint =
 | |
|             [self.topAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.topAnchor constant:margin];
 | |
|         constraint.active = YES;
 | |
|         return constraint;
 | |
|     } else {
 | |
|         return [self autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:margin];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToTrailingOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     return [self autoPinLeadingToTrailingOfView:view margin:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToTrailingOfView:(UIView *)view margin:(CGFloat)margin
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     if (@available(iOS 9.0, *)) {
 | |
|         NSLayoutConstraint *constraint =
 | |
|             [self.leadingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margin];
 | |
|         constraint.active = YES;
 | |
|         return constraint;
 | |
|     } else {
 | |
|         return [self autoPinEdge:ALEdgeLeading toEdge:ALEdgeTrailing ofView:view withOffset:margin];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToLeadingOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     return [self autoPinTrailingToLeadingOfView:view margin:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToLeadingOfView:(UIView *)view margin:(CGFloat)margin
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     if (@available(iOS 9.0, *)) {
 | |
|         NSLayoutConstraint *constraint =
 | |
|             [self.trailingAnchor constraintEqualToAnchor:view.leadingAnchor constant:-margin];
 | |
|         constraint.active = YES;
 | |
|         return constraint;
 | |
|     } else {
 | |
|         return [self autoPinEdge:ALEdgeTrailing toEdge:ALEdgeLeading ofView:view withOffset:margin];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToView:(UIView *)view
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     return [self autoPinLeadingToView:view margin:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToView:(UIView *)view margin:(CGFloat)margin
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     if (@available(iOS 9.0, *)) {
 | |
|         NSLayoutConstraint *constraint =
 | |
|             [self.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:margin];
 | |
|         constraint.active = YES;
 | |
|         return constraint;
 | |
|     } else {
 | |
|         return [self autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:view withOffset:margin];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToView:(UIView *)view
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     return [self autoPinTrailingToView:view margin:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToView:(UIView *)view margin:(CGFloat)margin
 | |
| {
 | |
|     OWSAssert(view);
 | |
| 
 | |
|     if (@available(iOS 9.0, *)) {
 | |
|         NSLayoutConstraint *constraint =
 | |
|             [self.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margin];
 | |
|         constraint.active = YES;
 | |
|         return constraint;
 | |
|     } else {
 | |
|         return [self autoPinEdge:ALEdgeTrailing toEdge:ALEdgeTrailing ofView:view withOffset:margin];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSTextAlignment)textAlignmentUnnatural
 | |
| {
 | |
|     return (self.isRTL ? NSTextAlignmentLeft : NSTextAlignmentRight);
 | |
| }
 | |
| 
 | |
| - (void)setHLayoutMargins:(CGFloat)value
 | |
| {
 | |
|     UIEdgeInsets layoutMargins = self.layoutMargins;
 | |
|     layoutMargins.left = value;
 | |
|     layoutMargins.right = value;
 | |
|     self.layoutMargins = layoutMargins;
 | |
| }
 | |
| 
 | |
| #pragma mark - Containers
 | |
| 
 | |
| + (UIView *)containerView
 | |
| {
 | |
|     UIView *view = [UIView new];
 | |
|     // Leading and trailing anchors honor layout margins.
 | |
|     // When using a UIView as a "div" to structure layout, we don't want it to have margins.
 | |
|     view.layoutMargins = UIEdgeInsetsZero;
 | |
|     return view;
 | |
| }
 | |
| 
 | |
| + (UIView *)verticalStackWithSubviews:(NSArray<UIView *> *)subviews spacing:(int)spacing
 | |
| {
 | |
|     UIView *container = [UIView containerView];
 | |
|     UIView *_Nullable lastSubview = nil;
 | |
|     for (UIView *subview in subviews) {
 | |
|         [container addSubview:subview];
 | |
|         [subview autoPinWidthToSuperview];
 | |
|         if (lastSubview) {
 | |
|             [subview autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview withOffset:spacing];
 | |
|         } else {
 | |
|             [subview autoPinEdgeToSuperviewEdge:ALEdgeTop];
 | |
|         }
 | |
|         lastSubview = subview;
 | |
|     }
 | |
|     [lastSubview autoPinEdgeToSuperviewEdge:ALEdgeBottom];
 | |
|     return container;
 | |
| }
 | |
| 
 | |
| #pragma mark - Debugging
 | |
| 
 | |
| - (void)addBorderWithColor:(UIColor *)color
 | |
| {
 | |
|     self.layer.borderColor = color.CGColor;
 | |
|     self.layer.borderWidth = 1;
 | |
| }
 | |
| 
 | |
| - (void)addRedBorder
 | |
| {
 | |
|     [self addBorderWithColor:[UIColor redColor]];
 | |
| }
 | |
| 
 | |
| - (void)addRedBorderRecursively
 | |
| {
 | |
|     [self addRedBorder];
 | |
|     for (UIView *subview in self.subviews) {
 | |
|         [subview addRedBorderRecursively];
 | |
|     }
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| NS_ASSUME_NONNULL_END
 |