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.

UIViewbackgroundView 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