From 5e846b773e3df1905e1b63d43a56cfb7e63bec35 Mon Sep 17 00:00:00 2001 From: Pavan Goyal Date: Tue, 30 Oct 2018 19:12:48 +0530 Subject: [PATCH] SJSegmentedViewController-ObjC code --- SJSegmentedScrollView-ObjC/SJContentView.h | 20 ++ SJSegmentedScrollView-ObjC/SJContentView.m | 99 +++++++ SJSegmentedScrollView-ObjC/SJSegmentTab.h | 28 ++ SJSegmentedScrollView-ObjC/SJSegmentTab.m | 79 ++++++ SJSegmentedScrollView-ObjC/SJSegmentView.h | 37 +++ SJSegmentedScrollView-ObjC/SJSegmentView.m | 224 +++++++++++++++ .../SJSegmentedScrollView.h | 49 ++++ .../SJSegmentedScrollView.m | 254 ++++++++++++++++++ .../SJSegmentedViewController.h | 50 ++++ .../SJSegmentedViewController.m | 239 ++++++++++++++++ SJSegmentedScrollView-ObjC/SJUtil.h | 18 ++ SJSegmentedScrollView-ObjC/SJUtil.m | 48 ++++ 12 files changed, 1145 insertions(+) create mode 100644 SJSegmentedScrollView-ObjC/SJContentView.h create mode 100644 SJSegmentedScrollView-ObjC/SJContentView.m create mode 100644 SJSegmentedScrollView-ObjC/SJSegmentTab.h create mode 100644 SJSegmentedScrollView-ObjC/SJSegmentTab.m create mode 100644 SJSegmentedScrollView-ObjC/SJSegmentView.h create mode 100644 SJSegmentedScrollView-ObjC/SJSegmentView.m create mode 100644 SJSegmentedScrollView-ObjC/SJSegmentedScrollView.h create mode 100644 SJSegmentedScrollView-ObjC/SJSegmentedScrollView.m create mode 100644 SJSegmentedScrollView-ObjC/SJSegmentedViewController.h create mode 100644 SJSegmentedScrollView-ObjC/SJSegmentedViewController.m create mode 100644 SJSegmentedScrollView-ObjC/SJUtil.h create mode 100644 SJSegmentedScrollView-ObjC/SJUtil.m diff --git a/SJSegmentedScrollView-ObjC/SJContentView.h b/SJSegmentedScrollView-ObjC/SJContentView.h new file mode 100644 index 0000000..ec134c7 --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJContentView.h @@ -0,0 +1,20 @@ +// +// SJContentView.h +// Hike +// +// Created by Pavan Goyal on 02/05/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import +#import "SJSegmentTab.h" + +@interface SJContentView : UIScrollView + +@property (nonatomic, copy) DidSelectSegmentAtIndex didSelectSegmentAtIndex; + +- (void)addContentView:(UIView *)view frame:(CGRect)frame; +- (void)updateContentControllersFrame:(CGRect)frame; +- (void)movePageToIndex:(NSInteger)index animated:(BOOL)animated; + +@end diff --git a/SJSegmentedScrollView-ObjC/SJContentView.m b/SJSegmentedScrollView-ObjC/SJContentView.m new file mode 100644 index 0000000..6a679fd --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJContentView.m @@ -0,0 +1,99 @@ +// +// SJContentView.m +// Hike +// +// Created by Pavan Goyal on 02/05/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import "SJContentView.h" + +@interface SJContentView () + +@property (nonatomic, assign) NSInteger pageIndex; +@property (nonatomic, strong) NSMutableArray *contentViews; +@property (nonatomic, strong) UIView *contentView; +@property (nonatomic, strong) NSLayoutConstraint *contentViewWidthConstraint; +@property (nonatomic, strong) NSMutableArray *contentSubViewWidthConstraints; +@property (nonatomic, assign) CGFloat animationDuration; + +@end + +@implementation SJContentView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + _pageIndex = 0; + _contentViews = [[NSMutableArray alloc] init]; + _animationDuration = 0.3; + _contentSubViewWidthConstraints = [[NSMutableArray alloc] init]; + self.delegate = self; + [self setPagingEnabled:YES]; + self.contentView = [[UIView alloc] init]; + self.contentView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:self.contentView]; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:nil views:@{@"contentView": self.contentView, @"mainView": self}]; + [self addConstraints:horizontalConstraints]; + self.contentViewWidthConstraint = [NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:0]; + [self addConstraint:self.contentViewWidthConstraint]; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView(==mainView)]|" options:0 metrics:nil views:@{@"contentView": self.contentView, @"mainView": self}]; + [self addConstraints:verticalConstraints]; + } + return self; +} + +- (void)addContentView:(UIView *)view frame:(CGRect)frame { + view.translatesAutoresizingMaskIntoConstraints = NO; + [self.contentView addSubview:view]; + CGFloat width = frame.size.width; + if (self.contentViews.count > 0) { + UIView *previousView = self.contentViews.lastObject; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[previousView]-0-[view]" options:0 metrics:@{@"xPos": @(self.contentViews.count * width)} views:@{@"view": view, @"previousView": previousView}]; + [self.contentView addConstraints:horizontalConstraints]; + } else { + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]" options:0 metrics:@{@"xPos": @(self.contentViews.count * width)} views:@{@"view": view}]; + [self.contentView addConstraints:horizontalConstraints]; + } + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:width]; + [self.contentView addConstraint:widthConstraint]; + [self.contentSubViewWidthConstraints addObject:widthConstraint]; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[view]-0-|" options:0 metrics:nil views:@{@"view": view}]; + [self.contentView addConstraints:verticalConstraints]; + [self.contentViews addObject:view]; + self.contentViewWidthConstraint.constant = self.contentViews.count * self.bounds.size.width; +} + +- (void)updateContentControllersFrame:(CGRect)frame { + CGFloat width = frame.size.width; + self.contentViewWidthConstraint.constant = self.contentViews.count * width; + for (NSLayoutConstraint *constraint in self.contentSubViewWidthConstraints) { + constraint.constant = width; + } + [self layoutIfNeeded]; + CGPoint point = self.contentOffset; + point.x = self.pageIndex * width; + [self setContentOffset:point animated:YES]; +} + +- (void)movePageToIndex:(NSInteger)index animated:(BOOL)animated { + self.pageIndex = index; + CGPoint point = CGPointMake((index * self.bounds.size.width), 0); + if (animated) { + [UIView animateWithDuration:self.animationDuration animations:^{ + self.contentOffset = point; + }]; + } else { + self.contentOffset = point; + } +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + self.pageIndex = self.contentOffset.x / self.bounds.size.width; + if (self.didSelectSegmentAtIndex) { + self.didSelectSegmentAtIndex(nil, self.pageIndex, YES); + } + [[NSNotificationCenter defaultCenter] postNotificationName:@"DidChangeSegmentIndex" object:@(self.pageIndex)]; +} + +@end diff --git a/SJSegmentedScrollView-ObjC/SJSegmentTab.h b/SJSegmentedScrollView-ObjC/SJSegmentTab.h new file mode 100644 index 0000000..7a21d92 --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJSegmentTab.h @@ -0,0 +1,28 @@ +// +// SJSegmentTab.h +// Hike +// +// Created by Pavan Goyal on 30/04/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import +#import + +@class SJSegmentTab; + +typedef void (^DidSelectSegmentAtIndex)(SJSegmentTab *segmentTab, NSInteger index, BOOL animated); + +@interface SJSegmentTab : UIView + +@property (nonatomic, assign) BOOL isSelected; +@property (nonatomic, copy) DidSelectSegmentAtIndex didSelectSegmentAtIndex; + +- (instancetype)initWithTitle:(NSString *)title; +- (instancetype)initWithView:(UIView *)view; +- (void)setTitle:(NSString *)title; +- (void)titleColor:(UIColor *)color; +- (void)titleFont:(UIFont *)font; +- (void)addConstraintsToView:(UIView *)view; + +@end diff --git a/SJSegmentedScrollView-ObjC/SJSegmentTab.m b/SJSegmentedScrollView-ObjC/SJSegmentTab.m new file mode 100644 index 0000000..cb0cdec --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJSegmentTab.m @@ -0,0 +1,79 @@ +// +// SJSegmentTab.m +// Hike +// +// Created by Pavan Goyal on 30/04/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import "SJSegmentTab.h" + +@interface SJSegmentTab () + +@property(nonatomic, strong) UIButton *button; + +@end + +@implementation SJSegmentTab + +- (instancetype)initWithTitle:(NSString *)title { + self = [self initWithFrame:CGRectZero]; + [self setTitle:title]; + return self; +} + +- (instancetype)initWithView:(UIView *)view { + self = [self initWithFrame:CGRectZero]; + [self insertSubview:view atIndex:0]; + [view removeConstraints:view.constraints]; + [self addConstraintsToView:view]; + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.translatesAutoresizingMaskIntoConstraints = NO; + self.button = [UIButton buttonWithType:UIButtonTypeCustom]; + self.button.frame = self.bounds; + [self.button setSelected:NO]; + [self.button addTarget:self action:@selector(onSegmentButtonPress:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:self.button]; + [self addConstraintsToView:self.button]; + } + return self; +} + +- (void)addConstraintsToView:(UIView *)view { + view.translatesAutoresizingMaskIntoConstraints = NO; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:@{@"view": view}]; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:@{@"view": view}]; + [self addConstraints:verticalConstraints]; + [self addConstraints:horizontalConstraints]; +} + +- (void)setTitle:(NSString *)title { + [self.button setTitle:title forState:UIControlStateNormal]; +} + +- (void)titleColor:(UIColor *)color { + [self.button setTitleColor:color forState:UIControlStateNormal]; +} + +- (void)titleFont:(UIFont *)font { + self.button.titleLabel.font = font; +} + +- (void)setIsSelected:(BOOL)isSelected { + [self.button setSelected:isSelected]; +} + +- (void)onSegmentButtonPress:(id)sender { + NSInteger index = self.tag - 100; + [[NSNotificationCenter defaultCenter] postNotificationName:@"DidChangeSegmentIndex" object:@(index)]; + if (self.didSelectSegmentAtIndex) { + self.didSelectSegmentAtIndex(self, index, YES); + } +} + +@end diff --git a/SJSegmentedScrollView-ObjC/SJSegmentView.h b/SJSegmentedScrollView-ObjC/SJSegmentView.h new file mode 100644 index 0000000..61dbec5 --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJSegmentView.h @@ -0,0 +1,37 @@ +// +// SJSegmentView.h +// Hike +// +// Created by Pavan Goyal on 02/05/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import +#import "SJSegmentTab.h" +#import "SJContentView.h" + +@interface SJSegmentView : UIScrollView + +@property (nonatomic, strong) NSMutableArray *segments; +@property (nonatomic, strong) UIFont *font; +@property (nonatomic, assign) CGFloat selectedSegmentViewHeight; +@property (nonatomic, assign) NSInteger kSegmentViewTagOffset; +@property (nonatomic, assign) CGFloat segmentViewOffsetWidth; +@property (nonatomic, strong) UIView *segmentContentView; +@property (nonatomic, copy) DidSelectSegmentAtIndex didSelectSegmentAtIndex; +@property (nonatomic, strong) UIView *selectedSegmentView; +@property (nonatomic, strong) NSLayoutConstraint *xPosConstraints; +@property (nonatomic, strong) NSLayoutConstraint *contentViewWidthConstraint; +@property (nonatomic, strong) NSLayoutConstraint *selectedSegmentViewWidthConstraint; +@property (nonatomic, strong) NSMutableArray *contentSubViewWidthConstraints; +@property (nonatomic, strong) NSArray *controllers; +@property (nonatomic, assign) CGFloat segmentViewHeight; +@property (nonatomic, strong) UIColor *selectedSegmentViewColor; +@property (nonatomic, strong) UIColor *titleColor; +@property (nonatomic, strong) UIColor *segmentBackgroundColor; +@property (nonatomic, strong) SJContentView *contentView; + +- (void)didChangeParentViewFrame:(CGRect)frame; +- (void)setSegmentsView:(CGRect)frame; + +@end diff --git a/SJSegmentedScrollView-ObjC/SJSegmentView.m b/SJSegmentedScrollView-ObjC/SJSegmentView.m new file mode 100644 index 0000000..a06228c --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJSegmentView.m @@ -0,0 +1,224 @@ +// +// SJSegmentView.m +// Hike +// +// Created by Pavan Goyal on 02/05/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import "SJSegmentView.h" +#import "SJUtil.h" + +@interface SJSegmentView () + +@end + +@implementation SJSegmentView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + _kSegmentViewTagOffset = 100; + _segmentViewOffsetWidth = 10.0; + _segments = [[NSMutableArray alloc] init]; + _contentSubViewWidthConstraints = [[NSMutableArray alloc] init]; + _controllers = [[NSArray alloc] init]; + _segmentViewHeight = 53.0; + self.showsHorizontalScrollIndicator = NO; + self.showsVerticalScrollIndicator = NO; + self.bounces = NO; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeSegmentIndex:) name:@"DidChangeSegmentIndex" object:nil]; + } + return self; +} + +- (void)setSelectedSegmentViewColor:(UIColor *)selectedSegmentViewColor { + _selectedSegmentViewColor = selectedSegmentViewColor; + self.selectedSegmentView.backgroundColor = selectedSegmentViewColor; +} + +- (void)setTitleColor:(UIColor *)titleColor { + _titleColor = titleColor; + for (SJSegmentTab *segment in self.segments) { + [segment titleColor:titleColor]; + } +} + +- (void)setSegmentBackgroundColor:(UIColor *)segmentBackgroundColor { + _segmentBackgroundColor = segmentBackgroundColor; + for (SJSegmentTab *segment in self.segments) { + [segment setBackgroundColor:segmentBackgroundColor]; + } +} + +- (void)setContentView:(SJContentView *)contentView { + _contentView = contentView; + [contentView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; +} + +- (void)didChangeSegmentIndex:(NSNotification *)notification { + for (SJSegmentTab *button in self.segments) { + button.isSelected = NO; + } + NSNumber *index = notification.object; + if (index.integerValue < self.segments.count) { + SJSegmentTab *button = self.segments[index.integerValue]; + button.isSelected = YES; + } +} + +- (void)dealloc { + [self.contentView removeObserver:self forKeyPath:@"contentOffset" context:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"DidChangeSegmentIndex" object:nil]; +} + +- (void)setSegmentsView:(CGRect)frame { + CGFloat segmentWidth = [self widthForSegment:frame]; + [self createSegmentContentView:segmentWidth]; + NSInteger index = 0; + for (UIViewController *controller in self.controllers) { + [self createSegmentForController:controller width:segmentWidth index:index]; + index += 1; + } + [self createSelectedSegmentView:segmentWidth]; + SJSegmentTab *button = self.segments.firstObject; + button.isSelected = YES; +} + +- (void)createSegmentContentView:(CGFloat)titleWidth { + self.segmentContentView = [[UIView alloc] initWithFrame:CGRectZero]; + self.segmentContentView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:self.segmentContentView]; + CGFloat contentViewWidth = titleWidth * self.controllers.count; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:nil views:@{@"contentView": self.segmentContentView, @"mainView": self}]; + [self addConstraints:horizontalConstraints]; + self.contentViewWidthConstraint = [NSLayoutConstraint constraintWithItem:self.segmentContentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:contentViewWidth]; + [self addConstraint:self.contentViewWidthConstraint]; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView(==mainView)]|" options:0 metrics:nil views:@{@"contentView": self.segmentContentView, @"mainView": self}]; + [self addConstraints:verticalConstraints]; +} + +- (void)createSegmentForController:(UIViewController *)controller width:(CGFloat)width index:(NSInteger)index { + SJSegmentTab *segmentTab = [self getSegmentTabForController:controller]; + segmentTab.tag = (index + self.kSegmentViewTagOffset); + segmentTab.translatesAutoresizingMaskIntoConstraints = NO; + [self.segmentContentView addSubview:segmentTab]; + if (self.segments.count == 0) { + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]" options:0 metrics:nil views:@{@"view": segmentTab}]; + [self.segmentContentView addConstraints:horizontalConstraints]; + } else { + SJSegmentTab *previousView = self.segments.lastObject; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[previousView]-0-[view]" options:0 metrics:nil views:@{@"view": segmentTab, @"previousView": previousView}]; + [self.segmentContentView addConstraints:horizontalConstraints]; + } + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:segmentTab attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:width]; + [self.segmentContentView addConstraint:widthConstraint]; + [self.contentSubViewWidthConstraints addObject:widthConstraint]; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]-1-|" options:0 metrics:nil views:@{@"view": segmentTab}]; + [self.segmentContentView addConstraints:verticalConstraints]; + [self.segments addObject:segmentTab]; +} + +- (void)createSelectedSegmentView:(CGFloat)width { + UIView *segmentView = [[UIView alloc] init]; + segmentView.backgroundColor = self.selectedSegmentViewColor; + segmentView.translatesAutoresizingMaskIntoConstraints = NO; + [self.segmentContentView addSubview:segmentView]; + self.selectedSegmentView = segmentView; + self.xPosConstraints = [NSLayoutConstraint constraintWithItem:segmentView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.segmentContentView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0.0]; + [self.segmentContentView addConstraint:self.xPosConstraints]; + SJSegmentTab *segment = self.segments.firstObject; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[view(==segment)]" options:0 metrics:nil views:@{@"view": segmentView, @"segment": segment}]; + [self.segmentContentView addConstraints:horizontalConstraints]; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[view(height)]|" options:0 metrics:@{@"height": @(self.selectedSegmentViewHeight)} views:@{@"view": segmentView}]; + [self.segmentContentView addConstraints:verticalConstraints]; +} + +- (SJSegmentTab *)getSegmentTabForController:(UIViewController *)controller { + SJSegmentTab *segmentTab = nil; + if (controller.navigationItem.titleView != nil) { + segmentTab = [[SJSegmentTab alloc] initWithView:controller.navigationItem.titleView]; + } else { + if (controller.title) { + segmentTab = [[SJSegmentTab alloc] initWithTitle:controller.title]; + } else { + segmentTab = [[SJSegmentTab alloc] initWithTitle:@""]; + } + segmentTab.backgroundColor = self.segmentBackgroundColor; + [segmentTab titleColor:self.titleColor]; + [segmentTab titleFont:self.font]; + } + segmentTab.didSelectSegmentAtIndex = self.didSelectSegmentAtIndex; + segmentTab.translatesAutoresizingMaskIntoConstraints = NO; + [segmentTab.heightAnchor constraintEqualToConstant:(self.segmentViewHeight-1)].active = YES; + return segmentTab; +} + +- (CGFloat)widthForSegment:(CGRect)frame { + CGFloat maxWidth = 0; + for (UIViewController *controller in self.controllers) { + CGFloat width = 0.0; + UIView *view = controller.navigationItem.titleView; + NSString *title = controller.title; + if (view) { + width = view.bounds.size.width; + } else if (title) { + width = [SJUtil widthForString:title withConstrainedWidth:CGFLOAT_MAX font:self.font]; + } + if (width > maxWidth) { + maxWidth = width; + } + } + CGFloat width = maxWidth + self.segmentViewOffsetWidth; + CGFloat totalWidth = width * self.controllers.count; + if (totalWidth < frame.size.width) { + maxWidth = frame.size.width / (CGFloat)self.controllers.count; + } else { + maxWidth = (CGFloat)width; + } + return maxWidth; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([change isKindOfClass:[NSDictionary class]]) { + id old = change[NSKeyValueChangeOldKey]; + id new = change[NSKeyValueChangeNewKey]; + if (old && new) { + if (![old isEqual:new]) { + if ([object isKindOfClass:[UIScrollView class]]) { + UIScrollView *scrollView = object; + CGFloat changeOffset = scrollView.contentSize.width / self.contentSize.width; + CGFloat value = scrollView.contentOffset.x / changeOffset; + if (!isnan(value)) { + CGRect frame = self.selectedSegmentView.frame; + frame.origin.x = scrollView.contentOffset.x / changeOffset; + self.selectedSegmentView.frame = frame; + } + CGFloat segmentScrollWidth = self.contentSize.width - self.bounds.size.width; + CGFloat contentScrollWidth = scrollView.contentSize.width - scrollView.bounds.size.width; + changeOffset = segmentScrollWidth / contentScrollWidth; + CGFloat x = scrollView.contentOffset.x * changeOffset; + CGFloat y = self.contentOffset.y; + self.contentOffset = CGPointMake(x, y); + } + } + } + } +} + +- (void)didChangeParentViewFrame:(CGRect)frame { + CGFloat segmentWidth = [self widthForSegment:frame]; + CGFloat contentViewWidth = segmentWidth * self.controllers.count; + self.contentViewWidthConstraint.constant = contentViewWidth; + for (NSLayoutConstraint *constraint in self.contentSubViewWidthConstraints) { + constraint.constant = segmentWidth; + } + CGFloat changeOffset = self.contentView.contentSize.width / self.contentSize.width; + CGFloat value = self.contentView.contentOffset.x / changeOffset; + if (!isnan(value)) { + self.xPosConstraints.constant = self.selectedSegmentView.frame.origin.x; + [self layoutIfNeeded]; + } +} + +@end diff --git a/SJSegmentedScrollView-ObjC/SJSegmentedScrollView.h b/SJSegmentedScrollView-ObjC/SJSegmentedScrollView.h new file mode 100644 index 0000000..55dc592 --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJSegmentedScrollView.h @@ -0,0 +1,49 @@ +// +// SJSegmentedScrollView.h +// Hike +// +// Created by Pavan Goyal on 02/05/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import +#import "SJSegmentView.h" +#import "SJContentView.h" + +@interface SJSegmentedScrollView : UIScrollView + +@property (nonatomic, strong) SJSegmentView *segmentView; +@property (nonatomic, assign) CGFloat headerViewHeight; +@property (nonatomic, assign) CGFloat segmentViewHeight; +@property (nonatomic, assign) CGFloat headerViewOffsetHeight; +@property (nonatomic, strong) UIColor *selectedSegmentViewColor; +@property (nonatomic, assign) CGFloat selectedSegmentViewHeight; +@property (nonatomic, assign) BOOL segmentBounces; +@property (nonatomic, strong) UIColor *segmentTitleColor; +@property (nonatomic, strong) UIColor *selectedSegmentTitleColor; +@property (nonatomic, strong) UIColor *segmentBackgroundColor; +@property (nonatomic, strong) UIColor *segmentViewBackgroundColor; +@property (nonatomic, strong) UIFont *segmentTitleFont; +@property (nonatomic, assign) CGFloat topSpacing; +@property (nonatomic, assign) CGFloat bottomSpacing; +@property (nonatomic, assign) BOOL observing; +@property (nonatomic, strong) UIView *headerView; +@property (nonatomic, strong) NSMutableArray *contentControllers; +@property (nonatomic, strong) NSMutableArray *contentViews; +@property (nonatomic, strong) SJContentView *contentView; +@property (nonatomic, strong) UIView *scrollContentView; +@property (nonatomic, strong) NSLayoutConstraint *contentViewHeightConstraint; +@property (nonatomic, copy) DidSelectSegmentAtIndex didSelectSegmentAtIndex; +@property (nonatomic, assign) BOOL sjShowsVerticalScrollIndicator; +@property (nonatomic, assign) BOOL sjShowsHorizontalScrollIndicator; +@property (nonatomic, strong) NSMutableArray *viewObservers; +@property (nonatomic, assign) BOOL sjDisableScrollOnContentView; + +- (void)updateSubviewsFrame:(CGRect)frame; +- (void)addContentView:(UIView *)contentView frame:(CGRect)frame; +- (void)addObserverForView:(UIView *)view; +- (void)addHeaderView:(UIView *)headerView; +- (void)setContentView; +- (void)addSegmentView:(NSArray *)controllers frame:(CGRect)frame; + +@end diff --git a/SJSegmentedScrollView-ObjC/SJSegmentedScrollView.m b/SJSegmentedScrollView-ObjC/SJSegmentedScrollView.m new file mode 100644 index 0000000..a7fff6e --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJSegmentedScrollView.m @@ -0,0 +1,254 @@ +// +// SJSegmentedScrollView.m +// Hike +// +// Created by Pavan Goyal on 02/05/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import "SJSegmentedScrollView.h" + +@interface SJSegmentedScrollView () + +@end + +@implementation SJSegmentedScrollView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + _headerViewHeight = 0.0; + _segmentViewHeight = 0.0; + _headerViewOffsetHeight = 0.0; + _selectedSegmentViewColor = [UIColor redColor]; + _selectedSegmentViewHeight = 0.0; + _segmentBounces = NO; + _segmentTitleColor = [UIColor redColor]; + _segmentViewBackgroundColor = [UIColor grayColor]; + _segmentTitleFont = [UIFont systemFontOfSize:12]; + _topSpacing = 0.0; + _bottomSpacing = 0.0; + _observing = YES; + _contentControllers = [[NSMutableArray alloc] init]; + _contentViews = [[NSMutableArray alloc] init]; + _sjShowsVerticalScrollIndicator = NO; + _sjShowsHorizontalScrollIndicator = NO; + _viewObservers = [[NSMutableArray alloc] init]; + _sjDisableScrollOnContentView = NO; + [self sizeToFit]; + self.translatesAutoresizingMaskIntoConstraints = NO; + self.showsHorizontalScrollIndicator = NO; + self.showsVerticalScrollIndicator = NO; + self.decelerationRate = UIScrollViewDecelerationRateFast; + self.bounces = NO; + [self addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; + } + return self; +} + +- (void)setSjShowsVerticalScrollIndicator:(BOOL)sjShowsVerticalScrollIndicator { + _sjShowsVerticalScrollIndicator = sjShowsVerticalScrollIndicator; + self.showsVerticalScrollIndicator = sjShowsVerticalScrollIndicator; + self.contentView.showsVerticalScrollIndicator = sjShowsVerticalScrollIndicator; +} + +- (void)setSjShowsHorizontalScrollIndicator:(BOOL)sjShowsHorizontalScrollIndicator { + _sjShowsHorizontalScrollIndicator = sjShowsHorizontalScrollIndicator; + self.showsHorizontalScrollIndicator = sjShowsHorizontalScrollIndicator; + self.contentView.showsHorizontalScrollIndicator = sjShowsHorizontalScrollIndicator; +} + +- (void)setSjDisableScrollOnContentView:(BOOL)sjDisableScrollOnContentView { + _sjDisableScrollOnContentView = sjDisableScrollOnContentView; + [self.contentView setScrollEnabled:!sjDisableScrollOnContentView]; +} + +- (void)dealloc { + [self removeObserver:self forKeyPath:@"contentOffset" context:nil]; + for (UIView *view in self.viewObservers) { + [view removeObserver:self forKeyPath:@"contentOffset" context:nil]; + } +} + +- (void)setContentView { + if (self.scrollContentView == nil) { + self.scrollContentView = [[UIView alloc] init]; + self.scrollContentView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:self.scrollContentView]; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView(==mainView)]|" options:0 metrics:nil views:@{@"contentView": self.scrollContentView, @"mainView": self}]; + [self addConstraints:horizontalConstraints]; + CGFloat contentHeight = [self getContentHeight]; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:nil views:@{@"contentView": self.scrollContentView}]; + [self addConstraints:verticalConstraints]; + self.contentViewHeightConstraint = [NSLayoutConstraint constraintWithItem:self.scrollContentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:contentHeight]; + [self addConstraint:self.contentViewHeightConstraint]; + } +} + +- (void)addHeaderView:(UIView *)headerView { + if (headerView != nil) { + self.headerView = headerView; + headerView.translatesAutoresizingMaskIntoConstraints = NO; + [self.scrollContentView addSubview:headerView]; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[headerView]-0-|" options:0 metrics:nil views:@{@"headerView": headerView}]; + [self.scrollContentView addConstraints:horizontalConstraints]; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[headerView(headerViewHeight)]" options:0 metrics:@{@"headerViewHeight": @(self.headerViewHeight)} views:@{@"headerView": headerView}]; + [self.scrollContentView addConstraints:verticalConstraints]; + } else { + self.headerViewHeight = self.headerViewOffsetHeight; + } +} + +- (void)addObserverForView:(UIView *)view { + [self.viewObservers addObject:view]; + [view addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; +} + +- (void)addContentView:(UIView *)contentView frame:(CGRect)frame { + if (self.contentView == nil) { + self.contentView = [self createContentView]; + } + [self.contentViews addObject:contentView]; + [self.contentView addContentView:contentView frame:frame]; + __weak typeof(self) weakSelf = self; + self.contentView.didSelectSegmentAtIndex = ^(SJSegmentTab *segmentTab, NSInteger index, BOOL animated) { + SJSegmentTab *newSegmentTab = [weakSelf.segmentView.segments objectAtIndex:index]; + if ([newSegmentTab isKindOfClass:[SJSegmentTab class]]) { + if (weakSelf.didSelectSegmentAtIndex) { + weakSelf.didSelectSegmentAtIndex(newSegmentTab, index, animated); + } + } + }; +} + +- (void)updateSubviewsFrame:(CGRect)frame { + self.contentViewHeightConstraint.constant = [self getContentHeight]; + [self.contentView layoutIfNeeded]; + [self.segmentView didChangeParentViewFrame:frame]; + [self.contentView updateContentControllersFrame:frame]; +} + +- (CGFloat)getContentHeight { + CGFloat contentHeight = self.superview.bounds.size.height + self.headerViewHeight; + contentHeight -= (self.topSpacing + self.bottomSpacing + self.headerViewOffsetHeight); + return contentHeight; +} + +- (void)addSegmentView:(NSArray *)controllers frame:(CGRect)frame { + if (controllers.count > 1) { + self.segmentView = [[SJSegmentView alloc] initWithFrame:CGRectZero]; + self.segmentView.backgroundColor = self.segmentViewBackgroundColor; + self.segmentView.controllers = controllers; + self.segmentView.selectedSegmentViewColor = self.selectedSegmentViewColor; + self.segmentView.selectedSegmentViewHeight = self.selectedSegmentViewHeight; + self.segmentView.titleColor = self.segmentTitleColor; + self.segmentView.segmentBackgroundColor = self.segmentBackgroundColor; + self.segmentView.font = self.segmentTitleFont; + self.segmentView.font = self.segmentTitleFont; + self.segmentView.segmentViewHeight = self.segmentViewHeight; + self.segmentView.bounces = NO; + self.segmentView.translatesAutoresizingMaskIntoConstraints = NO; + __weak typeof(self) weakSelf = self; + self.segmentView.didSelectSegmentAtIndex = ^(SJSegmentTab *segmentTab, NSInteger index, BOOL animated) { + [weakSelf.contentView movePageToIndex:index animated:animated]; + if (weakSelf.didSelectSegmentAtIndex) { + weakSelf.didSelectSegmentAtIndex(segmentTab, index, animated); + } + }; + [self.segmentView setSegmentsView:frame]; + [self addSubview:self.segmentView]; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[segmentView]-0-|" options:0 metrics:nil views:@{@"segmentView": self.segmentView}]; + [self addConstraints:horizontalConstraints]; + UIView *view = self.headerView == nil ? self : self.headerView; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[headerView]-0-[segmentView(segmentViewHeight)]" options:0 metrics:@{@"segmentViewHeight" : @(self.segmentViewHeight)} views:@{@"headerView": view, @"segmentView": self.segmentView}]; + [self addConstraints:verticalConstraints]; + } else { + self.segmentViewHeight = 0.0; + } +} + +- (void)addSegmentsForContentViews:(NSArray *)titles { + CGRect frame = CGRectMake(0, self.headerViewHeight, self.bounds.size.width, self.segmentViewHeight); + self.segmentView = [[SJSegmentView alloc] initWithFrame:frame]; + __weak typeof(self) weakSelf = self; + self.segmentView.didSelectSegmentAtIndex = ^(SJSegmentTab *segmentTab, NSInteger index, BOOL animated) { + [weakSelf.contentView movePageToIndex:index animated:animated]; + }; + [self addSubview:self.segmentView]; +} + +- (SJContentView *)createContentView { + SJContentView *contentView = [[SJContentView alloc] initWithFrame:CGRectZero]; + contentView.showsVerticalScrollIndicator = self.sjShowsVerticalScrollIndicator; + contentView.showsHorizontalScrollIndicator = self.sjShowsHorizontalScrollIndicator; + [contentView setScrollEnabled:!self.sjDisableScrollOnContentView]; + contentView.translatesAutoresizingMaskIntoConstraints = NO; + contentView.bounces = self.segmentBounces; + [self.scrollContentView addSubview:contentView]; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentView]-0-|" options:0 metrics:nil views:@{@"contentView": contentView}]; + [self.scrollContentView addConstraints:horizontalConstraints]; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[headerView]-segmentViewHeight-[contentView]-0-|" options:0 metrics:@{@"segmentViewHeight": @(self.segmentViewHeight)} views:@{@"headerView": self.headerView, @"contentView": contentView}]; + [self.scrollContentView addConstraints:verticalConstraints]; + return contentView; +} + +- (void)handleScrollUp:(UIScrollView *)scrollView change:(CGFloat)change oldPosition:(CGPoint)oldPosition { + if (self.headerViewHeight != 0.0 && self.contentOffset.y != 0.0) { + if (scrollView.contentOffset.y < 0.0) { + if (self.contentOffset.y >= 0.0) { + CGFloat yPos = self.contentOffset.y - change; + yPos = (yPos < 0) ? 0 : yPos; + CGPoint updatedPos = CGPointMake(self.contentOffset.x, yPos); + [self setContentOffset:self point:updatedPos]; + [self setContentOffset:scrollView point:oldPosition]; + } + } + } +} + +- (void)handleScrollDown:(UIScrollView *)scrollView change:(CGFloat)change oldPosition:(CGPoint)oldPosition { + CGFloat offset = (self.headerViewHeight - self.headerViewOffsetHeight); + if (self.contentOffset.y < offset) { + if (scrollView.contentOffset.y >= 0.0) { + CGFloat yPos = self.contentOffset.y - change; + yPos = (yPos > offset) ? offset : yPos; + CGPoint updatedPos = CGPointMake(self.contentOffset.x, yPos); + [self setContentOffset:self point:updatedPos]; + [self setContentOffset:scrollView point:oldPosition]; + } + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (!self.observing) { + return; + } + if (![object isKindOfClass:[UIScrollView class]]) { + return; + } + UIScrollView *scrollView = object; + if (scrollView == self) { + return; + } + NSValue *newValue = [change valueForKey:NSKeyValueChangeNewKey]; + NSValue *oldValue = [change valueForKey:NSKeyValueChangeOldKey]; + if ([newValue isKindOfClass:[NSValue class]] && [oldValue isKindOfClass:[NSValue class]]) { + CGPoint new = [newValue CGPointValue]; + CGPoint old = [oldValue CGPointValue]; + CGFloat diff = old.y - new.y; + if (diff > 0.0) { + [self handleScrollUp:scrollView change:diff oldPosition:old]; + } else { + [self handleScrollDown:scrollView change:diff oldPosition:old]; + } + } +} + +- (void)setContentOffset:(UIScrollView *)scrollView point:(CGPoint) point { + self.observing = NO; + scrollView.contentOffset = point; + self.observing = YES; +} + +@end + diff --git a/SJSegmentedScrollView-ObjC/SJSegmentedViewController.h b/SJSegmentedScrollView-ObjC/SJSegmentedViewController.h new file mode 100644 index 0000000..b1148bb --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJSegmentedViewController.h @@ -0,0 +1,50 @@ +// +// SJSegmentedViewController.h +// Hike +// +// Created by Pavan Goyal on 01/05/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import +#import "SJSegmentTab.h" +#import "SJSegmentedScrollView.h" + +@protocol SJSegmentedViewControllerDelegate + +@optional +- (void)didMoveToPage:(UIViewController *)controller segment:(SJSegmentTab *)segment index:(NSInteger)index; + +@end + +@protocol SJSegmentedViewControllerViewSource + +@optional +- (UIView *)viewForSegmentControllerToObserveContentOffsetChange; +@end + +@interface SJSegmentedViewController : UIViewController + +@property (nonatomic, assign) CGFloat headerViewHeight; +@property (nonatomic, assign) CGFloat segmentViewHeight; +@property (nonatomic, assign) CGFloat headerViewOffsetHeight; +@property (nonatomic, strong) SJSegmentedScrollView *segmentedScrollView; +@property (nonatomic, strong) UIColor *selectedSegmentViewColor; +@property (nonatomic, assign) CGFloat selectedSegmentViewHeight; +@property (nonatomic, strong) UIColor *segmentTitleColor; +@property (nonatomic, strong) UIColor *segmentBackgroundColor; +@property (nonatomic, strong) UIFont *segmentTitleFont; +@property (nonatomic, assign) BOOL segmentBounces; +@property (nonatomic, strong) UIViewController *headerViewController; +@property (nonatomic, strong) NSArray *segmentControllers; +@property (nonatomic, strong) NSArray *segments; +@property (nonatomic, strong) UIColor *segmentedScrollViewColor; +@property (nonatomic, assign) BOOL showsVerticalScrollIndicator; +@property (nonatomic, assign) BOOL showsHorizontalScrollIndicator; +@property (nonatomic, assign) BOOL disableScrollOnContentView; +@property (nonatomic, strong) NSLayoutConstraint *segmentScrollViewTopConstraint; +@property (nonatomic, weak) id delegate; + +- (instancetype)initWithHeaderViewController:(UIViewController *)headerViewController segmentControllers:(NSArray *)segmentControllers; + +@end diff --git a/SJSegmentedScrollView-ObjC/SJSegmentedViewController.m b/SJSegmentedScrollView-ObjC/SJSegmentedViewController.m new file mode 100644 index 0000000..188590f --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJSegmentedViewController.m @@ -0,0 +1,239 @@ +// +// SJSegmentedViewController.m +// Hike +// +// Created by Pavan Goyal on 01/05/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import "SJSegmentedViewController.h" +#import "SJUtil.h" + +@interface SJSegmentedViewController () + +@end + +@implementation SJSegmentedViewController + +- (instancetype)initWithHeaderViewController:(UIViewController *)headerViewController segmentControllers:(NSArray *)segmentControllers { + self = [super init]; + if (self) { + _headerViewHeight = 0.0; + _segmentViewHeight = 40.0; + _headerViewOffsetHeight = 0.0; + _selectedSegmentViewColor = [UIColor lightGrayColor]; + _selectedSegmentViewHeight = 5.0; + _segmentTitleColor = [UIColor blackColor]; + _segmentBackgroundColor = [UIColor whiteColor]; + _segmentTitleFont = [UIFont systemFontOfSize:14]; + _segmentBounces = YES; + _headerViewController = headerViewController; + _segmentControllers = segmentControllers; + _segmentedScrollViewColor = [UIColor whiteColor]; + _showsVerticalScrollIndicator = YES; + _showsHorizontalScrollIndicator = YES; + _disableScrollOnContentView = NO; + _segmentedScrollView = [[SJSegmentedScrollView alloc] initWithFrame:CGRectZero]; + [self setDefaultValuesToSegmentedScrollView]; + } + return self; +} + +- (void)setHeaderViewHeight:(CGFloat)headerViewHeight { + _headerViewHeight = headerViewHeight; + self.segmentedScrollView.headerViewHeight = headerViewHeight; +} + +- (void)setSegmentViewHeight:(CGFloat)segmentViewHeight { + _segmentViewHeight = segmentViewHeight; + self.segmentedScrollView.segmentViewHeight = segmentViewHeight; +} + +- (void)setHeaderViewOffsetHeight:(CGFloat)headerViewOffsetHeight { + _headerViewOffsetHeight = headerViewOffsetHeight; + self.segmentedScrollView.headerViewOffsetHeight = headerViewOffsetHeight; +} + +- (void)setSelectedSegmentViewColor:(UIColor *)selectedSegmentViewColor { + _selectedSegmentViewColor = selectedSegmentViewColor; + self.segmentedScrollView.selectedSegmentViewColor = selectedSegmentViewColor; +} + +- (void)setSelectedSegmentViewHeight:(CGFloat)selectedSegmentViewHeight { + _selectedSegmentViewHeight = selectedSegmentViewHeight; + self.segmentedScrollView.selectedSegmentViewHeight = selectedSegmentViewHeight; +} + +- (void)setSegmentTitleColor:(UIColor *)segmentTitleColor { + _segmentTitleColor = segmentTitleColor; + self.segmentedScrollView.segmentTitleColor = segmentTitleColor; +} + +- (void)setSegmentBackgroundColor:(UIColor *)segmentBackgroundColor { + _segmentBackgroundColor = segmentBackgroundColor; + self.segmentedScrollView.segmentBackgroundColor = segmentBackgroundColor; +} + +- (void)setSegmentTitleFont:(UIFont *)segmentTitleFont { + _segmentTitleFont = segmentTitleFont; + self.segmentedScrollView.segmentTitleFont = segmentTitleFont; +} + +- (void)setSegmentBounces:(BOOL)segmentBounces { + _segmentBounces = segmentBounces; + self.segmentedScrollView.segmentBounces = segmentBounces; +} + +- (void)setHeaderViewController:(UIViewController *)headerViewController { + _headerViewController = headerViewController; + [self setDefaultValuesToSegmentedScrollView]; +} + +- (void)setSegmentControllers:(NSArray *)segmentControllers { + _segmentControllers = segmentControllers; + [self setDefaultValuesToSegmentedScrollView]; +} + +- (NSArray *)segments { + NSArray *tabs = self.segmentedScrollView.segmentView.segments; + if ([tabs isKindOfClass:[NSArray class]]) { + return tabs; + } else { + return [[NSArray alloc] init]; + } +} + +- (void)setSegmentedScrollViewColor:(UIColor *)segmentedScrollViewColor { + _segmentedScrollViewColor = segmentedScrollViewColor; + self.segmentedScrollView.backgroundColor = segmentedScrollViewColor; +} + +- (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator { + _showsVerticalScrollIndicator = showsVerticalScrollIndicator; + self.segmentedScrollView.sjShowsVerticalScrollIndicator = showsVerticalScrollIndicator; +} + +- (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator { + _showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; + self.segmentedScrollView.sjShowsHorizontalScrollIndicator = showsHorizontalScrollIndicator; +} + +- (void)setDisableScrollOnContentView:(BOOL)disableScrollOnContentView { + _disableScrollOnContentView = disableScrollOnContentView; + self.segmentedScrollView.sjDisableScrollOnContentView = disableScrollOnContentView; +} + +- (void)loadView { + [super loadView]; + [self addSegmentedScrollView]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + [self loadControllers]; + if (@available(iOS 11, *)) { + self.segmentedScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + self.automaticallyAdjustsScrollViewInsets = NO; + } +} + +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + CGFloat topSpacing = [SJUtil getTopSpacing:self]; + self.segmentedScrollView.topSpacing = topSpacing; + self.segmentedScrollView.bottomSpacing = [SJUtil getBottomSpacing:self]; + self.segmentScrollViewTopConstraint.constant = topSpacing; + [self.segmentedScrollView updateSubviewsFrame:self.view.bounds]; +} + +- (void)setSelectedSegmentAt:(NSInteger)index animated:(BOOL)animated { + if (index >= 0 && index < self.segmentControllers.count) { + if (self.segmentedScrollView.segmentView.didSelectSegmentAtIndex) { + self.segmentedScrollView.segmentView.didSelectSegmentAtIndex(self.segments[index], index, animated); + } + [[NSNotificationCenter defaultCenter] postNotificationName:@"DidChangeSegmentIndex" object:@(index)]; + } +} + +- (void)setDefaultValuesToSegmentedScrollView { + self.segmentedScrollView.selectedSegmentViewColor = self.selectedSegmentViewColor; + self.segmentedScrollView.selectedSegmentViewHeight = self.selectedSegmentViewHeight; + self.segmentedScrollView.segmentTitleColor = self.segmentTitleColor; + self.segmentedScrollView.segmentBackgroundColor = self.segmentBackgroundColor; + self.segmentedScrollView.segmentTitleFont = self.segmentTitleFont; + self.segmentedScrollView.segmentBounces = self.segmentBounces; + self.segmentedScrollView.headerViewHeight = self.headerViewHeight; + self.segmentedScrollView.headerViewOffsetHeight = self.headerViewOffsetHeight; + self.segmentedScrollView.segmentViewHeight = self.segmentViewHeight; + self.segmentedScrollView.backgroundColor = self.segmentedScrollViewColor; + self.segmentedScrollView.sjDisableScrollOnContentView = self.disableScrollOnContentView; +} + +- (void)addSegmentedScrollView { + CGFloat topSpacing = [SJUtil getTopSpacing:self]; + self.segmentedScrollView.topSpacing = topSpacing; + CGFloat bottomSpacing = [SJUtil getBottomSpacing:self]; + self.segmentedScrollView.bottomSpacing = bottomSpacing; + [self.view addSubview:self.segmentedScrollView]; + NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[scrollView]-0-|" options:0 metrics:nil views:@{@"scrollView": self.segmentedScrollView}]; + [self.view addConstraints:horizontalConstraints]; + NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollView]-bp-|" options:0 metrics:@{@"tp": @(topSpacing), @"bp": @(bottomSpacing)} views:@{@"scrollView": self.segmentedScrollView}]; + [self.view addConstraints:verticalConstraints]; + self.segmentScrollViewTopConstraint = [NSLayoutConstraint constraintWithItem:self.segmentedScrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:topSpacing]; + [self.view addConstraint:self.segmentScrollViewTopConstraint]; + [self.segmentedScrollView setContentView]; + __weak typeof(self) weakSelf = self; + self.segmentedScrollView.didSelectSegmentAtIndex = ^(SJSegmentTab *segmentTab, NSInteger index, BOOL animated) { + UIViewController *selectedController = weakSelf.segmentControllers[index]; + if ([weakSelf.delegate respondsToSelector:@selector(didMoveToPage:segment:index:)]) { + [weakSelf.delegate didMoveToPage:selectedController segment:segmentTab index:index]; + } + }; +} + +- (void)addHeaderViewController:(UIViewController *)headerViewController { + [self addChildViewController:headerViewController]; + [self.segmentedScrollView addHeaderView:headerViewController.view]; + [headerViewController didMoveToParentViewController:self]; +} + +- (void)addContentControllers:(NSArray *)contentControllers { + [self.segmentedScrollView addSegmentView:contentControllers frame:self.view.bounds]; + NSInteger index = 0; + for (UIViewController *controller in contentControllers) { + [self addChildViewController:controller]; + [self.segmentedScrollView addContentView:controller.view frame:self.view.bounds]; + [controller didMoveToParentViewController:self]; + UIView *observeView = controller.view; + if ([controller isKindOfClass:[UICollectionViewController class]]) { + observeView = [(UICollectionViewController *)controller collectionView]; + } + id delegate = (id)controller; + if ([delegate respondsToSelector:@selector(viewForSegmentControllerToObserveContentOffsetChange)]) { + observeView = [delegate viewForSegmentControllerToObserveContentOffsetChange]; + } + [self.segmentedScrollView addObserverForView:observeView]; + index += 1; + } + self.segmentedScrollView.segmentView.contentView = self.segmentedScrollView.contentView; +} + +- (void)loadControllers { + if (self.headerViewController == nil) { + self.headerViewController = [[UIViewController alloc] init]; + self.headerViewHeight = 0.0; + } + [self addHeaderViewController:self.headerViewController]; + [self addContentControllers:self.segmentControllers]; + SJSegmentTab *segment = nil; + if (self.segments.count > 0) { + segment = self.segments[0]; + } + if ([self.delegate respondsToSelector:@selector(didMoveToPage:segment:index:)]) { + [self.delegate didMoveToPage:self.segmentControllers[0] segment:segment index:0]; + } +} + +@end diff --git a/SJSegmentedScrollView-ObjC/SJUtil.h b/SJSegmentedScrollView-ObjC/SJUtil.h new file mode 100644 index 0000000..047fff6 --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJUtil.h @@ -0,0 +1,18 @@ +// +// SJUtil.h +// Hike +// +// Created by Pavan Goyal on 30/04/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import +#import + +@interface SJUtil : NSObject + ++ (CGFloat)getTopSpacing:(UIViewController *)viewController; ++ (CGFloat)getBottomSpacing:(UIViewController *)viewController; ++ (CGFloat)widthForString:(NSString *)string withConstrainedWidth:(CGFloat)width font:(UIFont *)font; + +@end diff --git a/SJSegmentedScrollView-ObjC/SJUtil.m b/SJSegmentedScrollView-ObjC/SJUtil.m new file mode 100644 index 0000000..2a343fb --- /dev/null +++ b/SJSegmentedScrollView-ObjC/SJUtil.m @@ -0,0 +1,48 @@ +// +// SJUtil.m +// Hike +// +// Created by Pavan Goyal on 30/04/18. +// Copyright © 2018 Hike Pvt. Ltd. All rights reserved. +// + +#import "SJUtil.h" + +@implementation SJUtil + ++ (CGFloat)getTopSpacing:(UIViewController *)viewController { + if (viewController.splitViewController) { + CGFloat topSpacing = 0; + UINavigationController *navigationController = viewController.navigationController; + if (navigationController.childViewControllers.lastObject == viewController) { + if (!navigationController.isNavigationBarHidden) { + topSpacing = [[UIApplication sharedApplication] statusBarFrame].size.height; + if (!navigationController.navigationBar.isOpaque) { + topSpacing += navigationController.navigationBar.bounds.size.height; + } + } + } + return topSpacing; + } else { + return 0; + } +} + ++ (CGFloat)getBottomSpacing:(UIViewController *)viewController { + CGFloat bottomSpacing = 0.0; + UITabBarController *tabBarController = viewController.tabBarController; + if (tabBarController) { + if ((!tabBarController.tabBar.isHidden) && (!tabBarController.tabBar.isOpaque)) { + bottomSpacing += tabBarController.tabBar.bounds.size.height; + } + } + return bottomSpacing; +} + ++ (CGFloat)widthForString:(NSString *)string withConstrainedWidth:(CGFloat)width font:(UIFont *)font { + CGSize constraintRect = CGSizeMake(width, CGFLOAT_MAX); + CGRect boundingBox = [string boundingRectWithSize:constraintRect options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : font} context:nil]; + return boundingBox.size.width; +} + +@end