iOS应用的用户界面是用户体验的核心,优秀的UI设计和流畅的交互能够显著提升应用的用户满意度。本文将深入探讨iOS UI编程的高级技术,包括自定义控件开发、Core Animation应用、界面性能优化等关键内容。
UIKit框架核心架构
UIView层次结构与渲染机制
1. UIView基础架构
@interface CustomView : UIView
@property (nonatomic, strong) CALayer *customLayer;
@property (nonatomic, assign) CGRect contentFrame;
@property (nonatomic, strong) UIColor *borderColor;
@property (nonatomic, assign) CGFloat borderWidth;
- (void)setupView;
- (void)updateLayout;
- (void)performCustomDrawing;
@end
@implementation CustomView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupView];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setupView];
}
return self;
}
- (void)setupView {
// 1. 设置基本属性
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = YES;
// 2. 创建自定义图层
_customLayer = [CALayer layer];
_customLayer.backgroundColor = [UIColor lightGrayColor].CGColor;
_customLayer.cornerRadius = 8.0;
_customLayer.masksToBounds = YES;
[self.layer addSublayer:_customLayer];
// 3. 设置默认值
_borderColor = [UIColor blackColor];
_borderWidth = 1.0;
// 4. 添加观察者
[self addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"bounds"]) {
[self updateLayout];
}
}
- (void)updateLayout {
// 更新自定义图层的frame
CGRect bounds = self.bounds;
_customLayer.frame = CGRectInset(bounds, _borderWidth, _borderWidth);
// 更新内容区域
_contentFrame = CGRectInset(bounds, _borderWidth + 10, _borderWidth + 10);
// 触发重绘
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
[self performCustomDrawing];
}
- (void)performCustomDrawing {
CGContextRef context = UIGraphicsGetCurrentContext();
if (!context) return;
// 1. 绘制边框
CGContextSetStrokeColorWithColor(context, _borderColor.CGColor);
CGContextSetLineWidth(context, _borderWidth);
CGContextStrokeRect(context, CGRectInset(self.bounds, _borderWidth/2, _borderWidth/2));
// 2. 绘制自定义内容
CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
CGFloat centerX = CGRectGetMidX(_contentFrame);
CGFloat centerY = CGRectGetMidY(_contentFrame);
CGFloat radius = MIN(CGRectGetWidth(_contentFrame), CGRectGetHeight(_contentFrame)) / 4;
CGContextFillEllipseInRect(context, CGRectMake(centerX - radius, centerY - radius, radius * 2, radius * 2));
}
- (void)setBorderColor:(UIColor *)borderColor {
_borderColor = borderColor;
[self setNeedsDisplay];
}
- (void)setBorderWidth:(CGFloat)borderWidth {
_borderWidth = borderWidth;
[self updateLayout];
}
- (void)dealloc {
[self removeObserver:self forKeyPath:@"bounds"];
}
@end
Core Animation深度应用
图层动画系统
1. 基础动画管理器
@interface AnimationManager : NSObject
@property (nonatomic, weak) CALayer *targetLayer;
- (instancetype)initWithLayer:(CALayer *)layer;
- (void)performBasicAnimation:(NSString *)keyPath
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration;
- (void)performKeyframeAnimation:(NSString *)keyPath
values:(NSArray *)values
keyTimes:(NSArray *)keyTimes
duration:(NSTimeInterval)duration;
- (void)performSpringAnimation:(NSString *)keyPath
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration
damping:(CGFloat)damping
stiffness:(CGFloat)stiffness;
- (void)performGroupAnimation:(NSArray<CAAnimation *> *)animations
duration:(NSTimeInterval)duration;
@end
@implementation AnimationManager
- (instancetype)initWithLayer:(CALayer *)layer {
self = [super init];
if (self) {
_targetLayer = layer;
}
return self;
}
- (void)performBasicAnimation:(NSString *)keyPath
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
animation.fromValue = fromValue;
animation.toValue = toValue;
animation.duration = duration;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
// 设置动画完成回调
animation.delegate = self;
[_targetLayer addAnimation:animation forKey:keyPath];
// 更新模型层的值
[_targetLayer setValue:toValue forKeyPath:keyPath];
}
- (void)performKeyframeAnimation:(NSString *)keyPath
values:(NSArray *)values
keyTimes:(NSArray *)keyTimes
duration:(NSTimeInterval)duration {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keyPath];
animation.values = values;
animation.keyTimes = keyTimes;
animation.duration = duration;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[_targetLayer addAnimation:animation forKey:[NSString stringWithFormat:@"%@_keyframe", keyPath]];
// 更新到最终值
if (values.count > 0) {
[_targetLayer setValue:values.lastObject forKeyPath:keyPath];
}
}
- (void)performSpringAnimation:(NSString *)keyPath
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration
damping:(CGFloat)damping
stiffness:(CGFloat)stiffness {
CASpringAnimation *animation = [CASpringAnimation animationWithKeyPath:keyPath];
animation.fromValue = fromValue;
animation.toValue = toValue;
animation.duration = duration;
animation.damping = damping;
animation.stiffness = stiffness;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[_targetLayer addAnimation:animation forKey:[NSString stringWithFormat:@"%@_spring", keyPath]];
// 更新模型层的值
[_targetLayer setValue:toValue forKeyPath:keyPath];
}
- (void)performGroupAnimation:(NSArray<CAAnimation *> *)animations
duration:(NSTimeInterval)duration {
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = animations;
group.duration = duration;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[_targetLayer addAnimation:group forKey:@"groupAnimation"];
}
#pragma mark - CAAnimationDelegate
- (void)animationDidStart:(CAAnimation *)anim {
NSLog(@"Animation started");
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
NSLog(@"Animation finished: %@", flag ? @"YES" : @"NO");
}
@end
2. 复杂动画序列
@interface AnimationSequence : NSObject
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *animationSteps;
@property (nonatomic, weak) CALayer *targetLayer;
@property (nonatomic, assign) NSInteger currentStepIndex;
@property (nonatomic, copy) void(^completionBlock)(void);
- (instancetype)initWithLayer:(CALayer *)layer;
- (void)addAnimationStep:(NSString *)keyPath
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay;
- (void)addCustomAnimationStep:(CAAnimation *)animation
delay:(NSTimeInterval)delay;
- (void)executeSequenceWithCompletion:(void(^)(void))completion;
- (void)pauseSequence;
- (void)resumeSequence;
- (void)stopSequence;
@end
@implementation AnimationSequence
- (instancetype)initWithLayer:(CALayer *)layer {
self = [super init];
if (self) {
_targetLayer = layer;
_animationSteps = [NSMutableArray array];
_currentStepIndex = 0;
}
return self;
}
- (void)addAnimationStep:(NSString *)keyPath
fromValue:(id)fromValue
toValue:(id)toValue
duration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay {
NSDictionary *step = @{
@"type": @"basic",
@"keyPath": keyPath,
@"fromValue": fromValue ?: [NSNull null],
@"toValue": toValue,
@"duration": @(duration),
@"delay": @(delay)
};
[_animationSteps addObject:step];
}
- (void)addCustomAnimationStep:(CAAnimation *)animation
delay:(NSTimeInterval)delay {
NSDictionary *step = @{
@"type": @"custom",
@"animation": animation,
@"delay": @(delay)
};
[_animationSteps addObject:step];
}
- (void)executeSequenceWithCompletion:(void(^)(void))completion {
_completionBlock = completion;
_currentStepIndex = 0;
[self executeNextStep];
}
- (void)executeNextStep {
if (_currentStepIndex >= _animationSteps.count) {
// 序列完成
if (_completionBlock) {
_completionBlock();
}
return;
}
NSDictionary *step = _animationSteps[_currentStepIndex];
NSTimeInterval delay = [step[@"delay"] doubleValue];
// 延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self performAnimationStep:step];
});
}
- (void)performAnimationStep:(NSDictionary *)step {
NSString *type = step[@"type"];
if ([type isEqualToString:@"basic"]) {
[self performBasicAnimationStep:step];
} else if ([type isEqualToString:@"custom"]) {
[self performCustomAnimationStep:step];
}
}
- (void)performBasicAnimationStep:(NSDictionary *)step {
NSString *keyPath = step[@"keyPath"];
id fromValue = step[@"fromValue"];
id toValue = step[@"toValue"];
NSTimeInterval duration = [step[@"duration"] doubleValue];
if ([fromValue isKindOfClass:[NSNull class]]) {
fromValue = [_targetLayer valueForKeyPath:keyPath];
}
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
animation.fromValue = fromValue;
animation.toValue = toValue;
animation.duration = duration;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.delegate = self;
[_targetLayer addAnimation:animation forKey:[NSString stringWithFormat:@"step_%ld", (long)_currentStepIndex]];
// 更新模型层的值
[_targetLayer setValue:toValue forKeyPath:keyPath];
}
- (void)performCustomAnimationStep:(NSDictionary *)step {
CAAnimation *animation = step[@"animation"];
animation.delegate = self;
[_targetLayer addAnimation:animation forKey:[NSString stringWithFormat:@"custom_step_%ld", (long)_currentStepIndex]];
}
- (void)pauseSequence {
CFTimeInterval pausedTime = [_targetLayer convertTime:CACurrentMediaTime() fromLayer:nil];
_targetLayer.speed = 0.0;
_targetLayer.timeOffset = pausedTime;
}
- (void)resumeSequence {
CFTimeInterval pausedTime = _targetLayer.timeOffset;
_targetLayer.speed = 1.0;
_targetLayer.timeOffset = 0.0;
_targetLayer.beginTime = 0.0;
CFTimeInterval timeSincePause = [_targetLayer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
_targetLayer.beginTime = timeSincePause;
}
- (void)stopSequence {
[_targetLayer removeAllAnimations];
_currentStepIndex = 0;
}
#pragma mark - CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if (flag) {
_currentStepIndex++;
[self executeNextStep];
}
}
@end
高级视觉效果
1. 粒子系统
@interface ParticleEmitterView : UIView
@property (nonatomic, strong) CAEmitterLayer *emitterLayer;
@property (nonatomic, strong) CAEmitterCell *emitterCell;
// 发射器属性
@property (nonatomic, assign) CGPoint emitterPosition;
@property (nonatomic, assign) CGSize emitterSize;
@property (nonatomic, assign) NSString *emitterShape;
@property (nonatomic, assign) NSString *emitterMode;
// 粒子属性
@property (nonatomic, strong) UIImage *particleImage;
@property (nonatomic, assign) CGFloat birthRate;
@property (nonatomic, assign) CGFloat lifetime;
@property (nonatomic, assign) CGFloat velocity;
@property (nonatomic, assign) CGFloat velocityRange;
@property (nonatomic, assign) CGFloat emissionRange;
@property (nonatomic, assign) CGFloat scale;
@property (nonatomic, assign) CGFloat scaleRange;
@property (nonatomic, assign) CGFloat alphaSpeed;
- (void)startEmitting;
- (void)stopEmitting;
- (void)createFireworksEffect;
- (void)createSnowEffect;
- (void)createRainEffect;
@end
@implementation ParticleEmitterView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupEmitter];
}
return self;
}
- (void)setupEmitter {
// 1. 创建发射器图层
_emitterLayer = [CAEmitterLayer layer];
_emitterLayer.frame = self.bounds;
[self.layer addSublayer:_emitterLayer];
// 2. 设置默认发射器属性
_emitterPosition = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
_emitterSize = CGSizeMake(50, 50);
_emitterShape = kCAEmitterLayerPoint;
_emitterMode = kCAEmitterLayerOutline;
// 3. 创建粒子单元
_emitterCell = [CAEmitterCell emitterCell];
// 4. 设置默认粒子属性
_birthRate = 50;
_lifetime = 3.0;
_velocity = 100;
_velocityRange = 50;
_emissionRange = M_PI * 2;
_scale = 1.0;
_scaleRange = 0.5;
_alphaSpeed = -0.5;
[self updateEmitterProperties];
}
- (void)layoutSubviews {
[super layoutSubviews];
_emitterLayer.frame = self.bounds;
[self updateEmitterProperties];
}
- (void)updateEmitterProperties {
// 更新发射器属性
_emitterLayer.emitterPosition = _emitterPosition;
_emitterLayer.emitterSize = _emitterSize;
_emitterLayer.emitterShape = _emitterShape;
_emitterLayer.emitterMode = _emitterMode;
// 更新粒子属性
_emitterCell.birthRate = _birthRate;
_emitterCell.lifetime = _lifetime;
_emitterCell.velocity = _velocity;
_emitterCell.velocityRange = _velocityRange;
_emitterCell.emissionRange = _emissionRange;
_emitterCell.scale = _scale;
_emitterCell.scaleRange = _scaleRange;
_emitterCell.alphaSpeed = _alphaSpeed;
if (_particleImage) {
_emitterCell.contents = (id)_particleImage.CGImage;
}
_emitterLayer.emitterCells = @[_emitterCell];
}
- (void)startEmitting {
_emitterLayer.birthRate = 1.0;
}
- (void)stopEmitting {
_emitterLayer.birthRate = 0.0;
}
- (void)createFireworksEffect {
// 烟花效果配置
_emitterPosition = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMaxY(self.bounds));
_emitterShape = kCAEmitterLayerPoint;
_emitterMode = kCAEmitterLayerOutline;
_emitterCell.birthRate = 100;
_emitterCell.lifetime = 2.0;
_emitterCell.velocity = 200;
_emitterCell.velocityRange = 100;
_emitterCell.emissionRange = M_PI / 4;
_emitterCell.yAcceleration = 150; // 重力效果
_emitterCell.scale = 0.1;
_emitterCell.scaleRange = 0.05;
_emitterCell.alphaSpeed = -1.0;
// 设置颜色变化
_emitterCell.color = [UIColor redColor].CGColor;
_emitterCell.redRange = 1.0;
_emitterCell.greenRange = 1.0;
_emitterCell.blueRange = 1.0;
[self updateEmitterProperties];
[self startEmitting];
}
- (void)createSnowEffect {
// 雪花效果配置
_emitterPosition = CGPointMake(CGRectGetMidX(self.bounds), 0);
_emitterSize = CGSizeMake(CGRectGetWidth(self.bounds), 0);
_emitterShape = kCAEmitterLayerLine;
_emitterMode = kCAEmitterLayerSurface;
_emitterCell.birthRate = 20;
_emitterCell.lifetime = 10.0;
_emitterCell.velocity = 50;
_emitterCell.velocityRange = 20;
_emitterCell.emissionRange = M_PI / 8;
_emitterCell.yAcceleration = 30;
_emitterCell.scale = 0.05;
_emitterCell.scaleRange = 0.02;
_emitterCell.alphaSpeed = 0;
// 设置旋转
_emitterCell.spin = M_PI / 2;
_emitterCell.spinRange = M_PI;
_emitterCell.color = [UIColor whiteColor].CGColor;
[self updateEmitterProperties];
[self startEmitting];
}
- (void)createRainEffect {
// 雨滴效果配置
_emitterPosition = CGPointMake(CGRectGetMidX(self.bounds), 0);
_emitterSize = CGSizeMake(CGRectGetWidth(self.bounds), 0);
_emitterShape = kCAEmitterLayerLine;
_emitterMode = kCAEmitterLayerSurface;
_emitterCell.birthRate = 100;
_emitterCell.lifetime = 3.0;
_emitterCell.velocity = 300;
_emitterCell.velocityRange = 50;
_emitterCell.emissionRange = M_PI / 16;
_emitterCell.yAcceleration = 100;
_emitterCell.scale = 0.02;
_emitterCell.scaleRange = 0.01;
_emitterCell.alphaSpeed = 0;
_emitterCell.color = [UIColor blueColor].CGColor;
[self updateEmitterProperties];
[self startEmitting];
}
// 属性设置器
- (void)setEmitterPosition:(CGPoint)emitterPosition {
_emitterPosition = emitterPosition;
[self updateEmitterProperties];
}
- (void)setParticleImage:(UIImage *)particleImage {
_particleImage = particleImage;
[self updateEmitterProperties];
}
- (void)setBirthRate:(CGFloat)birthRate {
_birthRate = birthRate;
[self updateEmitterProperties];
}
@end
界面性能优化
渲染性能监控
@interface UIPerformanceMonitor : NSObject
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) CFTimeInterval lastTimestamp;
@property (nonatomic, assign) NSInteger frameCount;
@property (nonatomic, assign) CGFloat currentFPS;
@property (nonatomic, strong) NSMutableArray<NSNumber *> *fpsHistory;
@property (nonatomic, copy) void(^fpsUpdateBlock)(CGFloat fps);
+ (instancetype)sharedMonitor;
- (void)startMonitoring;
- (void)stopMonitoring;
- (void)setFPSUpdateBlock:(void(^)(CGFloat fps))updateBlock;
- (CGFloat)averageFPS;
- (CGFloat)minimumFPS;
- (void)logPerformanceReport;
@end
@implementation UIPerformanceMonitor
+ (instancetype)sharedMonitor {
static UIPerformanceMonitor *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[UIPerformanceMonitor alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_fpsHistory = [NSMutableArray array];
_frameCount = 0;
_currentFPS = 0;
}
return self;
}
- (void)startMonitoring {
if (_displayLink) {
[self stopMonitoring];
}
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTick:)];
_displayLink.preferredFramesPerSecond = 60;
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
_lastTimestamp = 0;
_frameCount = 0;
}
- (void)stopMonitoring {
[_displayLink invalidate];
_displayLink = nil;
}
- (void)displayLinkTick:(CADisplayLink *)displayLink {
if (_lastTimestamp == 0) {
_lastTimestamp = displayLink.timestamp;
return;
}
_frameCount++;
CFTimeInterval deltaTime = displayLink.timestamp - _lastTimestamp;
if (deltaTime >= 1.0) {
// 计算FPS
_currentFPS = _frameCount / deltaTime;
// 记录FPS历史
[_fpsHistory addObject:@(_currentFPS)];
if (_fpsHistory.count > 60) { // 保持最近60秒的数据
[_fpsHistory removeObjectAtIndex:0];
}
// 回调更新
if (_fpsUpdateBlock) {
_fpsUpdateBlock(_currentFPS);
}
// 重置计数器
_frameCount = 0;
_lastTimestamp = displayLink.timestamp;
}
}
- (void)setFPSUpdateBlock:(void(^)(CGFloat fps))updateBlock {
_fpsUpdateBlock = updateBlock;
}
- (CGFloat)averageFPS {
if (_fpsHistory.count == 0) return 0;
CGFloat total = 0;
for (NSNumber *fps in _fpsHistory) {
total += fps.floatValue;
}
return total / _fpsHistory.count;
}
- (CGFloat)minimumFPS {
if (_fpsHistory.count == 0) return 0;
CGFloat minimum = CGFLOAT_MAX;
for (NSNumber *fps in _fpsHistory) {
minimum = MIN(minimum, fps.floatValue);
}
return minimum;
}
- (void)logPerformanceReport {
NSLog(@"=== UI Performance Report ===");
NSLog(@"Current FPS: %.1f", _currentFPS);
NSLog(@"Average FPS: %.1f", [self averageFPS]);
NSLog(@"Minimum FPS: %.1f", [self minimumFPS]);
NSLog(@"Sample Count: %lu", (unsigned long)_fpsHistory.count);
NSLog(@"==============================");
}
@end
视图优化策略
@interface ViewOptimizer : NSObject
+ (void)optimizeView:(UIView *)view;
+ (void)optimizeScrollView:(UIScrollView *)scrollView;
+ (void)optimizeTableView:(UITableView *)tableView;
+ (void)optimizeImageView:(UIImageView *)imageView;
+ (void)enableAsyncDrawing:(UIView *)view;
+ (void)optimizeLayerProperties:(CALayer *)layer;
@end
@implementation ViewOptimizer
+ (void)optimizeView:(UIView *)view {
if (!view) return;
// 1. 设置不透明属性
if (view.backgroundColor && ![view.backgroundColor isEqual:[UIColor clearColor]]) {
view.opaque = YES;
}
// 2. 优化图层属性
[self optimizeLayerProperties:view.layer];
// 3. 递归优化子视图
for (UIView *subview in view.subviews) {
[self optimizeView:subview];
}
}
+ (void)optimizeScrollView:(UIScrollView *)scrollView {
if (!scrollView) return;
// 1. 基础优化
[self optimizeView:scrollView];
// 2. 滚动视图特定优化
scrollView.delaysContentTouches = NO;
scrollView.canCancelContentTouches = YES;
// 3. 如果是分页滚动,优化预加载
if (scrollView.pagingEnabled) {
scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
}
}
+ (void)optimizeTableView:(UITableView *)tableView {
if (!tableView) return;
// 1. 基础优化
[self optimizeScrollView:tableView];
// 2. 表格视图特定优化
tableView.estimatedRowHeight = 44; // 设置估算行高
tableView.rowHeight = UITableViewAutomaticDimension;
// 3. 分隔线优化
if (tableView.separatorStyle != UITableViewCellSeparatorStyleNone) {
tableView.separatorInset = UIEdgeInsetsZero;
tableView.layoutMargins = UIEdgeInsetsZero;
}
}
+ (void)optimizeImageView:(UIImageView *)imageView {
if (!imageView) return;
// 1. 基础优化
[self optimizeView:imageView];
// 2. 图片视图特定优化
if (imageView.image) {
// 检查图片尺寸是否合适
CGSize imageSize = imageView.image.size;
CGSize viewSize = imageView.bounds.size;
if (imageSize.width > viewSize.width * 2 || imageSize.height > viewSize.height * 2) {
NSLog(@"Warning: Image size (%@) is much larger than view size (%@)",
NSStringFromCGSize(imageSize), NSStringFromCGSize(viewSize));
}
// 设置内容模式
if (imageView.contentMode == UIViewContentModeScaleToFill) {
imageView.contentMode = UIViewContentModeScaleAspectFit;
}
}
}
+ (void)enableAsyncDrawing:(UIView *)view {
if (!view) return;
// 启用异步绘制
view.layer.drawsAsynchronously = YES;
// 对于复杂视图,启用光栅化
if (view.subviews.count > 5 || view.layer.shadowOpacity > 0) {
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [UIScreen mainScreen].scale;
}
}
+ (void)optimizeLayerProperties:(CALayer *)layer {
if (!layer) return;
// 1. 优化边界设置
if (layer.cornerRadius > 0) {
layer.masksToBounds = YES;
}
// 2. 优化阴影设置
if (layer.shadowOpacity > 0) {
// 使用阴影路径提高性能
if (!layer.shadowPath) {
layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds
cornerRadius:layer.cornerRadius].CGPath;
}
// 启用光栅化
layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale;
}
// 3. 优化边框设置
if (layer.borderWidth > 0) {
// 确保边框颜色已设置
if (!layer.borderColor) {
layer.borderColor = [UIColor blackColor].CGColor;
}
}
// 4. 递归优化子图层
for (CALayer *sublayer in layer.sublayers) {
[self optimizeLayerProperties:sublayer];
}
}
@end
响应式布局与适配
自适应布局管理器
@interface AdaptiveLayoutManager : NSObject
@property (nonatomic, weak) UIView *containerView;
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSArray<NSLayoutConstraint *> *> *constraintGroups;
- (instancetype)initWithContainerView:(UIView *)containerView;
- (void)addConstraintGroup:(NSString *)groupName constraints:(NSArray<NSLayoutConstraint *> *)constraints;
- (void)activateConstraintGroup:(NSString *)groupName;
- (void)deactivateConstraintGroup:(NSString *)groupName;
- (void)updateLayoutForTraitCollection:(UITraitCollection *)traitCollection;
- (void)setupResponsiveLayout;
@end
@implementation AdaptiveLayoutManager
- (instancetype)initWithContainerView:(UIView *)containerView {
self = [super init];
if (self) {
_containerView = containerView;
_constraintGroups = [NSMutableDictionary dictionary];
}
return self;
}
- (void)addConstraintGroup:(NSString *)groupName constraints:(NSArray<NSLayoutConstraint *> *)constraints {
_constraintGroups[groupName] = constraints;
}
- (void)activateConstraintGroup:(NSString *)groupName {
NSArray<NSLayoutConstraint *> *constraints = _constraintGroups[groupName];
if (constraints) {
[NSLayoutConstraint activateConstraints:constraints];
}
}
- (void)deactivateConstraintGroup:(NSString *)groupName {
NSArray<NSLayoutConstraint *> *constraints = _constraintGroups[groupName];
if (constraints) {
[NSLayoutConstraint deactivateConstraints:constraints];
}
}
- (void)updateLayoutForTraitCollection:(UITraitCollection *)traitCollection {
// 先停用所有约束组
for (NSString *groupName in _constraintGroups.allKeys) {
[self deactivateConstraintGroup:groupName];
}
// 根据特征集合激活相应的约束组
if (traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
// 紧凑宽度(iPhone竖屏)
[self activateConstraintGroup:@"compact"];
} else if (traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {
// 常规宽度(iPad或iPhone横屏)
[self activateConstraintGroup:@"regular"];
}
if (traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
// 紧凑高度(iPhone横屏)
[self activateConstraintGroup:@"compactHeight"];
} else if (traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
// 常规高度(iPhone竖屏或iPad)
[self activateConstraintGroup:@"regularHeight"];
}
// 动画更新布局
[UIView animateWithDuration:0.3 animations:^{
[self.containerView layoutIfNeeded];
}];
}
- (void)setupResponsiveLayout {
// 这个方法应该在具体的视图控制器中实现
// 这里提供一个示例
UIView *contentView = [[UIView alloc] init];
contentView.backgroundColor = [UIColor lightGrayColor];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
[_containerView addSubview:contentView];
// 紧凑宽度约束(iPhone竖屏)
NSArray *compactConstraints = @[
[contentView.leadingAnchor constraintEqualToAnchor:_containerView.leadingAnchor constant:16],
[contentView.trailingAnchor constraintEqualToAnchor:_containerView.trailingAnchor constant:-16],
[contentView.topAnchor constraintEqualToAnchor:_containerView.safeAreaLayoutGuide.topAnchor constant:20],
[contentView.heightAnchor constraintEqualToConstant:200]
];
// 常规宽度约束(iPad或iPhone横屏)
NSArray *regularConstraints = @[
[contentView.centerXAnchor constraintEqualToAnchor:_containerView.centerXAnchor],
[contentView.widthAnchor constraintEqualToConstant:400],
[contentView.topAnchor constraintEqualToAnchor:_containerView.safeAreaLayoutGuide.topAnchor constant:40],
[contentView.heightAnchor constraintEqualToConstant:300]
];
[self addConstraintGroup:@"compact" constraints:compactConstraints];
[self addConstraintGroup:@"regular" constraints:regularConstraints];
}
@end
UI编程最佳实践
设计原则
- 单一职责原则:每个自定义控件只负责一个特定的功能
- 可复用性:设计通用的、可配置的控件组件
- 性能优先:优化渲染性能,避免不必要的重绘
- 响应式设计:支持不同屏幕尺寸和方向
- 可访问性:确保控件支持VoiceOver等辅助功能
开发建议
- 合理使用Auto Layout:避免约束冲突,优化约束性能
- 图层优化:合理设置图层属性,避免离屏渲染
- 内存管理:及时释放不需要的视图和图层
- 动画性能:使用Core Animation进行高性能动画
- 测试验证:在不同设备和系统版本上测试界面效果
调试技巧
- 视图调试器:使用Xcode的视图调试器检查视图层次
- 性能监控:监控FPS和渲染性能
- 约束调试:使用约束调试工具解决布局问题
- 内存分析:使用Instruments分析内存使用
- 动画调试:使用Core Animation工具调试动画性能
总结
iOS UI编程是一个复杂而精细的领域,需要深入理解UIKit框架、Core Animation、性能优化等多个方面。通过掌握自定义控件开发、高级动画技术、性能优化策略和响应式布局,开发者可以创建出优秀的用户界面。
随着iOS系统的不断发展,新的UI技术和设计理念也在不断涌现。开发者应该持续学习新技术,关注用户体验,并在实践中不断优化和改进自己的UI编程技能。良好的UI设计不仅能提升应用的视觉效果,更能显著改善用户的使用体验。
2. 视图层次管理
@interface ViewHierarchyManager : NSObject
@property (nonatomic, weak) UIView *containerView;
- (instancetype)initWithContainerView:(UIView *)containerView;
- (void)addSubview:(UIView *)subview withConstraints:(NSDictionary *)constraints;
- (void)removeSubview:(UIView *)subview;
- (void)bringSubviewToFront:(UIView *)subview;
- (void)sendSubviewToBack:(UIView *)subview;
- (void)optimizeViewHierarchy;
@end
@implementation ViewHierarchyManager
- (instancetype)initWithContainerView:(UIView *)containerView {
self = [super init];
if (self) {
_containerView = containerView;
}
return self;
}
- (void)addSubview:(UIView *)subview withConstraints:(NSDictionary *)constraints {
if (!subview || !self.containerView) return;
// 1. 添加子视图
[self.containerView addSubview:subview];
subview.translatesAutoresizingMaskIntoConstraints = NO;
// 2. 应用约束
NSMutableArray *constraintArray = [NSMutableArray array];
// 处理水平约束
if (constraints[@"leading"]) {
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:[constraints[@"leading"] floatValue]];
[constraintArray addObject:leading];
}
if (constraints[@"trailing"]) {
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:-[constraints[@"trailing"] floatValue]];
[constraintArray addObject:trailing];
}
// 处理垂直约束
if (constraints[@"top"]) {
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:[constraints[@"top"] floatValue]];
[constraintArray addObject:top];
}
if (constraints[@"bottom"]) {
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-[constraints[@"bottom"] floatValue]];
[constraintArray addObject:bottom];
}
// 处理尺寸约束
if (constraints[@"width"]) {
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:[constraints[@"width"] floatValue]];
[constraintArray addObject:width];
}
if (constraints[@"height"]) {
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:[constraints[@"height"] floatValue]];
[constraintArray addObject:height];
}
// 3. 激活约束
[NSLayoutConstraint activateConstraints:constraintArray];
// 4. 设置视图标识
subview.tag = self.containerView.subviews.count;
}
- (void)removeSubview:(UIView *)subview {
if (!subview) return;
// 1. 移除约束
[subview removeFromSuperview];
// 2. 重新排列其他子视图的tag
[self reorderSubviewTags];
}
- (void)reorderSubviewTags {
NSArray *subviews = self.containerView.subviews;
for (NSInteger i = 0; i < subviews.count; i++) {
UIView *subview = subviews[i];
subview.tag = i + 1;
}
}
- (void)bringSubviewToFront:(UIView *)subview {
if (!subview || subview.superview != self.containerView) return;
[self.containerView bringSubviewToFront:subview];
[self reorderSubviewTags];
}
- (void)sendSubviewToBack:(UIView *)subview {
if (!subview || subview.superview != self.containerView) return;
[self.containerView sendSubviewToBack:subview];
[self reorderSubviewTags];
}
- (void)optimizeViewHierarchy {
// 1. 移除隐藏的视图
NSMutableArray *viewsToRemove = [NSMutableArray array];
for (UIView *subview in self.containerView.subviews) {
if (subview.hidden && subview.alpha == 0) {
[viewsToRemove addObject:subview];
}
}
for (UIView *view in viewsToRemove) {
[self removeSubview:view];
}
// 2. 合并相似的视图
[self mergeSimilarViews];
// 3. 优化图层设置
[self optimizeLayerSettings];
}
- (void)mergeSimilarViews {
// 查找可以合并的相似视图
NSMutableArray *similarGroups = [NSMutableArray array];
for (UIView *subview in self.containerView.subviews) {
if ([subview isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)subview;
// 查找相同样式的标签
NSMutableArray *similarLabels = [NSMutableArray arrayWithObject:label];
for (UIView *otherSubview in self.containerView.subviews) {
if (otherSubview != subview && [otherSubview isKindOfClass:[UILabel class]]) {
UILabel *otherLabel = (UILabel *)otherSubview;
if ([self areLabelsStyleSimilar:label other:otherLabel]) {
[similarLabels addObject:otherLabel];
}
}
}
if (similarLabels.count > 1) {
[similarGroups addObject:similarLabels];
}
}
}
// 处理相似视图组
for (NSArray *group in similarGroups) {
[self processSimilarViewGroup:group];
}
}
- (BOOL)areLabelsStyleSimilar:(UILabel *)label1 other:(UILabel *)label2 {
return [label1.font isEqual:label2.font] &&
[label1.textColor isEqual:label2.textColor] &&
label1.textAlignment == label2.textAlignment;
}
- (void)processSimilarViewGroup:(NSArray *)group {
// 这里可以实现具体的合并逻辑
// 例如:创建一个容器视图来包含相似的视图
NSLog(@"Found %lu similar views that could be optimized", (unsigned long)group.count);
}
- (void)optimizeLayerSettings {
for (UIView *subview in self.containerView.subviews) {
// 1. 优化不透明设置
if (subview.backgroundColor && ![subview.backgroundColor isEqual:[UIColor clearColor]]) {
subview.opaque = YES;
}
// 2. 优化光栅化设置
if (subview.layer.shadowOpacity > 0 || subview.layer.cornerRadius > 0) {
subview.layer.shouldRasterize = YES;
subview.layer.rasterizationScale = [UIScreen mainScreen].scale;
}
// 3. 优化边界设置
if (subview.layer.cornerRadius > 0) {
subview.layer.masksToBounds = YES;
}
}
}
@end
自定义控件开发
高级按钮控件
@interface AdvancedButton : UIControl
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIImageView *iconImageView;
@property (nonatomic, strong) CAGradientLayer *gradientLayer;
@property (nonatomic, strong) CAShapeLayer *borderLayer;
// 外观属性
@property (nonatomic, strong) UIColor *normalBackgroundColor;
@property (nonatomic, strong) UIColor *highlightedBackgroundColor;
@property (nonatomic, strong) UIColor *disabledBackgroundColor;
@property (nonatomic, assign) CGFloat cornerRadius;
@property (nonatomic, assign) CGFloat borderWidth;
@property (nonatomic, strong) UIColor *borderColor;
// 渐变属性
@property (nonatomic, strong) NSArray<UIColor *> *gradientColors;
@property (nonatomic, assign) CGPoint gradientStartPoint;
@property (nonatomic, assign) CGPoint gradientEndPoint;
// 图标属性
@property (nonatomic, strong) UIImage *iconImage;
@property (nonatomic, assign) CGFloat iconSize;
@property (nonatomic, assign) CGFloat iconTitleSpacing;
// 动画属性
@property (nonatomic, assign) NSTimeInterval animationDuration;
@property (nonatomic, assign) BOOL enablePressAnimation;
@property (nonatomic, assign) CGFloat pressAnimationScale;
- (void)setTitle:(NSString *)title forState:(UIControlState)state;
- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state;
- (void)setBackgroundColor:(UIColor *)backgroundColor forState:(UIControlState)state;
- (void)updateAppearance;
@end
@implementation AdvancedButton
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupButton];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setupButton];
}
return self;
}
- (void)setupButton {
// 1. 设置默认属性
_cornerRadius = 8.0;
_borderWidth = 1.0;
_borderColor = [UIColor clearColor];
_iconSize = 24.0;
_iconTitleSpacing = 8.0;
_animationDuration = 0.15;
_enablePressAnimation = YES;
_pressAnimationScale = 0.95;
// 2. 设置默认颜色
_normalBackgroundColor = [UIColor systemBlueColor];
_highlightedBackgroundColor = [UIColor systemBlueColor];
_disabledBackgroundColor = [UIColor systemGrayColor];
// 3. 创建渐变图层
_gradientLayer = [CAGradientLayer layer];
_gradientLayer.cornerRadius = _cornerRadius;
_gradientLayer.masksToBounds = YES;
[self.layer insertSublayer:_gradientLayer atIndex:0];
// 4. 创建边框图层
_borderLayer = [CAShapeLayer layer];
_borderLayer.fillColor = [UIColor clearColor].CGColor;
_borderLayer.strokeColor = _borderColor.CGColor;
_borderLayer.lineWidth = _borderWidth;
[self.layer addSublayer:_borderLayer];
// 5. 创建标题标签
_titleLabel = [[UILabel alloc] init];
_titleLabel.textAlignment = NSTextAlignmentCenter;
_titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
_titleLabel.textColor = [UIColor whiteColor];
_titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_titleLabel];
// 6. 创建图标视图
_iconImageView = [[UIImageView alloc] init];
_iconImageView.contentMode = UIViewContentModeScaleAspectFit;
_iconImageView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_iconImageView];
// 7. 设置约束
[self setupConstraints];
// 8. 添加触摸事件
[self addTarget:self action:@selector(touchDown:) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:@selector(touchUp:) forControlEvents:UIControlEventTouchUpInside];
[self addTarget:self action:@selector(touchUp:) forControlEvents:UIControlEventTouchUpOutside];
[self addTarget:self action:@selector(touchUp:) forControlEvents:UIControlEventTouchCancel];
}
- (void)setupConstraints {
// 图标约束
[NSLayoutConstraint activateConstraints:@[
[_iconImageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:16],
[_iconImageView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
[_iconImageView.widthAnchor constraintEqualToConstant:_iconSize],
[_iconImageView.heightAnchor constraintEqualToConstant:_iconSize]
]];
// 标题约束
[NSLayoutConstraint activateConstraints:@[
[_titleLabel.leadingAnchor constraintEqualToAnchor:_iconImageView.trailingAnchor constant:_iconTitleSpacing],
[_titleLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-16],
[_titleLabel.centerYAnchor constraintEqualToAnchor:self.centerYAnchor]
]];
}
- (void)layoutSubviews {
[super layoutSubviews];
// 更新渐变图层frame
_gradientLayer.frame = self.bounds;
// 更新边框图层路径
UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:_cornerRadius];
_borderLayer.path = borderPath.CGPath;
// 更新外观
[self updateAppearance];
}
- (void)updateAppearance {
// 1. 更新渐变
if (_gradientColors && _gradientColors.count > 0) {
NSMutableArray *cgColors = [NSMutableArray array];
for (UIColor *color in _gradientColors) {
[cgColors addObject:(id)color.CGColor];
}
_gradientLayer.colors = cgColors;
_gradientLayer.startPoint = _gradientStartPoint;
_gradientLayer.endPoint = _gradientEndPoint;
_gradientLayer.hidden = NO;
} else {
_gradientLayer.hidden = YES;
// 使用纯色背景
UIColor *backgroundColor;
if (!self.enabled) {
backgroundColor = _disabledBackgroundColor;
} else if (self.highlighted) {
backgroundColor = _highlightedBackgroundColor;
} else {
backgroundColor = _normalBackgroundColor;
}
self.backgroundColor = backgroundColor;
}
// 2. 更新圆角
_gradientLayer.cornerRadius = _cornerRadius;
self.layer.cornerRadius = _cornerRadius;
// 3. 更新边框
_borderLayer.strokeColor = _borderColor.CGColor;
_borderLayer.lineWidth = _borderWidth;
// 4. 更新图标显示
_iconImageView.hidden = (_iconImage == nil);
if (_iconImage) {
_iconImageView.image = _iconImage;
}
}
- (void)setTitle:(NSString *)title forState:(UIControlState)state {
if (state == UIControlStateNormal) {
_titleLabel.text = title;
}
}
- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
if (state == UIControlStateNormal) {
_titleLabel.textColor = color;
}
}
- (void)setBackgroundColor:(UIColor *)backgroundColor forState:(UIControlState)state {
switch (state) {
case UIControlStateNormal:
_normalBackgroundColor = backgroundColor;
break;
case UIControlStateHighlighted:
_highlightedBackgroundColor = backgroundColor;
break;
case UIControlStateDisabled:
_disabledBackgroundColor = backgroundColor;
break;
default:
break;
}
[self updateAppearance];
}
- (void)setGradientColors:(NSArray<UIColor *> *)gradientColors {
_gradientColors = gradientColors;
[self updateAppearance];
}
- (void)setCornerRadius:(CGFloat)cornerRadius {
_cornerRadius = cornerRadius;
[self updateAppearance];
}
- (void)setBorderWidth:(CGFloat)borderWidth {
_borderWidth = borderWidth;
[self updateAppearance];
}
- (void)setBorderColor:(UIColor *)borderColor {
_borderColor = borderColor;
[self updateAppearance];
}
- (void)setIconImage:(UIImage *)iconImage {
_iconImage = iconImage;
[self updateAppearance];
}
- (void)touchDown:(AdvancedButton *)sender {
if (_enablePressAnimation) {
[UIView animateWithDuration:_animationDuration animations:^{
self.transform = CGAffineTransformMakeScale(self.pressAnimationScale, self.pressAnimationScale);
}];
}
}
- (void)touchUp:(AdvancedButton *)sender {
if (_enablePressAnimation) {
[UIView animateWithDuration:_animationDuration animations:^{
self.transform = CGAffineTransformIdentity;
}];
}
}
@end
自定义进度指示器
@interface CircularProgressView : UIView
@property (nonatomic, assign) CGFloat progress; // 0.0 - 1.0
@property (nonatomic, strong) UIColor *progressColor;
@property (nonatomic, strong) UIColor *trackColor;
@property (nonatomic, assign) CGFloat lineWidth;
@property (nonatomic, assign) BOOL showPercentageLabel;
@property (nonatomic, strong) UILabel *percentageLabel;
@property (nonatomic, strong) CAShapeLayer *trackLayer;
@property (nonatomic, strong) CAShapeLayer *progressLayer;
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated;
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated duration:(NSTimeInterval)duration;
@end
@implementation CircularProgressView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupProgressView];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setupProgressView];
}
return self;
}
- (void)setupProgressView {
// 1. 设置默认属性
_progress = 0.0;
_progressColor = [UIColor systemBlueColor];
_trackColor = [UIColor systemGray5Color];
_lineWidth = 8.0;
_showPercentageLabel = YES;
self.backgroundColor = [UIColor clearColor];
// 2. 创建轨道图层
_trackLayer = [CAShapeLayer layer];
_trackLayer.fillColor = [UIColor clearColor].CGColor;
_trackLayer.strokeColor = _trackColor.CGColor;
_trackLayer.lineWidth = _lineWidth;
_trackLayer.lineCap = kCALineCapRound;
[self.layer addSublayer:_trackLayer];
// 3. 创建进度图层
_progressLayer = [CAShapeLayer layer];
_progressLayer.fillColor = [UIColor clearColor].CGColor;
_progressLayer.strokeColor = _progressColor.CGColor;
_progressLayer.lineWidth = _lineWidth;
_progressLayer.lineCap = kCALineCapRound;
_progressLayer.strokeEnd = 0.0;
[self.layer addSublayer:_progressLayer];
// 4. 创建百分比标签
_percentageLabel = [[UILabel alloc] init];
_percentageLabel.textAlignment = NSTextAlignmentCenter;
_percentageLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
_percentageLabel.textColor = [UIColor labelColor];
_percentageLabel.text = @"0%";
_percentageLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_percentageLabel];
// 5. 设置约束
[NSLayoutConstraint activateConstraints:@[
[_percentageLabel.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
[_percentageLabel.centerYAnchor constraintEqualToAnchor:self.centerYAnchor]
]];
}
- (void)layoutSubviews {
[super layoutSubviews];
[self updatePaths];
}
- (void)updatePaths {
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = (MIN(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)) - _lineWidth) / 2;
// 创建圆形路径(从顶部开始,顺时针)
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:-M_PI_2
endAngle:3 * M_PI_2
clockwise:YES];
_trackLayer.path = path.CGPath;
_progressLayer.path = path.CGPath;
}
- (void)setProgress:(CGFloat)progress {
[self setProgress:progress animated:NO];
}
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
[self setProgress:progress animated:animated duration:0.3];
}
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated duration:(NSTimeInterval)duration {
// 限制进度值范围
progress = MAX(0.0, MIN(1.0, progress));
CGFloat oldProgress = _progress;
_progress = progress;
if (animated) {
// 创建进度动画
CABasicAnimation *progressAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
progressAnimation.fromValue = @(oldProgress);
progressAnimation.toValue = @(progress);
progressAnimation.duration = duration;
progressAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[_progressLayer addAnimation:progressAnimation forKey:@"progressAnimation"];
// 创建百分比标签动画
if (_showPercentageLabel) {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updatePercentageLabel:)];
displayLink.preferredFramesPerSecond = 60;
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
// 存储动画信息
objc_setAssociatedObject(displayLink, "startProgress", @(oldProgress), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(displayLink, "endProgress", @(progress), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(displayLink, "startTime", @(CACurrentMediaTime()), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(displayLink, "duration", @(duration), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
} else {
_progressLayer.strokeEnd = progress;
[self updatePercentageLabelWithProgress:progress];
}
}
- (void)updatePercentageLabel:(CADisplayLink *)displayLink {
NSNumber *startProgressNum = objc_getAssociatedObject(displayLink, "startProgress");
NSNumber *endProgressNum = objc_getAssociatedObject(displayLink, "endProgress");
NSNumber *startTimeNum = objc_getAssociatedObject(displayLink, "startTime");
NSNumber *durationNum = objc_getAssociatedObject(displayLink, "duration");
CGFloat startProgress = startProgressNum.floatValue;
CGFloat endProgress = endProgressNum.floatValue;
CFTimeInterval startTime = startTimeNum.doubleValue;
NSTimeInterval duration = durationNum.doubleValue;
CFTimeInterval currentTime = CACurrentMediaTime();
CGFloat elapsed = (currentTime - startTime) / duration;
if (elapsed >= 1.0) {
// 动画完成
[self updatePercentageLabelWithProgress:endProgress];
[displayLink invalidate];
} else {
// 计算当前进度
CGFloat currentProgress = startProgress + (endProgress - startProgress) * elapsed;
[self updatePercentageLabelWithProgress:currentProgress];
}
}
- (void)updatePercentageLabelWithProgress:(CGFloat)progress {
if (_showPercentageLabel) {
NSInteger percentage = (NSInteger)(progress * 100);
_percentageLabel.text = [NSString stringWithFormat:@"%ld%%", (long)percentage];
}
}
- (void)setProgressColor:(UIColor *)progressColor {
_progressColor = progressColor;
_progressLayer.strokeColor = progressColor.CGColor;
}
- (void)setTrackColor:(UIColor *)trackColor {
_trackColor = trackColor;
_trackLayer.strokeColor = trackColor.CGColor;
}
- (void)setLineWidth:(CGFloat)lineWidth {
_lineWidth = lineWidth;
_trackLayer.lineWidth = lineWidth;
_progressLayer.lineWidth = lineWidth;
[self updatePaths];
}
- (void)setShowPercentageLabel:(BOOL)showPercentageLabel {
_showPercentageLabel = showPercentageLabel;
_percentageLabel.hidden = !showPercentageLabel;
}
@end
上一篇 下一篇