Files
2024-05-08 14:56:11 -04:00

759 lines
44 KiB
Objective-C

/*
Copyright (c) 2015, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors
may be used to endorse or promote products derived from this software without
specific prior written permission. No license is granted to the trademarks of
the copyright holders even if such marks are included in this software.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "ORKVerticalContainerView.h"
#import "ORKVerticalContainerView_Internal.h"
#import "ORKNavigationContainerView.h"
#import "ORKStepHeaderView_Internal.h"
#import "ORKTintedImageView.h"
#import "ORKHelpers_Internal.h"
#import "ORKSkin.h"
// Enable this define to see outlines and colors of all the views laid out at this level.
// #define LAYOUT_DEBUG
/*
view hierachy in ORKVerticalContainerView (from top to bottom):
scrollContainer
- container
- customViewContainer
- headerView
- stepViewContainer
*/
@implementation ORKVerticalContainerView {
UIView *_scrollContainer;
UIView *_container;
ORKTintedImageView *_imageView;
NSMutableArray *_variableConstraints;
NSLayoutConstraint *_headerMinimumHeightConstraint;
NSLayoutConstraint *_illustrationHeightConstraint;
NSLayoutConstraint *_stepViewCenterInStepViewContainerConstraint;
NSLayoutConstraint *_topToIllustrationConstraint;
NSLayoutConstraint *_scrollContainerHeightConstraint;
CGFloat _keyboardOverlap;
UIView *_stepViewContainer;
BOOL _keyboardIsUp;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_scrollContainerShouldCollapseNavbar = YES;
_scrollContainer = [UIView new];
[self addSubview:_scrollContainer];
_container = [UIView new];
[_scrollContainer addSubview:_container];
{
_headerView = [ORKStepHeaderView new];
_headerView.layoutMargins = UIEdgeInsetsZero;
_headerView.translatesAutoresizingMaskIntoConstraints = NO;
[_container addSubview:_headerView];
}
{
_stepViewContainer = [UIView new];
_stepViewContainer.preservesSuperviewLayoutMargins = YES;
[_container addSubview:_stepViewContainer];
}
{
// Custom View
_customViewContainer = [UIView new];
[_container addSubview:self.customViewContainer];
}
ORKEnableAutoLayoutForViews(@[_scrollContainer, _container, _headerView, _stepViewContainer, _customViewContainer]);
[self setUpStaticConstraints];
[self setNeedsUpdateConstraints];
UITapGestureRecognizer *tapOffRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapOffAction:)];
[self addGestureRecognizer:tapOffRecognizer];
UISwipeGestureRecognizer *swipeOffRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeOffAction:)];
swipeOffRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
[self addGestureRecognizer:swipeOffRecognizer];
}
return self;
}
- (void)setUpStaticConstraints {
NSMutableArray *constraints = [NSMutableArray new];
NSDictionary *views = NSDictionaryOfVariableBindings(_scrollContainer, _container);
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_scrollContainer]|"
options:(NSLayoutFormatOptions)0
metrics:nil
views:views]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_scrollContainer]|"
options:(NSLayoutFormatOptions)0
metrics:nil
views:views]];
_scrollContainerHeightConstraint = [NSLayoutConstraint constraintWithItem:_scrollContainer
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:self
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:_scrollContainerShouldCollapseNavbar ? 0.3 : 0.0]; //anything less than 0.3 does not work for smaller devices.
[constraints addObject: _scrollContainerHeightConstraint];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_scrollContainer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0]];
// This constraint is needed to get the scroll container not to size itself too large (we don't want scrolling if it's not needed)
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:_scrollContainer
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0];
heightConstraint.priority = UILayoutPriorityDefaultLow;
[constraints addObject:heightConstraint];
[NSLayoutConstraint activateConstraints:constraints];
}
- (void)setScrollContainerShouldCollapseNavbar:(BOOL)scrollContainerShouldCollapseNavbar {
if (!scrollContainerShouldCollapseNavbar) {
_scrollContainerShouldCollapseNavbar = scrollContainerShouldCollapseNavbar;
[NSLayoutConstraint deactivateConstraints:@[_scrollContainerHeightConstraint]];
_scrollContainerHeightConstraint = [NSLayoutConstraint constraintWithItem:_scrollContainer
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0];
[_scrollContainerHeightConstraint setActive:YES];
[self setNeedsUpdateConstraints];
}
}
- (void)swipeOffAction:(UITapGestureRecognizer *)recognizer {
[self endEditing:NO];
}
- (void)tapOffAction:(UITapGestureRecognizer *)recognizer {
// On a tap, dismiss the keyboard if the tap was not inside a view that is first responder or a child of a first responder.
CGPoint point = [recognizer locationInView:self];
UIView *view = [self hitTest:point withEvent:nil];
BOOL viewIsChildOfFirstResponder = NO;
while (view) {
if ([view isFirstResponder]) {
viewIsChildOfFirstResponder = YES;
break;
}
view = [view superview];
}
if (!viewIsChildOfFirstResponder) {
[self endEditing:NO];
}
}
- (void)dealloc {
[self registerForKeyboardNotifications:NO];
}
- (void)registerForKeyboardNotifications:(BOOL)shouldRegister {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
if (shouldRegister) {
[notificationCenter addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification object:nil];
[notificationCenter addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification object:nil];
[notificationCenter addObserver:self
selector:@selector(keyboardFrameWillChange:)
name:UIKeyboardWillChangeFrameNotification object:nil];
} else {
[notificationCenter removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[notificationCenter removeObserver:self name:UIKeyboardWillHideNotification object:nil];
[notificationCenter removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil];
}
}
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
[self updateLayoutMargins];
}
- (void)setFrame:(CGRect)frame {
[super setFrame:frame];
[self updateLayoutMargins];
}
- (void)willMoveToWindow:(UIWindow *)newWindow {
[super willMoveToWindow:newWindow];
[self updateConstraintConstantsForWindow:newWindow];
if (newWindow) {
[self registerForKeyboardNotifications:YES];
} else {
[self registerForKeyboardNotifications:NO];
}
}
- (CGSize)keyboardIntersectionSizeFromNotification:(NSNotification *)notification {
CGRect keyboardFrame = [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardFrame = [self convertRect:keyboardFrame fromView:nil];
CGRect scrollFrame = self.bounds;
// The origin of this is in our superview's coordinate system, but I don't think
// we actually use the origin - so just return the size.
CGRect intersectionFrame = CGRectIntersection(scrollFrame, keyboardFrame);
return intersectionFrame.size;
}
- (void)animateLayoutForKeyboardNotification:(NSNotification *)notification {
NSTimeInterval animationDuration = ((NSNumber *)notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]).doubleValue;
[UIView animateWithDuration:animationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
CGRect bounds = self.bounds;
CGSize contentSize = self.contentSize;
CGSize intersectionSize = [self keyboardIntersectionSizeFromNotification:notification];
CGFloat visibleHeight = bounds.size.height - intersectionSize.height;
// Keep track of the keyboard overlap, so we can adjust the constraint properly.
_keyboardOverlap = intersectionSize.height;
// Trigger layout inside the animation block to get the constraint change to animate.
[self layoutIfNeeded];
if (_keyboardIsUp) {
CGFloat contentMaxY = CGRectGetMaxY([self convertRect:_container.bounds fromView:_container]);
// First compute the contentOffset.y that would make the continue and skip buttons visible
CGFloat yOffset = MAX(contentMaxY - visibleHeight, 0);
yOffset = MIN(yOffset, contentSize.height - visibleHeight);
// If that yOffset would not make the stepView visible, override to align with the top of the stepView.
CGRect potentialVisibleRect = (CGRect){{0,yOffset},{bounds.size.width,visibleHeight}};
CGRect targetBounds = [self convertRect:_stepView.bounds fromView:_stepView];
if (!CGRectContainsRect(potentialVisibleRect, targetBounds)) {
yOffset = targetBounds.origin.y;
}
CGFloat keyboardOverlapWithActualContent = MAX(contentMaxY - (contentSize.height - intersectionSize.height), 0);
UIEdgeInsets insets = (UIEdgeInsets){.bottom = keyboardOverlapWithActualContent };
self.contentInset = insets;
// Rather than setContentOffset, setBounds so that we get a smooth animation
if (ABS(yOffset - bounds.origin.y) > 1) {
bounds.origin.y = yOffset;
[self setBounds:bounds];
}
}
} completion:nil];
}
- (void)keyboardFrameWillChange:(NSNotification *)notification {
CGSize intersectionSize = [self keyboardIntersectionSizeFromNotification:notification];
// Assume the overlap is at the bottom of the view
ORKUpdateScrollViewBottomInset(self, intersectionSize.height);
_keyboardIsUp = YES;
[self animateLayoutForKeyboardNotification:notification];
}
- (void)keyboardWillShow:(NSNotification *)notification {
CGSize intersectionSize = [self keyboardIntersectionSizeFromNotification:notification];
// Assume the overlap is at the bottom of the view
ORKUpdateScrollViewBottomInset(self, intersectionSize.height);
_keyboardIsUp = YES;
[self animateLayoutForKeyboardNotification:notification];
}
- (void)keyboardWillHide:(NSNotification *)notification {
ORKUpdateScrollViewBottomInset(self, 0);
_keyboardIsUp = NO;
[self animateLayoutForKeyboardNotification:notification];
}
- (void)updateStepViewCenteringConstraint {
BOOL hasIllustration = (_imageView.image != nil);
BOOL hasCaption = _headerView.captionLabel.text.length > 0;
BOOL hasInstruction = _headerView.instructionLabel.text.length > 0;
BOOL hasLearnMore = (_headerView.learnMoreButton.alpha > 0);
if (_stepViewCenterInStepViewContainerConstraint) {
BOOL offsetCentering = !(hasIllustration || hasCaption || hasInstruction || hasLearnMore);
_stepViewCenterInStepViewContainerConstraint.active = offsetCentering;
}
}
- (void)updateLayoutMargins {
CGFloat margin = ORKStandardHorizontalMarginForView(self);
UIEdgeInsets layoutMargins = (UIEdgeInsets){.left = margin, .right = margin};
self.layoutMargins = layoutMargins;
_scrollContainer.layoutMargins = layoutMargins;
_container.layoutMargins = layoutMargins;
}
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
{
BOOL hasIllustration = (_imageView.image != nil);
_headerView.hasContentAbove = hasIllustration;
const CGFloat IllustrationHeight = ORKGetMetricForWindow(ORKScreenMetricIllustrationHeight, window);
const CGFloat IllustrationTopMargin = ORKGetMetricForWindow(ORKScreenMetricTopToIllustration, window);
_illustrationHeightConstraint.constant = (_imageView.image ? IllustrationHeight : 0);
_topToIllustrationConstraint.constant = (_imageView.image ?IllustrationTopMargin : 0);
}
{
_headerMinimumHeightConstraint.constant = _minimumStepHeaderHeight;
}
}
- (void)setContinueHugsContent:(BOOL)continueHugsContent {
_continueHugsContent = continueHugsContent;
[self setNeedsUpdateConstraints];
}
- (void)setVerticalCenteringEnabled:(BOOL)verticalCenteringEnabled {
_verticalCenteringEnabled = verticalCenteringEnabled;
[self setNeedsUpdateConstraints];
}
- (void)setStepViewFillsAvailableSpace:(BOOL)stepViewFillsAvailableSpace {
_stepViewFillsAvailableSpace = stepViewFillsAvailableSpace;
[self setNeedsUpdateConstraints];
}
- (void)setMinimumStepHeaderHeight:(CGFloat)minimumStepHeaderHeight {
_minimumStepHeaderHeight = minimumStepHeaderHeight;
[self updateConstraintConstantsForWindow:self.window];
}
- (void)updateConstraints {
[NSLayoutConstraint deactivateConstraints:_variableConstraints];
[_variableConstraints removeAllObjects];
if (!_variableConstraints) {
_variableConstraints = [NSMutableArray new];
}
NSArray *views = @[_headerView, _customViewContainer, _stepViewContainer];
// Roughly center the container, but put it a little above the center if possible
if (_verticalCenteringEnabled) {
NSLayoutConstraint *verticalCentering1 = [NSLayoutConstraint constraintWithItem:_container
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_scrollContainer
attribute:NSLayoutAttributeCenterY
multiplier:0.8
constant:0.0];
verticalCentering1.priority = UILayoutPriorityDefaultLow;
[_variableConstraints addObject:verticalCentering1];
NSLayoutConstraint *verticalCentering2 = [NSLayoutConstraint constraintWithItem:_container
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:_scrollContainer
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0];
verticalCentering2.priority = UILayoutPriorityDefaultHigh;
[_variableConstraints addObject:verticalCentering2];
NSLayoutConstraint *verticalCentering3 = [NSLayoutConstraint constraintWithItem:_container
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:_scrollContainer
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
verticalCentering3.priority = UILayoutPriorityDefaultHigh;
[_variableConstraints addObject:verticalCentering3];
} else {
NSLayoutConstraint *verticalTop = [NSLayoutConstraint constraintWithItem:_container
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_scrollContainer
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
[_variableConstraints addObject:verticalTop];
}
// Don't let the container get too tall
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:_container
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:_scrollContainer
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0];
[_variableConstraints addObject:heightConstraint];
[_variableConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[container]|"
options:(NSLayoutFormatOptions)0
metrics:nil
views:@{@"container":_container}]];
#ifdef LAYOUT_DEBUG
_container.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.3];
_scrollContainer.backgroundColor = [[UIColor blueColor] colorWithAlphaComponent:0.3];
#endif
// All items with constants use constraint ivars
_illustrationHeightConstraint = [NSLayoutConstraint constraintWithItem:_customViewContainer
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:198.0];
[_variableConstraints addObject:_illustrationHeightConstraint];
_topToIllustrationConstraint = [NSLayoutConstraint constraintWithItem:_customViewContainer
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_container
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
[_variableConstraints addObject:_topToIllustrationConstraint];
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_headerView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_customViewContainer
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_stepViewContainer
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_headerView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
_headerMinimumHeightConstraint = [NSLayoutConstraint constraintWithItem:_headerView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:_minimumStepHeaderHeight];
[_variableConstraints addObject:_headerMinimumHeightConstraint];
// Force all to stay within the container's width.
for (UIView *view in views) {
#ifdef LAYOUT_DEBUG
view.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.3];
view.layer.borderColor = [UIColor redColor].CGColor;
view.layer.borderWidth = 1.0;
#endif
if (view == _stepViewContainer) {
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:_container
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0]];
} else {
NSLayoutRelation relation = NSLayoutRelationGreaterThanOrEqual;
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeLeft
relatedBy:relation
toItem:_container
attribute:NSLayoutAttributeLeftMargin
multiplier:1.0
constant:0.0]];
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeRight
relatedBy:relation
toItem:_container
attribute:NSLayoutAttributeRightMargin
multiplier:1.0
constant:0.0]];
}
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:_container
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
NSLayoutConstraint *viewToContainerBottomConstraint = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:_container
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0];
// Because the bottom items are not always present, we add individual "bottom" constraints
// for all views to ensure the parent sizes large enough to contain everything.
[_variableConstraints addObject:viewToContainerBottomConstraint];
}
[self prepareCustomViewContainerConstraints];
[self prepareStepViewContainerConstraints];
[NSLayoutConstraint activateConstraints:_variableConstraints];
[self updateLayoutMargins];
[self updateConstraintConstantsForWindow:self.window];
[self updateStepViewCenteringConstraint];
[super updateConstraints];
}
- (void)prepareStepViewContainerConstraints {
if (_stepView) {
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:_stepViewContainer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:ORKScreenMetricMaxDimension];
widthConstraint.priority = UILayoutPriorityFittingSizeLevel;
[_variableConstraints addObject:widthConstraint];
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_stepView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:_stepViewContainer
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
NSLayoutConstraint *stepViewWidthConstraint = [NSLayoutConstraint constraintWithItem:_stepView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:_stepViewContainer
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0];
stepViewWidthConstraint.priority = UILayoutPriorityRequired;
[_variableConstraints addObject:stepViewWidthConstraint];
if (_stepViewFillsAvailableSpace) {
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_stepViewContainer
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:ORKScreenMetricMaxDimension];
constraint.priority = UILayoutPriorityFittingSizeLevel;
[_variableConstraints addObject:constraint];
NSLayoutConstraint *verticalCentering = [NSLayoutConstraint constraintWithItem:_stepView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_stepViewContainer
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0];
verticalCentering.priority = UILayoutPriorityRequired - 2;
[_variableConstraints addObject:verticalCentering];
{
NSLayoutConstraint *verticalCentering2 = [NSLayoutConstraint constraintWithItem:_stepView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_stepViewContainer
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0];
verticalCentering2.priority = UILayoutPriorityRequired - 1;
[_variableConstraints addObject:verticalCentering2];
_stepViewCenterInStepViewContainerConstraint = verticalCentering2;
}
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_stepView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:_stepViewContainer
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0]];
} else {
[_variableConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[stepView]|"
options:(NSLayoutFormatOptions)0
metrics:nil
views:@{@"stepView": _stepView}]];
}
} else {
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:_stepViewContainer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:0.0];
widthConstraint.priority = UILayoutPriorityFittingSizeLevel;
[_variableConstraints addObject:widthConstraint];
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_stepViewContainer
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:0.0]];
}
}
- (void)prepareCustomViewContainerConstraints {
if (_customView) {
[_variableConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[customView]|"
options:(NSLayoutFormatOptions)0
metrics:nil
views:@{@"customView": _customView}]];
[_variableConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[customView]|"
options:(NSLayoutFormatOptions)0
metrics:nil
views:@{@"customView": _customView}]];
}
if (_imageView) {
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_imageView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0]];
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_imageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:_customViewContainer
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0]];
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_imageView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:_customViewContainer
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_imageView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_customViewContainer
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
}
}
- (void)setCustomView:(UIView *)customView {
[_customView removeFromSuperview];
_customView = customView;
[_customViewContainer addSubview:_customView];
if (_customView && [_customView constraints].count == 0) {
[_customView setTranslatesAutoresizingMaskIntoConstraints:NO];
CGSize requiredSize = [_customView sizeThatFits:(CGSize){self.bounds.size.width, CGFLOAT_MAX}];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:_customView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:requiredSize.width];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:_customView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:requiredSize.height];
widthConstraint.priority = UILayoutPriorityDefaultLow;
heightConstraint.priority = UILayoutPriorityDefaultLow;
[NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint]];
}
[self setNeedsUpdateConstraints];
}
- (UIImageView *)imageView {
if (_imageView == nil) {
_imageView = [[ORKTintedImageView alloc] init];
[_customViewContainer addSubview:_imageView];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView.userInteractionEnabled = YES;
[self setNeedsUpdateConstraints];
}
return _imageView;
}
- (void)setStepView:(UIView *)customView {
[_stepView removeFromSuperview];
_stepView = customView;
[_stepViewContainer addSubview:_stepView];
[self setNeedsUpdateConstraints];
}
@end