// // ProductView.m // Mobile Buy SDK // // Created by Shopify. // Copyright (c) 2015 Shopify Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #import "ProductView.h" #import "ProductViewHeader.h" #import "HeaderBackgroundView.h" #import "ActionableFooterView.h" #import "GradientView.h" #import "ProductVariantCell.h" #import "ProductDescriptionCell.h" #import "ProductHeaderCell.h" #import "AsyncImageView.h" #import "ErrorView.h" #import "Theme+Additions.h" @interface ProductView () @property (nonatomic, strong) ErrorView *errorView; @property (nonatomic, strong) NSLayoutConstraint *topInsetConstraint; @end @implementation ProductView - (instancetype)initWithFrame:(CGRect)rect product:(BUYProduct*)product shouldShowApplePaySetup:(BOOL)showApplePaySetup { self = [super initWithFrame:rect]; if (self) { _backgroundImageView = [[HeaderBackgroundView alloc] init]; _backgroundImageView.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:_backgroundImageView]; [self addConstraint:[NSLayoutConstraint constraintWithItem:_backgroundImageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0.0]]; [self addConstraint:[NSLayoutConstraint constraintWithItem:_backgroundImageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]]; _stickyFooterView = [UIView new]; _stickyFooterView.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:_stickyFooterView]; _footerHeightLayoutConstraint = [NSLayoutConstraint constraintWithItem:_stickyFooterView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:0.0]; [self addConstraint:_footerHeightLayoutConstraint]; _footerOffsetLayoutConstraint = [NSLayoutConstraint constraintWithItem:_stickyFooterView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]; [self addConstraint:_footerOffsetLayoutConstraint]; [self addConstraint:[NSLayoutConstraint constraintWithItem:_stickyFooterView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]]; _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; _tableView.backgroundColor = [UIColor clearColor]; _tableView.translatesAutoresizingMaskIntoConstraints = NO; _tableView.estimatedRowHeight = 60.0; _tableView.rowHeight = UITableViewAutomaticDimension; _tableView.tableFooterView = [UIView new]; _tableView.layoutMargins = UIEdgeInsetsMake(_tableView.layoutMargins.top, kBuyPaddingExtraLarge, _tableView.layoutMargins.bottom, kBuyPaddingMedium); [self addSubview:_tableView]; [_tableView registerClass:[ProductHeaderCell class] forCellReuseIdentifier:@"headerCell"]; [_tableView registerClass:[ProductVariantCell class] forCellReuseIdentifier:@"variantCell"]; [_tableView registerClass:[ProductDescriptionCell class] forCellReuseIdentifier:@"descriptionCell"]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_tableView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_tableView)]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_tableView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_tableView)]]; _topInsetConstraint = [NSLayoutConstraint constraintWithItem:self.tableView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; [self addConstraint:_topInsetConstraint]; CGFloat size = MIN(CGRectGetWidth(rect), CGRectGetHeight(rect)); if ([product.images count] > 0) { _productViewHeader = [[ProductViewHeader alloc] initWithFrame:CGRectMake(0, 0, size, size)]; _tableView.tableHeaderView = self.productViewHeader; } else { _tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 1)]; } _productViewFooter = [ActionableFooterView new]; _productViewFooter.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:_productViewFooter]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_productViewFooter]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_productViewFooter)]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_productViewFooter]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_productViewFooter)]]; if (_productViewHeader) { _topGradientView = [[GradientView alloc] init]; _topGradientView.topColor = [Theme topGradientViewTopColor]; _topGradientView.translatesAutoresizingMaskIntoConstraints = NO; _topGradientView.userInteractionEnabled = NO; [self addSubview:_topGradientView]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_topGradientView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_topGradientView)]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_topGradientView(height)]" options:0 metrics:@{ @"height" : @(kBuyTopGradientViewHeight) } views:NSDictionaryOfVariableBindings(_topGradientView)]]; } } return self; } - (void)layoutSubviews { [super layoutSubviews]; [self setInsets:UIEdgeInsetsMake(self.tableView.contentInset.top, self.tableView.contentInset.left, CGRectGetHeight(self.bounds) - CGRectGetMinY(self.productViewFooter.frame), self.tableView.contentInset.right) appendToCurrentInset:NO]; } - (void)updateBackgroundImage:(NSArray *)images { if ([images count] > 0) { NSInteger page = 0; if (CGSizeEqualToSize(self.productViewHeader.collectionView.contentSize, CGSizeZero) == NO) { page = (int)(self.productViewHeader.collectionView.contentOffset.x / self.productViewHeader.collectionView.frame.size.width); } [self.productViewHeader setCurrentPage:page]; BUYImageLink *image = images[page]; [self.backgroundImageView setBackgroundProductImage:image]; } } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGFloat imageHeight = 0; if (self.productViewHeader) { imageHeight = [self.productViewHeader imageHeightWithScrollViewDidScroll:scrollView]; } CGFloat footerViewHeight = self.bounds.size.height - imageHeight; if (scrollView.contentOffset.y > 0) { footerViewHeight += scrollView.contentOffset.y; } if (footerViewHeight <= 0) { footerViewHeight = 0; } // Add the top inset for the navigation bar (when pushed in a navigation controller stack, not presented) footerViewHeight += -self.topInsetConstraint.constant; self.footerHeightLayoutConstraint.constant = footerViewHeight; self.footerOffsetLayoutConstraint.constant = -footerViewHeight; CGFloat opaqueOffset = CGRectGetHeight(self.productViewHeader.bounds); CGFloat whiteStartingOffset = opaqueOffset - CGRectGetHeight(self.topGradientView.bounds); self.topGradientView.alpha = -(scrollView.contentOffset.y - whiteStartingOffset) / (opaqueOffset - whiteStartingOffset); } #pragma mark - Error Handling - (ErrorView *)errorView { if (_errorView == nil) { _errorView = [[ErrorView alloc] init]; _errorView.alpha = 0; _errorView.translatesAutoresizingMaskIntoConstraints = NO; [self insertSubview:_errorView belowSubview:self.productViewFooter]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_errorView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_errorView)]]; _errorView.hiddenConstraint = [NSLayoutConstraint constraintWithItem:_errorView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]; [self addConstraint:_errorView.hiddenConstraint]; _errorView.visibleConstraint = [NSLayoutConstraint constraintWithItem:_errorView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.productViewFooter attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]; [NSLayoutConstraint activateConstraints:@[_errorView.hiddenConstraint]]; [_errorView layoutIfNeeded]; } return _errorView; } - (void)showErrorWithMessage:(NSString*)errorMessage { [self.errorView presentErrorViewWithMessage:errorMessage completion:^{ self.errorView = nil; }]; } - (void)setInsets:(UIEdgeInsets)edgeInsets appendToCurrentInset:(BOOL)appendToCurrentInset { CGFloat top = appendToCurrentInset ? self.tableView.contentInset.top + edgeInsets.top : edgeInsets.top; CGFloat left = appendToCurrentInset ? self.tableView.contentInset.left + edgeInsets.left : edgeInsets.left; CGFloat bottom = appendToCurrentInset ? self.tableView.contentInset.bottom + edgeInsets.bottom : edgeInsets.bottom; CGFloat right = appendToCurrentInset ? self.tableView.contentInset.right + edgeInsets.right : edgeInsets.right; self.tableView.contentInset = self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(top, left, bottom, right); } - (void)setTopInset:(CGFloat)topInset { self.topInsetConstraint.constant = topInset; } #pragma mark - Appearance - (void)setBackgroundColor:(UIColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; _stickyFooterView.backgroundColor = backgroundColor; } - (void)setShowsProductImageBackground:(BOOL)showsProductImageBackground { _backgroundImageView.hidden = showsProductImageBackground == NO; } @end