// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "OWSSearchBar.h" #import "OWSTextField.h" #import "Theme.h" #import "UIFont+OWS.h" #import "UIView+OWS.h" #import #import NS_ASSUME_NONNULL_BEGIN @interface OWSSearchBar () @property (nonatomic) OWSTextField *textField; @property (nonatomic) UIButton *cancelButton; @property (nonatomic) CAShapeLayer *pillboxLayer; @property (nonatomic) OWSLayerView *pillboxView; @property (nonatomic) UIButton *clearButton; @property (nonatomic) UIImageView *searchIconView; @property (nonatomic) UIStackView *contentStackView; @end #pragma mark - @implementation OWSSearchBar - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self ows_configure]; } return self; } - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self ows_configure]; } return self; } - (CGFloat)contentHMargin { return 8.f; } - (CGFloat)contentVMargin { return 10.f; } - (CGFloat)pillboxHMargin { return 8.f; } - (CGFloat)pillboxVMargin { return 7.f; } - (CGFloat)contentSpacing { return 12.f; } - (CGFloat)pillboxSpacing { return 8.f; } + (CGFloat)pillboxRadius { return 10.f; } - (CGSize)sizeThatFits:(CGSize)size { // All that matters is the height. CGSize result = [self.textField sizeThatFits:CGSizeZero]; result.width += self.pillboxHMargin * 2; result.height += self.pillboxVMargin * 2; result.width += self.contentHMargin * 2; result.height += self.contentVMargin * 2; return result; } - (void)ows_configure { OWSAssert(!self.searchIconView); self.opaque = NO; self.backgroundColor = [UIColor clearColor]; UIImage *_Nullable searchIcon = [UIImage imageNamed:@"searchbar_search"]; searchIcon = [searchIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; OWSAssert(searchIcon); self.searchIconView = [[UIImageView alloc] initWithImage:searchIcon]; [self.searchIconView setContentHuggingHigh]; [self.searchIconView setCompressionResistanceHigh]; UIImage *_Nullable clearIcon = [UIImage imageNamed:@"searchbar_clear"]; clearIcon = [clearIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; OWSAssert(clearIcon); self.clearButton = [UIButton buttonWithType:UIButtonTypeCustom]; [self.clearButton setImage:clearIcon forState:UIControlStateNormal]; [self.clearButton addTarget:self action:@selector(clearButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [self.clearButton setContentHuggingHigh]; [self.clearButton setCompressionResistanceHigh]; self.textField = [OWSTextField new]; self.textField.font = [UIFont ows_dynamicTypeBodyFont]; self.textField.delegate = self; self.textField.ows_delegate = self; self.textField.returnKeyType = UIReturnKeySearch; self.textField.enablesReturnKeyAutomatically = YES; [self.textField addTarget:self action:@selector(textDidChange:) forControlEvents:UIControlEventEditingChanged]; [self.textField addTarget:self action:@selector(textFieldReturnWasPressed:) forControlEvents:UIControlEventEditingDidEndOnExit]; [self.textField setContentHuggingVerticalLow]; [self.textField setContentHuggingVerticalHigh]; [self.textField setCompressionResistanceHorizontalLow]; [self.textField setCompressionResistanceVerticalHigh]; UIStackView *pillboxStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ self.searchIconView, self.textField, self.clearButton, ]]; pillboxStackView.spacing = self.pillboxSpacing; pillboxStackView.axis = UILayoutConstraintAxisHorizontal; pillboxStackView.alignment = UIStackViewAlignmentCenter; pillboxStackView.layoutMargins = UIEdgeInsetsMake(self.pillboxVMargin, self.pillboxHMargin, self.pillboxVMargin, self.pillboxHMargin); pillboxStackView.layoutMarginsRelativeArrangement = YES; self.cancelButton = [UIButton buttonWithType:UIButtonTypeCustom]; [self.cancelButton setTitle:CommonStrings.cancelButton forState:UIControlStateNormal]; self.cancelButton.titleLabel.font = UIFont.ows_dynamicTypeBodyFont; [self.cancelButton setContentHuggingHigh]; [self.cancelButton setCompressionResistanceHigh]; [self.cancelButton addTarget:self action:@selector(cancelButtonPressed) forControlEvents:UIControlEventTouchUpInside]; self.contentStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ pillboxStackView, self.cancelButton, ]]; self.contentStackView.spacing = self.contentSpacing; self.contentStackView.axis = UILayoutConstraintAxisHorizontal; self.contentStackView.alignment = UIStackViewAlignmentCenter; [self addSubview:self.contentStackView]; [self.contentStackView autoPinWidthToSuperviewWithMargin:self.contentHMargin]; [self.contentStackView autoPinHeightToSuperviewWithMargin:self.contentVMargin]; CAShapeLayer *pillboxLayer = [CAShapeLayer new]; self.pillboxLayer = pillboxLayer; self.pillboxView = [[OWSLayerView alloc] initWithFrame:CGRectZero layoutCallback:^(UIView *layerView) { pillboxLayer.path = [UIBezierPath bezierPathWithRoundedRect:layerView.bounds cornerRadius:OWSSearchBar.pillboxRadius] .CGPath; }]; self.pillboxView.userInteractionEnabled = NO; [self.pillboxView.layer addSublayer:pillboxLayer]; [pillboxStackView addSubview:self.pillboxView]; [self.pillboxView autoPinEdgesToSuperviewEdges]; [self.pillboxView setContentHuggingLow]; [self.pillboxView setCompressionResistanceLow]; self.pillboxView.layer.zPosition = -1; self.userInteractionEnabled = YES; [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapSearchBar:)]]; [self ows_applyTheme]; [self updateState]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(themeDidChange:) name:ThemeDidChangeNotification object:nil]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)ows_applyTheme { OWSAssertIsOnMainThread(); self.searchIconView.tintColor = Theme.placeholderColor; self.clearButton.tintColor = Theme.placeholderColor; self.pillboxLayer.fillColor = Theme.offBackgroundColor.CGColor; self.textField.textColor = Theme.primaryColor; if (self.placeholder.length > 0) { self.textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder attributes:@{ NSForegroundColorAttributeName : Theme.placeholderColor, }]; } [self.cancelButton setTitleColor:Theme.primaryColor forState:UIControlStateNormal]; } - (void)updateState { self.cancelButton.hidden = (!self.textField.isFirstResponder && self.textField.text.length < 1); self.clearButton.hidden = self.textField.text.length < 1; } - (void)setPlaceholder:(nullable NSString *)placeholder { self.textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:placeholder attributes:@{ NSForegroundColorAttributeName : Theme.placeholderColor, }]; } - (nullable NSString *)placeholder { return self.textField.placeholder; } - (void)setText:(nullable NSString *)text { self.textField.text = text; [self updateState]; } - (nullable NSString *)text { return self.textField.text; } - (BOOL)becomeFirstResponder { return [self.textField becomeFirstResponder]; } - (BOOL)resignFirstResponder { return [self.textField resignFirstResponder]; } #pragma mark - OWSTextFieldDelegate - (void)textFieldDidBecomeFirstResponder:(OWSTextField *)textField { [self updateState]; if ([self.delegate respondsToSelector:@selector(searchBarDidBeginEditing:)]) { [self.delegate searchBarDidBeginEditing:self]; } } - (void)textFieldDidResignFirstResponder:(OWSTextField *)textField { [self updateState]; } #pragma mark - Events - (void)themeDidChange:(NSNotification *)notification { OWSAssertIsOnMainThread(); [self ows_applyTheme]; } - (void)textDidChange:(id)sender { [self updateState]; [self.delegate searchBar:self textDidChange:self.text]; } - (void)textFieldReturnWasPressed:(id)sender { [self.textField resignFirstResponder]; [self updateState]; [self.delegate searchBar:self returnWasPressed:self.text]; } - (void)cancelButtonPressed { self.text = nil; [self updateState]; [self.delegate searchBar:self textDidChange:self.text]; } - (void)clearButtonPressed { self.text = nil; [self updateState]; [self.delegate searchBar:self textDidChange:self.text]; } - (void)didTapSearchBar:(UIGestureRecognizer *)sender { [self becomeFirstResponder]; } @end NS_ASSUME_NONNULL_END