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.
		
		
		
		
		
			
		
			
				
	
	
		
			603 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			603 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Objective-C
		
	
| //
 | |
| //  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "UIView+OWS.h"
 | |
| #import "OWSMath.h"
 | |
| #import <SignalCoreKit/iOSVersions.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 (CGFloat)round(CGFloatLerp(iPhone5Value,
 | |
|         iPhone7PlusValue,
 | |
|         CGFloatClamp01(CGFloatInverseLerp(screenShortDimension, kIPhone5ScreenWidth, kIPhone7PlusScreenWidth))));
 | |
| }
 | |
| 
 | |
| CGFloat ScaleFromIPhone5(CGFloat iPhone5Value)
 | |
| {
 | |
|     CGFloat screenShortDimension = ScreenShortDimension();
 | |
|     return (CGFloat)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 *> *)autoPinWidthToSuperviewMargins
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinEdgeToSuperviewMargin:ALEdgeLeading],
 | |
|         [self autoPinEdgeToSuperviewMargin:ALEdgeTrailing],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)autoPinWidthToSuperview
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeLeft],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeRight],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)autoPinLeadingAndTrailingToSuperviewMargin
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinLeadingToSuperviewMargin],
 | |
|         [self autoPinTrailingToSuperviewMargin],
 | |
|     ];
 | |
|     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 *> *)ows_autoPinToSuperviewEdges
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeLeft],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeRight],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeTop],
 | |
|         [self autoPinEdgeToSuperviewEdge:ALEdgeBottom],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)ows_autoPinToSuperviewMargins
 | |
