Files
keyboard/keyBoard/Class/Guard/V/KBKeyboardMaskView.m
2025-11-28 16:06:34 +08:00

229 lines
8.2 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBKeyboardMaskView.m
// keyBoard
//
// Created by Mac on 2025/11/27.
//
#import "KBKeyboardMaskView.h"
#import <AVFoundation/AVFoundation.h>
@interface KBKeyboardMaskView ()
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UIView *videoContainerView; // 承载引导视频的容器,复用原 GIF 尺寸逻辑
@property (nonatomic, strong) AVPlayer *kb_maskPlayer; // 键盘蒙层引导视频播放器
@property (nonatomic, strong) AVPlayerLayer *kb_maskPlayerLayer;
@property (nonatomic, strong) UIImageView *tipLabel; // 顶部提示图
@property (nonatomic, assign) CGFloat keyboardHeight;
@end
@implementation KBKeyboardMaskView
static const CGFloat KGifViewH = (209);
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (!self) return nil;
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
self.userInteractionEnabled = YES;
// 返回按钮
_backButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *backImg = [UIImage imageNamed:@"close_white2_icon"];
[_backButton setImage:backImg forState:UIControlStateNormal];
[self addSubview:_backButton];
[_backButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(9);
make.top.equalTo(self.mas_safeAreaLayoutGuideTop).offset(4);
make.width.height.mas_equalTo(40);
}];
// 视频区域(尺寸与原 GIF 一致)
_videoContainerView = [UIView new];
_videoContainerView.backgroundColor = [UIColor clearColor];
_videoContainerView.clipsToBounds = YES;
_videoContainerView.layer.cornerRadius = 30.0;
_videoContainerView.layer.masksToBounds = YES;
[self addSubview:_videoContainerView];
[_videoContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self);
make.width.mas_equalTo(KBFit(316));
make.height.mas_equalTo(KBFit(KGifViewH));
// 竖直方向不在这里约束,由 layoutSubviews 手动布局
}];
// 顶部提示图
_tipLabel = [UIImageView new];
_tipLabel.image = [UIImage imageNamed:@"mask_top_title"];
_tipLabel.contentMode = UIViewContentModeScaleAspectFit;
[self addSubview:_tipLabel];
// 整个蒙层点击:激活输入框
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapMask:)];
[self addGestureRecognizer:tap];
// 监听 App 前后台,保证从后台返回时视频能继续播放
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(kb_maskAppDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(kb_maskAppDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
// 初始化并开始播放引导视频(循环)
[self kb_setupGuideVideoPlayerIfNeeded];
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)layoutSubviews {
[super layoutSubviews];
// 根据键盘高度,保证视频不被遮挡:
// - 无键盘:居中显示;
// - 有键盘:底部距离键盘上方 20pt若空间不足则向上顶到顶部预留的 safe 区域。
CGFloat viewH = CGRectGetHeight(self.bounds);
CGFloat gifH = KBFit(KGifViewH);
CGFloat topMargin = 80.0; // 预留给返回按钮和标题等
CGFloat bottomMargin = 20.0;
CGFloat y = 0;
if (self.keyboardHeight <= 0) {
// 无键盘:垂直居中
y = (viewH - gifH) * 0.5;
if (y < topMargin) y = topMargin;
} else {
CGFloat maxBottom = viewH - self.keyboardHeight - bottomMargin;
y = maxBottom - gifH;
if (y < topMargin) y = topMargin;
}
CGRect gifFrame = self.videoContainerView.frame;
gifFrame.origin.y = y;
self.videoContainerView.frame = gifFrame;
// 布局顶部提示图:底部距离视频容器顶部 20pt居中显示
CGFloat labelMaxWidth = CGRectGetWidth(self.bounds) - 40.0; // 左右各留 20
if (labelMaxWidth < 0) { labelMaxWidth = 0; }
UIImage *tipImage = self.tipLabel.image;
if (!tipImage) { return; }
CGFloat imgW = tipImage.size.width;
CGFloat imgH = tipImage.size.height;
if (imgW <= 0 || imgH <= 0) { return; }
CGFloat scale = 1.0;
if (imgW > labelMaxWidth && labelMaxWidth > 0) {
scale = labelMaxWidth / imgW;
}
CGFloat labelW = imgW * scale;
CGFloat labelH = imgH * scale;
CGFloat labelX = (CGRectGetWidth(self.bounds) - labelW) * 0.5;
CGFloat labelBottom = CGRectGetMinY(self.videoContainerView.frame) - 20.0;
CGFloat labelY = labelBottom - labelH;
self.tipLabel.frame = CGRectMake(labelX, labelY, labelW, labelH);
// 使播放器图层铺满容器
if (self.kb_maskPlayerLayer) {
self.kb_maskPlayerLayer.frame = self.videoContainerView.bounds;
}
}
- (void)setHidden:(BOOL)hidden {
[super setHidden:hidden];
if (hidden) {
[self.kb_maskPlayer pause];
} else {
[self.kb_maskPlayer play];
}
}
- (void)onTapMask:(UITapGestureRecognizer *)gr {
CGPoint p = [gr locationInView:self];
// 如果点在返回按钮区域内,则不走 tapHandler交由按钮自己的事件处理
if (CGRectContainsPoint(self.backButton.frame, p)) {
return;
}
if (self.tapHandler) {
self.tapHandler();
}
}
- (void)updateForKeyboardHeight:(CGFloat)kbHeight
duration:(NSTimeInterval)duration
curve:(UIViewAnimationOptions)curve {
self.keyboardHeight = MAX(kbHeight, 0);
// 触发布局刷新,以便在 layoutSubviews 里根据最新键盘高度重算视频容器的 Y 值
[self setNeedsLayout];
[UIView animateWithDuration:duration
delay:0
options:curve
animations:^{
[self layoutIfNeeded];
}
completion:nil];
}
#pragma mark - App Lifecycle
- (void)kb_maskAppDidBecomeActive:(NSNotification *)note {
// 仅在当前蒙层真正显示在窗口上时恢复播放
if (self.window && !self.hidden) {
if (!self.kb_maskPlayer) {
[self kb_setupGuideVideoPlayerIfNeeded];
} else {
[self.kb_maskPlayer play];
}
}
}
- (void)kb_maskAppDidEnterBackground:(NSNotification *)note {
[self.kb_maskPlayer pause];
}
#pragma mark - Video
- (void)kb_setupGuideVideoPlayerIfNeeded {
if (self.kb_maskPlayer) { return; }
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:@"permiss_video_2" withExtension:@"mp4"];
if (!videoURL) { return; }
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:videoURL];
self.kb_maskPlayer = [AVPlayer playerWithPlayerItem:item];
self.kb_maskPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
self.kb_maskPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.kb_maskPlayer];
self.kb_maskPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
self.kb_maskPlayerLayer.cornerRadius = 30.0;
self.kb_maskPlayerLayer.masksToBounds = YES;
[self.videoContainerView.layer addSublayer:self.kb_maskPlayerLayer];
// 循环播放
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(kb_maskPlayerDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:item];
[self.kb_maskPlayer play];
}
- (void)kb_maskPlayerDidReachEnd:(NSNotification *)note {
AVPlayerItem *item = (AVPlayerItem *)note.object;
if (!item) return;
__weak typeof(self) weakSelf = self;
[item seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.kb_maskPlayer play];
}];
}
@end