Go directly to the complete code
// // BannerView.m //Test // // Created by liubo on 2023/7/20. // #import "LB3DBannerView.h" #import <Masonry/Masonry.h> #import <CoreMotion/CoreMotion.h> @interface LB3DBannerView () { CGFloat maxOffset; CGFloat lastGravigyX; CGFloat lastGravityY; NSTimeInterval deviceMotionUpdateInterval; CGPoint frontImageViewCenter; CGPoint secondFrontImageViewCenter; CGPoint backImageViewCenter; } @property (nonatomic, strong) UIImageView *frontImageView; @property (nonatomic, strong) UIImageView *secondFrontImageView; @property (nonatomic, strong) UIImageView *middleImageView; @property (nonatomic, strong) UIImageView *backImageView; @property (nonatomic, strong) CMMotionManager *motionManager; @end @implementationLB3DBannerView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setUpUI]; [self setUpContraints]; self.clipsToBounds = YES; } return self; } - (void)setUpUI { maxOffset = 15; deviceMotionUpdateInterval = 1 / 120.0; frontImageViewCenter = CGPointZero; backImageViewCenter = CGPointZero; secondFrontImageViewCenter = CGPointZero; [self addSubview:self.backImageView]; [self addSubview:self.middleImageView]; [self addSubview:self.secondFrontImageView]; [self addSubview:self.frontImageView]; } - (void)setUpContraints { [self.backImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(self.mas_width).with.offset(maxOffset * 2); make.height.equalTo(self.mas_height).with.offset(maxOffset * 2); make.centerX.mas_equalTo(0); make.centerY.mas_equalTo(0); }]; [self.middleImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(self.mas_width).with.offset(maxOffset * 2/3); make.height.equalTo(self.mas_height).with.offset(maxOffset * 2/3); make.centerX.mas_equalTo(0); make.centerY.mas_equalTo(0); }]; [self.secondFrontImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(self.mas_width).with.offset(maxOffset); make.height.equalTo(self.mas_height).with.offset(maxOffset); make.centerX.mas_equalTo(0); make.centerY.mas_equalTo(0); }]; [self.frontImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(self.mas_width).with.offset(maxOffset * 2); make.height.equalTo(self.mas_height).with.offset(maxOffset * 2); make.centerX.centerY.mas_equalTo(0); }]; } - (void)layoutSubviews { [super layoutSubviews]; backImageViewCenter = self.center; frontImageViewCenter = self.center; } - (void)start { [self startMotion]; } - (void)stop { [self.motionManager stopDeviceMotionUpdates]; } - (void)updateWithArray:(NSArray *)imageNames { self.frontImageView.image = [UIImage imageNamed:imageNames[0]]; self.secondFrontImageView.image = [UIImage imageNamed:imageNames[1]]; self.middleImageView.image = [UIImage imageNamed:imageNames[2]]; self.backImageView.image = [UIImage imageNamed:imageNames[3]]; } - (void)startMotion { if (!self.motionManager.isDeviceMotionAvailable) { return; } __weak LB3DBannerView *weakSelf = self; [self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) { if (!motion) { return; } [weakSelf updateWithGravityX:motion.gravity.x gravityY:motion.gravity.y gravityZ:motion.gravity.z]; }]; } - (void)updateWithGravityX:(double)gravityX gravityY:(double)gravityY gravityZ:(double)gravityZ { //Because at an angle of 45 degrees upward, the gravity value is -0.5. The design requires this position as the basis, so -0.5 must be subtracted. gravityY -= (-0.5); gravityY *= 2; //The maximum bargaining amount is maxoffset, so gravityY is maximum 1 gravityY = MIN(1, MAX(-1, gravityY)); gravityX *= 2; gravityX = MIN(1, MAX(-1, gravityX)); double timeInterval = sqrt(pow((gravityX - lastGravigyX),2) + pow((gravityY - lastGravityY), 2)) * deviceMotionUpdateInterval; NSString *animationKey = @"positionAnimation"; CGPoint newBackImageViewCenter = self.backImageView.center; newBackImageViewCenter.x = (newBackImageViewCenter.x - gravityX * maxOffset); newBackImageViewCenter.y = (newBackImageViewCenter.y + gravityY * maxOffset); CABasicAnimation *backImageViewAnimation = [CABasicAnimation animationWithKeyPath:@"position"]; backImageViewAnimation.fromValue = [NSValue valueWithCGPoint:backImageViewCenter]; backImageViewAnimation.toValue = [NSValue valueWithCGPoint:newBackImageViewCenter]; backImageViewAnimation.duration = timeInterval; backImageViewAnimation.fillMode = kCAFillModeForwards; backImageViewAnimation.removedOnCompletion = NO; [self.backImageView.layer removeAnimationForKey:animationKey]; [self.backImageView.layer addAnimation:backImageViewAnimation forKey:animationKey]; CGPoint newFrontImageViewCenter = self.frontImageView.center; newFrontImageViewCenter.x + = gravityX * maxOffset; newFrontImageViewCenter.y -= gravityY * maxOffset; CABasicAnimation *frontImageViewAnimation = [CABasicAnimation animationWithKeyPath:@"position"]; frontImageViewAnimation.fromValue = [NSValue valueWithCGPoint:frontImageViewCenter]; frontImageViewAnimation.toValue = [NSValue valueWithCGPoint:newFrontImageViewCenter]; frontImageViewAnimation.duration = timeInterval; frontImageViewAnimation.fillMode = kCAFillModeForwards; frontImageViewAnimation.removedOnCompletion = NO; [self.frontImageView.layer removeAnimationForKey:animationKey]; [self.frontImageView.layer addAnimation:frontImageViewAnimation forKey:animationKey]; CGPoint newSecondFrontImageViewCenter = self.middleImageView.center; newSecondFrontImageViewCenter.x -= gravityX * maxOffset/3; newSecondFrontImageViewCenter.y + = gravityY * maxOffset/3; CABasicAnimation *secondfrontImageViewAnimation = [CABasicAnimation animationWithKeyPath:@"position"]; secondfrontImageViewAnimation.fromValue = [NSValue valueWithCGPoint:secondFrontImageViewCenter]; secondfrontImageViewAnimation.toValue = [NSValue valueWithCGPoint:newSecondFrontImageViewCenter]; secondfrontImageViewAnimation.duration = timeInterval; secondfrontImageViewAnimation.fillMode = kCAFillModeForwards; secondfrontImageViewAnimation.removedOnCompletion = NO; [self.middleImageView.layer removeAnimationForKey:animationKey]; [self.middleImageView.layer addAnimation:secondfrontImageViewAnimation forKey:animationKey]; backImageViewCenter = newBackImageViewCenter; frontImageViewCenter = newFrontImageViewCenter; secondFrontImageViewCenter = newSecondFrontImageViewCenter; } #pragma mark - lazy load - (UIImageView *)frontImageView { if (!_frontImageView) { _frontImageView = [[UIImageView alloc] init]; _frontImageView.contentMode = UIViewContentModeScaleAspectFill; } return _frontImageView; } - (UIImageView *)secondFrontImageView { if (!_secondFrontImageView) { _secondFrontImageView = [[UIImageView alloc] init]; _secondFrontImageView.contentMode = UIViewContentModeScaleAspectFill; } return _secondFrontImageView; } - (UIImageView *)middleImageView { if (!_middleImageView) { _middleImageView = [[UIImageView alloc] init]; _middleImageView.contentMode = UIViewContentModeScaleAspectFill; } return _middleImageView; } - (UIImageView *)backImageView { if (!_backImageView) { _backImageView = [[UIImageView alloc] init]; _backImageView.contentMode = UIViewContentModeScaleAspectFill; } return _backImageView; } - (CMMotionManager *)motionManager { if (!_motionManager) { _motionManager = [[CMMotionManager alloc] init]; _motionManager.deviceMotionUpdateInterval = deviceMotionUpdateInterval; } return _motionManager; } @end