Swift
, I implement it in Objective-C
.delegate
and dataSource
.UIView+
dependent elements. You need to make a universal solution that will display any UIView
. For example, our item has a contentView
. It should be designed so that the user can assign any UIView
without thinking about the implementation of our UI element.UIControl
. If you need any custom button or other control, it is better to inherit from UIControl
, rather than from UIView
. UIControl
has a Target-Action
system that allows you to pull IBAction
from Interface Builder
directly from the button into the code. Its advantage over UIView
is the presence of states and the best tracking of touches.UIImpactFeedbackGenerator
class) when working with action components.UIView
, which resembles a UIPickerView
. It was meant for timing.UIPickerView
. Accordingly, we need to implement:UIView
, make it round and hang a UILabel
with numbers on it. To rotate, add a UIScrollView
with infinite contentSize
and based on the shift we will calculate the angle of rotation.x
, y
shift on UIScrollView
,contentView
,UIView
.AYNCircleView
. This will be the class that contains our entire custom element. At this stage, he has nothing public, we make everything private. Next, we begin to create a hierarchy. First we build our view
in Interface Builder
. Let's make AYNCircleView.xib
and deal with the hierarchy.contentView
- a circle on which all other subviews
will be,scrollView
will provide rotation.constraints
. We are most interested in the height of the contentView
and the bottom space
. They will ensure the size and position of our circle. The remaining constraints
do not allow the contentView
to get out of the superview
. For convenience, we denote the side by contentSize
the scrollView
. This does not greatly affect the performance, but simulates the "infinity" of rotation. If you are attentive to the details, you can implement a "jump" system to significantly reduce the contentSize
the scrollView
.AYNCircleView
. @interface AYNCircleView : UIView @end static CGFloat const kAYNCircleViewScrollViewContentSizeLength = 1000000000; @interface AYNCircleView () @property (assign, nonatomic) BOOL isInitialized; @property (assign, nonatomic) CGFloat circleRadius; @property (weak, nonatomic) IBOutlet UIView *contentView; @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *contentViewDimension; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *contentViewOffset; @end
view
is initialized from Interface Builder
and in code. @implementation AYNCircleView #pragma mark - Initializers - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self commonInit]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self commonInit]; } return self; } #pragma mark - Private - (void)commonInit { UIView *nibView = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil].firstObject; [self addSubview:nibView]; self.scrollView.contentSize = CGSizeMake(kAYNCircleViewScrollViewContentSizeLength, kAYNCircleViewScrollViewContentSizeLength); self.scrollView.contentOffset = CGPointMake(kAYNCircleViewScrollViewContentSizeLength / 2.0, kAYNCircleViewScrollViewContentSizeLength / 2.0); self.scrollView.delegate = self; }
- (void)layoutSubviews
, so we adjust the dimensions there. To do this, enter the radius of the circle, which depends on the minimum width and height. @property (assign, nonatomic) CGFloat circleRadius;
@property (assign, nonatomic) BOOL isInitialized;
- (void)layoutSubviews
, it would be wrong to constantly calculate the position of our hierarchy. Update constraints to set the correct size of our views
. #pragma mark - Layout - (void)layoutSubviews { [super layoutSubviews]; if (!self.isInitialized) { self.isInitialized = YES; self.subviews.firstObject.frame = self.bounds; self.circleRadius = MIN(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)) / 2; self.contentView.layer.cornerRadius = self.circleRadius; self.contentView.layer.masksToBounds = YES; [self setNeedsUpdateConstraints]; } } - (void)updateConstraints { self.contentViewDimension.constant = self.circleRadius * 2; self.contentViewOffset.constant = self.circleRadius; [super updateConstraints]; }
view controller
on which our control will be located.UIView
backgroundView
support. Our custom control thinks so that you can put any view
on the background, and the user of this control does not think about implementation.backgroundView
: @property (strong, nonatomic) UIView *backgroundView;
setter
. - (void)setBackgroundView:(UIView *)backgroundView { [_backgroundView removeFromSuperview]; _backgroundView = backgroundView; [_contentView insertSubview:_backgroundView atIndex:0]; if (_isInitialized) { [self layoutBackgroundView]; } }
view
from the hierarchy, add a new backgroundView
to the lowest level of the hierarchy and change its size in the method. - (void)layoutBackgroundView { self.backgroundView.frame = CGRectMake(0, 0, self.circleRadius * 2, self.circleRadius * 2); self.backgroundView.layer.masksToBounds = YES; self.backgroundView.layer.cornerRadius = self.circleRadius; }
view
only created. To resize correctly, add a call to this method in - (void)layoutSubviews
.UIView
and look at the hierarchy. UIView *redView = [UIView new]; redView.backgroundColor = [UIColor redColor]; self.circleView.backgroundView = redView;
UILabel
. If you need to improve performance, CoreGraphics
down to the level of CoreGraphics
and add signatures already there. Our solution is a category over UILabel
, where we define a “rotated” label
. I added a little customization to the method: text color and font. @interface UILabel (AYNHelpers) + (UILabel *)ayn_rotatedLabelWithText:(NSString *)text angle:(CGFloat)angle circleRadius:(CGFloat)circleRadius offset:(CGFloat)offset font:(UIFont *)font textColor:(UIColor *)textColor; @end
label
on a circle. circleRadius
defines the radius of this circle, offset
defines the offset relative to this circle, angle
is the central angle. Create a rotated label
in the center of this circle, and then use xOffset
and yOffset
shift the center of this label
to the right place. #import "UILabel+AYNHelpers.h" @implementation UILabel (AYNHelpers) + (UILabel *)ayn_rotatedLabelWithText:(NSString *)text angle:(CGFloat)angle circleRadius:(CGFloat)circleRadius offset:(CGFloat)offset font:(UIFont *)font textColor:(UIColor *)textColor { UILabel *rotatedLabel = [[UILabel alloc] initWithFrame:CGRectZero]; rotatedLabel.text = text; rotatedLabel.font = font ?: [UIFont boldSystemFontOfSize:22.0]; rotatedLabel.textColor = textColor ?: [UIColor blackColor]; [rotatedLabel sizeToFit]; rotatedLabel.transform = CGAffineTransformMakeRotation(angle); CGFloat angleForPoint = M_PI - angle; CGFloat xOffset = sin(angleForPoint) * (circleRadius - offset); CGFloat yOffset = cos(angleForPoint) * (circleRadius - offset); rotatedLabel.center = CGPointMake(circleRadius + xOffset, circleRadius + yOffset); return rotatedLabel; } @end
- (void)addLabelsWithNumber:
to our contentView
labels. For this, it is convenient to store the step of the angle along which the captions are located. If we take a circle of 360 degrees, and signatures 12, then the step will be 360/12 = 30 degrees. Create a property, it is useful to us to normalize the angle of rotation. @property (assign, nonatomic) CGFloat angleStep; offset , . static CGFloat const kAYNCircleViewLabelOffset = 10;
offset
for the labels, which will also be needed later. - (void)addLabelsWithNumber:(NSInteger)numberOfLabels { if (numberOfLabels > 0) { [self.contentView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj isKindOfClass:[UILabel class]]) { [obj removeFromSuperview]; } }]; self.angleStep = 2 * M_PI / numberOfLabels; for (NSInteger i = 0; i < numberOfLabels; i++) { UILabel *rotatedLabel = [UILabel ayn_rotatedLabelWithText:[NSString stringWithFormat:@"%ld", i] angle:self.angleStep * i circleRadius:self.circleRadius offset:kAYNCircleViewLabelOffset font:self.labelFont textColor:self.labelTextColor]; [self.contentView addSubview:rotatedLabel]; } } }
@property (assign, nonatomic) NSUInteger numberOfLabels;
- (void)setNumberOfLabels:(NSUInteger)numberOfLabels { _numberOfLabels = numberOfLabels; if (_isInitialized) { [self addLabelsWithNumber:_numberOfLabels]; } }
setter
for it by analogy with backgroundView
.view
already created, set the number of digits on the dial. Do not forget about the method - (void)layoutSubviews
and initialization of AYNCircleView
. There should also be signed. - (void)layoutSubviews { [super layoutSubviews]; if (!self.isInitialized) { self.isInitialized = YES; …. [self addLabelsWithNumber:self.numberOfLabels]; ... } }
- (void)viewDidLoad
controller, on the view
which our control is shown, has the following form: - (void)viewDidLoad { [super viewDidLoad]; UIView *redView = [UIView new]; redView.backgroundColor = [UIColor redColor]; self.circleView.backgroundView = redView; self.circleView.numberOfLabels = 12; self.circleView.delegate = self; }
views
and the arrangement of numbers.contentView
.NSNotification
class) about changing the orientation of the interface. We are interested in UIDeviceOrientationDidChangeNotification
.observer
this notification in the initializer of our control and process it in the same block. __weak __typeof(self) weakSelf = self; [[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceOrientationDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { __strong __typeof(weakSelf) strongSelf = weakSelf; strongSelf.isInitialized = NO; [strongSelf setNeedsLayout]; }];
self
, this can lead to a retain cycle
, so loosening the self
reference. When the orientation changes, we kind of re-initialize the controls to recalculate the radius of the circle, the new center, etc.- (void)dealloc
. - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; }
Source: https://habr.com/ru/post/326324/
All Articles