| {
 | |
|     NSArray<NSLayoutConstraint *> *result = @[
 | |
|         [self autoPinTopToSuperviewMargin],
 | |
|         [self autoPinLeadingToSuperviewMargin],
 | |
|         [self autoPinTrailingToSuperviewMargin],
 | |
|         [self autoPinBottomToSuperviewMargin],
 | |
|     ];
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoHCenterInSuperview
 | |
| {
 | |
|     return [self autoAlignAxis:ALAxisVertical toSameAxisOfView:self.superview];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoVCenterInSuperview
 | |
| {
 | |
|     return [self autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.superview];
 | |
| }
 | |
| 
 | |
| - (void)autoPinWidthToWidthOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     [self autoPinEdge:ALEdgeLeft toEdge:ALEdgeLeft ofView:view];
 | |
|     [self autoPinEdge:ALEdgeRight toEdge:ALEdgeRight ofView:view];
 | |
| }
 | |
| 
 | |
| - (void)autoPinHeightToHeightOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     [self autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:view];
 | |
|     [self autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:view];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinToSquareAspectRatio
 | |
| {
 | |
|     return [self autoPinToAspectRatio:1.0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinToAspectRatioWithSize:(CGSize)size {
 | |
|     return [self autoPinToAspectRatio:size.width / size.height];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinToAspectRatio:(CGFloat)ratio
 | |
| {
 | |
|     // Clamp to ensure view has reasonable aspect ratio.
 | |
|     CGFloat clampedRatio = CGFloatClamp(ratio, 0.05f, 95.0f);
 | |
|     if (clampedRatio != ratio) {
 | |
|         OWSFailDebug(@"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
 | |
| {
 | |
|     OWSAssertDebug(self.superview);
 | |
| 
 | |
|     CGFloat x = (CGFloat)round((self.superview.width - self.width) * 0.5f);
 | |
|     CGFloat y = (CGFloat)round((self.superview.height - self.height) * 0.5f);
 | |
|     self.frame = CGRectMake(x, y, self.width, self.height);
 | |
| }
 | |
| 
 | |
| #pragma mark - RTL
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToSuperviewMargin
 | |
| {
 | |
|     return [self autoPinLeadingToSuperviewMarginWithInset:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToSuperviewMarginWithInset:(CGFloat)inset
 | |
| {
 | |
|     self.translatesAutoresizingMaskIntoConstraints = NO;
 | |
| 
 | |
|     NSLayoutConstraint *constraint =
 | |
|         [self.leadingAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.leadingAnchor constant:inset];
 | |
|     constraint.active = YES;
 | |
|     return constraint;
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToSuperviewMargin
 | |
| {
 | |
|     return [self autoPinTrailingToSuperviewMarginWithInset:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToSuperviewMarginWithInset:(CGFloat)inset
 | |
| {
 | |
|     self.translatesAutoresizingMaskIntoConstraints = NO;
 | |
| 
 | |
|     NSLayoutConstraint *constraint =
 | |
|         [self.trailingAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.trailingAnchor constant:-inset];
 | |
|     constraint.active = YES;
 | |
|     return constraint;
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinBottomToSuperviewMargin
 | |
| {
 | |
|     return [self autoPinBottomToSuperviewMarginWithInset:0.f];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinBottomToSuperviewMarginWithInset:(CGFloat)inset
 | |
| {
 | |
|     self.translatesAutoresizingMaskIntoConstraints = NO;
 | |
| 
 | |
|     NSLayoutConstraint *constraint =
 | |
|         [self.bottomAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.bottomAnchor constant:-inset];
 | |
|     constraint.active = YES;
 | |
|     return constraint;
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTopToSuperviewMargin
 | |
| {
 | |
|     return [self autoPinTopToSuperviewMarginWithInset:0.f];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTopToSuperviewMarginWithInset:(CGFloat)inset
 | |
| {
 | |
|     self.translatesAutoresizingMaskIntoConstraints = NO;
 | |
| 
 | |
|     NSLayoutConstraint *constraint =
 | |
|         [self.topAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.topAnchor constant:inset];
 | |
|     constraint.active = YES;
 | |
|     return constraint;
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToTrailingEdgeOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     return [self autoPinLeadingToTrailingEdgeOfView:view offset:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToTrailingEdgeOfView:(UIView *)view offset:(CGFloat)offset
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     self.translatesAutoresizingMaskIntoConstraints = NO;
 | |
| 
 | |
|     NSLayoutConstraint *constraint = [self.leadingAnchor constraintEqualToAnchor:view.trailingAnchor constant:offset];
 | |
|     constraint.active = YES;
 | |
|     return constraint;
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToLeadingEdgeOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     return [self autoPinTrailingToLeadingEdgeOfView:view offset:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToLeadingEdgeOfView:(UIView *)view offset:(CGFloat)offset
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     self.translatesAutoresizingMaskIntoConstraints = NO;
 | |
| 
 | |
|     NSLayoutConstraint *constraint = [self.trailingAnchor constraintEqualToAnchor:view.leadingAnchor constant:-offset];
 | |
|     constraint.active = YES;
 | |
|     return constraint;
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToEdgeOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     return [self autoPinLeadingToEdgeOfView:view offset:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinLeadingToEdgeOfView:(UIView *)view offset:(CGFloat)offset
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     self.translatesAutoresizingMaskIntoConstraints = NO;
 | |
| 
 | |
|     NSLayoutConstraint *constraint = [self.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:offset];
 | |
|     constraint.active = YES;
 | |
|     return constraint;
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToEdgeOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     return [self autoPinTrailingToEdgeOfView:view offset:0];
 | |
| }
 | |
| 
 | |
| - (NSLayoutConstraint *)autoPinTrailingToEdgeOfView:(UIView *)view offset:(CGFloat)margin
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     self.translatesAutoresizingMaskIntoConstraints = NO;
 | |
| 
 | |
|     NSLayoutConstraint *constraint = [self.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margin];
 | |
|     constraint.active = YES;
 | |
|     return constraint;
 | |
| }
 | |
| 
 | |
| - (NSTextAlignment)textAlignmentUnnatural
 | |
| {
 | |
|     return (CurrentAppContext().isRTL ? NSTextAlignmentLeft : NSTextAlignmentRight);
 | |
| }
 | |
| 
 | |
| - (void)setHLayoutMargins:(CGFloat)value
 | |
| {
 | |
|     UIEdgeInsets layoutMargins = self.layoutMargins;
 | |
|     layoutMargins.left = value;
 | |
|     layoutMargins.right = value;
 | |
|     self.layoutMargins = layoutMargins;
 | |
| }
 | |
| 
 | |
| - (NSArray<NSLayoutConstraint *> *)autoPinToEdgesOfView:(UIView *)view
 | |
| {
 | |
|     OWSAssertDebug(view);
 | |
| 
 | |
|     return @[
 | |
|         [self autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:view],
 | |
|         [self autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:view],
 | |
|         [self autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:view],
 | |
|         [self autoPinEdge:ALEdgeTrailing toEdge:ALEdgeTrailing ofView:view],
 | |
|     ];
 | |
| }
 | |
| 
 | |
| #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];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (void)logFrame
 | |
| {
 | |
|     [self logFrameWithLabel:@""];
 | |
| }
 | |
| 
 | |
| - (void)logFrameWithLabel:(NSString *)label
 | |
| {
 | |
|     OWSLogVerbose(@"%@ frame: %@, hidden: %d, opacity: %f, layoutMargins: %@",
 | |
|         label,
 | |
|         NSStringFromCGRect(self.frame),
 | |
|         self.hidden,
 | |
|         self.layer.opacity,
 | |
|         NSStringFromUIEdgeInsets(self.layoutMargins));
 | |
| }
 | |
| 
 | |
| - (void)logFrameLater
 | |
| {
 | |
|     [self logFrameLaterWithLabel:@""];
 | |
| }
 | |
| 
 | |
| - (void)logFrameLaterWithLabel:(NSString *)label
 | |
| {
 | |
|     dispatch_async(dispatch_get_main_queue(), ^{
 | |
|         [self logFrameWithLabel:label];
 | |
|     });
 | |
| }
 | |
| 
 | |
| - (void)logHierarchyUpwardLaterWithLabel:(NSString *)label
 | |
| {
 | |
|     dispatch_async(dispatch_get_main_queue(), ^{
 | |
|         OWSLogVerbose(@"%@ ----", label);
 | |
|     });
 | |
| 
 | |
|     UIResponder *responder = self;
 | |
|     while (responder) {
 | |
|         if ([responder isKindOfClass:[UIView class]]) {
 | |
|             UIView *view = (UIView *)responder;
 | |
|             [view logFrameLaterWithLabel:@"\t"];
 | |
|         }
 | |
|         responder = responder.nextResponder;
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (void)traverseViewHierarchyWithVisitor:(UIViewVisitorBlock)visitor
 | |
| {
 | |
|     OWSAssertIsOnMainThread();
 | |
|     OWSAssertDebug(visitor);
 | |
| 
 | |
|     visitor(self);
 | |
| 
 | |
|     for (UIView *subview in self.subviews) {
 | |
|         [subview traverseViewHierarchyWithVisitor:visitor];
 | |
|     }
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| #pragma mark -
 | |
| 
 | |
| @implementation UIScrollView (OWS)
 | |
| 
 | |
| - (BOOL)applyScrollViewInsetsFix
 | |
| {
 | |
|     // Fix a bug that only affects iOS 11.0.x and 11.1.x.
 | |
|     // The symptom is a fix weird animation that happens when using the interactive pop gesture.
 | |
|     if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0) && !SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 2)) {
 | |
| #pragma clang diagnostic push
 | |
| #pragma clang diagnostic ignored "-Wpartial-availability"
 | |
|         self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
 | |
| #pragma clang diagnostic pop
 | |
|         return YES;
 | |
|     }
 | |
|     return NO;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| #pragma mark -
 | |
| 
 | |
| @implementation UIStackView (OWS)
 | |
| 
 | |
| - (UIView *)addBackgroundViewWithBackgroundColor:(UIColor *)backgroundColor
 | |
| {
 | |
|     UIView *subview = [UIView new];
 | |
|     subview.backgroundColor = backgroundColor;
 | |
|     [self addSubview:subview];
 | |
|     [subview autoPinEdgesToSuperviewEdges];
 | |
|     [subview setCompressionResistanceLow];
 | |
|     [subview setContentHuggingLow];
 | |
|     [self sendSubviewToBack:subview];
 | |
|     return subview;
 | |
| }
 | |
| 
 | |
| - (UIView *)addBorderViewWithColor:(UIColor *)color strokeWidth:(CGFloat)strokeWidth cornerRadius:(CGFloat)cornerRadius
 | |
| {
 | |
| 
 | |
|     UIView *borderView = [UIView new];
 | |
|     borderView.userInteractionEnabled = NO;
 | |
|     borderView.backgroundColor = UIColor.clearColor;
 | |
|     borderView.opaque = NO;
 | |
|     borderView.layer.borderColor = color.CGColor;
 | |
|     borderView.layer.borderWidth = strokeWidth;
 | |
|     borderView.layer.cornerRadius = cornerRadius;
 | |
|     [self addSubview:borderView];
 | |
|     [borderView autoPinEdgesToSuperviewEdges];
 | |
|     [borderView setCompressionResistanceLow];
 | |
|     [borderView setContentHuggingLow];
 | |
|     return borderView;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| CGFloat CGHairlineWidth()
 | |
| {
 | |
|     return 1.f / UIScreen.mainScreen.scale;
 | |
| }
 | |
| 
 | |
| NS_ASSUME_NONNULL_END
 |