Files
Pariece McKinney b14e5cfcb0 Public Release 3.0
2024-03-28 19:39:04 -04:00

813 lines
44 KiB
Objective-C

/*
Copyright (c) 2015, Apple Inc. All rights reserved.
Copyright (c) 2015, Ricardo Sánchez-Sáez.
Copyright (c) 2015, Bruce Duncan.
Copyright (c) 2018, Brian Ganninger.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors
may be used to endorse or promote products derived from this software without
specific prior written permission. No license is granted to the trademarks of
the copyright holders even if such marks are included in this software.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "ORKScaleSliderView.h"
#import "ORKScaleRangeDescriptionLabel.h"
#import "ORKScaleRangeImageView.h"
#import "ORKScaleRangeLabel.h"
#import "ORKScaleSlider.h"
#import "ORKScaleValueLabel.h"
#import "ORKDontKnowButton.h"
#import "ORKAnswerFormat_Internal.h"
#import "ORKSkin.h"
#import "ORKHelpers_Internal.h"
static const CGFloat ValueLabelTopPadding = 16.0;
static const CGFloat ValueLabelBottomPadding = 20.5;
static const CGFloat MoveSliderLabelTopPadding = 8.0;
static const CGFloat MoveSliderLabelBottomPadding = 38.0;
static const CGFloat RangeViewHorizontalPadding = 16.0;
static const CGFloat SliderBottomPadding = 16.0;
static const CGFloat DontKnowButtonTopBottomPadding = 16.0;
static const CGFloat DontKnowButtonMaxWidthOffset = 16.0;
static const CGFloat kMargin = 25.0;
// #define LAYOUT_DEBUG 1
@implementation ORKScaleSliderView {
id<ORKScaleAnswerFormatProvider> _formatProvider;
UIStackView *_topStackView;
ORKScaleSlider *_slider;
UILabel *_moveSliderLabel;
ORKDontKnowButton *_dontKnowButton;
UIView *_dontKnowBackgroundView;
ORKScaleRangeDescriptionLabel *_leftRangeDescriptionLabel;
ORKScaleRangeDescriptionLabel *_rightRangeDescriptionLabel;
UIView *_leftRangeView;
UIView *_rightRangeView;
UIView *_dividerView;
ORKScaleValueLabel *_valueLabel;
NSMutableArray<ORKScaleRangeLabel *> *_textChoiceLabels;
NSNumber *_currentNumberValue;
NSMutableArray *constraints;
}
- (instancetype)initWithFormatProvider:(id<ORKScaleAnswerFormatProvider>)formatProvider
delegate:(id<ORKScaleSliderViewDelegate>)delegate {
self = [self initWithFrame:CGRectZero];
if (self) {
_formatProvider = formatProvider;
_delegate = delegate;
_slider = [[ORKScaleSlider alloc] initWithFrame:CGRectZero];
_slider.hideValueMarkers = [formatProvider shouldHideValueMarkers];
_slider.isWaitingForUserFeedback = ([formatProvider defaultAnswer] == nil && ![formatProvider isVertical]) ? YES : NO;
_slider.minimumTrackTintColor = self.tintColor;
_slider.userInteractionEnabled = YES;
_slider.contentMode = UIViewContentModeRedraw;
self.accessibilityElements = [self.accessibilityElements arrayByAddingObject:_slider];
[self addSubview:_slider];
_slider.maximumValue = [formatProvider maximumNumber].floatValue;
_slider.minimumValue = [formatProvider minimumNumber].floatValue;
NSInteger numberOfSteps = [formatProvider numberOfSteps];
_slider.numberOfSteps = numberOfSteps;
[_slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
BOOL isVertical = [formatProvider isVertical];
_slider.vertical = isVertical;
NSArray<ORKTextChoice *> *textChoices = [[self textScaleFormatProvider] textChoices];
_slider.textChoices = textChoices;
_slider.gradientColors = [formatProvider gradientColors];
_slider.gradientLocations = [formatProvider gradientLocations];
if (isVertical && textChoices) {
// Generate an array of labels for all the text choices
_textChoiceLabels = [NSMutableArray new];
for (int i = 0; i <= numberOfSteps; i++) {
ORKTextChoice *textChoice = textChoices[i];
ORKScaleRangeLabel *stepLabel = [[ORKScaleRangeLabel alloc] initWithFrame:CGRectZero];
stepLabel.text = textChoice.text;
stepLabel.textAlignment = NSTextAlignmentLeft;
stepLabel.numberOfLines = 0;
stepLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:stepLabel];
[_textChoiceLabels addObject:stepLabel];
}
} else {
[self setupTopLabels];
[self setUpSliderAndRangeLabels];
[self setupRangeDescriptionLabels];
[self setupDontKnowButton];
_topStackView = [[UIStackView alloc] init];
_topStackView.axis = UILayoutConstraintAxisHorizontal;
_topStackView.distribution = UIStackViewDistributionFill;
_topStackView.alignment = _slider.isWaitingForUserFeedback ? UIStackViewAlignmentLeading : UIStackViewAlignmentCenter;
_topStackView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_leftRangeView];
[self addSubview:_rightRangeView];
[self addSubview:_leftRangeDescriptionLabel];
[self addSubview:_rightRangeDescriptionLabel];
[self addSubview:_valueLabel];
if (![formatProvider isVertical]) {
[self addSubview:_topStackView];
[_topStackView addArrangedSubview: _slider.isWaitingForUserFeedback ? _moveSliderLabel : _valueLabel];
if ([formatProvider shouldShowDontKnowButton]) {
[self addSubview:_dontKnowBackgroundView];
[self addSubview:_dividerView];
[self addSubview:_dontKnowButton];
self.accessibilityElements = [self.accessibilityElements arrayByAddingObject:_dontKnowButton];
}
}
if (textChoices) {
[_leftRangeDescriptionLabel setTextColor:[UIColor labelColor]];
[_rightRangeDescriptionLabel setTextColor:[UIColor labelColor]];
_leftRangeLabel.text = @"";
_rightRangeLabel.text = @"";
}
#if LAYOUT_DEBUG
self.backgroundColor = [UIColor greenColor];
_valueLabel.backgroundColor = [UIColor blueColor];
_slider.backgroundColor = [UIColor redColor];
_leftRangeDescriptionLabel.backgroundColor = [UIColor yellowColor];
_rightRangeDescriptionLabel.backgroundColor = [UIColor yellowColor];
#endif
if (isVertical) {
_leftRangeDescriptionLabel.textAlignment = NSTextAlignmentLeft;
_rightRangeDescriptionLabel.textAlignment = NSTextAlignmentLeft;
} else {
_leftRangeDescriptionLabel.textAlignment = NSTextAlignmentLeft;
_rightRangeDescriptionLabel.textAlignment = NSTextAlignmentRight;
}
_leftRangeDescriptionLabel.text = [formatProvider shouldHideLabels] ? @"" : [formatProvider minimumValueDescription];
_rightRangeDescriptionLabel.text = [formatProvider shouldHideLabels] ? @"" : [formatProvider maximumValueDescription];
_moveSliderLabel.translatesAutoresizingMaskIntoConstraints = NO;
_valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
_leftRangeView.translatesAutoresizingMaskIntoConstraints = NO;
_rightRangeView.translatesAutoresizingMaskIntoConstraints = NO;
_leftRangeLabel.translatesAutoresizingMaskIntoConstraints = NO;
_rightRangeLabel.translatesAutoresizingMaskIntoConstraints = NO;
_rightRangeDescriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
_leftRangeDescriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
_dontKnowButton.translatesAutoresizingMaskIntoConstraints = NO;
_dividerView.translatesAutoresizingMaskIntoConstraints = NO;
}
self.translatesAutoresizingMaskIntoConstraints = NO;
_slider.translatesAutoresizingMaskIntoConstraints = NO;
[self setUpConstraints];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
if (self.frame.size.width > 0 && _dontKnowButton) {
[[_dontKnowButton.widthAnchor constraintLessThanOrEqualToConstant:self.frame.size.width - DontKnowButtonMaxWidthOffset] setActive:YES];
}
}
- (void)setupTopLabels {
_moveSliderLabel = [UILabel new];
_moveSliderLabel.text = ORKLocalizedString(@"SLIDER_MOVE_SLIDER_FOR_VALUE", nil);
_moveSliderLabel.textAlignment = NSTextAlignmentLeft;
_moveSliderLabel.numberOfLines = 0;
_moveSliderLabel.lineBreakMode = NSLineBreakByWordWrapping;
UIFontDescriptor *moveSliderDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleFootnote];
UIFontDescriptor *moveSliderFontDescriptor = [moveSliderDescriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold)];
[_moveSliderLabel setFont: [UIFont fontWithDescriptor:moveSliderFontDescriptor size:[[moveSliderFontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
_moveSliderLabel.textColor = [UIColor secondaryLabelColor];
_valueLabel = [[ORKScaleValueLabel alloc] initWithFrame:CGRectZero];
_valueLabel.text = @"";
_valueLabel.textAlignment = NSTextAlignmentCenter;
UIFontDescriptor *valueLabelDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleTitle2];
UIFontDescriptor *valueLabelFontDescriptor = [valueLabelDescriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold)];
[_valueLabel setFont: [UIFont fontWithDescriptor:valueLabelFontDescriptor size:[[valueLabelFontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
[_valueLabel setTextColor:self.tintColor];
}
- (void)setUpSliderAndRangeLabels {
UIFontDescriptor *rangeLabelDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];
UIFontDescriptor *rangeLabelRangeFontDescriptor = [rangeLabelDescriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold)];
_leftRangeLabel = [[ORKScaleRangeLabel alloc] initWithFrame:CGRectZero];
_leftRangeLabel.textAlignment = NSTextAlignmentCenter;
[_leftRangeLabel setTextColor:[UIColor labelColor]];
[_leftRangeLabel setFont: [UIFont fontWithDescriptor:rangeLabelRangeFontDescriptor size:[[rangeLabelRangeFontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
_rightRangeLabel = [[ORKScaleRangeLabel alloc] initWithFrame:CGRectZero];
_rightRangeLabel.textAlignment = NSTextAlignmentCenter;
[_rightRangeLabel setTextColor:[UIColor labelColor]];
[_rightRangeLabel setFont: [UIFont fontWithDescriptor:rangeLabelRangeFontDescriptor size:[[rangeLabelRangeFontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
if ([_formatProvider minimumImage]) {
_leftRangeView = [[ORKScaleRangeImageView alloc] initWithImage:[_formatProvider minimumImage]];
} else {
ORKScaleRangeLabel *leftRangeLabel = [[ORKScaleRangeLabel alloc] initWithFrame:CGRectZero];
leftRangeLabel.textAlignment = NSTextAlignmentCenter;
leftRangeLabel.text = [_formatProvider localizedStringForNumber:[_formatProvider minimumNumber]];
if ([_formatProvider shouldHideRanges]) {
leftRangeLabel.text = @"";
} else {
leftRangeLabel.text = [_formatProvider localizedStringForNumber:[_formatProvider minimumNumber]];
}
_leftRangeView = leftRangeLabel;
}
if ([_formatProvider maximumImage]) {
_rightRangeView = [[ORKScaleRangeImageView alloc] initWithImage:[_formatProvider maximumImage]];
} else {
ORKScaleRangeLabel *rightRangeLabel = [[ORKScaleRangeLabel alloc] initWithFrame:CGRectZero];
rightRangeLabel.textAlignment = NSTextAlignmentCenter;
if ([_formatProvider shouldHideRanges]) {
rightRangeLabel.text = @"";
} else {
rightRangeLabel.text = [_formatProvider localizedStringForNumber:[_formatProvider maximumNumber]];
}
_rightRangeView = rightRangeLabel;
}
}
- (void)setupRangeDescriptionLabels {
UIFontDescriptor *rangeDescriptionLabelDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleFootnote];
UIFontDescriptor *rangeDescriptionLabelFontDescriptor = [rangeDescriptionLabelDescriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold)];
_leftRangeDescriptionLabel = [[ORKScaleRangeDescriptionLabel alloc] initWithFrame:CGRectZero];
_leftRangeDescriptionLabel.numberOfLines = 0;
_leftRangeDescriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
[_leftRangeDescriptionLabel setFont: [UIFont fontWithDescriptor:rangeDescriptionLabelFontDescriptor size:[[rangeDescriptionLabelFontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
_leftRangeDescriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
[_leftRangeDescriptionLabel setTextColor:[UIColor labelColor]];
_rightRangeDescriptionLabel = [[ORKScaleRangeDescriptionLabel alloc] initWithFrame:CGRectZero];
_rightRangeDescriptionLabel.numberOfLines = 0;
_rightRangeDescriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
[_rightRangeDescriptionLabel setFont: [UIFont fontWithDescriptor:rangeDescriptionLabelFontDescriptor size:[[rangeDescriptionLabelFontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
_rightRangeDescriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
[_rightRangeDescriptionLabel setTextColor:[UIColor labelColor]];
}
- (void)setupDontKnowButton {
if(!_dontKnowBackgroundView) {
_dontKnowBackgroundView = [UIView new];
UITapGestureRecognizer *tapGesture1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
[_dontKnowBackgroundView addGestureRecognizer:tapGesture1];
_dontKnowBackgroundView.translatesAutoresizingMaskIntoConstraints = NO;
}
if (!_dontKnowButton) {
_dontKnowButton = [ORKDontKnowButton new];
_dontKnowButton.customDontKnowButtonText = [_formatProvider customDontKnowButtonText];
[_dontKnowButton addTarget:self action:@selector(dontKnowButtonWasPressed) forControlEvents:UIControlEventTouchUpInside];
}
if (!_dividerView) {
_dividerView = [UIView new];
[_dividerView setBackgroundColor:[UIColor separatorColor]];
}
}
- (void)setUpConstraints {
BOOL isVertical = [_formatProvider isVertical];
NSArray<ORKTextChoice *> *textChoices = _slider.textChoices;
NSDictionary *views = nil;
if (isVertical && textChoices) {
views = NSDictionaryOfVariableBindings(_slider);
} else {
views = NSDictionaryOfVariableBindings(_topStackView, _slider, _leftRangeView, _rightRangeView, _leftRangeDescriptionLabel, _rightRangeDescriptionLabel, _dividerView, _dontKnowButton, _valueLabel);
}
if (constraints) {
[NSLayoutConstraint deactivateConstraints:constraints];
}
constraints = [NSMutableArray new];
if (isVertical) {
_leftRangeDescriptionLabel.textAlignment = NSTextAlignmentLeft;
_rightRangeDescriptionLabel.textAlignment = NSTextAlignmentLeft;
// Vertical slider constraints
// Keep the thumb the same distance from the value label as in horizontal mode
const CGFloat ValueLabelSliderMargin = 23.0;
// Keep the shadow of the thumb inside the bounds
const CGFloat SliderMargin = 20.0;
const CGFloat SideLabelMargin = 24;
if (textChoices) {
[constraints addObject:[NSLayoutConstraint constraintWithItem:_slider
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_slider
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:0.25
constant:0.0]];
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-kSliderMargin-[_slider]-kSliderMargin-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:@{@"kSliderMargin": @(SliderMargin)}
views:views]];
for (int i = 0; i < _textChoiceLabels.count; i++) {
// Put labels to the right side of the slider.
[constraints addObject:[NSLayoutConstraint constraintWithItem:_textChoiceLabels[i]
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:_slider
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:SideLabelMargin]];
if (i == 0) {
// First label
[constraints addObject:[NSLayoutConstraint constraintWithItem:_textChoiceLabels[i]
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_slider
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_textChoiceLabels[i]
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self
attribute:NSLayoutAttributeWidth
multiplier:0.75
constant:0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_textChoiceLabels[i]
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:-SideLabelMargin]];
} else {
// Middle labels
[constraints addObject:[NSLayoutConstraint constraintWithItem:_textChoiceLabels[i - 1]
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_textChoiceLabels[i]
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_textChoiceLabels[i - 1]
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:_textChoiceLabels[i]
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_textChoiceLabels[i - 1]
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:_textChoiceLabels[i]
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0]];
// Last label
if (i == (_textChoiceLabels.count - 1)) {
[constraints addObject:[NSLayoutConstraint constraintWithItem:_textChoiceLabels[i]
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_slider
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
}
}
}
} else {
[constraints addObject:[NSLayoutConstraint constraintWithItem:_slider
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_valueLabel]-(>=kValueLabelSliderMargin)-[_slider]-(>=kSliderMargin)-|"
options:NSLayoutFormatAlignAllCenterX | NSLayoutFormatDirectionLeadingToTrailing
metrics:@{@"kValueLabelSliderMargin": @(ValueLabelSliderMargin), @"kSliderMargin": @(SliderMargin)}
views:views]];
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_valueLabel]-(>=8)-[_rightRangeDescriptionLabel]"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:views]];
[constraints addObjectsFromArray
:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_rightRangeView(==_leftRangeView)]"
options:(NSLayoutFormatOptions)0
metrics:nil
views:views]];
// Set the margin between slider and the rangeViews
[constraints addObject:[NSLayoutConstraint constraintWithItem:_rightRangeView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:_slider
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:-SideLabelMargin]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_leftRangeView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:_slider
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:-SideLabelMargin]];
// Align the rangeViews with the slider's bottom
[constraints addObject:[NSLayoutConstraint constraintWithItem:_rightRangeView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_slider
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_leftRangeView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_slider
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_rightRangeDescriptionLabel]-(>=8)-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:views]];
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_leftRangeDescriptionLabel(==_rightRangeDescriptionLabel)]-(>=8)-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:views]];
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_rightRangeDescriptionLabel]-(>=8)-[_leftRangeDescriptionLabel]-(>=8)-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:views]];
// Set the margin between the slider and the descriptionLabels
[constraints addObject:[NSLayoutConstraint constraintWithItem:_rightRangeDescriptionLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:_slider
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:SideLabelMargin]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_leftRangeDescriptionLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:_slider
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:SideLabelMargin]];
// Limit the height of the descriptionLabels
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.rightRangeDescriptionLabel
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:_slider
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:SliderMargin]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.leftRangeDescriptionLabel
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:_slider
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:SliderMargin]];
// Align the descriptionLabels with the rangeViews
[constraints addObject:[NSLayoutConstraint constraintWithItem:_rightRangeDescriptionLabel
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_rightRangeView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_leftRangeDescriptionLabel
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_leftRangeView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
}
} else {
[[_topStackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:kMargin] setActive:YES];
[[_topStackView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-kMargin] setActive:YES];
//Vertical Constraints
if (_slider.isWaitingForUserFeedback) {
[constraints addObject:[_topStackView.topAnchor constraintEqualToAnchor:self.topAnchor constant:MoveSliderLabelTopPadding]];
[constraints addObject:[_slider.topAnchor constraintEqualToAnchor:_topStackView.bottomAnchor constant:MoveSliderLabelBottomPadding]];
} else {
[constraints addObject:[_topStackView.topAnchor constraintEqualToAnchor:self.topAnchor constant:ValueLabelTopPadding]];
[constraints addObject:[_slider.topAnchor constraintEqualToAnchor:_topStackView.bottomAnchor constant:ValueLabelBottomPadding]];
}
[[_leftRangeDescriptionLabel.topAnchor constraintEqualToAnchor:_slider.bottomAnchor constant:SliderBottomPadding] setActive:YES];
[[_rightRangeDescriptionLabel.topAnchor constraintEqualToAnchor:_slider.bottomAnchor constant:SliderBottomPadding] setActive:YES];
//Horizontal constraints for center elements
[[_leftRangeView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:kMargin] setActive:YES];
[[_rightRangeView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-kMargin] setActive:YES];
[[_leftRangeView.centerYAnchor constraintEqualToAnchor:_slider.centerYAnchor] setActive:YES];
[[_rightRangeView.centerYAnchor constraintEqualToAnchor:_slider.centerYAnchor] setActive:YES];
[[_slider.leadingAnchor constraintEqualToAnchor:_leftRangeView.trailingAnchor constant:RangeViewHorizontalPadding] setActive:YES];
[[_slider.trailingAnchor constraintEqualToAnchor:_rightRangeView.leadingAnchor constant:-RangeViewHorizontalPadding] setActive:YES];
//Horizontal constraints for bottom elements
[[_leftRangeDescriptionLabel.leadingAnchor constraintEqualToAnchor:_slider.leadingAnchor] setActive:YES];
[[_leftRangeDescriptionLabel.trailingAnchor constraintLessThanOrEqualToAnchor:self.centerXAnchor] setActive:YES];
[[_rightRangeDescriptionLabel.trailingAnchor constraintEqualToAnchor:_slider.trailingAnchor] setActive:YES];
[[_rightRangeDescriptionLabel.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.centerXAnchor] setActive:YES];
UILabel *largerDescriptionLabel = _leftRangeDescriptionLabel.text.length >= _rightRangeDescriptionLabel.text.length ? _leftRangeDescriptionLabel : _rightRangeDescriptionLabel;
//Constraints for dont know button elements
if ([_formatProvider shouldShowDontKnowButton]) {
[[_dontKnowBackgroundView.topAnchor constraintEqualToAnchor:_dividerView.topAnchor] setActive:YES];
[[_dontKnowBackgroundView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor] setActive:YES];
[[_dontKnowBackgroundView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor] setActive:YES];
[[_dontKnowBackgroundView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor] setActive:YES];
CGFloat separatorHeight = 1.0 / [UIScreen mainScreen].scale;
[[_dividerView.leftAnchor constraintEqualToAnchor:self.leftAnchor] setActive:YES];
[[_dividerView.rightAnchor constraintEqualToAnchor:self.rightAnchor] setActive:YES];
[[_dividerView.heightAnchor constraintEqualToConstant:separatorHeight] setActive:YES];
[[_dividerView.topAnchor constraintGreaterThanOrEqualToAnchor: largerDescriptionLabel.bottomAnchor constant:8.0] setActive:YES];
[[_dontKnowButton.topAnchor constraintGreaterThanOrEqualToAnchor:_dividerView.bottomAnchor constant:DontKnowButtonTopBottomPadding] setActive:YES];
[[_dontKnowButton.centerXAnchor constraintEqualToAnchor:_dividerView.centerXAnchor] setActive:YES];
[[self.bottomAnchor constraintEqualToAnchor:_dontKnowButton.bottomAnchor constant:DontKnowButtonTopBottomPadding] setActive: YES];
} else {
[[self.bottomAnchor constraintEqualToAnchor:largerDescriptionLabel.bottomAnchor constant:8.0] setActive: YES];
}
}
if ([_formatProvider shouldHideSelectedValueLabel] &&
!([_formatProvider isVertical] && [self textScaleFormatProvider]) && !_slider.isWaitingForUserFeedback) {
_valueLabel.layer.opacity = 0.0;
} else {
_valueLabel.layer.opacity = 1.0;
}
[NSLayoutConstraint activateConstraints:constraints];
}
- (void)tintColorDidChange {
_valueLabel.textColor = self.tintColor;
_slider.minimumTrackTintColor = self.tintColor;
}
- (id<ORKTextScaleAnswerFormatProvider>)textScaleFormatProvider {
if ([[_formatProvider class] conformsToProtocol:@protocol(ORKTextScaleAnswerFormatProvider)]) {
return (id<ORKTextScaleAnswerFormatProvider>)_formatProvider;
}
return nil;
}
- (void)setCurrentNumberValue:(NSNumber *)value {
_currentNumberValue = value ? [_formatProvider normalizedValueForNumber:value] : nil;
_slider.showThumb = YES;
if (_currentNumberValue && _slider.isWaitingForUserFeedback) {
[self resetViewToDefault];
}
[self updateCurrentValueLabel];
_slider.value = _currentNumberValue.floatValue;
}
- (NSUInteger)currentTextChoiceIndex {
return _currentNumberValue.unsignedIntegerValue - 1;
}
- (void)updateCurrentValueLabel {
if (_currentNumberValue) {
if ([self textScaleFormatProvider]) {
ORKTextChoice *textChoice = [[self textScaleFormatProvider] textChoiceAtIndex:[self currentTextChoiceIndex]];
self.valueLabel.text = textChoice.text;
if (textChoice.primaryTextAttributedString) {
self.valueLabel.attributedText = textChoice.primaryTextAttributedString;
}
} else {
NSNumber *newValue = [_formatProvider normalizedValueForNumber:_currentNumberValue];
_valueLabel.text = [_formatProvider localizedStringForNumber:newValue];
}
} else {
_valueLabel.text = @"";
}
}
- (IBAction)sliderValueChanged:(id)sender {
if (_slider.isWaitingForUserFeedback) {
[self resetViewToDefault];
}
if (_dontKnowButton) {
[_dontKnowButton setActive:NO];
}
_currentNumberValue = [_formatProvider normalizedValueForNumber:@(_slider.value)];
[self updateCurrentValueLabel];
[self notifyDelegate];
}
- (void)resetViewToDefault {
_slider.isWaitingForUserFeedback = NO;
[_topStackView removeArrangedSubview:_moveSliderLabel];
[_moveSliderLabel removeFromSuperview];
[_topStackView addArrangedSubview:_valueLabel];
_topStackView.alignment = UIStackViewAlignmentCenter;
[NSLayoutConstraint deactivateConstraints:constraints];
[constraints removeAllObjects];
[self setUpConstraints];
}
- (void)dontKnowButtonWasPressed {
if (_dontKnowButton && ![_dontKnowButton active]) {
[_slider setShowThumb:YES];
[_dontKnowButton setActive:YES];
_currentNumberValue = nil;
[self notifyDelegate];
}
if (!_slider.isWaitingForUserFeedback) {
_slider.isWaitingForUserFeedback = YES;
[_topStackView removeArrangedSubview:_valueLabel];
[_valueLabel removeFromSuperview];
[_topStackView addArrangedSubview:_moveSliderLabel];
_topStackView.alignment = UIStackViewAlignmentLeading;
[NSLayoutConstraint deactivateConstraints:constraints];
[constraints removeAllObjects];
[self setUpConstraints];
_slider.value = [_formatProvider minimumNumber].floatValue;
}
}
- (void)tapGesture:(id)sender {
//this tap gesture is here to avoid the cell being selected if the user missed the dont know button
}
- (void)notifyDelegate {
if (self.delegate && [self.delegate respondsToSelector:@selector(scaleSliderViewCurrentValueDidChange:)]) {
[self.delegate scaleSliderViewCurrentValueDidChange:self];
}
}
- (void)setCurrentTextChoiceValue:(NSObject<NSCopying, NSSecureCoding> *)currentTextChoiceValue {
if (currentTextChoiceValue) {
NSUInteger index = [[self textScaleFormatProvider] textChoiceIndexForValue:currentTextChoiceValue];
if (index != NSNotFound) {
[self setCurrentNumberValue:@(index + 1)];
} else {
[self setCurrentNumberValue:nil];
}
} else {
[self setCurrentNumberValue:nil];
}
}
- (NSObject<NSCopying, NSSecureCoding> *)currentTextChoiceValue {
NSObject<NSCopying, NSSecureCoding> *value = [[self textScaleFormatProvider] textChoiceAtIndex:[self currentTextChoiceIndex]].value;
return value;
}
- (id)currentAnswerValue {
if ([_dontKnowButton active]) {
return [ORKDontKnowAnswer answer];
}
if ([self textScaleFormatProvider]) {
NSObject<NSCopying, NSSecureCoding> *value = [self currentTextChoiceValue];
return value ? @[value] : @[];
} else {
return _currentNumberValue;
}
}
- (void)setCurrentAnswerValue:(id)currentAnswerValue {
if (currentAnswerValue == [ORKDontKnowAnswer answer] && _dontKnowButton) {
[self dontKnowButtonWasPressed];
} else if ([self textScaleFormatProvider]) {
if (ORKIsAnswerEmpty(currentAnswerValue)) {
[self setCurrentTextChoiceValue:nil];
} else {
[self setCurrentTextChoiceValue:[currentAnswerValue firstObject]];
}
} else {
[self setCurrentNumberValue:currentAnswerValue];
}
}
#pragma mark - Accessibility
// Since the slider is the only interesting thing within this cell, we make the
// cell a container with only one element, i.e. the slider.
- (BOOL)isAccessibilityElement {
return NO;
}
- (NSInteger)accessibilityElementCount {
return self.accessibilityElements.count;
}
- (id)accessibilityElementAtIndex:(NSInteger)index {
return [self.accessibilityElements objectAtIndex:index];
}
- (NSInteger)indexOfAccessibilityElement:(id)element {
return [self.accessibilityElements indexOfObject:element];
}
@end