Files
keyboard/keyBoard/VC/KBPermissionViewController.m
2025-11-28 13:20:50 +08:00

351 lines
13 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.

//
// KBPermissionViewController.m
// CustomKeyboard
//
// Created by Mac on 2025/10/27.
//
#import "KBPermissionViewController.h"
#import <AVFoundation/AVFoundation.h>
static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSizeContext;
@interface KBPermissionViewController ()
@property (nonatomic, strong) UILabel *titleLabel; // 标题
@property (nonatomic, strong) UILabel *tipsLabel; // 步骤提示
@property (nonatomic, strong) UIButton *openButton; // 去设置
@property (nonatomic, strong) UIButton *closeButton; // 去设置
@property (nonatomic, strong) UILabel *helpLabel; // 底部帮助
@property (nonatomic, strong) UIImageView *redImageView;
@property (nonatomic, strong) UIImageView *bgImageView;
// 权限引导视频播放器(循环播放,不提供暂停交互)
@property (nonatomic, strong) AVPlayer *kb_permPlayer;
@property (nonatomic, strong) AVPlayerLayer *kb_permPlayerLayer;
// 承载视频的裁剪容器,高度固定为 KBFit(550),内部内容从顶部开始显示,超出的部分从底部裁剪
@property (nonatomic, strong) UIView *videoContainerView;
@end
@implementation KBPermissionViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.bgImageView];
[self.view addSubview:self.redImageView];
// 懒加载控件 + 添加到视图
[self.view addSubview:self.closeButton];
[self.view addSubview:self.titleLabel];
[self.view addSubview:self.tipsLabel];
[self.view addSubview:self.videoContainerView];
[self.view addSubview:self.openButton];
[self.view addSubview:self.helpLabel];
[self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
// Masonry 约束
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(16);
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(20);
make.width.mas_equalTo(26);
make.height.mas_equalTo(26);
}];
[self.redImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.closeButton.mas_centerY);
make.right.equalTo(self.view).offset(20);
make.width.mas_equalTo(143);
make.height.mas_equalTo(132);
}];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.closeButton.mas_bottom).offset(13);
make.left.equalTo(self.view).offset(22);
make.height.mas_equalTo(24);
}];
[self.tipsLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.titleLabel.mas_bottom).offset(8);
make.left.equalTo(self.titleLabel);
}];
// 视频容器:左右各 25顶部距离 tipsLabel 32高度固定为 KBFit(550)
[self.videoContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.tipsLabel.mas_bottom).offset(KB_DEVICE_HAS_NOTCH ? 32 : 0);
make.left.equalTo(self.view).offset(25);
make.right.equalTo(self.view).offset(-25);
make.height.mas_equalTo(KB_DEVICE_HAS_NOTCH ? KBFit(550) : KBFit(500));
}];
[self.openButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(self.view).offset(-KB_SAFE_BOTTOM-20);
make.left.equalTo(self.view).offset(47);
make.right.equalTo(self.view).offset(-47);
make.height.mas_equalTo(60);
}];
// [self.helpLabel mas_makeConstraints:^(MASConstraintMaker *make) {
// make.top.equalTo(self.openButton.mas_bottom).offset(12);
// make.left.equalTo(self.view).offset(24);
// make.right.equalTo(self.view).offset(-24);
// }];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 进入页面时自动开始播放
[self kb_setupPermissionVideoPlayer];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 页面离开时停止播放,避免声音继续在其他界面播放
[self.kb_permPlayer pause];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (!self.kb_permPlayerLayer) { return; }
// 以「宽度」为基准等比缩放视频,高度按宽高比计算,从 videoContainerView 顶部对齐,
// 多出的部分由容器(固定高度 KBFit(550))从底部裁剪
CGSize videoSize = self.kb_permPlayer.currentItem.presentationSize;
if (videoSize.width <= 0) { return; } // 尚未拿到真实尺寸时先不布局
CGFloat aspect = videoSize.height / videoSize.width;
CGRect containerBounds = self.videoContainerView.bounds;
if (CGRectIsEmpty(containerBounds)) { return; }
CGFloat width = CGRectGetWidth(containerBounds); // 以容器宽度为基准
CGFloat height = width * aspect; // 按宽度等比算出真实视频高度
// 顶部对齐y = 0若 height > 容器高度,则底部会被裁剪
self.kb_permPlayerLayer.frame = CGRectMake(0, 0, width, height);
}
- (void)dealloc {
// 移除通知监听
[[NSNotificationCenter defaultCenter] removeObserver:self];
@try {
[self.kb_permPlayer.currentItem removeObserver:self
forKeyPath:@"presentationSize"
context:KBPermPlayerPresentationSizeContext];
} @catch (__unused NSException *e) {
// ignore if observer not set
}
}
#pragma mark - Actions
- (void)onBack {
// 支持两种展示方式:
// 1) 作为子控制器嵌入(无 presentingViewController→ 交由回调让父 VC 处理(通常 pop
// 2) 作为模态弹出 → 按原策略先 pop 再 dismiss
UIViewController *presenter = self.presentingViewController;
if (!presenter) {
if (self.backHandler) self.backHandler();
return;
}
UINavigationController *nav = nil;
if ([presenter isKindOfClass:UINavigationController.class]) {
nav = (UINavigationController *)presenter;
} else if (presenter.navigationController) {
nav = presenter.navigationController;
}
if (nav) {
[nav popViewControllerAnimated:NO];
[nav dismissViewControllerAnimated:YES completion:^{ if (self.backHandler) self.backHandler(); }];
} else {
[self dismissViewControllerAnimated:YES completion:^{ if (self.backHandler) self.backHandler(); }];
}
}
- (void)openSettings {
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
UIApplication *app = [UIApplication sharedApplication];
if ([app canOpenURL:url]) {
if (@available(iOS 10.0, *)) {
[app openURL:url options:@{} completionHandler:nil];
} else {
[app openURL:url];
}
}
}
- (void)closeButtonAction{
[self.navigationController popViewControllerAnimated:true];
}
#pragma mark - Video Player
// 初始化并开始在 cardView 中循环播放视频
- (void)kb_setupPermissionVideoPlayer {
// 避免重复初始化
if (self.kb_permPlayer) {
[self.kb_permPlayer play];
return;
}
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:@"permiss_video" withExtension:@"mp4"];
if (!videoURL) {
return;
}
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:videoURL];
self.kb_permPlayer = [AVPlayer playerWithPlayerItem:item];
self.kb_permPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
self.kb_permPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.kb_permPlayer];
// 使用等比模式,按我们计算好的 frame 显示;多余部分由容器裁剪
self.kb_permPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
self.kb_permPlayerLayer.cornerRadius = 20;
self.kb_permPlayerLayer.masksToBounds = true;
// 将视频图层添加到裁剪容器内部,由容器控制圆角与裁剪
[self.videoContainerView.layer addSublayer:self.kb_permPlayerLayer];
// 监听 presentationSize 变化,尺寸从 {0,0} 变为真实值时触发布局
[item addObserver:self
forKeyPath:@"presentationSize"
options:NSKeyValueObservingOptionNew
context:KBPermPlayerPresentationSizeContext];
// 播放结束后从头循环
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(kb_playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:item];
[self.kb_permPlayer play];
}
- (void)kb_playerItemDidReachEnd:(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_permPlayer play];
}];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {
if (context == KBPermPlayerPresentationSizeContext) {
// presentationSize 从 {0,0} 变为真实尺寸时,触发布局更新
dispatch_async(dispatch_get_main_queue(), ^{
[self.view setNeedsLayout];
});
return;
}
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
#pragma mark - Lazy Subviews
- (UIButton *)backButton {
if (!_backButton) {
_backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[_backButton setTitle:KBLocalized(@"common_back") forState:UIControlStateNormal];
_backButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
[_backButton setTitleColor:[UIColor darkTextColor] forState:UIControlStateNormal];
[_backButton addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside];
}
return _backButton;
}
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [UILabel new];
_titleLabel.text = (@"key of love keyboard");
_titleLabel.font = [UIFont systemFontOfSize:20 weight:UIFontWeightSemibold];
_titleLabel.textColor = [UIColor blackColor];
_titleLabel.textAlignment = NSTextAlignmentCenter;
}
return _titleLabel;
}
- (UILabel *)tipsLabel {
if (!_tipsLabel) {
_tipsLabel = [UILabel new];
_tipsLabel.text = (@"One-click to find a partner");
_tipsLabel.font = [UIFont systemFontOfSize:14];
_tipsLabel.textColor = [UIColor darkGrayColor];
_tipsLabel.textAlignment = NSTextAlignmentCenter;
}
return _tipsLabel;
}
- (UIButton *)openButton {
if (!_openButton) {
_openButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_openButton setTitle:@"Turn on the keyboard" forState:UIControlStateNormal];
_openButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
[_openButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_openButton.backgroundColor = [UIColor colorWithHex:KBBlackValue];
_openButton.layer.cornerRadius = 30;
[_openButton addTarget:self action:@selector(openSettings) forControlEvents:UIControlEventTouchUpInside];
}
return _openButton;
}
- (UIButton *)closeButton {
if (!_closeButton) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_closeButton setImage:[UIImage imageNamed:@"close_icon"] forState:UIControlStateNormal];
[_closeButton addTarget:self action:@selector(closeButtonAction) forControlEvents:UIControlEventTouchUpInside];
}
return _closeButton;
}
- (UILabel *)helpLabel {
if (!_helpLabel) {
_helpLabel = [UILabel new];
_helpLabel.text = KBLocalized(@"perm_help");
_helpLabel.font = [UIFont systemFontOfSize:12];
_helpLabel.textColor = [UIColor grayColor];
_helpLabel.textAlignment = NSTextAlignmentCenter;
_helpLabel.numberOfLines = 2;
}
return _helpLabel;
}
- (UIImageView *)bgImageView{
if (!_bgImageView) {
_bgImageView = [[UIImageView alloc] init];
_bgImageView.image = [UIImage imageNamed:@"qx_bg_icon"];
}
return _bgImageView;
}
- (UIImageView *)redImageView{
if (!_redImageView) {
_redImageView = [[UIImageView alloc] init];
_redImageView.image = [UIImage imageNamed:@"qx_ax_icon"];
}
return _redImageView;
}
- (UIView *)videoContainerView {
if (!_videoContainerView) {
_videoContainerView = [UIView new];
_videoContainerView.backgroundColor = [UIColor clearColor];
_videoContainerView.layer.cornerRadius = 20.0;
_videoContainerView.clipsToBounds = YES; // 超出容器高度的部分直接裁剪
}
return _videoContainerView;
}
@end