|
@@ -0,0 +1,250 @@
|
|
|
+//
|
|
|
+// XCComCategorySelectView.m
|
|
|
+// notebook
|
|
|
+//
|
|
|
+// Created by 邢铖 on 2023/6/6.
|
|
|
+//
|
|
|
+
|
|
|
+#import "XCComCategorySelectView.h"
|
|
|
+#import <XCPublicModule/XCPublicModule.h>
|
|
|
+#import <Masonry/Masonry.h>
|
|
|
+#import "OCUtils.h"
|
|
|
+
|
|
|
+static char kXCBCCategorySelectTitleViewKVOKey[] = "kXCBCCategorySelectTitleViewKVOKey";
|
|
|
+
|
|
|
+@interface XCBCCategorySelectTitleView : UIButton
|
|
|
+
|
|
|
+@property (nonatomic, strong) NSString *categoryTitle;
|
|
|
+@property (nonatomic, copy) void(^touchBlock)(void);
|
|
|
+@property (nonatomic, assign) CGFloat selectPrecentage;
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation XCBCCategorySelectTitleView
|
|
|
+
|
|
|
+- (instancetype)init {
|
|
|
+ self = [super init];
|
|
|
+ [self addTarget:self
|
|
|
+ action:@selector(onTouchUpInside)
|
|
|
+ forControlEvents:UIControlEventTouchUpInside];
|
|
|
+ [self addObserver:self
|
|
|
+ forKeyPath:@"categoryTitle"
|
|
|
+ options:NSKeyValueObservingOptionNew
|
|
|
+ context:kXCBCCategorySelectTitleViewKVOKey];
|
|
|
+ [self addObserver:self
|
|
|
+ forKeyPath:@"selectPrecentage"
|
|
|
+ options:NSKeyValueObservingOptionNew
|
|
|
+ context:kXCBCCategorySelectTitleViewKVOKey];
|
|
|
+ return self;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)onTouchUpInside {
|
|
|
+ if (self.touchBlock) {
|
|
|
+ self.touchBlock();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateSelectable {
|
|
|
+ CGFloat maximumSize = 20;
|
|
|
+ CGFloat minimumSize = 15;
|
|
|
+ UIFontWeight maximumWeight = UIFontWeightHeavy;
|
|
|
+ UIFontWeight minimumWeight = UIFontWeightThin;
|
|
|
+#define clamp(t, min, max) ((min) + ((max) - (min)) * (t))
|
|
|
+ NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:self.categoryTitle];
|
|
|
+ NSRange allRange = NSMakeRange(0, attr.length);
|
|
|
+ [attr setAttributes:@{
|
|
|
+ NSForegroundColorAttributeName : [UIColor grayScaleWithDiff:[self clampValue:self.selectPrecentage
|
|
|
+ withBoundLeft:0.4
|
|
|
+ andBoundRight:1]
|
|
|
+ alpha:1]
|
|
|
+// NSFontAttributeName : [UIFont systemFontOfSize:clamp(self.selectPrecentage, minimumSize, maximumSize)]
|
|
|
+ } range:allRange];
|
|
|
+#undef clamp
|
|
|
+ [self setAttributedTitle:attr forState:UIControlStateNormal];
|
|
|
+ [self layoutSubviews];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
|
|
|
+ [super traitCollectionDidChange:previousTraitCollection];
|
|
|
+ [self updateSelectable];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
|
|
+ if (context == kXCBCCategorySelectTitleViewKVOKey) {
|
|
|
+ [self updateSelectable];
|
|
|
+ } else {
|
|
|
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)dealloc {
|
|
|
+ [self removeObserver:self
|
|
|
+ forKeyPath:@"categoryTitle"
|
|
|
+ context:kXCBCCategorySelectTitleViewKVOKey];
|
|
|
+ [self removeObserver:self
|
|
|
+ forKeyPath:@"selectPrecentage"
|
|
|
+ context:kXCBCCategorySelectTitleViewKVOKey];
|
|
|
+}
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+static char kXCBCCategorySelectViewKVOKey[] = "kXCBCCategorySelectViewKVOKey";
|
|
|
+
|
|
|
+@interface XCBCCategorySelectView ()
|
|
|
+
|
|
|
+@property (nonatomic, strong) UIStackView *stackView;
|
|
|
+@property (nonatomic, strong) UIView *indicatorView;
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation XCBCCategorySelectView
|
|
|
+
|
|
|
+- (instancetype)init {
|
|
|
+ self = [super init];
|
|
|
+ self.selectorHeight = 3;
|
|
|
+ [self configureSubviews];
|
|
|
+ return self;
|
|
|
+}
|
|
|
+
|
|
|
+- (UIStackView *)stackView {
|
|
|
+ if (!_stackView) {
|
|
|
+ _stackView = [UIStackView new];
|
|
|
+ _stackView.axis = UILayoutConstraintAxisHorizontal;
|
|
|
+ _stackView.distribution = UIStackViewDistributionEqualSpacing;
|
|
|
+ _stackView.spacing = 10;
|
|
|
+ }
|
|
|
+ return _stackView;
|
|
|
+}
|
|
|
+
|
|
|
+- (UIView *)indicatorView {
|
|
|
+ if (!_indicatorView) {
|
|
|
+ _indicatorView = [UIView new];
|
|
|
+ _indicatorView.backgroundColor = UIColor.systemOrangeColor;
|
|
|
+ _indicatorView.layer.masksToBounds = true;
|
|
|
+ _indicatorView.layer.cornerRadius = 1.5;
|
|
|
+ }
|
|
|
+ return _indicatorView;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)configureSubviews {
|
|
|
+ [self addSubview:self.stackView];
|
|
|
+ [self mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.height.equalTo(@42);
|
|
|
+ }];
|
|
|
+ [self.stackView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.equalTo(self.mas_left);
|
|
|
+ make.right.equalTo(self.mas_right);
|
|
|
+ make.top.equalTo(self.mas_top);
|
|
|
+ make.bottom.equalTo(self.mas_bottom).with.offset(-4);
|
|
|
+ }];
|
|
|
+ [self addSubview:self.indicatorView];
|
|
|
+ [self.indicatorView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.height.equalTo(@(self.selectorHeight));
|
|
|
+ make.width.equalTo(@0);
|
|
|
+ }];
|
|
|
+
|
|
|
+ [self addObserver:self
|
|
|
+ forKeyPath:@"categoryNames"
|
|
|
+ options:NSKeyValueObservingOptionNew
|
|
|
+ context:kXCBCCategorySelectViewKVOKey];
|
|
|
+ [self addObserver:self
|
|
|
+ forKeyPath:@"selectedIndex"
|
|
|
+ options:NSKeyValueObservingOptionNew
|
|
|
+ context:kXCBCCategorySelectViewKVOKey];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)reconfigureStackView {
|
|
|
+ for (UIView *view in self.stackView.arrangedSubviews) {
|
|
|
+ [self.stackView removeArrangedSubview:view];
|
|
|
+ [view removeFromSuperview];
|
|
|
+ }
|
|
|
+ NSInteger index = 0;
|
|
|
+ for (NSString *categoryName in self.categoryNames) {
|
|
|
+ XCBCCategorySelectTitleView *button = [XCBCCategorySelectTitleView new];
|
|
|
+ button.categoryTitle = categoryName;
|
|
|
+ XC_WEAKIFY_SELF;
|
|
|
+ button.touchBlock = ^{
|
|
|
+ XC_STRONG_SELF;
|
|
|
+ [self userDidSelectCategoryAtIndex:index];
|
|
|
+ };
|
|
|
+ [self.stackView addArrangedSubview:button];
|
|
|
+ index++;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateSelectedIndex {
|
|
|
+ _selectedIndex = MAX(MIN(self.selectedIndex, self.categoryNames.count - 1), 0);
|
|
|
+ if (self.stackView.arrangedSubviews.count < 2) {
|
|
|
+
|
|
|
+ } else {
|
|
|
+ for (XCBCCategorySelectTitleView *view in self.stackView.arrangedSubviews) {
|
|
|
+ view.selectPrecentage = 0;
|
|
|
+ }
|
|
|
+ NSUInteger leftObjectIndex = floor(self.selectedIndex);
|
|
|
+ NSUInteger rightObjectIndex = ceil(self.selectedIndex);
|
|
|
+ if (leftObjectIndex == rightObjectIndex) {
|
|
|
+ XCBCCategorySelectTitleView *targetView = self.stackView.arrangedSubviews[leftObjectIndex];
|
|
|
+ [self.indicatorView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.equalTo(self.mas_left).with.offset(targetView.frame.origin.x);
|
|
|
+ make.width.equalTo(@(targetView.frame.size.width));
|
|
|
+ make.height.equalTo(@(self.selectorHeight));
|
|
|
+ make.bottom.equalTo(self.mas_bottom).with.offset(-0.5);
|
|
|
+ }];
|
|
|
+ targetView.selectPrecentage = 1;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ CGFloat leftRightPercentageValue = self.selectedIndex - leftObjectIndex;
|
|
|
+ XCBCCategorySelectTitleView *leftView = self.stackView.arrangedSubviews[leftObjectIndex];
|
|
|
+ XCBCCategorySelectTitleView *rightView = self.stackView.arrangedSubviews[rightObjectIndex];
|
|
|
+ CGFloat selectorWidth = [self clampValue:leftRightPercentageValue
|
|
|
+ withBoundLeft:leftView.frame.size.width
|
|
|
+ andBoundRight:rightView.frame.size.width];
|
|
|
+ CGFloat selectorLeft = [self clampValue:leftRightPercentageValue
|
|
|
+ withBoundLeft:leftView.frame.origin.x
|
|
|
+ andBoundRight:rightView.frame.origin.x];
|
|
|
+ leftView.selectPrecentage = 1 - leftRightPercentageValue;
|
|
|
+ rightView.selectPrecentage = leftRightPercentageValue;
|
|
|
+ [self.indicatorView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.equalTo(self.mas_left).with.offset(selectorLeft);
|
|
|
+ make.width.equalTo(@(selectorWidth));
|
|
|
+ make.height.equalTo(@(self.selectorHeight));
|
|
|
+ make.bottom.equalTo(self.mas_bottom).with.offset(-0.5);
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
|
|
+ if (context == kXCBCCategorySelectViewKVOKey) {
|
|
|
+ if ([@"categoryNames" isEqualToString:keyPath]) {
|
|
|
+ [self reconfigureStackView];
|
|
|
+ }
|
|
|
+ if ([@"selectedIndex" isEqualToString:keyPath]) {
|
|
|
+ [self updateSelectedIndex];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)layoutSubviews {
|
|
|
+ [self updateSelectedIndex];
|
|
|
+ [super layoutSubviews];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)userDidSelectCategoryAtIndex:(NSInteger)index {
|
|
|
+ if (self.categoryClickedBlock) {
|
|
|
+ self.categoryClickedBlock(index);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)dealloc {
|
|
|
+ [self removeObserver:self
|
|
|
+ forKeyPath:@"categoryNames"
|
|
|
+ context:kXCBCCategorySelectViewKVOKey];
|
|
|
+ [self removeObserver:self
|
|
|
+ forKeyPath:@"selectedIndex"
|
|
|
+ context:kXCBCCategorySelectViewKVOKey];
|
|
|
+}
|
|
|
+
|
|
|
+@end
|