From 0b31b46f4b4ad4b278861311ec21a85a107d73f0 Mon Sep 17 00:00:00 2001 From: Andrew Podkovyrin Date: Wed, 23 Jul 2014 23:56:33 +0400 Subject: [PATCH 1/2] Added support for custom segment indicator view --- NYSegmentedControl/NYSegmentedControl.h | 10 ++ NYSegmentedControl/NYSegmentedControl.m | 96 +++++++++++++------ .../project.pbxproj | 6 ++ .../DemoViewController.m | 16 ++++ .../NYSegmentIndicatorSingleLine.h | 12 +++ .../NYSegmentIndicatorSingleLine.m | 38 ++++++++ 6 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 NYSegmentedControlDemo/NYSegmentedControlDemo/NYSegmentIndicatorSingleLine.h create mode 100644 NYSegmentedControlDemo/NYSegmentedControlDemo/NYSegmentIndicatorSingleLine.m diff --git a/NYSegmentedControl/NYSegmentedControl.h b/NYSegmentedControl/NYSegmentedControl.h index 2fb7131..361b8b6 100644 --- a/NYSegmentedControl/NYSegmentedControl.h +++ b/NYSegmentedControl/NYSegmentedControl.h @@ -139,6 +139,16 @@ */ - (instancetype)initWithItems:(NSArray *)items; +/** + Initializes and returns a control with segments having the specified titles with custom selected segment indicator. + + @param items An array of NSString objects representing the titles of segments in the control. + @param segmentIndicatorView A custom `UIView` to use for selected segment indicator + + @return An initialized NYSegmentedControl object, or nil if it could not be created. + */ +- (instancetype)initWithItems:(NSArray *)items segmentIndicatorView:(UIView *)segmentIndicatorView; + /** Inserts a segment at the specified index. diff --git a/NYSegmentedControl/NYSegmentedControl.m b/NYSegmentedControl/NYSegmentedControl.m index 0d57c41..a484d90 100644 --- a/NYSegmentedControl/NYSegmentedControl.m +++ b/NYSegmentedControl/NYSegmentedControl.m @@ -13,8 +13,8 @@ @interface NYSegmentedControl () -@property NSArray *segments; -@property NYSegmentIndicator *selectedSegmentIndicator; +@property (copy, nonatomic) NSArray *segments; +@property (strong, nonatomic) NYSegmentIndicator *selectedSegmentIndicator; - (void)moveSelectedSegmentIndicatorToSegmentAtIndex:(NSUInteger)index animated:(BOOL)animated; - (CGRect)indicatorFrameForSegment:(NYSegment *)segment; @@ -28,29 +28,29 @@ + (Class)layerClass { } - (instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super initWithCoder:aDecoder]; - - if (self) { - [self initialize]; - } + self = [self initWithItems:nil segmentIndicatorView:nil]; return self; } - (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - - if (self) { - [self initialize]; - } + self = [self initWithItems:nil segmentIndicatorView:nil]; return self; } - (instancetype)initWithItems:(NSArray *)items { - self = [self initWithFrame:CGRectZero]; + self = [self initWithItems:items segmentIndicatorView:nil]; + + return self; +} + +- (instancetype)initWithItems:(NSArray *)items segmentIndicatorView:(UIView *)segmentIndicatorView { + self = [super initWithFrame:CGRectZero]; if (self) { + [self initializeWithSegmentIndicatorView:segmentIndicatorView]; + NSMutableArray *mutableSegments = [NSMutableArray array]; for (NSString *segmentTitle in items) { @@ -66,7 +66,7 @@ - (instancetype)initWithItems:(NSArray *)items { return self; } -- (void)initialize { +- (void)initializeWithSegmentIndicatorView:(UIView *)segmentIndicatorView { // We need to directly access the ivars for UIAppearance properties in the initializer _titleFont = [UIFont systemFontOfSize:13.0f]; _titleTextColor = [UIColor blackColor]; @@ -88,7 +88,11 @@ - (void)initialize { self.opaque = NO; self.segments = [NSArray array]; - self.selectedSegmentIndicator = [[NYSegmentIndicator alloc] initWithFrame:CGRectZero]; + if (!segmentIndicatorView) { + segmentIndicatorView = [[NYSegmentIndicator alloc] initWithFrame:CGRectZero]; + } + + self.selectedSegmentIndicator = (NYSegmentIndicator *)segmentIndicatorView; self.drawsSegmentIndicatorGradientBackground = YES; [self addSubview:self.selectedSegmentIndicator]; } @@ -329,7 +333,9 @@ - (CGFloat)borderWidth { - (void)setCornerRadius:(CGFloat)cornerRadius { self.layer.cornerRadius = cornerRadius; - self.selectedSegmentIndicator.cornerRadius = cornerRadius * ((self.frame.size.height - self.segmentIndicatorInset * 2) / self.frame.size.height); + if ([self.selectedSegmentIndicator respondsToSelector:@selector(setCornerRadius:)]) { + self.selectedSegmentIndicator.cornerRadius = cornerRadius * ((self.frame.size.height - self.segmentIndicatorInset * 2) / self.frame.size.height); + } [self setNeedsDisplay]; } @@ -338,16 +344,25 @@ - (CGFloat)cornerRadius { } - (void)setDrawsSegmentIndicatorGradientBackground:(BOOL)drawsSegmentIndicatorGradientBackground { - self.selectedSegmentIndicator.drawsGradientBackground = drawsSegmentIndicatorGradientBackground; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(setDrawsGradientBackground:)]) { + self.selectedSegmentIndicator.drawsGradientBackground = drawsSegmentIndicatorGradientBackground; + } } - (BOOL)drawsSegmentIndicatorGradientBackground { - return self.selectedSegmentIndicator.drawsGradientBackground; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(drawsGradientBackground)]) { + return self.selectedSegmentIndicator.drawsGradientBackground; + } + + return NO; } - (void)setFrame:(CGRect)frame { [super setFrame:frame]; - self.selectedSegmentIndicator.cornerRadius = self.cornerRadius * ((self.frame.size.height - self.segmentIndicatorInset * 2) / self.frame.size.height); + + if ([self.selectedSegmentIndicator respondsToSelector:@selector(setCornerRadius:)]) { + self.selectedSegmentIndicator.cornerRadius = self.cornerRadius * ((self.frame.size.height - self.segmentIndicatorInset * 2) / self.frame.size.height); + } } - (void)setSegmentIndicatorBackgroundColor:(UIColor *)segmentIndicatorBackgroundColor { @@ -360,40 +375,65 @@ - (UIColor *)segmentIndicatorBackgroundColor { - (void)setSegmentIndicatorInset:(CGFloat)segmentIndicatorInset { _segmentIndicatorInset = segmentIndicatorInset; - self.selectedSegmentIndicator.cornerRadius = self.cornerRadius * ((self.frame.size.height - self.segmentIndicatorInset * 2) / self.frame.size.height); + if ([self.selectedSegmentIndicator respondsToSelector:@selector(setCornerRadius:)]) { + self.selectedSegmentIndicator.cornerRadius = self.cornerRadius * ((self.frame.size.height - self.segmentIndicatorInset * 2) / self.frame.size.height); + } [self setNeedsLayout]; } - (void)setSegmentIndicatorGradientTopColor:(UIColor *)segmentIndicatorGradientTopColor { - self.selectedSegmentIndicator.gradientTopColor = segmentIndicatorGradientTopColor; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(setGradientTopColor:)]) { + self.selectedSegmentIndicator.gradientTopColor = segmentIndicatorGradientTopColor; + } } - (UIColor *)segmentIndicatorGradientTopColor { - return self.selectedSegmentIndicator.gradientTopColor; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(gradientTopColor)]) { + return self.selectedSegmentIndicator.gradientTopColor; + } + + return nil; } - (void)setSegmentIndicatorGradientBottomColor:(UIColor *)segmentIndicatorGradientBottomColor { - self.selectedSegmentIndicator.gradientBottomColor = segmentIndicatorGradientBottomColor; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(setGradientBottomColor:)]) { + self.selectedSegmentIndicator.gradientBottomColor = segmentIndicatorGradientBottomColor; + } } - (UIColor *)segmentIndicatorGradientBottomColor { - return self.selectedSegmentIndicator.gradientBottomColor; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(gradientBottomColor)]) { + return self.selectedSegmentIndicator.gradientBottomColor; + } + + return nil; } - (void)setSegmentIndicatorBorderColor:(UIColor *)segmentIndicatorBorderColor { - self.selectedSegmentIndicator.borderColor = segmentIndicatorBorderColor; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(setBorderColor:)]) { + self.selectedSegmentIndicator.borderColor = segmentIndicatorBorderColor; + } } - (UIColor *)segmentIndicatorBorderColor { - return self.selectedSegmentIndicator.borderColor; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(borderColor)]) { + return self.selectedSegmentIndicator.borderColor; + } + + return nil; } - (void)setSegmentIndicatorBorderWidth:(CGFloat)segmentIndicatorBorderWidth { - self.selectedSegmentIndicator.borderWidth = segmentIndicatorBorderWidth; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(setBorderWidth:)]) { + self.selectedSegmentIndicator.borderWidth = segmentIndicatorBorderWidth; + } } - (CGFloat)segmentIndicatorBorderWidth { - return self.selectedSegmentIndicator.borderWidth; + if ([self.selectedSegmentIndicator respondsToSelector:@selector(borderWidth)]) { + return self.selectedSegmentIndicator.borderWidth; + } + return 0.f; } - (void)setTitleFont:(UIFont *)titleFont { diff --git a/NYSegmentedControlDemo/NYSegmentedControlDemo.xcodeproj/project.pbxproj b/NYSegmentedControlDemo/NYSegmentedControlDemo.xcodeproj/project.pbxproj index 8bf5440..dd53bca 100644 --- a/NYSegmentedControlDemo/NYSegmentedControlDemo.xcodeproj/project.pbxproj +++ b/NYSegmentedControlDemo/NYSegmentedControlDemo.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 0E3CBE3C18DECF82001EC237 /* NYSegmentedControlDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CBE3B18DECF82001EC237 /* NYSegmentedControlDemoTests.m */; }; 0E3CBE4918DECFAC001EC237 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CBE4618DECFAC001EC237 /* AppDelegate.m */; }; 0E3CBE4A18DECFAC001EC237 /* DemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CBE4818DECFAC001EC237 /* DemoViewController.m */; }; + 2A4F2A58197DB1D000ADEB96 /* NYSegmentIndicatorSingleLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A4F2A57197DB1D000ADEB96 /* NYSegmentIndicatorSingleLine.m */; }; C53E59451932DA7100087BCD /* NYSegment.m in Sources */ = {isa = PBXBuildFile; fileRef = C53E59401932DA7100087BCD /* NYSegment.m */; }; C53E59461932DA7100087BCD /* NYSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = C53E59421932DA7100087BCD /* NYSegmentedControl.m */; }; C53E59471932DA7100087BCD /* NYSegmentIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = C53E59441932DA7100087BCD /* NYSegmentIndicator.m */; }; @@ -53,6 +54,8 @@ 0E3CBE4618DECFAC001EC237 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 0E3CBE4718DECFAC001EC237 /* DemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoViewController.h; sourceTree = ""; }; 0E3CBE4818DECFAC001EC237 /* DemoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoViewController.m; sourceTree = ""; }; + 2A4F2A56197DB1D000ADEB96 /* NYSegmentIndicatorSingleLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NYSegmentIndicatorSingleLine.h; sourceTree = ""; }; + 2A4F2A57197DB1D000ADEB96 /* NYSegmentIndicatorSingleLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NYSegmentIndicatorSingleLine.m; sourceTree = ""; }; C53E593F1932DA7100087BCD /* NYSegment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NYSegment.h; sourceTree = ""; }; C53E59401932DA7100087BCD /* NYSegment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NYSegment.m; sourceTree = ""; }; C53E59411932DA7100087BCD /* NYSegmentedControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NYSegmentedControl.h; sourceTree = ""; }; @@ -124,6 +127,8 @@ 0E3CBE4818DECFAC001EC237 /* DemoViewController.m */, 0E3CBE2818DECF82001EC237 /* Images.xcassets */, 0E3CBE1718DECF82001EC237 /* Supporting Files */, + 2A4F2A56197DB1D000ADEB96 /* NYSegmentIndicatorSingleLine.h */, + 2A4F2A57197DB1D000ADEB96 /* NYSegmentIndicatorSingleLine.m */, ); path = NYSegmentedControlDemo; sourceTree = ""; @@ -269,6 +274,7 @@ buildActionMask = 2147483647; files = ( C53E59451932DA7100087BCD /* NYSegment.m in Sources */, + 2A4F2A58197DB1D000ADEB96 /* NYSegmentIndicatorSingleLine.m in Sources */, 0E3CBE4A18DECFAC001EC237 /* DemoViewController.m in Sources */, C53E59471932DA7100087BCD /* NYSegmentIndicator.m in Sources */, 0E3CBE4918DECFAC001EC237 /* AppDelegate.m in Sources */, diff --git a/NYSegmentedControlDemo/NYSegmentedControlDemo/DemoViewController.m b/NYSegmentedControlDemo/NYSegmentedControlDemo/DemoViewController.m index cb23ac2..a7168fc 100644 --- a/NYSegmentedControlDemo/NYSegmentedControlDemo/DemoViewController.m +++ b/NYSegmentedControlDemo/NYSegmentedControlDemo/DemoViewController.m @@ -8,6 +8,7 @@ #import "DemoViewController.h" #import "NYSegmentedControl.h" +#import "NYSegmentIndicatorSingleLine.h" @interface DemoViewController () @@ -85,6 +86,21 @@ - (void)viewDidLoad { foursquareSegmentedControlBackgroundView.center = foursquareSegmentedControl.center; [lightControlExampleView addSubview:foursquareSegmentedControl]; + NYSegmentIndicatorSingleLine *segmentIndicatorLineView = [[NYSegmentIndicatorSingleLine alloc] initWithFrame:CGRectZero]; + NYSegmentedControl *plainSegmentedControl = [[NYSegmentedControl alloc] initWithItems:@[@"Left", @"Right"] + segmentIndicatorView:segmentIndicatorLineView]; + plainSegmentedControl.titleTextColor = [UIColor grayColor]; + plainSegmentedControl.selectedTitleTextColor = [UIColor redColor]; + plainSegmentedControl.selectedTitleFont = [UIFont systemFontOfSize:13.0f]; + plainSegmentedControl.backgroundColor = lightControlExampleView.backgroundColor; + plainSegmentedControl.borderWidth = 0.0f; + plainSegmentedControl.segmentIndicatorBorderWidth = 0.0f; + plainSegmentedControl.segmentIndicatorInset = 1.0f; + plainSegmentedControl.cornerRadius = 0.f; + [plainSegmentedControl sizeToFit]; + plainSegmentedControl.center = CGPointMake(lightControlExampleView.center.x, lightControlExampleView.center.y + 100.0f); + [lightControlExampleView addSubview:plainSegmentedControl]; + UIView *darkControlExampleView = [[UIView alloc] initWithFrame:self.view.bounds]; darkControlExampleView.backgroundColor = [UIColor colorWithWhite:0.2f alpha:1.0f]; darkControlExampleView.hidden = YES; diff --git a/NYSegmentedControlDemo/NYSegmentedControlDemo/NYSegmentIndicatorSingleLine.h b/NYSegmentedControlDemo/NYSegmentedControlDemo/NYSegmentIndicatorSingleLine.h new file mode 100644 index 0000000..89c0c35 --- /dev/null +++ b/NYSegmentedControlDemo/NYSegmentedControlDemo/NYSegmentIndicatorSingleLine.h @@ -0,0 +1,12 @@ +// +// NYSegmentIndicatorSingleLine.h +// +// Created by Andrew Podkovyrin on 22/07/14. +// Copyright (c) 2014 Neal Young. All rights reserved. +// + +#import + +@interface NYSegmentIndicatorSingleLine : UIView + +@end diff --git a/NYSegmentedControlDemo/NYSegmentedControlDemo/NYSegmentIndicatorSingleLine.m b/NYSegmentedControlDemo/NYSegmentedControlDemo/NYSegmentIndicatorSingleLine.m new file mode 100644 index 0000000..67e7e34 --- /dev/null +++ b/NYSegmentedControlDemo/NYSegmentedControlDemo/NYSegmentIndicatorSingleLine.m @@ -0,0 +1,38 @@ +// +// NYSegmentIndicatorSingleLine.m +// +// Created by Andrew Podkovyrin on 22/07/14. +// Copyright (c) 2014 Neal Young. All rights reserved. +// + +#import "NYSegmentIndicatorSingleLine.h" + +@interface NYSegmentIndicatorSingleLine () + +@property (strong, readwrite, nonatomic) UIView *bottomLineView; + +@end + + +@implementation NYSegmentIndicatorSingleLine + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.opaque = NO; + self.userInteractionEnabled = NO; + + self.bottomLineView = [[UIView alloc] initWithFrame:CGRectZero]; + self.bottomLineView.backgroundColor = [UIColor redColor]; + [self addSubview:self.bottomLineView]; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + self.bottomLineView.frame = CGRectMake(0.f, CGRectGetHeight(self.bounds) - 1.f, CGRectGetWidth(self.bounds), 1.f); +} + +@end From 724794c6c50d259bc1c13e6e6bf793de12d6fd32 Mon Sep 17 00:00:00 2001 From: Andrew Podkovyrin Date: Wed, 23 Jul 2014 23:57:08 +0400 Subject: [PATCH 2/2] Added option for setting minimum segment width --- NYSegmentedControl/NYSegment.m | 4 +--- NYSegmentedControl/NYSegmentedControl.h | 5 +++++ NYSegmentedControl/NYSegmentedControl.m | 3 ++- .../NYSegmentedControlDemo/DemoViewController.m | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/NYSegmentedControl/NYSegment.m b/NYSegmentedControl/NYSegment.m index b4c32ff..c4fbb96 100644 --- a/NYSegmentedControl/NYSegment.m +++ b/NYSegmentedControl/NYSegment.m @@ -9,8 +9,6 @@ #import "NYSegment.h" -static CGFloat const kMinimumSegmentWidth = 68.0f; - @implementation NYSegment - (instancetype)initWithTitle:(NSString *)title { @@ -37,7 +35,7 @@ - (id)initWithFrame:(CGRect)frame { - (CGSize)sizeThatFits:(CGSize)size { CGSize sizeThatFits = [self.titleLabel sizeThatFits:size]; - return CGSizeMake(MAX(sizeThatFits.width * 1.4f, kMinimumSegmentWidth), sizeThatFits.height); + return CGSizeMake(sizeThatFits.width * 1.4f, sizeThatFits.height); } @end diff --git a/NYSegmentedControl/NYSegmentedControl.h b/NYSegmentedControl/NYSegmentedControl.h index 361b8b6..eb1b53b 100644 --- a/NYSegmentedControl/NYSegmentedControl.h +++ b/NYSegmentedControl/NYSegmentedControl.h @@ -130,6 +130,11 @@ */ @property (nonatomic) NSUInteger selectedSegmentIndex; +/** + The width of the selected segment indicator's border + */ +@property (nonatomic) CGFloat minimumSegmentWidth UI_APPEARANCE_SELECTOR; + /** Initializes and returns a control with segments having the specified titles. diff --git a/NYSegmentedControl/NYSegmentedControl.m b/NYSegmentedControl/NYSegmentedControl.m index a484d90..2c20f2b 100644 --- a/NYSegmentedControl/NYSegmentedControl.m +++ b/NYSegmentedControl/NYSegmentedControl.m @@ -77,6 +77,7 @@ - (void)initializeWithSegmentIndicatorView:(UIView *)segmentIndicatorView { _segmentIndicatorAnimationDuration = 0.15f; _gradientTopColor = [UIColor colorWithRed:0.21f green:0.21f blue:0.21f alpha:1.0f]; _gradientBottomColor = [UIColor colorWithRed:0.16f green:0.16f blue:0.16f alpha:1.0f]; + _minimumSegmentWidth = 68.f; self.layer.borderColor = [[UIColor lightGrayColor] CGColor]; self.layer.masksToBounds = YES; @@ -101,7 +102,7 @@ - (CGSize)sizeThatFits:(CGSize)size { CGFloat maxSegmentWidth = 0.0f; for (NYSegment *segment in self.segments) { - CGFloat segmentWidth = [segment sizeThatFits:size].width; + CGFloat segmentWidth = MAX([segment sizeThatFits:size].width, self.minimumSegmentWidth); if (segmentWidth > maxSegmentWidth) { maxSegmentWidth = segmentWidth; } diff --git a/NYSegmentedControlDemo/NYSegmentedControlDemo/DemoViewController.m b/NYSegmentedControlDemo/NYSegmentedControlDemo/DemoViewController.m index a7168fc..70cdbdc 100644 --- a/NYSegmentedControlDemo/NYSegmentedControlDemo/DemoViewController.m +++ b/NYSegmentedControlDemo/NYSegmentedControlDemo/DemoViewController.m @@ -97,6 +97,7 @@ - (void)viewDidLoad { plainSegmentedControl.segmentIndicatorBorderWidth = 0.0f; plainSegmentedControl.segmentIndicatorInset = 1.0f; plainSegmentedControl.cornerRadius = 0.f; + plainSegmentedControl.minimumSegmentWidth = 50.f; [plainSegmentedControl sizeToFit]; plainSegmentedControl.center = CGPointMake(lightControlExampleView.center.x, lightControlExampleView.center.y + 100.0f); [lightControlExampleView addSubview:plainSegmentedControl];