567 Commits

Author SHA1 Message Date
47291934a2 应用的皮肤不能删除 2026-02-28 21:23:38 +08:00
e619f48f93 移除主App后台音频声明负责审查风险 2026-02-28 18:21:36 +08:00
f55a70681c 处理KBFunctionTagCell正在执行又可以点击别的 2026-02-28 16:03:05 +08:00
cb86f7c32c 处理header 2026-02-28 15:38:12 +08:00
40ef964b8c 添加注销账号 2026-02-28 14:50:27 +08:00
4269fde923 1 2026-02-27 16:28:15 +08:00
c3e037e070 添加隐私,注销功能 2026-02-27 14:49:46 +08:00
a711be4c4d 跨进程 键盘用ai 在主应用里也要显示 2026-02-26 21:47:22 +08:00
69bd2b2af9 1:修改ios26tabbar的问题
2:修改键盘AI点击必须要登录装填
2026-02-26 19:38:17 +08:00
82222afd76 修复 KBChatPanel 发送内容校验 2026-02-25 20:16:31 +08:00
92ca5c6180 Fix KBAICommentInputView弹出位置 2026-02-25 17:13:25 +08:00
851c0d9531 去除假的用户信息 2026-02-25 11:15:04 +08:00
1c9013bede 新增获取客服接口 2026-02-24 20:45:15 +08:00
0a16a4f240 修改KBKeyboardPanelModeFunction 必须要登录状态 2026-02-24 18:04:13 +08:00
27d4b2b817 添加hud容错处理 2026-02-24 16:23:57 +08:00
bc623676ca 修改在手机信息页面,复制短信后,键盘按钮不存在, 背景也不存在 2026-02-24 15:24:23 +08:00
5edf1751ff 修改sign。
键盘里ai回复的bug
2026-02-24 14:59:06 +08:00
0ac47925fd 先提交 2026-02-24 13:38:51 +08:00
635ad932c7 修改vip 2026-02-12 20:06:44 +08:00
cbe0a53cac 删除无用的国家化 2026-02-11 21:16:15 +08:00
5c273c3963 修改bug 2026-02-11 21:09:37 +08:00
c9743cb363 处理第一次不滑动界面不传递数据的问题 2026-02-11 20:52:56 +08:00
f0cb69948e bug修复 2026-02-11 19:40:41 +08:00
0144f9cc6d 1 2026-02-11 19:31:12 +08:00
ae4070ae88 1 2026-02-11 19:18:26 +08:00
a83fd918a8 删除无关代码 2026-02-11 18:58:30 +08:00
4168da618e 1 2026-02-11 18:29:00 +08:00
d2ffada83f 添加KeyboardViewControllerHelp文件夹 2026-02-10 19:41:32 +08:00
76d387e08b 修改UI遮挡后面爱心和评论的问题 2026-02-10 19:15:05 +08:00
ea0df4fb19 修改UI 2026-02-10 19:12:09 +08:00
02323fb5f1 处理键盘闪的问题 2026-02-10 18:53:31 +08:00
3c71797b7b 处理键盘崩溃 2026-02-10 13:22:19 +08:00
4c57f16058 1 2026-02-10 10:21:21 +08:00
cb2e8467a7 处理UI 2026-02-09 19:31:47 +08:00
4dfd6f5cbb 处理UI 2026-02-09 16:53:30 +08:00
e4223b3a4c 处理国际化 2026-02-09 16:30:00 +08:00
3d19403539 修改bug 2026-02-09 14:24:31 +08:00
3cb02d5b76 处理键盘ai功能 2026-02-05 16:01:21 +08:00
750b391100 处理键盘语音svip权限弹窗,跳转主app充值 2026-02-05 14:10:24 +08:00
faccf6f10f 1 2026-02-04 23:40:06 +08:00
35b1fc0f1e 处理vip逻辑 2026-02-04 21:49:28 +08:00
b73f225d15 语音vip限制ai弹窗 2026-02-04 20:23:20 +08:00
dd59094a16 处理了评论新增评论,在聊天里的评论数也一同改变 2026-02-04 19:31:30 +08:00
bacaf537f3 调用删除接口 2026-02-04 19:01:58 +08:00
619d356d31 新增接口 2026-02-04 18:45:57 +08:00
db9f07d199 优化长按聊天记录 如果是自己 不要显示举报 2026-02-04 18:39:25 +08:00
3ed120106e 处理二级评论回复用户名称显示问题 2026-02-04 18:29:12 +08:00
ff4edab820 1 2026-02-04 18:10:39 +08:00
3e30f619b9 新增删除弹窗,修改bug 2026-02-04 16:57:19 +08:00
533e23ebfe 1 2026-02-04 16:03:39 +08:00
85fb407717 处理支付 2026-02-04 15:57:22 +08:00
c1b50b407d 2 2026-02-04 15:40:45 +08:00
7c7e2477cb 1 2026-02-04 15:20:53 +08:00
faae0297cb 1 2026-02-04 15:09:03 +08:00
e50eaecbd9 1 2026-02-04 14:59:02 +08:00
879dbb860c 2 2026-02-04 13:14:26 +08:00
b4e4b7b606 处理svip 2026-02-04 12:48:18 +08:00
68a610e0a8 处理kbpayvip 2026-02-04 12:33:01 +08:00
305326aa9a 添加placehold颜色 2026-02-03 20:51:48 +08:00
61095a379f 处理网络 2026-02-03 20:22:28 +08:00
822a814f85 处理搜索bug 2026-02-03 18:03:21 +08:00
0bd0392191 1 2026-02-03 17:00:42 +08:00
b9663037f5 添加侧边栏 2026-02-03 16:54:38 +08:00
a0923c8572 添加消息长按弹窗 2026-02-03 15:53:07 +08:00
d482cfcb7d 处理滚动 2026-02-03 15:01:08 +08:00
9e6d2906f8 修改KBChatUserMessageCell文字居中问题 2026-02-03 14:40:39 +08:00
6f7bb4f960 键盘的背景图添加高斯模糊 2026-02-03 14:22:44 +08:00
fa9af5ff1b 处理聊天信息没回来 切刀下一个collectionviewcell ,信息不对的问题(禁用滚动) 2026-02-03 14:05:29 +08:00
08628bcd1d aihome里统一发布消息控件 2026-02-03 13:31:52 +08:00
19cb29616f 优化发送输入框 2026-02-02 21:25:28 +08:00
6e50cdcd2a 1 2026-02-02 20:36:38 +08:00
f1b52151be 添加音频动画 2026-02-02 19:49:56 +08:00
993ec623af 1 2026-02-02 19:07:00 +08:00
0416a64235 处理为识别到语音 2026-02-02 17:41:23 +08:00
2b75ad90fb 修改举报和UI 2026-02-02 17:07:46 +08:00
0ac9030f80 1 2026-02-02 15:28:00 +08:00
ea9c40f64f 处理语音在B界面删除历史记录有声音问题 2026-02-02 15:27:55 +08:00
48c90fa0be 1 2026-02-02 14:29:42 +08:00
fe59a0cb45 1 2026-02-02 13:26:38 +08:00
81bc50ce17 1 2026-01-31 23:17:58 +08:00
6ae504823b tableview倒置 2026-01-31 22:40:50 +08:00
d2f582b7f8 处理崩溃 2026-01-30 21:38:58 +08:00
cc82396195 基本ok 2026-01-30 21:24:17 +08:00
2ff8a7a4af 处理键盘bug 2026-01-30 13:46:08 +08:00
3c0b7e754c 1 2026-01-30 13:33:23 +08:00
3705db4aab 简单封装 2026-01-30 13:26:02 +08:00
36774a8a2c 处理键盘 2026-01-30 13:17:11 +08:00
36135313d8 处理语音 2026-01-29 20:56:24 +08:00
23c0d14128 处理键盘部分 2026-01-29 19:18:38 +08:00
d0c5cada35 发送文本处理ui和逻辑 2026-01-29 17:56:53 +08:00
b556e6841d 添加举报 2026-01-29 16:42:43 +08:00
26096abbcc 添加个人主页 2026-01-29 16:03:21 +08:00
766c62f3c0 1 2026-01-29 15:53:26 +08:00
07a77149fc 处理评论数 2026-01-29 14:51:42 +08:00
32ebc6fb65 处理滚动底部问题 2026-01-29 14:42:49 +08:00
25fbe9b64e 分类移动文件 2026-01-29 13:44:52 +08:00
4392296616 修复聊天最后一条跑到最上面的问题 2026-01-29 13:13:42 +08:00
ef52cd4872 1 2026-01-28 20:18:18 +08:00
70a8466d9f 1 2026-01-28 19:31:27 +08:00
66d85f78a0 1 2026-01-28 18:58:30 +08:00
93a20cd92a 2 2026-01-28 18:15:58 +08:00
9a54a2ae6c 1 2026-01-28 18:11:46 +08:00
1b9ce1622d 修改动画 2026-01-28 17:21:19 +08:00
b4db79eba8 2 2026-01-28 16:35:47 +08:00
22f77d56ea 隐藏无更多消息文案 2026-01-28 15:32:56 +08:00
d8d5bdc3ae 1 2026-01-28 13:55:11 +08:00
7d583ceb1d 1 2026-01-28 13:43:36 +08:00
51b744ecd7 1 2026-01-28 12:04:31 +08:00
3fd7d2af2e 1 2026-01-27 21:32:52 +08:00
db869552e4 1 2026-01-27 18:53:19 +08:00
b34de116a3 修改布局 2026-01-27 17:49:45 +08:00
e67bc37571 修改bug 2026-01-27 17:03:16 +08:00
2b749cd2b0 1 2026-01-27 16:28:17 +08:00
ce889e1ed0 1 2026-01-27 13:57:32 +08:00
e8b4b2c58a 2 2026-01-26 20:36:51 +08:00
3a5a6395af 1 2026-01-26 18:51:37 +08:00
a22599feda 1 2026-01-26 18:43:07 +08:00
6a177ceebc 新增聊天记录 2026-01-26 18:17:02 +08:00
f9d7579536 新增接口,界面 2026-01-26 16:53:41 +08:00
0fa31418f6 1 2026-01-26 13:51:38 +08:00
77fd46aa34 1 2026-01-23 21:51:37 +08:00
6ad9783bcb 1 2026-01-22 22:03:56 +08:00
edc25c159d 1 2026-01-22 13:47:34 +08:00
06a572c08a 1 2026-01-21 17:59:12 +08:00
36c0b0b210 1 2026-01-21 17:25:38 +08:00
d1d47336c2 处理tabbar 2026-01-19 19:14:20 +08:00
063ceae10f 添加渐变色 2026-01-16 21:37:18 +08:00
552387293c 修改BUG 2026-01-16 20:44:10 +08:00
93489b09d9 1 2026-01-16 20:31:42 +08:00
663cb8493b 处理发送评论 2026-01-16 19:36:41 +08:00
ac0d9584d8 添加二级评论多个的时候的逻辑,默认每次点击出现5条 2026-01-16 19:29:42 +08:00
7fa124d45f 处理一级评论cell头像闪的问题 2026-01-16 19:13:26 +08:00
3dfb8f31e2 处理好评论了 2026-01-16 19:09:54 +08:00
619c02f236 1 2026-01-16 17:41:03 +08:00
28852a8d4b 添加评论 2026-01-16 15:55:08 +08:00
b021fd308f 添加语音websocket等,还没测试 2026-01-16 13:38:03 +08:00
169a1929d7 修改UI 2026-01-15 20:30:03 +08:00
b5da9f35a5 4 2026-01-15 19:14:34 +08:00
8f4deaac4e 删除录音相关文件 2026-01-15 19:00:25 +08:00
d479d1903b 2 2026-01-15 18:49:31 +08:00
32c4138ae0 1 2026-01-15 18:16:56 +08:00
da62d4f411 添加发送ai文本埋点 2026-01-13 17:37:34 +08:00
85dcd72a5d 删除测试精灵3d相关代码
修改上报数据
2026-01-13 16:55:24 +08:00
21fcbe3665 修改暗黑模式功能UI 2026-01-09 19:35:36 +08:00
1b6724f043 1 2026-01-09 19:13:35 +08:00
ef332ecaa1 默认皮肤适配暗黑模式 2026-01-09 13:07:11 +08:00
3d6d673c0b 添加注释 2026-01-09 12:56:53 +08:00
674f09d5b6 处理按钮之间点不到的问题 HitInside 2026-01-08 20:18:37 +08:00
11d8f78b1b 2 2026-01-08 20:04:23 +08:00
bbacef4ff7 1 2026-01-08 18:57:17 +08:00
8e692647d3 2 2026-01-08 17:23:22 +08:00
6f80f969a4 1 2026-01-08 16:54:38 +08:00
bdf2a9af80 修改人设长按排序后,键盘人设要同步问题 2026-01-07 19:05:52 +08:00
e858d35722 修改首页pop对钩问题 2026-01-07 15:45:01 +08:00
f2d5210313 1 2026-01-07 14:32:57 +08:00
1b0af3e2d6 修改颜色 2026-01-07 14:32:49 +08:00
0965cd3c7e 修改了横屏键盘不居中为题 2026-01-07 13:11:23 +08:00
c3909d63da 添加埋点 2026-01-06 19:25:34 +08:00
1096f24c57 修改hud 2025-12-31 17:32:39 +08:00
7ed84fd445 修改分享方式 2025-12-30 20:41:30 +08:00
4e2d7d2908 添加部分通用上报
修改bug 未登录在键盘点击充值要先去跳转登录
2025-12-30 15:27:35 +08:00
34089ddeea 1 2025-12-26 15:51:27 +08:00
6ec98468de Merge branch 'dev_处理立刻清空和撤销删除' into dev_st
# Conflicts:
#	CustomKeyboard/Utils/KBBackspaceLongPressHandler.m
解决冲突
2025-12-26 15:08:14 +08:00
2d5919016f 测试上报 接口报错 2025-12-26 15:02:48 +08:00
c0fa51bb2e 修改用户名UI 2025-12-26 14:48:24 +08:00
6713f36387 修改国际化 2025-12-26 14:38:29 +08:00
f24750458a 修改立刻清空按钮 2025-12-26 14:19:39 +08:00
510a2f4d66 添加邀请链接 2025-12-26 14:07:21 +08:00
ae37730da6 修改 我已经退出界面,然后从新进入界面弹起键盘,为什么撤销删除按钮显示? 2025-12-26 13:55:07 +08:00
203f104ece 修改键盘长按立即清空和撤销删除 2025-12-26 11:47:44 +08:00
8e934dd83a 1 2025-12-25 17:58:33 +08:00
1676916a5c 1 2025-12-25 17:32:34 +08:00
1af5a0e849 添加部分埋点 2025-12-25 17:20:24 +08:00
5b6e0a8fbf 修改key 2025-12-25 13:07:28 +08:00
9968883bab 修改邀请 2025-12-25 13:01:14 +08:00
af5f637d31 1 2025-12-24 20:17:37 +08:00
0a725e845e 处理详情tag的背景色 2025-12-23 20:56:00 +08:00
6a539dc3c5 处理键盘长按删除 撤销出现的bug 2025-12-23 18:05:01 +08:00
73d6ec933a 修改金币超出问题 2025-12-23 15:33:14 +08:00
000d603241 优化下载皮肤❤️ 2025-12-23 15:26:32 +08:00
fbf9fe9f2a 2 2025-12-23 14:37:11 +08:00
8e4d7e1ee8 处理按钮 2025-12-23 14:15:27 +08:00
262eb57b36 修改接口 2025-12-23 14:07:53 +08:00
2e1c261775 修改UI 2025-12-23 14:02:44 +08:00
6ad2079351 修改emoji 更换api 2025-12-23 13:27:25 +08:00
a477592f5d 新增按钮 2025-12-22 21:16:38 +08:00
6f336e8368 国际化 2025-12-22 20:53:24 +08:00
17e038beb1 修改接口 2025-12-22 19:22:12 +08:00
4e6fd90668 1 2025-12-22 19:19:28 +08:00
5cfc76e6c5 修改我的皮肤逻辑 2025-12-22 16:42:56 +08:00
9e33c93763 修改弹窗 2025-12-22 15:49:28 +08:00
1c9ae7bc06 1 2025-12-22 15:37:22 +08:00
472e9ad341 修改联想背景色 2025-12-22 14:02:43 +08:00
19c69f4f6f Merge branch 'dev_联想' into dev_st 2025-12-22 13:47:23 +08:00
8788cbb105 处理UI 2025-12-22 13:46:45 +08:00
ea77e9a5f8 处理苹果bug 默认键盘颜色改为 2025-12-22 13:29:00 +08:00
eaaf0e1ed6 修改UI 2025-12-22 13:08:59 +08:00
8a344b293d 添加联想 2025-12-22 12:54:28 +08:00
9cdd024ce2 修改UI 2025-12-19 22:00:52 +08:00
d612346db5 删除立即充值按钮 2025-12-19 21:36:11 +08:00
2cacaab974 更换AI皮肤逻辑图片 + 更换placeholdimage 2025-12-19 21:29:11 +08:00
200b1ab9f8 修复键盘功能键 发送按钮距离底部很远 2025-12-19 20:40:53 +08:00
8d0939cd78 1 2025-12-19 20:25:22 +08:00
df51620ca9 Emoji 默认选中第一个笑脸 2025-12-19 20:13:57 +08:00
70520fb7d9 AI键移除 放在顶部KBToolBar 2025-12-19 20:08:13 +08:00
7587fe6714 KBFunctionView 长按删除不弹出立刻清空按钮 2025-12-19 19:56:39 +08:00
1c8834caf6 键盘添加撤销操作 2025-12-19 19:21:08 +08:00
68306aa07f 1 2025-12-19 18:58:29 +08:00
9544ad75ff 重构键盘长按删除 2025-12-19 18:45:14 +08:00
d90e080981 修改立刻清空出现延迟时间 2025-12-19 18:14:28 +08:00
fc65052583 1 2025-12-19 18:11:09 +08:00
e0d5ae0257 新增键盘删除长按删除 2025-12-19 18:08:51 +08:00
639ce7eafd 1 2025-12-19 17:06:01 +08:00
e0379d3717 添加长按多文字删除 2025-12-19 16:24:47 +08:00
c108077178 刷新详情头部 2025-12-19 15:40:39 +08:00
182e5b9da1 未登录处理 2025-12-19 13:34:58 +08:00
ea4ecc05b4 添加右滑返回 2025-12-18 14:57:28 +08:00
ae05127292 1 2025-12-18 13:46:14 +08:00
38a3d2879e 优化未登录 键盘点击充值去登录 2025-12-18 13:20:49 +08:00
10ba4cd80f 添加反馈接口 2025-12-17 20:52:23 +08:00
857822c49c 2 2025-12-17 20:43:16 +08:00
85012eab78 2 2025-12-17 20:37:53 +08:00
8aa43d723a 1 2025-12-17 20:33:14 +08:00
1ecb7d60e5 新增搜索 2025-12-17 20:30:01 +08:00
904a6c932a 1 2025-12-17 19:56:22 +08:00
886de394d0 1 2025-12-17 19:45:39 +08:00
8bad475288 1 2025-12-17 19:08:44 +08:00
4a26419e67 1 2025-12-17 18:22:37 +08:00
1e04e7c39a 2 2025-12-17 17:01:49 +08:00
dde8716262 添加键盘里的充值界面 2025-12-17 16:22:41 +08:00
c6b4444589 处理网络请求token为空的问题 2025-12-17 15:39:45 +08:00
5bd20a911f 修改UI 2025-12-16 21:43:00 +08:00
b43567748c 1 2025-12-16 20:20:57 +08:00
59297eac77 重构 2025-12-16 20:05:33 +08:00
9f7d805a52 修改时间 2025-12-16 19:04:21 +08:00
6800864866 新增恢复购买 2025-12-16 16:25:50 +08:00
e8a980ff5b 2 2025-12-16 16:09:14 +08:00
dfbd5efe69 2 2025-12-16 16:03:14 +08:00
4a474e9b44 2 2025-12-16 16:00:00 +08:00
30f2e4f24f 1 2025-12-16 15:47:12 +08:00
c898d16688 删除storkit 1 2025-12-16 14:29:47 +08:00
f10ddd9a31 1 2025-12-16 14:14:49 +08:00
1651258eec 处理storkit2 2025-12-16 13:49:08 +08:00
fd0ddfd45a 1 2025-12-16 13:10:50 +08:00
444877fb73 2 2025-12-15 21:02:48 +08:00
1436464eca 1 2025-12-15 18:56:29 +08:00
05b9a0b823 添加删除按钮 2025-12-15 18:32:54 +08:00
053001170a 1 2025-12-15 18:02:58 +08:00
9cafb0f70e 引导UI修改 2025-12-15 17:56:06 +08:00
a399af53b5 1 2025-12-15 16:52:16 +08:00
a7574cd286 1 2025-12-15 16:41:53 +08:00
06dee39566 1 2025-12-15 15:47:16 +08:00
d5b4ef2b59 2 2025-12-15 15:40:25 +08:00
2620bd6845 1 2025-12-15 15:22:27 +08:00
1f0bdb1bd4 2 2025-12-15 15:11:16 +08:00
b21a2a8193 1 2025-12-15 15:03:35 +08:00
1f07120289 处理点击一个label,再次点击一个label崩溃了 2025-12-15 14:21:42 +08:00
64e0218add 添加emoji的map图标对应key 2025-12-15 13:45:14 +08:00
7972188ac3 修改emoji 2025-12-15 13:32:47 +08:00
6963c8016f 添加emoji1 2025-12-15 13:24:43 +08:00
633e6a9123 1 2025-12-12 20:08:34 +08:00
437f796a08 1 2025-12-12 16:09:14 +08:00
2b08dd44ee 2 2025-12-12 14:54:45 +08:00
1eeeef266b 1 2025-12-12 14:46:38 +08:00
3813974eae 1 2025-12-12 14:16:48 +08:00
6a5bda44e6 1 2025-12-11 22:02:18 +08:00
704553cd4e 1 2025-12-11 21:00:43 +08:00
35597f89ca 2 2025-12-11 20:40:49 +08:00
577b749198 2 2025-12-11 19:43:55 +08:00
cccced6afa 1 2025-12-11 18:36:14 +08:00
14637a21ad 1 2025-12-11 17:59:25 +08:00
111fe42782 1 2025-12-11 17:51:00 +08:00
58da905ade 1 2025-12-11 17:36:16 +08:00
f338a54e41 3 2025-12-11 16:59:14 +08:00
526ac1a7df 2 2025-12-11 16:45:39 +08:00
7f90240731 1 2025-12-11 16:39:22 +08:00
e4442afe72 1 2025-12-11 16:18:00 +08:00
be1d1ad70d 1 2025-12-11 15:39:33 +08:00
4fd0a52a36 2 2025-12-11 15:19:23 +08:00
04c7d19c37 1 2025-12-11 15:00:58 +08:00
94269209e0 2 2025-12-11 14:03:13 +08:00
2b4123741a 1 2025-12-11 13:51:04 +08:00
45695364e9 1 2025-12-11 13:40:32 +08:00
d348b35984 1 2025-12-11 13:16:06 +08:00
e39104c431 处理流逝返回
处理粘贴
2025-12-09 16:12:54 +08:00
7b86b739eb 处理searchresult 2025-12-09 15:19:10 +08:00
ade23e7a20 1 2025-12-09 14:32:21 +08:00
1b2b0c1143 1 2025-12-09 13:59:32 +08:00
0400d2020b 1 2025-12-08 20:46:24 +08:00
2cc93e0b48 1 2025-12-08 19:48:13 +08:00
fd8c08316b 1 2025-12-08 16:39:47 +08:00
0a1c30f669 处理AI回复问题 2025-12-05 21:54:10 +08:00
ca3ea0630e 2 2025-12-05 21:23:54 +08:00
a26b6b58a9 1 2025-12-05 21:15:48 +08:00
6fd4a86a7e 扩展添加token 2025-12-05 20:22:53 +08:00
fa999f502f 1 2025-12-05 20:18:18 +08:00
ad18a47d21 一次性配置内购服务 2025-12-05 13:49:12 +08:00
d2258883df 1 2025-12-04 21:53:47 +08:00
f7d11c5f8b 1 2025-12-04 21:10:32 +08:00
40d9b5aad4 1 2025-12-04 20:57:39 +08:00
6ac6514f89 1 2025-12-04 20:34:23 +08:00
eb7ad1a9f1 1 2025-12-04 20:27:26 +08:00
17ce91d40a 1 2025-12-04 20:04:02 +08:00
515487e748 1 2025-12-04 19:44:54 +08:00
7a25a6a5fa 1 2025-12-04 19:24:42 +08:00
64887054e0 1 2025-12-04 19:12:34 +08:00
8f63741d8c 1 2025-12-04 16:59:59 +08:00
f593ef0b4a 1 2025-12-04 16:41:33 +08:00
231f7f8c13 1 2025-12-04 16:18:43 +08:00
c9863cd353 11 2025-12-04 15:44:43 +08:00
ce4c4f0531 1 2025-12-04 15:35:23 +08:00
af2bcc42fd 1 2025-12-04 15:28:11 +08:00
1596aac717 2 2025-12-04 15:00:15 +08:00
d13bb734b1 1 2025-12-04 14:53:25 +08:00
2665b5ad1f 2 2025-12-04 14:44:56 +08:00
cffd77eeb5 1 2025-12-04 14:26:22 +08:00
b8f8d2e6b0 1 2025-12-04 14:17:47 +08:00
279255a14c 1 2025-12-04 14:07:12 +08:00
b216ddaa61 1 2025-12-04 13:37:11 +08:00
f770f8055e 1 2025-12-03 20:41:24 +08:00
819d74cc8d 2 2025-12-03 20:31:33 +08:00
9651ae7ad7 1 2025-12-03 20:14:14 +08:00
eca168957d 1 2025-12-03 20:02:37 +08:00
f026b9f9fd 1 2025-12-03 19:50:23 +08:00
b368ba0159 2 2025-12-03 19:43:45 +08:00
43e8b85656 1 2025-12-03 19:38:55 +08:00
716f91bdd0 3 2025-12-03 19:19:20 +08:00
b00283cd96 2 2025-12-03 19:05:53 +08:00
25edf2d817 1 2025-12-03 19:00:55 +08:00
1d6371c37e 1 2025-12-03 18:53:15 +08:00
82123fc232 1 2025-12-03 18:49:18 +08:00
6f02bc7cf5 2 2025-12-03 18:19:51 +08:00
a5ff2ce51b 1 2025-12-03 18:02:20 +08:00
4deccb76dc 1 2025-12-03 17:46:28 +08:00
93556ddb9c 6 2025-12-03 16:54:23 +08:00
681fced59d 2 2025-12-03 16:51:46 +08:00
91e2b047eb 5 2025-12-03 16:48:25 +08:00
c1c4c85bd2 2 2025-12-03 16:45:58 +08:00
d06e0499bc 1 2025-12-03 16:38:20 +08:00
49f730b609 3 2025-12-03 16:26:49 +08:00
04a392e7c7 4 2025-12-03 16:05:00 +08:00
22e393e588 2 2025-12-03 15:53:26 +08:00
6556689c8f 1 2025-12-03 15:46:26 +08:00
a50d18b486 1 2025-12-03 15:19:03 +08:00
599a5de3bc 5 2025-12-03 14:30:02 +08:00
c1eb6a3458 4 2025-12-03 13:54:57 +08:00
b87998549c 3 2025-12-03 13:31:02 +08:00
27aa723e7d 1 2025-12-03 12:55:51 +08:00
6be90ebb10 1 2025-12-02 21:32:49 +08:00
2f55e7bfa1 3 2025-12-02 20:33:17 +08:00
c56655c728 1 2025-12-02 19:39:37 +08:00
5e4c16c577 2 2025-12-02 19:19:25 +08:00
8245e7b3d1 1 2025-12-02 18:29:04 +08:00
cafde48f4a 修改UI逻辑 2025-11-28 16:55:26 +08:00
c897111855 1 2025-11-28 16:19:06 +08:00
a2bb61408b 2 2025-11-28 16:06:34 +08:00
9268a21eb8 处理bug 2025-11-28 14:25:13 +08:00
d4c553f072 处理视频退到后台进前台没有继续播放 2025-11-28 13:56:29 +08:00
73802b6e80 1 2025-11-28 13:20:50 +08:00
1a0a444a99 1 2025-11-28 13:07:35 +08:00
c37038f163 2 2025-11-27 21:39:03 +08:00
3144315de5 1 2025-11-27 20:05:39 +08:00
8f16250cbe 1 2025-11-27 19:20:20 +08:00
95d5e2b972 更改显示 2025-11-27 15:36:56 +08:00
2760a070a3 添加guard 蒙层 2025-11-27 15:34:33 +08:00
2435d760e8 添加键盘功能viewUI改动 2025-11-26 21:16:56 +08:00
80e4db86e4 1 2025-11-26 19:46:23 +08:00
4ab8a61a3c 处理因为网络下载失败导致之前的皮肤不在的bug 2025-11-25 22:00:13 +08:00
73c83153f9 缺少键盘图
修改网络环境下载图
2025-11-25 21:50:07 +08:00
1b67998f6a 2 2025-11-25 20:35:08 +08:00
b8cc38aa61 3 2025-11-25 18:54:53 +08:00
c4398a689b 2 2025-11-25 16:53:38 +08:00
b660eb19f4 1 2025-11-25 16:10:08 +08:00
1eb73f5257 封装KBFont,适配字体 2025-11-25 15:36:16 +08:00
71423df1c0 添加苹果登录的测试 2025-11-24 20:47:36 +08:00
709e0f4453 添加local文本 2025-11-24 20:31:23 +08:00
18df76a2b4 新增页面 2025-11-24 20:15:41 +08:00
15e37841bb 处理ios18从其他app用自己键盘 拉起主app的bug
其他问题
2025-11-24 19:58:19 +08:00
8e93f8f86f 动态化高度比例 2025-11-21 21:50:40 +08:00
fd35c5c993 1 2025-11-21 20:59:39 +08:00
fd7b3a7f75 优化键盘弹出宽度
优化键盘按钮视觉效果
2025-11-21 19:40:57 +08:00
af8fff5b13 添加无网络去设置,优化多语言 2025-11-21 18:36:00 +08:00
fc87c545a0 封装跨应用拉起, 2025-11-21 18:26:02 +08:00
0f4ca89060 固定键盘高度250
优化kbkeyboardview
优化ui
2025-11-21 16:22:00 +08:00
c371c7224e 优化键盘 预览宽度固定 2025-11-21 13:48:22 +08:00
31bb72c8f4 按钮之间无间距,按钮里的图片设置间距 2025-11-20 21:53:46 +08:00
faa05e2a10 添加按钮文字预览提示 2025-11-20 21:11:27 +08:00
6bdd111a3a 添加按下去动画 2025-11-20 20:36:31 +08:00
8296ac12b6 修改键盘UI 2025-11-20 20:17:19 +08:00
b2994adc1c UI 2025-11-20 19:57:11 +08:00
75d2e4072a 1 2025-11-20 18:23:56 +08:00
b23927968f 2 2025-11-20 15:35:22 +08:00
c27fd099f6 2 2025-11-20 15:10:22 +08:00
bc1264e28f 2 2025-11-20 14:56:15 +08:00
799b0f3989 添加扩展键盘本地皮肤 2025-11-20 14:27:57 +08:00
b3ce856ad4 添加启动图 2025-11-19 21:58:54 +08:00
f51fe1fac9 恢复默认皮肤 2025-11-19 20:30:30 +08:00
8dbaa9dcf6 配置化json strings 2025-11-19 20:16:19 +08:00
0196128008 1 2025-11-19 19:15:28 +08:00
4108aed4e0 处理键盘图片和自定义文字同时存在的bug 2025-11-19 16:13:30 +08:00
cc55bb107a 添加皮肤 2025-11-19 15:39:47 +08:00
7518a29d2f 2 2025-11-19 15:07:24 +08:00
37e131eb09 1 2025-11-19 14:54:45 +08:00
3dcc4932c3 3 2025-11-18 20:53:47 +08:00
254e65906a 添加app groups 2025-11-18 14:41:35 +08:00
ced0b88ca4 1 2025-11-18 13:48:22 +08:00
b2021dcb3c 6 2025-11-17 21:35:25 +08:00
0ef7b7d1d8 2 2025-11-17 21:08:25 +08:00
7254e2dbd9 2 2025-11-17 20:55:11 +08:00
26ef29ac4e 1 2025-11-17 20:26:39 +08:00
005e3c7581 2 2025-11-17 20:07:39 +08:00
ee433db4ad 66 2025-11-17 18:51:06 +08:00
ea4b8168b7 1 2025-11-17 16:42:32 +08:00
f366a4aa6c 1 2025-11-17 16:16:38 +08:00
d849b201ca 3 2025-11-17 15:39:03 +08:00
dc813fcabc 2 2025-11-17 15:06:05 +08:00
1d215ffdb3 1 2025-11-17 14:53:23 +08:00
d9bfc30c88 1 2025-11-17 13:30:01 +08:00
9305acb69b 1 2025-11-15 14:27:41 +08:00
f9a8955384 1 2025-11-15 00:33:29 +08:00
1f9dbba39d 1 2025-11-14 23:09:04 +08:00
dace0a9309 1 2025-11-14 19:48:15 +08:00
4f2e80e482 1 2025-11-14 18:43:08 +08:00
b27b9f9ee1 1 2025-11-14 18:24:38 +08:00
66a1ddef66 1 2025-11-14 16:34:01 +08:00
eacac8425c 1 2025-11-14 14:07:04 +08:00
d164514fcf 添加pay 2025-11-13 21:22:10 +08:00
ae79d1b1ba 更新UI 2025-11-13 19:20:57 +08:00
50163d02a7 3 2025-11-13 19:07:59 +08:00
5ec950cc61 统一api 2025-11-13 18:03:26 +08:00
a61b5fa2fd 1 2025-11-13 16:23:46 +08:00
f406416698 1 2025-11-13 15:34:56 +08:00
debbe2777b 1 2025-11-13 14:11:44 +08:00
bc261661ae 2 2025-11-12 21:23:31 +08:00
0aead49816 删除无关代码
退出登录
2025-11-12 19:46:07 +08:00
66b7a9218e 处理\n 2025-11-12 17:55:59 +08:00
2f4205ad1a 3 2025-11-12 16:49:19 +08:00
fea22aecab 重构了KBFunctionView 2025-11-12 16:03:30 +08:00
62f3ddae4a 删除测试数据 2025-11-12 15:40:30 +08:00
c317afc0fe 2 2025-11-12 15:31:22 +08:00
1dbe04cdf9 2 2025-11-12 14:36:15 +08:00
afc44cb471 1 2025-11-12 14:18:56 +08:00
39d8b3d547 1 2025-11-12 13:43:48 +08:00
f387b95d0d 测试假数据 2025-11-11 21:48:26 +08:00
1d064c1f31 1 2025-11-11 20:24:13 +08:00
3440cc4773 1 2025-11-11 19:39:33 +08:00
20b13bcffa 1 2025-11-11 17:36:12 +08:00
105e2ddf9b 1 2025-11-11 16:46:05 +08:00
a1a38d821c 添加复制 2025-11-11 15:59:19 +08:00
83987db5ac fix ui 2025-11-11 15:55:52 +08:00
d10114572e name pop 2025-11-11 15:28:22 +08:00
e34288ae56 fix ui 2025-11-11 15:13:43 +08:00
17b8bf2bfd fix 2025-11-11 14:56:57 +08:00
57bd4ba109 添加弹窗 2025-11-11 14:38:38 +08:00
e4ba237a00 1 2025-11-11 14:02:36 +08:00
9059a24637 1 2025-11-10 21:33:00 +08:00
dc0c55c495 2 2025-11-10 20:40:11 +08:00
a007a77db9 fix color 2025-11-10 19:55:50 +08:00
3eb3a86376 2 2025-11-10 19:51:23 +08:00
1dc9560a1f 1 2025-11-10 19:22:31 +08:00
8069b08fab Merge branch 'dev_st'
# Conflicts:
#	keyBoard.xcodeproj/project.pbxproj
解决冲突
2025-11-10 16:12:38 +08:00
2c8142c0d2 增加webview页面 2025-11-10 16:09:47 +08:00
9f4110b24a 添加弹窗 2025-11-10 15:55:36 +08:00
1cdc17b710 1 2025-11-10 15:38:30 +08:00
97316c7989 添加底部view 2025-11-10 15:29:21 +08:00
fac5e7657c Merge branch 'dev_st' 2025-11-10 13:27:44 +08:00
50dcb78417 提交 2025-11-10 13:27:26 +08:00
998fa7aa67 修改项目配置,锦支持iPhone 2025-11-10 13:25:39 +08:00
5e1a1f540e 1 2025-11-09 21:41:35 +08:00
2415e97c97 fix 2025-11-09 21:05:03 +08:00
aa71cc3c4f 1 2025-11-09 20:54:30 +08:00
e5ddcc4308 添加LYEmptyView '~> 0.3.10' 2025-11-09 20:54:14 +08:00
883b222254 fix 2025-11-09 18:07:47 +08:00
dc9ee10023 1 2025-11-09 17:07:43 +08:00
2c4a4329ff 2 2025-11-09 16:05:42 +08:00
553238de0c 1 2025-11-09 15:59:18 +08:00
80b6102673 1 2025-11-09 14:58:44 +08:00
705b0f374e 1 2025-11-09 14:44:31 +08:00
5bdc7ddec0 1 2025-11-09 14:26:02 +08:00
5d2a3de2f4 fix 2025-11-09 13:56:13 +08:00
675a9f6d64 3 2025-11-08 22:25:57 +08:00
41b14ceea4 3 2025-11-08 21:44:41 +08:00
a729396401 3 2025-11-08 20:49:05 +08:00
3b0beb52da 2 2025-11-08 20:04:50 +08:00
faeb930fe3 1 2025-11-08 11:48:06 +08:00
9a39c29e88 2 2025-11-07 22:22:41 +08:00
b23c9a678b 处理所有UI 2025-11-07 21:57:42 +08:00
96cd32ed99 添加箭头 2025-11-07 21:37:31 +08:00
50dd53b0c0 1 2025-11-07 21:05:25 +08:00
48a12f0919 1 2025-11-07 20:58:14 +08:00
91d754b389 1 2025-11-07 19:55:11 +08:00
450798c8bd 2 2025-11-07 19:33:54 +08:00
c3acc11f6a 1 2025-11-07 19:32:02 +08:00
d592c9f12e 1 2025-11-07 16:58:33 +08:00
26e39ce416 1 2025-11-07 16:46:08 +08:00
32521208a0 2 2025-11-07 16:29:15 +08:00
6e969648c6 1 2025-11-07 15:03:45 +08:00
074596ebcb 添加homeheadView 2025-11-07 14:21:03 +08:00
f0542c11c8 修改三方库 JXCategoryIndicatorCell self.contentView.layer.cornerRadius = 4; 2025-11-07 00:12:28 +08:00
0fa3d10284 9 2025-11-06 21:38:58 +08:00
0d13192723 优化ui 2025-11-06 19:51:50 +08:00
a72aae84ef 处理tabbar
处理HomeRankContentVC去除弹性效果 - 适配HWPanModal下拉效果
2025-11-06 19:29:52 +08:00
6ba1339c0b 修改UI 2025-11-06 19:19:12 +08:00
a75afbe4c1 创建nav宏
去除tabbar透明
2025-11-06 16:57:28 +08:00
1f45564539 5 2025-11-06 16:05:28 +08:00
41aec6b89e 4 2025-11-06 15:16:03 +08:00
a1db745b6c 3 2025-11-06 14:59:00 +08:00
15fc9621cd 2 2025-11-06 14:02:22 +08:00
d7874829d9 1 2025-11-06 13:18:27 +08:00
abf32e8457 添加HWPanModal和FLAnimatedImage 2025-11-05 22:04:56 +08:00
efdcf60ed1 在非刘海添加地球 2025-11-05 20:11:10 +08:00
7a1b17d060 处理键盘不能拉起主app的问题 2025-11-05 18:10:56 +08:00
f43f94b94d 添加键盘背景 2025-11-04 21:01:46 +08:00
3e2dc4bcb6 1 2025-11-04 16:37:24 +08:00
6fb9e56720 修改项目方向设置 2025-11-04 13:55:45 +08:00
e2cff76d13 合并代码 2025-11-04 13:52:13 +08:00
6e57f1c853 Merge branch 'main' of https://git.hanxiaokj.cn/zw/keyboard 2025-11-04 13:51:42 +08:00
4fe18c77dc temp 2025-11-04 13:51:15 +08:00
cb5819e330 bugly模拟器可用配置
修改包名
2025-11-03 21:04:39 +08:00
6f20e6aeb1 111 2025-11-03 20:03:53 +08:00
5af2612ff7 fix 2025-11-03 20:02:11 +08:00
cac2f13b88 修改引导逻辑 2025-11-03 19:00:47 +08:00
edf88721da 调整逻辑 2025-11-03 18:45:06 +08:00
915b329805 1 2025-11-03 16:57:24 +08:00
1673a2f4be 添加多语言 2025-11-03 16:37:28 +08:00
e4cebeac85 处理再次进入弹起权限弹窗 2025-11-03 15:04:19 +08:00
c7021e382e fixUI 2025-11-03 13:25:41 +08:00
ffea9d2022 修改KBAuthSession主程序添加token extension没有拿到的情况 2025-10-31 16:50:15 +08:00
90c1e7ff6c 添加token管理 2025-10-31 16:06:54 +08:00
59d04bb33c extension添加提示 2025-10-31 15:08:30 +08:00
eb0d3aaa71 提取baseurl配置 2025-10-31 13:05:34 +08:00
10dfe9b1d6 处理通用链接宏控制 2025-10-30 20:53:44 +08:00
6993bfd682 3 2025-10-30 20:46:54 +08:00
247a87891e 2 2025-10-30 20:23:34 +08:00
9af91cc4bc 1 2025-10-30 18:31:12 +08:00
4f23118ec0 修改逻辑 2025-10-30 18:28:36 +08:00
482756f6f0 优化apple sign 2025-10-30 14:35:06 +08:00
85a3694e35 apple login 2025-10-30 14:29:11 +08:00
f58bf61500 1 2025-10-30 14:04:55 +08:00
783d088f22 处理UI 2025-10-30 13:57:34 +08:00
74476cd592 修改功能 2025-10-30 13:27:09 +08:00
9b43274e93 测试网络,修改UI 2025-10-30 13:10:33 +08:00
8ce1d95c8c 添加图片库、封装 2025-10-29 20:57:45 +08:00
e8c88a6148 修改hud 2025-10-29 19:13:35 +08:00
e218c1bf3d 添加color 2025-10-29 18:25:52 +08:00
c5326a3079 移动文件 2025-10-29 16:44:00 +08:00
9101ffaab0 添加指引 2025-10-29 16:26:57 +08:00
e594711fa3 添加轮询粘贴 2025-10-29 15:49:43 +08:00
7fd084e529 1 2025-10-29 15:02:37 +08:00
11b25241bf basetableview封装 2025-10-29 14:49:35 +08:00
8fcfce7376 添加DZNEmptyDataSet 2025-10-29 14:30:19 +08:00
23317c9fd4 添加基本页面 2025-10-29 14:28:57 +08:00
045d5eaff8 创建base 2025-10-29 14:17:26 +08:00
72b6dbb157 添加MBP 2025-10-29 13:25:46 +08:00
e78b56e2cb 修改UI 2025-10-29 12:59:22 +08:00
6c05026402 fix 2025-10-28 21:27:01 +08:00
13facba33a 4 2025-10-28 20:11:40 +08:00
935284388c 数字面板#+=/123”切换 2025-10-28 20:03:43 +08:00
4c7fd9049f 默认小写 2025-10-28 19:24:35 +08:00
0031b7a5f6 添加设置 2025-10-28 18:02:10 +08:00
02dd204744 处理网络请求 2025-10-28 16:11:35 +08:00
f28f7de49d pod 网络库 2025-10-28 15:59:09 +08:00
c2859f888a 添加网络库,修改问题 2025-10-28 15:34:19 +08:00
a2b51189aa 添加功能组件 2025-10-28 15:18:12 +08:00
2f2f20cfc2 2 2025-10-28 15:10:38 +08:00
377e88b6db 1 2025-10-28 14:30:03 +08:00
1deca2ae5b 1 2025-10-28 10:18:10 +08:00
1608 changed files with 402460 additions and 5099 deletions

View File

@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"WebSearch",
"Bash(git checkout:*)",
"Bash(xcodebuild:*)",
"Bash(plutil:*)",
"Bash(find:*)",
"Bash(ls:*)",
"Bash(wc:*)"
]
}
}

View File

@@ -0,0 +1,17 @@
Dec 18 13:42:00 macbookpro com.apple.dt.xcodebuild[42901] <Error>: Unable to deliver request ({
"developer_dir" = "/Applications/Xcode.app/Contents/Developer";
request = "set_developer_dir";
}) because we are not connected to CoreSimulatorService.
Dec 18 13:42:00 macbookpro com.apple.dt.xcodebuild[42901] <Warning>: Unable to discover any Simulator runtimes. Developer Directory is /Applications/Xcode.app/Contents/Developer.
Dec 18 13:42:00 macbookpro com.apple.dt.xcodebuild[42901] <Error>: Could not kickstart simdiskimaged; SimDiskImageManager services will not be available: Error Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort" UserInfo={NSLocalizedDescription=Error returned in reply from CoreSimulatorService: Connection invalid}
Dec 18 13:42:00 macbookpro com.apple.dt.xcodebuild[42901] <Error>: simdiskimaged returned error (invalid), marking disconnected.
Dec 18 13:42:00 macbookpro com.apple.dt.xcodebuild[42901] <Error>: simdiskimaged returned error (invalid), marking disconnected.
Dec 18 13:42:00 macbookpro com.apple.dt.xcodebuild[42901] <Error>: Could not get list of trusted mount directories: Error Domain=com.apple.CoreSimulator.SimError Code=410 "The service used to manage runtime disk images (simdiskimaged) crashed or is not responding" UserInfo={NSLocalizedDescription=The service used to manage runtime disk images (simdiskimaged) crashed or is not responding}
Dec 18 13:42:00 macbookpro com.apple.dt.xcodebuild[42901] <Error>: Unable to deliver request ({
request = "notification_subscription";
"set_path" = "/Users/mac/Library/Developer/CoreSimulator/Devices";
}) because we are not connected to CoreSimulatorService.
Dec 18 13:42:00 macbookpro com.apple.dt.xcodebuild[42901] <Error>: Unable to deliver request ({
request = "notification_subscription";
"set_path" = "/Users/mac/Library/Developer/CoreSimulator/Devices";
}) because we are not connected to CoreSimulatorService.

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:app.tknb.net</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.loveKey.nyx</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.loveKey.nyx.shared</string>
</array>
</dict>
</plist>

View File

@@ -2,6 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>kbkeyboardAppExtension</string>
</array>
<key>NSMicrophoneUsageDescription</key>
<string>需要使用麦克风进行语音输入</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "App_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "App_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "App_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "切图 271@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "切图 271@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_limit_close@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_limit_close@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_limit_goto@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_limit_goto@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_limit_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_limit_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "back_keybord_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "back_keybord_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "buy_sel_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "buy_sel_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "close_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "close_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "home_ai_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "home_ai_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "home_chat_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "home_chat_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "home_emotion_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "home_emotion_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "home_keyboard_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "home_keyboard_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,86 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "kb_del_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "kb_del_icon@2x 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "切图 256@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "kb_del_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "kb_del_icon@3x 1.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "切图 256@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "kb_zt_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "kb_zt_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "key_revoke@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "key_revoke@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "keybord_bg_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "keybord_bg_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "upgrad_vip_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "upgrad_vip_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -5,75 +5,237 @@
// Created by Mac on 2025/10/27.
//
#import "KeyboardViewController.h"
#import "KeyboardViewController+Private.h"
static CGFloat KEYBOARDHEIGHT = 256;
#import "KBBackspaceUndoManager.h"
#import "KBChatLimitPopView.h"
#import "KBChatPanelView.h"
#import "KBFullAccessManager.h"
#import "KBFunctionView.h"
#import "KBInputBufferManager.h"
#import "KBKeyBoardMainView.h"
#import "KBKeyboardSubscriptionView.h"
#import "KBLocalizationManager.h"
#import "KBSkinManager.h"
#import "KBSuggestionEngine.h"
#import <SDWebImage/SDWebImage.h>
@interface KeyboardViewController ()
@property (nonatomic, strong) UIButton *nextKeyboardButton;
@end
#if DEBUG
#import <mach/mach.h>
#endif
#if DEBUG
static NSInteger sKBKeyboardVCAliveCount = 0;
static uint64_t KBPhysFootprintBytes(void) {
task_vm_info_data_t vmInfo;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kr = task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&vmInfo, &count);
if (kr != KERN_SUCCESS) {
return 0;
}
return (uint64_t)vmInfo.phys_footprint;
}
static NSString *KBFormatMB(uint64_t bytes) {
double mb = (double)bytes / 1024.0 / 1024.0;
return [NSString stringWithFormat:@"%.1fMB", mb];
}
#endif
@implementation KeyboardViewController
- (void)updateViewConstraints {
[super updateViewConstraints];
// Add custom view sizing constraints here
{
BOOL _kb_didTriggerLoginDeepLinkOnce;
#if DEBUG
BOOL _kb_debugDidCountAlive;
#endif
}
- (void)viewDidLoad {
[super viewDidLoad];
//
// // Perform custom UI setup here
// self.nextKeyboardButton = [UIButton buttonWithType:UIButtonTypeSystem];
//
// [self.nextKeyboardButton setTitle:NSLocalizedString(@"Next Keyboard", @"Title for 'Next Keyboard' button") forState:UIControlStateNormal];
// [self.nextKeyboardButton sizeToFit];
// self.nextKeyboardButton.translatesAutoresizingMaskIntoConstraints = NO;
//
// [self.nextKeyboardButton addTarget:self action:@selector(handleInputModeListFromView:withEvent:) forControlEvents:UIControlEventAllTouchEvents];
//
// [self.view addSubview:self.nextKeyboardButton];
//
// [self.nextKeyboardButton.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;
// [self.nextKeyboardButton.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
[self setupUI];
[super viewDidLoad];
#if DEBUG
if (!_kb_debugDidCountAlive) {
_kb_debugDidCountAlive = YES;
sKBKeyboardVCAliveCount += 1;
}
NSLog(@"[Keyboard] KeyboardViewController viewDidLoad alive=%ld self=%p mem=%@",
(long)sKBKeyboardVCAliveCount, self, KBFormatMB(KBPhysFootprintBytes()));
#endif
// /
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self setupUI];
self.suggestionEngine = [KBSuggestionEngine shared];
self.currentWord = @"";
// HUD App KeyWindow
[KBHUD setContainerView:self.view];
// 访便
[[KBFullAccessManager shared] bindInputController:self];
self.kb_fullAccessObserverToken = [[NSNotificationCenter defaultCenter]
addObserverForName:KBFullAccessChangedNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(__unused NSNotification *_Nonnull note){
// 访 UI
}];
//
__weak typeof(self) weakSelf = self;
self.kb_skinObserverToken = [[NSNotificationCenter defaultCenter]
addObserverForName:KBSkinDidChangeNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(__unused NSNotification *_Nonnull note) {
__strong typeof(weakSelf) self = weakSelf;
if (!self) {
return;
}
[self kb_applyTheme];
}];
[self kb_applyTheme];
[self kb_registerDarwinSkinInstallObserver];
[self kb_consumePendingShopSkin];
[self kb_applyDefaultSkinIfNeeded];
}
- (void)setupUI {
CGFloat toolBarHeight = 40;
CGFloat bottom = 5;
CGFloat buttonSpace = 8;
CGFloat eachButtonHeight = (KEYBOARDHEIGHT - toolBarHeight - 10 - 8 * 3 - bottom) / 4;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 30)];
view.backgroundColor = [UIColor redColor];
[self.view addSubview:view];
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
//
self.kb_cachedGradientImage = nil;
[self.kb_defaultGradientLayer removeFromSuperlayer];
self.kb_defaultGradientLayer = nil;
[[KBSkinManager shared] clearRuntimeImageCaches];
[[SDImageCache sharedImageCache] clearMemory];
}
- (void)viewWillLayoutSubviews
{
self.nextKeyboardButton.hidden = !self.needsInputModeSwitchKey;
[super viewWillLayoutSubviews];
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// FIX: iOS 26
// setupUI 0
//
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth];
if (self.kb_heightConstraint) {
self.kb_heightConstraint.constant = keyboardHeight;
}
// /
[[KBBackspaceUndoManager shared] registerNonClearAction];
[[KBInputBufferManager shared] resetWithText:@""];
[[KBLocalizationManager shared] reloadFromSharedStorageIfNeeded];
// HUD viewDidDisappear /
[KBHUD setContainerView:self.view];
[self kb_ensureKeyBoardMainViewIfNeeded];
[self kb_applyTheme];
#if DEBUG
NSLog(@"[Keyboard] viewWillAppear self=%p mem=%@",
self, KBFormatMB(KBPhysFootprintBytes()));
#endif
// /QQ 宿 documentContext
// liveText manualSnapshot
[[KBInputBufferManager shared]
updateFromExternalContextBefore:self.textDocumentProxy
.documentContextBeforeInput
after:self.textDocumentProxy
.documentContextAfterInput];
}
- (void)textWillChange:(id<UITextInput>)textInput {
// The app is about to change the document's contents. Perform any preparation here.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self kb_releaseMemoryWhenKeyboardHidden];
#if DEBUG
NSLog(@"[Keyboard] viewWillDisappear self=%p mem=%@",
self, KBFormatMB(KBPhysFootprintBytes()));
#endif
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 宿 willDisappear didDisappear
[self kb_releaseMemoryWhenKeyboardHidden];
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if (previousTraitCollection.userInterfaceStyle !=
self.traitCollection.userInterfaceStyle) {
self.kb_cachedGradientImage = nil;
[self kb_applyDefaultSkinIfNeeded];
}
}
}
- (void)textDidChange:(id<UITextInput>)textInput {
// The app has just changed the document's contents, the document context has been updated.
UIColor *textColor = nil;
if (self.textDocumentProxy.keyboardAppearance == UIKeyboardAppearanceDark) {
textColor = [UIColor whiteColor];
} else {
textColor = [UIColor blackColor];
}
[self.nextKeyboardButton setTitleColor:textColor forState:UIControlStateNormal];
[super textDidChange:textInput];
[[KBInputBufferManager shared]
updateFromExternalContextBefore:self.textDocumentProxy
.documentContextBeforeInput
after:self.textDocumentProxy
.documentContextAfterInput];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// if (!_kb_didTriggerLoginDeepLinkOnce) {
// _kb_didTriggerLoginDeepLinkOnce = YES;
// // App
// if (!KBAuthManager.shared.isLoggedIn) {
// [self kb_tryOpenContainerForLoginIfNeeded];
// }
// }
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// [self kb_updateKeyboardLayoutIfNeeded];
//
if (self.contentView.hidden) {
self.contentView.hidden = NO;
}
if (self.kb_defaultGradientLayer) {
self.kb_defaultGradientLayer.frame = self.bgImageView.bounds;
}
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
__weak typeof(self) weakSelf = self;
[coordinator
animateAlongsideTransition:^(
id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf kb_updateKeyboardLayoutIfNeeded];
}
completion:^(
__unused id<
UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf kb_updateKeyboardLayoutIfNeeded];
}];
}
- (void)dealloc {
if (self.kb_fullAccessObserverToken) {
[[NSNotificationCenter defaultCenter]
removeObserver:self.kb_fullAccessObserverToken];
self.kb_fullAccessObserverToken = nil;
}
if (self.kb_skinObserverToken) {
[[NSNotificationCenter defaultCenter]
removeObserver:self.kb_skinObserverToken];
self.kb_skinObserverToken = nil;
}
[self kb_unregisterDarwinSkinInstallObserver];
#if DEBUG
if (_kb_debugDidCountAlive) {
sKBKeyboardVCAliveCount -= 1;
}
NSLog(@"[Keyboard] KeyboardViewController dealloc alive=%ld self=%p mem=%@",
(long)sKBKeyboardVCAliveCount, self, KBFormatMB(KBPhysFootprintBytes()));
#endif
}
@end

View File

@@ -0,0 +1,712 @@
//
// KeyboardViewController+Chat.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBChatLimitPopView.h"
#import "KBChatMessage.h"
#import "KBChatPanelView.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBNetworkManager.h"
#import "KBVM.h"
#import "Masonry.h"
#import <AVFoundation/AVFoundation.h>
static const NSUInteger kKBChatMessageLimit = 6;
@implementation KeyboardViewController (Chat)
#pragma mark - KBChatPanelViewDelegate
- (void)chatPanelView:(KBChatPanelView *)view didSendText:(NSString *)text {
NSString *trim =
[text stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (trim.length == 0) {
return;
}
[self kb_sendChatText:trim];
}
- (void)chatPanelView:(KBChatPanelView *)view
didTapMessage:(KBChatMessage *)message {
if (message.audioFilePath.length == 0) {
return;
}
[self kb_playChatAudioAtPath:message.audioFilePath];
}
- (void)chatPanelView:(KBChatPanelView *)view
didTapVoiceButtonForMessage:(KBChatMessage *)message {
if (!message)
return;
// audioData
if (message.audioData && message.audioData.length > 0) {
[self kb_playChatAudioData:message.audioData];
return;
}
// audioFilePath
if (message.audioFilePath.length > 0) {
[self kb_playChatAudioAtPath:message.audioFilePath];
return;
}
NSLog(@"[Keyboard] 没有音频数据可播放");
}
- (void)chatPanelViewDidTapClose:(KBChatPanelView *)view {
// chatPanelView
[view kb_reloadWithMessages:@[]];
if (self.chatAudioPlayer.isPlaying) {
[self.chatAudioPlayer stop];
}
self.chatAudioPlayer = nil;
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
#pragma mark - Chat Helpers
- (void)kb_handleChatSendAction {
if (!self.chatPanelVisible) {
return;
}
[[KBInputBufferManager shared]
refreshFromProxyIfPossible:self.textDocumentProxy];
NSString *fullText = [KBInputBufferManager shared].liveText ?: @"";
// 宿线
NSString *baseline = self.chatPanelBaselineText ?: @"";
NSString *rawText = fullText;
if (baseline.length > 0 && [fullText hasPrefix:baseline]) {
rawText = [fullText substringFromIndex:baseline.length];
}
NSString *trim =
[rawText stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *textToClear = rawText;
if (trim.length == 0) {
//
//
NSString *fullTrim =
[fullText stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (fullTrim.length > 0) {
trim = fullTrim;
textToClear = fullText;
}
}
if (trim.length == 0) {
[KBHUD showInfo:KBLocalized(@"请输入内容")];
return;
}
[self kb_sendChatText:trim];
//
[self kb_clearHostInputForText:textToClear];
}
- (void)kb_sendChatText:(NSString *)text {
if (text.length == 0) {
return;
}
NSLog(@"[KB] 发送消息: %@", text);
KBChatMessage *outgoing = [KBChatMessage userMessageWithText:text];
outgoing.avatarURL = [self kb_sharedUserAvatarURL];
[self.chatPanelView kb_addUserMessage:text];
[self kb_prefetchAvatarForMessage:outgoing];
if (![[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self.view]) {
[KBHUD showInfo:KBLocalized(@"请开启完全访问后使用")];
return;
}
// loading
[self.chatPanelView kb_addLoadingAssistantMessage];
//
[self kb_requestChatMessageWithContent:text];
}
#pragma mark - Chat Limit Pop
- (void)kb_showChatLimitPopWithMessage:(NSString *)message {
[self kb_dismissChatLimitPop];
UIControl *mask = [[UIControl alloc] init];
mask.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
mask.alpha = 0.0;
[mask addTarget:self
action:@selector(kb_dismissChatLimitPop)
forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:mask];
[mask mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
CGFloat width = 252.0;
CGFloat height = 252.0 + 18.0 + 53.0 + 18.0 + 28.0;
KBChatLimitPopView *content =
[[KBChatLimitPopView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
content.message = message ?: @"";
content.delegate = self;
[mask addSubview:content];
[content mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(mask);
make.width.mas_equalTo(width);
make.height.mas_equalTo(height);
}];
self.chatLimitMaskView = mask;
[self.contentView bringSubviewToFront:mask];
[UIView animateWithDuration:0.18
animations:^{
mask.alpha = 1.0;
}];
}
- (void)kb_dismissChatLimitPop {
if (!self.chatLimitMaskView) {
return;
}
UIControl *mask = self.chatLimitMaskView;
self.chatLimitMaskView = nil;
[UIView animateWithDuration:0.15
animations:^{
mask.alpha = 0.0;
}
completion:^(__unused BOOL finished) {
[mask removeFromSuperview];
}];
}
- (void)kb_clearHostInputForText:(NSString *)text {
if (text.length == 0) {
return;
}
NSUInteger count = [self kb_composedCharacterCountForString:text];
for (NSUInteger i = 0; i < count; i++) {
[self.textDocumentProxy deleteBackward];
}
[[KBInputBufferManager shared] clearAllLiveText];
[self kb_clearCurrentWord];
}
- (NSUInteger)kb_composedCharacterCountForString:(NSString *)text {
if (text.length == 0) {
return 0;
}
__block NSUInteger count = 0;
[text enumerateSubstringsInRange:NSMakeRange(0, text.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(__unused NSString *substring,
__unused NSRange substringRange,
__unused NSRange enclosingRange,
__unused BOOL *stop) {
count += 1;
}];
return count;
}
- (NSString *)kb_sharedUserAvatarURL {
NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
NSString *url = [ud stringForKey:AppGroup_UserAvatarURL];
return url ?: @"";
}
- (void)kb_prefetchAvatarForMessage:(KBChatMessage *)message {
if (!message || message.avatarImage) {
return;
}
NSString *urlString = message.avatarURL ?: @"";
if (urlString.length == 0) {
return;
}
if (![[KBFullAccessManager shared] hasFullAccess]) {
return;
}
__weak typeof(self) weakSelf = self;
[[KBVM shared] downloadAvatarFromURL:urlString
completion:^(UIImage *image, NSError *error) {
__strong typeof(weakSelf) self = weakSelf;
if (!self || !image)
return;
message.avatarImage = image;
[self kb_reloadChatRowForMessage:message];
}];
}
- (void)kb_reloadChatRowForMessage:(KBChatMessage *)message {
//
//
//
}
- (void)kb_requestChatAudioForText:(NSString *)text {
NSString *mockPath = [self kb_mockChatAudioPath];
if (mockPath.length > 0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.35 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
NSString *displayText = KBLocalized(@"语音回复");
KBChatMessage *incoming =
[KBChatMessage messageWithText:displayText
outgoing:NO
audioFilePath:mockPath];
incoming.displayName = KBLocalized(@"AI助手");
[self kb_appendChatMessage:incoming];
[self kb_playChatAudioAtPath:mockPath];
});
return;
}
NSDictionary *payload = @{@"message" : text ?: @""};
__weak typeof(self) weakSelf = self;
[[KBNetworkManager shared] POST:API_AI_TALK
jsonBody:payload
headers:nil
completion:^(NSDictionary *json, NSURLResponse *response,
NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self) {
return;
}
if (error) {
NSString *tip = error.localizedDescription
?: KBLocalized(@"请求失败");
[KBHUD showInfo:tip];
return;
}
NSString *displayText =
[self kb_chatTextFromJSON:json];
NSString *audioURL =
[self kb_chatAudioURLFromJSON:json];
NSString *audioBase64 =
[self kb_chatAudioBase64FromJSON:json];
if (audioURL.length > 0) {
[self kb_downloadChatAudioFromURL:audioURL
displayText:displayText];
return;
}
if (audioBase64.length > 0) {
NSData *data = [[NSData alloc]
initWithBase64EncodedString:audioBase64
options:0];
if (data.length == 0) {
[KBHUD showInfo:KBLocalized(@"音频数据解析失败")];
return;
}
[self kb_handleChatAudioData:data
fileExtension:@"m4a"
displayText:displayText];
return;
}
[KBHUD showInfo:KBLocalized(@"未获取到音频文件")];
});
}];
}
#pragma mark - New Chat API (with typewriter effect and audio preload)
/// audioId
- (void)kb_requestChatMessageWithContent:(NSString *)content {
if (content.length == 0) {
[self.chatPanelView kb_removeLoadingAssistantMessage];
return;
}
NSInteger companionId = [[KBVM shared] selectedCompanionIdFromAppGroup];
NSLog(@"[KB] 请求聊天: companionId=%ld", (long)companionId);
__weak typeof(self) weakSelf = self;
[[KBVM shared] sendChatMessageWithContent:content
companionId:companionId
completion:^(KBChatResponse *response) {
__strong typeof(weakSelf) self = weakSelf;
if (!self)
return;
if (response.code != 0) {
if (response.code == 50030) {
NSLog(@"[KB] ⚠️ 次数用尽: %@",
response.message);
[self.chatPanelView
kb_removeLoadingAssistantMessage];
[self kb_showChatLimitPopWithMessage:
response.message];
return;
}
NSLog(@"[KB] ❌ 请求失败: %@",
response.message);
[self.chatPanelView
kb_removeLoadingAssistantMessage];
[KBHUD showInfo:response.message
?: KBLocalized(@"请求失败")];
return;
}
NSLog(@"[KB] ✅ 收到回复: %@",
response.data.aiResponse);
if (response.data.aiResponse.length == 0) {
[self.chatPanelView
kb_removeLoadingAssistantMessage];
[KBHUD showInfo:KBLocalized(@"未获取到回复内容")];
return;
}
// AI
NSLog(@"[KB] 准备添加 AI 消息");
[self.chatPanelView
kb_addAssistantMessage:response.data.aiResponse
audioId:response.data.audioId];
NSLog(@"[KB] AI 消息添加完成");
// App persona
[self kb_notifyMainAppChatUpdatedWithCompanionId:companionId];
// audioId
if (response.data.audioId.length > 0) {
[self kb_preloadAudioWithAudioId:
response.data.audioId];
}
}];
}
/// AppGroup persona companionId
- (NSInteger)kb_selectedCompanionId {
return [[KBVM shared] selectedCompanionIdFromAppGroup];
}
#pragma mark - Audio Preload
/// audioURL
- (void)kb_preloadAudioWithAudioId:(NSString *)audioId {
if (audioId.length == 0)
return;
NSLog(@"[Keyboard] 开始预加载音频audioId: %@", audioId);
__weak typeof(self) weakSelf = self;
[[KBVM shared] pollAudioURLWithAudioId:audioId
maxRetries:10
interval:1.0
completion:^(KBAudioResponse *response) {
__strong typeof(weakSelf) self = weakSelf;
if (!self)
return;
if (!response.success ||
response.audioURL.length == 0) {
NSLog(@"[Keyboard] ❌ 预加载音频 URL 获取失败: %@",
response.errorMessage);
return;
}
NSLog(@"[Keyboard] ✅ 预加载音频 URL 获取成功");
//
[[KBVM shared]
downloadAudioFromURL:response.audioURL
completion:^(
KBAudioResponse *audioResponse) {
if (!audioResponse.success) {
NSLog(@"[Keyboard] ❌ 预加载音频下载失败: %@",
audioResponse.errorMessage);
return;
}
// AI
[self.chatPanelView
kb_updateLastAssistantMessageWithAudioData:
audioResponse.audioData
duration:
audioResponse.duration];
NSLog(@"[Keyboard] ✅ 预加载音频完成,音频时长: %.2f秒",
audioResponse.duration);
}];
}];
}
- (void)kb_downloadChatAudioFromURL:(NSString *)audioURL
displayText:(NSString *)displayText {
__weak typeof(self) weakSelf = self;
[[KBVM shared] downloadAudioFromURL:audioURL
completion:^(KBAudioResponse *response) {
__strong typeof(weakSelf) self = weakSelf;
if (!self)
return;
if (!response.success) {
[KBHUD showInfo:response.errorMessage
?: KBLocalized(@"下载失败")];
return;
}
if (!response.audioData ||
response.audioData.length == 0) {
[KBHUD showInfo:KBLocalized(@"未获取到音频数据")];
return;
}
NSString *ext = @"m4a";
NSURL *url = [NSURL URLWithString:audioURL];
if (url.pathExtension.length > 0) {
ext = url.pathExtension;
}
[self kb_handleChatAudioData:response.audioData
fileExtension:ext
displayText:displayText];
}];
}
- (void)kb_handleChatAudioData:(NSData *)data
fileExtension:(NSString *)extension
displayText:(NSString *)displayText {
if (data.length == 0) {
[KBHUD showInfo:KBLocalized(@"音频数据为空")];
return;
}
NSString *ext = extension.length > 0 ? extension : @"m4a";
NSString *fileName = [NSString
stringWithFormat:@"kb_chat_%@.%@",
@((long long)([NSDate date].timeIntervalSince1970 *
1000)),
ext];
NSString *filePath =
[NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
if (![data writeToFile:filePath atomically:YES]) {
[KBHUD showInfo:KBLocalized(@"音频保存失败")];
return;
}
NSString *text =
displayText.length > 0 ? displayText : KBLocalized(@"语音消息");
KBChatMessage *incoming =
[KBChatMessage messageWithText:text outgoing:NO audioFilePath:filePath];
incoming.displayName = KBLocalized(@"AI助手");
[self kb_appendChatMessage:incoming];
}
- (void)kb_appendChatMessage:(KBChatMessage *)message {
if (!message) {
return;
}
[self.chatMessages addObject:message];
if (self.chatMessages.count > kKBChatMessageLimit) {
NSUInteger overflow = self.chatMessages.count - kKBChatMessageLimit;
NSArray<KBChatMessage *> *removed =
[self.chatMessages subarrayWithRange:NSMakeRange(0, overflow)];
[self.chatMessages removeObjectsInRange:NSMakeRange(0, overflow)];
for (KBChatMessage *msg in removed) {
if (msg.audioFilePath.length > 0) {
NSString *tmpRoot = NSTemporaryDirectory();
if (tmpRoot.length > 0 && [msg.audioFilePath hasPrefix:tmpRoot]) {
[[NSFileManager defaultManager] removeItemAtPath:msg.audioFilePath
error:nil];
}
}
}
}
[self.chatPanelView kb_reloadWithMessages:self.chatMessages];
}
- (NSString *)kb_mockChatAudioPath {
NSString *path = [[NSBundle mainBundle] pathForResource:@"ai_test"
ofType:@"m4a"];
return path ?: @"";
}
- (NSString *)kb_chatTextFromJSON:(NSDictionary *)json {
NSDictionary *data = [self kb_chatDataDictionaryFromJSON:json];
NSString *text =
[self kb_stringValueInDict:data keys:@[ @"text", @"message", @"content" ]];
if (text.length == 0) {
text = [self kb_stringValueInDict:json
keys:@[ @"text", @"message", @"content" ]];
}
return text ?: @"";
}
- (NSString *)kb_chatAudioURLFromJSON:(NSDictionary *)json {
NSDictionary *data = [self kb_chatDataDictionaryFromJSON:json];
NSArray<NSString *> *keys =
@[ @"audioUrl", @"audioURL", @"audio_url", @"url", @"fileUrl",
@"file_url", @"audioFileUrl", @"audio_file_url" ];
NSString *url = [self kb_stringValueInDict:data keys:keys];
if (url.length == 0) {
url = [self kb_stringValueInDict:json keys:keys];
}
return url ?: @"";
}
- (NSString *)kb_chatAudioBase64FromJSON:(NSDictionary *)json {
NSDictionary *data = [self kb_chatDataDictionaryFromJSON:json];
NSArray<NSString *> *keys =
@[ @"audioBase64", @"audio_base64", @"audioData", @"audio_data",
@"base64" ];
NSString *b64 = [self kb_stringValueInDict:data keys:keys];
if (b64.length == 0) {
b64 = [self kb_stringValueInDict:json keys:keys];
}
return b64 ?: @"";
}
- (NSDictionary *)kb_chatDataDictionaryFromJSON:(NSDictionary *)json {
if (![json isKindOfClass:[NSDictionary class]]) {
return @{};
}
id dataObj = json[@"data"] ?: json[@"result"] ?: json[@"response"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
return (NSDictionary *)dataObj;
}
return @{};
}
- (NSString *)kb_stringValueInDict:(NSDictionary *)dict
keys:(NSArray<NSString *> *)keys {
if (![dict isKindOfClass:[NSDictionary class]]) {
return @"";
}
for (NSString *key in keys) {
id value = dict[key];
if ([value isKindOfClass:[NSString class]] &&
((NSString *)value).length > 0) {
return (NSString *)value;
}
}
return @"";
}
- (void)kb_playChatAudioAtPath:(NSString *)path {
if (path.length == 0) {
return;
}
NSURL *url = [NSURL fileURLWithPath:path];
if (![NSFileManager.defaultManager fileExistsAtPath:path]) {
[KBHUD showInfo:KBLocalized(@"音频文件不存在")];
return;
}
if (self.chatAudioPlayer && self.chatAudioPlayer.isPlaying) {
NSURL *currentURL = self.chatAudioPlayer.url;
if ([currentURL isEqual:url]) {
[self.chatAudioPlayer stop];
self.chatAudioPlayer = nil;
return;
}
[self.chatAudioPlayer stop];
self.chatAudioPlayer = nil;
}
NSError *sessionError = nil;
AVAudioSession *session = [AVAudioSession sharedInstance];
if ([session respondsToSelector:@selector(setCategory:options:error:)]) {
[session setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionDuckOthers
error:&sessionError];
} else {
[session setCategory:AVAudioSessionCategoryPlayback error:&sessionError];
}
[session setActive:YES error:nil];
NSError *playerError = nil;
AVAudioPlayer *player =
[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&playerError];
if (playerError || !player) {
[KBHUD showInfo:KBLocalized(@"音频播放失败")];
return;
}
self.chatAudioPlayer = player;
[player prepareToPlay];
[player play];
}
///
- (void)kb_playChatAudioData:(NSData *)audioData {
if (!audioData || audioData.length == 0) {
NSLog(@"[Keyboard] 音频数据为空");
return;
}
//
if (self.chatAudioPlayer && self.chatAudioPlayer.isPlaying) {
[self.chatAudioPlayer stop];
self.chatAudioPlayer = nil;
}
//
NSError *sessionError = nil;
AVAudioSession *session = [AVAudioSession sharedInstance];
if ([session respondsToSelector:@selector(setCategory:options:error:)]) {
[session setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionDuckOthers
error:&sessionError];
} else {
[session setCategory:AVAudioSessionCategoryPlayback error:&sessionError];
}
[session setActive:YES error:nil];
//
NSError *playerError = nil;
AVAudioPlayer *player =
[[AVAudioPlayer alloc] initWithData:audioData error:&playerError];
if (playerError || !player) {
NSLog(@"[Keyboard] 音频播放器初始化失败: %@",
playerError.localizedDescription);
[KBHUD showInfo:KBLocalized(@"音频播放失败")];
return;
}
self.chatAudioPlayer = player;
player.volume = 1.0;
[player prepareToPlay];
[player play];
NSLog(@"[Keyboard] 开始播放音频,时长: %.2f秒", player.duration);
}
#pragma mark - Notify Main App
/// App persona
- (void)kb_notifyMainAppChatUpdatedWithCompanionId:(NSInteger)companionId {
NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
[ud setInteger:companionId forKey:AppGroup_ChatUpdatedCompanionId];
[ud synchronize];
CFNotificationCenterPostNotification(
CFNotificationCenterGetDarwinNotifyCenter(),
(__bridge CFStringRef)kKBDarwinChatUpdated,
NULL, NULL, true);
NSLog(@"[KB] 已通知主 App 刷新 companionId=%ld 的聊天记录", (long)companionId);
}
#pragma mark - KBChatLimitPopViewDelegate
- (void)chatLimitPopViewDidTapCancel:(KBChatLimitPopView *)view {
[self kb_dismissChatLimitPop];
}
- (void)chatLimitPopViewDidTapRecharge:(KBChatLimitPopView *)view {
[self kb_dismissChatLimitPop];
NSString *urlString =
[NSString stringWithFormat:@"%@://recharge?src=keyboard&vipType=svip",
KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:urlString];
BOOL success = [KBHostAppLauncher openHostAppURL:scheme
fromResponder:self.view];
if (!success) {
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
}
}
@end

View File

@@ -0,0 +1,96 @@
//
// KeyboardViewController+Layout.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
// 375 稿
static const CGFloat kKBKeyboardBaseHeight = 250.0f;
static const CGFloat kKBChatPanelHeight = 180;
@implementation KeyboardViewController (Layout)
- (CGFloat)kb_portraitWidth {
CGSize s = [UIScreen mainScreen].bounds.size;
return MIN(s.width, s.height);
}
- (CGFloat)kb_keyboardHeightForWidth:(CGFloat)width {
if (width <= 0) {
width = KB_DESIGN_WIDTH;
}
CGFloat scale = width / KB_DESIGN_WIDTH;
CGFloat baseHeight = kKBKeyboardBaseHeight * scale;
CGFloat chatHeight = kKBChatPanelHeight * scale;
if (self.chatPanelVisible) {
return baseHeight + chatHeight;
}
return baseHeight;
}
- (CGFloat)kb_keyboardBaseHeightForWidth:(CGFloat)width {
if (width <= 0) {
width = KB_DESIGN_WIDTH;
}
CGFloat scale = width / KB_DESIGN_WIDTH;
return kKBKeyboardBaseHeight * scale;
}
- (CGFloat)kb_chatPanelHeightForWidth:(CGFloat)width {
if (width <= 0) {
width = KB_DESIGN_WIDTH;
}
CGFloat scale = width / KB_DESIGN_WIDTH;
return kKBChatPanelHeight * scale;
}
- (void)kb_updateKeyboardLayoutIfNeeded {
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth];
CGFloat keyboardBaseHeight =
[self kb_keyboardBaseHeightForWidth:portraitWidth];
CGFloat chatPanelHeight = [self kb_chatPanelHeightForWidth:portraitWidth];
CGFloat containerWidth = CGRectGetWidth(self.view.superview.bounds);
if (containerWidth <= 0) {
containerWidth = CGRectGetWidth(self.view.window.bounds);
}
if (containerWidth <= 0) {
containerWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
}
BOOL widthChanged = (fabs(self.kb_lastPortraitWidth - portraitWidth) >= 0.5);
BOOL heightChanged =
(fabs(self.kb_lastKeyboardHeight - keyboardHeight) >= 0.5);
if (!widthChanged && !heightChanged && containerWidth > 0 &&
self.kb_widthConstraint.constant == containerWidth) {
return;
}
self.kb_lastPortraitWidth = portraitWidth;
self.kb_lastKeyboardHeight = keyboardHeight;
if (self.kb_heightConstraint) {
self.kb_heightConstraint.constant = keyboardHeight;
}
if (containerWidth > 0 && self.kb_widthConstraint) {
self.kb_widthConstraint.constant = containerWidth;
}
if (self.contentWidthConstraint) {
[self.contentWidthConstraint setOffset:portraitWidth];
}
if (self.contentHeightConstraint) {
[self.contentHeightConstraint setOffset:keyboardHeight];
}
if (self.keyBoardMainHeightConstraint) {
[self.keyBoardMainHeightConstraint setOffset:keyboardBaseHeight];
}
if (self.chatPanelHeightConstraint) {
[self.chatPanelHeightConstraint setOffset:chatPanelHeight];
}
[self.view layoutIfNeeded];
}
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,656 @@
//
// KeyboardViewController+Panels.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBAuthManager.h"
#import "KBBackspaceUndoManager.h"
#import "KBChatMessage.h"
#import "KBChatPanelView.h"
#import "KBFunctionView.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBKey.h"
#import "KBKeyBoardMainView.h"
#import "KBKeyboardSubscriptionView.h"
#import "KBSettingView.h"
#import "Masonry.h"
#import <SDWebImage/SDWebImage.h>
#import <AVFoundation/AVAudioPlayer.h>
@implementation KeyboardViewController (Panels)
#pragma mark - Panel Mode
- (void)kb_setPanelMode:(KBKeyboardPanelMode)mode animated:(BOOL)animated {
if (mode == self.kb_panelMode) {
return;
}
KBKeyboardPanelMode fromMode = self.kb_panelMode;
// AI 访
if (mode == KBKeyboardPanelModeFunction &&
![[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self.view]) {
return;
}
// mode Function
BOOL islogin = YES;
if (mode == KBKeyboardPanelModeFunction) {
[[KBAuthManager shared] reloadFromKeychain];
islogin = KBAuthManager.shared.isLoggedIn;
}
#if DEBUG
if (mode == KBKeyboardPanelModeFunction) {
NSString *token = [KBAuthManager shared].current.accessToken ?: @"";
NSLog(@"[AuthTrace][Ext] tapAI mode=%ld isLoggedIn=%d tokenLen=%lu",
(long)mode, islogin, (unsigned long)token.length);
}
#endif
if (mode == KBKeyboardPanelModeFunction && !islogin) {
[KBHUD showInfo:KBLocalized(@"请先登录后使用AI功能")];
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!ok) {
[KBHUD showInfo:KBLocalized(@"请回到桌面手动打开App登录")];
}
return;
}
self.kb_panelMode = mode;
//
[self kb_ensureKeyBoardMainViewIfNeeded];
// 1) /
[self kb_setSubscriptionPanelVisible:NO animated:animated];
[self kb_setSettingViewVisible:NO animated:animated];
[self kb_setChatPanelVisible:NO animated:animated];
[self kb_setFunctionPanelVisible:NO];
// 2)
switch (mode) {
case KBKeyboardPanelModeFunction:
[self kb_setFunctionPanelVisible:YES];
break;
case KBKeyboardPanelModeChat:
[self kb_setChatPanelVisible:YES animated:animated];
break;
case KBKeyboardPanelModeSettings:
[self kb_setSettingViewVisible:YES animated:animated];
break;
case KBKeyboardPanelModeSubscription:
[self kb_setSubscriptionPanelVisible:YES animated:animated];
break;
case KBKeyboardPanelModeMain:
default:
break;
}
// 3) /
if (mode == KBKeyboardPanelModeFunction) {
[[KBMaiPointReporter sharedReporter]
reportPageExposureWithEventName:@"enter_keyboard_function_panel"
pageId:@"keyboard_function_panel"
extra:nil
completion:nil];
} else if (mode == KBKeyboardPanelModeMain &&
fromMode == KBKeyboardPanelModeFunction) {
[[KBMaiPointReporter sharedReporter]
reportPageExposureWithEventName:@"enter_keyboard_main_panel"
pageId:@"keyboard_main_panel"
extra:nil
completion:nil];
} else if (mode == KBKeyboardPanelModeSettings) {
[[KBMaiPointReporter sharedReporter]
reportPageExposureWithEventName:@"enter_keyboard_settings"
pageId:@"keyboard_settings"
extra:nil
completion:nil];
} else if (mode == KBKeyboardPanelModeSubscription) {
[[KBMaiPointReporter sharedReporter]
reportPageExposureWithEventName:@"enter_keyboard_subscription_panel"
pageId:@"keyboard_subscription_panel"
extra:nil
completion:nil];
}
// 4)
if (mode == KBKeyboardPanelModeSubscription) {
[self.contentView bringSubviewToFront:self.subscriptionView];
} else if (mode == KBKeyboardPanelModeSettings) {
[self.contentView bringSubviewToFront:self.settingView];
} else if (mode == KBKeyboardPanelModeChat) {
[self.contentView bringSubviewToFront:self.chatPanelView];
} else if (mode == KBKeyboardPanelModeFunction) {
[self.contentView bringSubviewToFront:self.functionView];
} else {
[self.contentView bringSubviewToFront:self.keyBoardMainView];
}
}
/// /
- (void)showFunctionPanel:(BOOL)show {
if (show) {
[self kb_setPanelMode:KBKeyboardPanelModeFunction animated:NO];
return;
}
if (self.kb_panelMode == KBKeyboardPanelModeFunction) {
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:NO];
}
}
/// / keyBoardMainView /
- (void)showSettingView:(BOOL)show {
if (show) {
[self kb_setPanelMode:KBKeyboardPanelModeSettings animated:YES];
return;
}
if (self.kb_panelMode == KBKeyboardPanelModeSettings) {
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
}
/// /
- (void)showChatPanel:(BOOL)show {
if (show) {
[self kb_setPanelMode:KBKeyboardPanelModeChat animated:YES];
return;
}
if (self.kb_panelMode == KBKeyboardPanelModeChat) {
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
}
- (void)kb_setFunctionPanelVisible:(BOOL)visible {
if (visible) {
[self kb_ensureFunctionViewIfNeeded];
}
if (_functionView) {
_functionView.hidden = !visible;
} else if (visible) {
// ensure
self.functionView.hidden = NO;
}
self.keyBoardMainView.hidden = visible;
}
- (void)kb_setChatPanelVisible:(BOOL)visible animated:(BOOL)animated {
if (visible == self.chatPanelVisible) {
return;
}
self.chatPanelVisible = visible;
if (visible) {
// 宿
[[KBInputBufferManager shared]
refreshFromProxyIfPossible:self.textDocumentProxy];
self.chatPanelBaselineText = [KBInputBufferManager shared].liveText ?: @"";
[self kb_ensureChatPanelViewIfNeeded];
self.chatPanelView.hidden = NO;
self.chatPanelView.alpha = 0.0;
if (animated) {
[UIView animateWithDuration:0.2
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.chatPanelView.alpha = 1.0;
}
completion:nil];
} else {
self.chatPanelView.alpha = 1.0;
}
} else {
// show/hide
if (!_chatPanelView) {
[self kb_updateKeyboardLayoutIfNeeded];
return;
}
if (animated) {
[UIView animateWithDuration:0.18
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
self.chatPanelView.alpha = 0.0;
}
completion:^(BOOL finished) {
self.chatPanelView.hidden = YES;
}];
} else {
self.chatPanelView.alpha = 0.0;
self.chatPanelView.hidden = YES;
}
}
[self kb_updateKeyboardLayoutIfNeeded];
}
- (void)kb_setSettingViewVisible:(BOOL)visible animated:(BOOL)animated {
if (visible) {
KBSettingView *settingView = self.settingView;
if (!settingView.superview) {
settingView.hidden = YES;
[self.contentView addSubview:settingView];
[settingView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
[settingView.backButton addTarget:self
action:@selector(onTapSettingsBack)
forControlEvents:UIControlEventTouchUpInside];
}
[self.contentView bringSubviewToFront:settingView];
// keyBoardMainView self.view
[self.contentView layoutIfNeeded];
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
if (w <= 0) {
w = CGRectGetWidth(self.contentView.bounds);
}
if (w <= 0) {
w = [self kb_portraitWidth];
}
settingView.transform = CGAffineTransformMakeTranslation(w, 0);
settingView.hidden = NO;
if (animated) {
[UIView animateWithDuration:0.25
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
settingView.transform = CGAffineTransformIdentity;
}
completion:nil];
} else {
settingView.transform = CGAffineTransformIdentity;
}
} else {
KBSettingView *settingView = _settingView;
if (!settingView) {
return;
}
if (!settingView.superview || settingView.hidden) {
return;
}
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
if (w <= 0) {
w = CGRectGetWidth(self.contentView.bounds);
}
if (w <= 0) {
w = [self kb_portraitWidth];
}
if (animated) {
[UIView animateWithDuration:0.22
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
settingView.transform = CGAffineTransformMakeTranslation(w, 0);
}
completion:^(BOOL finished) {
settingView.hidden = YES;
}];
} else {
settingView.transform = CGAffineTransformMakeTranslation(w, 0);
settingView.hidden = YES;
}
}
}
- (void)kb_setSubscriptionPanelVisible:(BOOL)visible animated:(BOOL)animated {
if (visible) {
KBKeyboardSubscriptionView *panel = self.subscriptionView;
if (!panel.superview) {
panel.hidden = YES;
[self.contentView addSubview:panel];
[panel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
}
[self.contentView bringSubviewToFront:panel];
panel.hidden = NO;
panel.alpha = 0.0;
CGFloat height = CGRectGetHeight(self.contentView.bounds);
if (height <= 0) {
height = 260;
}
panel.transform = CGAffineTransformMakeTranslation(0, height);
[panel refreshProductsIfNeeded];
if (animated) {
[UIView animateWithDuration:0.25
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
panel.alpha = 1.0;
panel.transform = CGAffineTransformIdentity;
}
completion:nil];
} else {
panel.alpha = 1.0;
panel.transform = CGAffineTransformIdentity;
}
return;
}
KBKeyboardSubscriptionView *panel = _subscriptionView;
if (!panel) {
return;
}
if (!panel.superview || panel.hidden) {
return;
}
CGFloat height = CGRectGetHeight(panel.bounds);
if (height <= 0) {
height = CGRectGetHeight(self.contentView.bounds);
}
if (animated) {
[UIView animateWithDuration:0.22
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
panel.alpha = 0.0;
panel.transform = CGAffineTransformMakeTranslation(0, height);
}
completion:^(BOOL finished) {
panel.hidden = YES;
panel.alpha = 1.0;
panel.transform = CGAffineTransformIdentity;
}];
} else {
panel.hidden = YES;
panel.alpha = 1.0;
panel.transform = CGAffineTransformIdentity;
}
}
// /
- (void)kb_ensureFunctionViewIfNeeded {
if (_functionView && _functionView.superview) {
return;
}
KBFunctionView *v = self.functionView;
if (!v.superview) {
v.hidden = YES;
[self.contentView addSubview:v];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
}
}
// /
- (void)kb_ensureChatPanelViewIfNeeded {
if (_chatPanelView && _chatPanelView.superview) {
return;
}
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat chatPanelHeight = [self kb_chatPanelHeightForWidth:portraitWidth];
KBChatPanelView *v = self.chatPanelView;
if (!v.superview) {
[self.contentView addSubview:v];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.keyBoardMainView.mas_top);
self.chatPanelHeightConstraint =
make.height.mas_equalTo(chatPanelHeight);
}];
v.hidden = YES;
}
}
//
- (void)kb_ensureKeyBoardMainViewIfNeeded {
if (_keyBoardMainView && _keyBoardMainView.superview) {
return;
}
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat keyboardBaseHeight =
[self kb_keyboardBaseHeightForWidth:portraitWidth];
KBKeyBoardMainView *v = self.keyBoardMainView;
if (!v.superview) {
[self.contentView addSubview:v];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.contentView);
self.keyBoardMainHeightConstraint =
make.height.mas_equalTo(keyboardBaseHeight);
}];
}
[self.contentView bringSubviewToFront:v];
}
// //
- (void)kb_releaseMemoryWhenKeyboardHidden {
[KBHUD setContainerView:nil];
self.bgImageView.image = nil;
self.kb_cachedGradientImage = nil;
[self.kb_defaultGradientLayer removeFromSuperlayer];
self.kb_defaultGradientLayer = nil;
[[SDImageCache sharedImageCache] clearMemory];
// /
if (self.chatAudioPlayer) {
[self.chatAudioPlayer stop];
self.chatAudioPlayer = nil;
}
if (_chatMessages.count > 0) {
NSString *tmpRoot = NSTemporaryDirectory();
for (KBChatMessage *msg in _chatMessages.copy) {
if (tmpRoot.length > 0 && msg.audioFilePath.length > 0 &&
[msg.audioFilePath hasPrefix:tmpRoot]) {
[[NSFileManager defaultManager] removeItemAtPath:msg.audioFilePath
error:nil];
}
}
[_chatMessages removeAllObjects];
}
if (_keyBoardMainView) {
[_keyBoardMainView removeFromSuperview];
_keyBoardMainView = nil;
}
self.keyBoardMainHeightConstraint = nil;
if (_functionView) {
[_functionView removeFromSuperview];
_functionView = nil;
}
if (_chatPanelView) {
[_chatPanelView removeFromSuperview];
_chatPanelView = nil;
}
self.chatPanelVisible = NO;
self.kb_panelMode = KBKeyboardPanelModeMain;
if (_subscriptionView) {
[_subscriptionView removeFromSuperview];
_subscriptionView = nil;
}
if (_settingView) {
[_settingView removeFromSuperview];
_settingView = nil;
}
}
// MARK: - KBKeyBoardMainViewDelegate
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView
didTapKey:(KBKey *)key {
switch (key.type) {
case KBKeyTypeCharacter: {
[[KBBackspaceUndoManager shared] registerNonClearAction];
NSString *text = key.output ?: key.title ?: @"";
[self.textDocumentProxy insertText:text];
[self kb_updateCurrentWordWithInsertedText:text];
[[KBInputBufferManager shared] appendText:text];
} break;
case KBKeyTypeBackspace:
[[KBInputBufferManager shared]
refreshFromProxyIfPossible:self.textDocumentProxy];
[[KBInputBufferManager shared]
prepareSnapshotForDeleteWithContextBefore:
self.textDocumentProxy.documentContextBeforeInput
after:
self.textDocumentProxy
.documentContextAfterInput];
[[KBBackspaceUndoManager shared]
captureAndDeleteBackwardFromProxy:self.textDocumentProxy
count:1];
[self kb_scheduleContextRefreshResetSuppression:NO];
[[KBInputBufferManager shared] applyHoldDeleteCount:1];
break;
case KBKeyTypeSpace:
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:@" "];
[self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:@" "];
break;
case KBKeyTypeReturn:
if (self.chatPanelVisible) {
[self kb_handleChatSendAction];
break;
}
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:@"\n"];
[self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:@"\n"];
break;
case KBKeyTypeGlobe:
[self advanceToNextInputMode];
break;
case KBKeyTypeCustom:
[[KBBackspaceUndoManager shared] registerNonClearAction];
//
[self kb_setPanelMode:KBKeyboardPanelModeFunction animated:NO];
[self kb_clearCurrentWord];
break;
case KBKeyTypeModeChange:
case KBKeyTypeShift:
// KBKeyBoardMainView/KBKeyboardView
break;
}
}
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView
didTapToolActionAtIndex:(NSInteger)index {
NSDictionary *extra = @{@"index" : @(index)};
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_toolbar_action"
pageId:@"keyboard_main_panel"
elementId:@"toolbar_action"
extra:extra
completion:nil];
if (index == 0) {
[self kb_setPanelMode:KBKeyboardPanelModeFunction animated:YES];
[self kb_clearCurrentWord];
return;
}
if (index == 1) {
[self kb_setPanelMode:KBKeyboardPanelModeChat animated:YES];
return;
}
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
- (void)keyBoardMainViewDidTapSettings:(KBKeyBoardMainView *)keyBoardMainView {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_settings_btn"
pageId:@"keyboard_main_panel"
elementId:@"settings_btn"
extra:nil
completion:nil];
[self kb_setPanelMode:KBKeyboardPanelModeSettings animated:YES];
}
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView
didSelectEmoji:(NSString *)emoji {
if (emoji.length == 0) {
return;
}
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:emoji];
[self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:emoji];
}
- (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_undo_btn"
pageId:@"keyboard_main_panel"
elementId:@"undo_btn"
extra:nil
completion:nil];
[[KBBackspaceUndoManager shared] performUndoFromResponder:self.view];
[self kb_scheduleContextRefreshResetSuppression:YES];
}
- (void)keyBoardMainViewDidTapEmojiSearch:
(KBKeyBoardMainView *)keyBoardMainView {
// [[KBMaiPointReporter sharedReporter]
// reportClickWithEventName:@"click_keyboard_emoji_search_btn"
// pageId:@"keyboard_main_panel"
// elementId:@"emoji_search_btn"
// extra:nil
// completion:nil];
[KBHUD showInfo:KBLocalized(@"Search coming soon")];
}
// MARK: - KBFunctionViewDelegate
- (void)functionView:(KBFunctionView *)functionView
didTapToolActionAtIndex:(NSInteger)index {
// index == 0
if (index == 0) {
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:NO];
}
}
- (void)functionView:(KBFunctionView *_Nullable)functionView
didRightTapToolActionAtIndex:(NSInteger)index {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_function_right_action"
pageId:@"keyboard_function_panel"
elementId:@"right_action"
extra:@{@"action" : @"login_or_recharge"}
completion:nil];
if (!KBAuthManager.shared.isLoggedIn) {
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
return;
}
NSString *schemeStr =
[NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!ok) {
//
// XXX App /
[KBHUD showInfo:@"请回到桌面手动打开App进行充值"];
}
}
- (void)functionViewDidRequestSubscription:(KBFunctionView *)functionView {
[self showSubscriptionPanel];
}
#pragma mark - Actions
- (void)onTapSettingsBack {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_settings_back_btn"
pageId:@"keyboard_settings"
elementId:@"back_btn"
extra:nil
completion:nil];
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
@end

View File

@@ -0,0 +1,154 @@
//
// KeyboardViewController+Private.h
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController.h"
#import "Masonry.h"
@class AVAudioPlayer;
@class CAGradientLayer;
@class KBChatMessage;
@class KBChatPanelView;
@class KBFunctionView;
@class KBKeyBoardMainView;
@class KBKeyboardSubscriptionView;
@class KBSettingView;
@class KBSuggestionEngine;
@protocol KBChatLimitPopViewDelegate;
@protocol KBChatPanelViewDelegate;
@protocol KBFunctionViewDelegate;
@protocol KBKeyBoardMainViewDelegate;
@protocol KBKeyboardSubscriptionViewDelegate;
typedef NS_ENUM(NSInteger, KBKeyboardPanelMode) {
KBKeyboardPanelModeMain = 0,
KBKeyboardPanelModeFunction,
KBKeyboardPanelModeChat,
KBKeyboardPanelModeSettings,
KBKeyboardPanelModeSubscription,
};
@interface KeyboardViewController () <KBKeyBoardMainViewDelegate,
KBFunctionViewDelegate,
KBKeyboardSubscriptionViewDelegate,
KBChatPanelViewDelegate,
KBChatLimitPopViewDelegate>
{
UIButton *_nextKeyboardButton;
UIView *_contentView;
KBKeyBoardMainView *_keyBoardMainView;
KBFunctionView *_functionView;
KBSettingView *_settingView;
UIImageView *_bgImageView;
KBChatPanelView *_chatPanelView;
KBKeyboardSubscriptionView *_subscriptionView;
KBSuggestionEngine *_suggestionEngine;
NSString *_currentWord;
UIControl *_chatLimitMaskView;
MASConstraint *_contentWidthConstraint;
MASConstraint *_contentHeightConstraint;
MASConstraint *_keyBoardMainHeightConstraint;
MASConstraint *_chatPanelHeightConstraint;
NSLayoutConstraint *_kb_heightConstraint;
NSLayoutConstraint *_kb_widthConstraint;
CGFloat _kb_lastPortraitWidth;
CGFloat _kb_lastKeyboardHeight;
UIImage *_kb_cachedGradientImage;
CGSize _kb_cachedGradientSize;
CAGradientLayer *_kb_defaultGradientLayer;
NSString *_kb_lastAppliedThemeKey;
NSMutableArray<KBChatMessage *> *_chatMessages;
AVAudioPlayer *_chatAudioPlayer;
BOOL _suppressSuggestions;
BOOL _chatPanelVisible;
NSString *_chatPanelBaselineText;
id _kb_fullAccessObserverToken;
id _kb_skinObserverToken;
KBKeyboardPanelMode _kb_panelMode;
}
@property(nonatomic, strong)
UIButton *nextKeyboardButton; // 系统“下一个键盘”按钮(可选)
@property(nonatomic, strong) UIView *contentView;
@property(nonatomic, strong) KBKeyBoardMainView
*keyBoardMainView; // 功能面板视图点击工具栏第0个时显示
@property(nonatomic, strong)
KBFunctionView *functionView; // 功能面板视图点击工具栏第0个时显示
@property(nonatomic, strong) KBSettingView *settingView; // 设置页
@property(nonatomic, strong) UIImageView *bgImageView; // 背景图(在底层)
@property(nonatomic, strong) KBChatPanelView *chatPanelView;
@property(nonatomic, strong) KBKeyboardSubscriptionView *subscriptionView;
@property(nonatomic, strong) KBSuggestionEngine *suggestionEngine;
@property(nonatomic, copy) NSString *currentWord;
@property(nonatomic, assign) BOOL suppressSuggestions;
@property(nonatomic, strong) UIControl *chatLimitMaskView;
@property(nonatomic, strong) MASConstraint *contentWidthConstraint;
@property(nonatomic, strong) MASConstraint *contentHeightConstraint;
@property(nonatomic, strong) MASConstraint *keyBoardMainHeightConstraint;
@property(nonatomic, strong) MASConstraint *chatPanelHeightConstraint;
@property(nonatomic, strong) NSLayoutConstraint *kb_heightConstraint;
@property(nonatomic, strong) NSLayoutConstraint *kb_widthConstraint;
@property(nonatomic, assign) CGFloat kb_lastPortraitWidth;
@property(nonatomic, assign) CGFloat kb_lastKeyboardHeight;
@property(nonatomic, strong) UIImage *kb_cachedGradientImage;
@property(nonatomic, assign) CGSize kb_cachedGradientSize;
@property(nonatomic, strong, nullable) CAGradientLayer *kb_defaultGradientLayer;
@property(nonatomic, copy, nullable) NSString *kb_lastAppliedThemeKey;
@property(nonatomic, strong) NSMutableArray<KBChatMessage *> *chatMessages;
@property(nonatomic, strong) AVAudioPlayer *chatAudioPlayer;
@property(nonatomic, assign) BOOL chatPanelVisible;
@property(nonatomic, copy) NSString *chatPanelBaselineText; // 打开聊天面板时宿主输入框已有的文本
@property(nonatomic, strong, nullable) id kb_fullAccessObserverToken;
@property(nonatomic, strong, nullable) id kb_skinObserverToken;
@property(nonatomic, assign) KBKeyboardPanelMode kb_panelMode;
@end
@interface KeyboardViewController (KBPrivate)
// UI
- (void)setupUI;
- (nullable KBFunctionView *)kb_functionViewIfCreated;
// Panels
- (void)showFunctionPanel:(BOOL)show;
- (void)showSettingView:(BOOL)show;
- (void)showChatPanel:(BOOL)show;
- (void)showSubscriptionPanel;
- (void)hideSubscriptionPanel;
- (void)kb_setPanelMode:(KBKeyboardPanelMode)mode animated:(BOOL)animated;
- (void)kb_ensureFunctionViewIfNeeded;
- (void)kb_ensureChatPanelViewIfNeeded;
- (void)kb_ensureKeyBoardMainViewIfNeeded;
- (void)kb_releaseMemoryWhenKeyboardHidden;
// Suggestions
- (void)kb_updateCurrentWordWithInsertedText:(NSString *)text;
- (void)kb_clearCurrentWord;
- (void)kb_scheduleContextRefreshResetSuppression:(BOOL)resetSuppression;
- (void)kb_refreshCurrentWordFromDocumentContextResetSuppression:
(BOOL)resetSuppression;
- (void)kb_updateSuggestionsForCurrentWord;
// Chat
- (void)kb_handleChatSendAction;
// Theme
- (void)kb_applyTheme;
- (void)kb_applyDefaultSkinIfNeeded;
- (void)kb_consumePendingShopSkin;
- (void)kb_registerDarwinSkinInstallObserver;
- (void)kb_unregisterDarwinSkinInstallObserver;
// Layout
- (CGFloat)kb_portraitWidth;
- (CGFloat)kb_keyboardHeightForWidth:(CGFloat)width;
- (CGFloat)kb_keyboardBaseHeightForWidth:(CGFloat)width;
- (CGFloat)kb_chatPanelHeightForWidth:(CGFloat)width;
- (void)kb_updateKeyboardLayoutIfNeeded;
@end

View File

@@ -0,0 +1,117 @@
//
// KeyboardViewController+Subscription.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBAuthManager.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "KBKeyboardSubscriptionProduct.h"
#import "KBKeyboardSubscriptionView.h"
@implementation KeyboardViewController (Subscription)
- (void)showSubscriptionPanel {
// 1) 访
if (![[KBFullAccessManager shared] hasFullAccess]) {
// 访
// [KBHUD showInfo:KBLocalized(@"处理中…")];
[[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self.view];
return;
}
//
// 2) -> App App
if (!KBAuthManager.shared.isLoggedIn) {
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
return;
}
[self kb_setPanelMode:KBKeyboardPanelModeSubscription animated:YES];
}
- (void)hideSubscriptionPanel {
if (self.kb_panelMode != KBKeyboardPanelModeSubscription) {
return;
}
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
#pragma mark - KBKeyboardSubscriptionViewDelegate
- (void)subscriptionViewDidTapClose:(KBKeyboardSubscriptionView *)view {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_subscription_close_btn"
pageId:@"keyboard_subscription_panel"
elementId:@"close_btn"
extra:nil
completion:nil];
[self hideSubscriptionPanel];
}
- (void)subscriptionView:(KBKeyboardSubscriptionView *)view
didTapPurchaseForProduct:(KBKeyboardSubscriptionProduct *)product {
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([product.productId isKindOfClass:NSString.class] &&
product.productId.length > 0) {
extra[@"product_id"] = product.productId;
}
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_subscription_product_btn"
pageId:@"keyboard_subscription_panel"
elementId:@"product_btn"
extra:extra.copy
completion:nil];
[self hideSubscriptionPanel];
[self kb_openRechargeForProduct:product];
}
#pragma mark - Actions
- (void)kb_openRechargeForProduct:(KBKeyboardSubscriptionProduct *)product {
if (![product isKindOfClass:KBKeyboardSubscriptionProduct.class] ||
product.productId.length == 0) {
[KBHUD showInfo:KBLocalized(@"Product unavailable")];
return;
}
NSString *encodedId = [self.class kb_urlEncodedString:product.productId];
NSString *title = [product displayTitle];
NSString *encodedTitle = [self.class kb_urlEncodedString:title];
NSMutableArray<NSString *> *params =
[NSMutableArray arrayWithObjects:@"autoPay=1", @"prefill=1", nil];
if (encodedId.length) {
[params addObject:[NSString stringWithFormat:@"productId=%@", encodedId]];
}
if (encodedTitle.length) {
[params
addObject:[NSString stringWithFormat:@"productTitle=%@", encodedTitle]];
}
NSString *query = [params componentsJoinedByString:@"&"];
NSString *urlString = [NSString
stringWithFormat:@"%@://recharge?src=keyboard&%@", KB_APP_SCHEME, query];
NSURL *scheme = [NSURL URLWithString:urlString];
BOOL success = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!success) {
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
}
}
+ (NSString *)kb_urlEncodedString:(NSString *)value {
if (value.length == 0) {
return @"";
}
NSString *reserved = @"!*'();:@&=+$,/?%#[]";
NSMutableCharacterSet *allowed =
[[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowed removeCharactersInString:reserved];
return [value stringByAddingPercentEncodingWithAllowedCharacters:allowed]
?: @"";
}
@end

View File

@@ -0,0 +1,178 @@
//
// KeyboardViewController+Suggestions.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBBackspaceUndoManager.h"
#import "KBInputBufferManager.h"
#import "KBKeyBoardMainView.h"
#import "KBSuggestionEngine.h"
@implementation KeyboardViewController (Suggestions)
// MARK: - Suggestions
- (void)kb_updateCurrentWordWithInsertedText:(NSString *)text {
if (text.length == 0) {
return;
}
if ([self kb_isAlphabeticString:text]) {
NSString *current = self.currentWord ?: @"";
self.currentWord = [current stringByAppendingString:text];
self.suppressSuggestions = NO;
[self kb_updateSuggestionsForCurrentWord];
} else {
[self kb_clearCurrentWord];
}
}
- (void)kb_clearCurrentWord {
self.currentWord = @"";
[self.keyBoardMainView kb_setSuggestions:@[]];
self.suppressSuggestions = NO;
}
- (void)kb_scheduleContextRefreshResetSuppression:(BOOL)resetSuppression {
dispatch_async(dispatch_get_main_queue(), ^{
[self kb_refreshCurrentWordFromDocumentContextResetSuppression:
resetSuppression];
});
}
- (void)kb_refreshCurrentWordFromDocumentContextResetSuppression:
(BOOL)resetSuppression {
NSString *context = self.textDocumentProxy.documentContextBeforeInput ?: @"";
NSString *word = [self kb_extractTrailingWordFromContext:context];
self.currentWord = word ?: @"";
if (resetSuppression) {
self.suppressSuggestions = NO;
}
[self kb_updateSuggestionsForCurrentWord];
}
- (NSString *)kb_extractTrailingWordFromContext:(NSString *)context {
if (context.length == 0) {
return @"";
}
static NSCharacterSet *letters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
letters = [NSCharacterSet
characterSetWithCharactersInString:
@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
});
NSInteger idx = (NSInteger)context.length - 1;
while (idx >= 0) {
unichar ch = [context characterAtIndex:(NSUInteger)idx];
if (![letters characterIsMember:ch]) {
break;
}
idx -= 1;
}
NSUInteger start = (NSUInteger)(idx + 1);
if (start >= context.length) {
return @"";
}
return [context substringFromIndex:start];
}
- (BOOL)kb_isAlphabeticString:(NSString *)text {
if (text.length == 0) {
return NO;
}
static NSCharacterSet *letters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
letters = [NSCharacterSet
characterSetWithCharactersInString:
@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
});
for (NSUInteger i = 0; i < text.length; i++) {
if (![letters characterIsMember:[text characterAtIndex:i]]) {
return NO;
}
}
return YES;
}
- (void)kb_updateSuggestionsForCurrentWord {
NSString *prefix = self.currentWord ?: @"";
if (prefix.length == 0) {
[self.keyBoardMainView kb_setSuggestions:@[]];
return;
}
if (self.suppressSuggestions) {
[self.keyBoardMainView kb_setSuggestions:@[]];
return;
}
NSArray<NSString *> *items =
[self.suggestionEngine suggestionsForPrefix:prefix limit:5];
NSArray<NSString *> *cased = [self kb_applyCaseToSuggestions:items
prefix:prefix];
[self.keyBoardMainView kb_setSuggestions:cased];
}
- (NSArray<NSString *> *)kb_applyCaseToSuggestions:(NSArray<NSString *> *)items
prefix:(NSString *)prefix {
if (items.count == 0 || prefix.length == 0) {
return items;
}
BOOL allUpper = [prefix isEqualToString:prefix.uppercaseString];
BOOL firstUpper = [[prefix substringToIndex:1]
isEqualToString:[[prefix substringToIndex:1] uppercaseString]];
if (!allUpper && !firstUpper) {
return items;
}
NSMutableArray<NSString *> *result =
[NSMutableArray arrayWithCapacity:items.count];
for (NSString *word in items) {
if (allUpper) {
[result addObject:word.uppercaseString];
} else {
NSString *first = [[word substringToIndex:1] uppercaseString];
NSString *rest = (word.length > 1) ? [word substringFromIndex:1] : @"";
[result addObject:[first stringByAppendingString:rest]];
}
}
return result.copy;
}
// MARK: - KBKeyBoardMainViewDelegate (Suggestion)
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView
didSelectSuggestion:(NSString *)suggestion {
if (suggestion.length == 0) {
return;
}
NSDictionary *extra = @{@"suggestion_len" : @(suggestion.length)};
// [[KBMaiPointReporter sharedReporter]
// reportClickWithEventName:@"click_keyboard_suggestion_item"
// pageId:@"keyboard_main_panel"
// elementId:@"suggestion_item"
// extra:extra
// completion:nil];
[[KBBackspaceUndoManager shared] registerNonClearAction];
NSString *current = self.currentWord ?: @"";
if (current.length > 0) {
for (NSUInteger i = 0; i < current.length; i++) {
[self.textDocumentProxy deleteBackward];
}
}
[self.textDocumentProxy insertText:suggestion];
self.currentWord = suggestion;
[self.suggestionEngine recordSelection:suggestion];
self.suppressSuggestions = YES;
[self.keyBoardMainView kb_setSuggestions:@[]];
[[KBInputBufferManager shared] replaceTailWithText:suggestion
deleteCount:current.length];
}
@end

View File

@@ -0,0 +1,376 @@
//
// KeyboardViewController+Theme.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBFunctionView.h"
#import "KBKeyBoardMainView.h"
#import "KBSkinInstallBridge.h"
#import "KBSkinManager.h"
#import "UIImage+KBColor.h"
#import <QuartzCore/QuartzCore.h>
static NSString *const kKBDefaultSkinIdLight = @"normal_them";
static NSString *const kKBDefaultSkinZipNameLight = @"normal_them";
static NSString *const kKBDefaultSkinIdDark = @"normal_hei_them";
static NSString *const kKBDefaultSkinZipNameDark = @"normal_hei_them";
// 使 static kb_consumePendingShopSkin
@interface KeyboardViewController (KBSkinShopBridge)
- (void)kb_consumePendingShopSkin;
@end
static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
void *observer, CFStringRef name,
const void *object,
CFDictionaryRef userInfo) {
KeyboardViewController *strongSelf =
(__bridge KeyboardViewController *)observer;
if (!strongSelf) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
if ([strongSelf respondsToSelector:@selector(kb_consumePendingShopSkin)]) {
[strongSelf kb_consumePendingShopSkin];
}
});
}
@implementation KeyboardViewController (Theme)
- (void)kb_registerDarwinSkinInstallObserver {
CFNotificationCenterAddObserver(
CFNotificationCenterGetDarwinNotifyCenter(),
(__bridge const void *)(self), KBSkinInstallNotificationCallback,
(__bridge CFStringRef)KBDarwinSkinInstallRequestNotification, NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
}
- (void)kb_unregisterDarwinSkinInstallObserver {
CFNotificationCenterRemoveObserver(
CFNotificationCenterGetDarwinNotifyCenter(),
(__bridge const void *)(self),
(__bridge CFStringRef)KBDarwinSkinInstallRequestNotification, NULL);
}
- (void)kb_applyTheme {
@autoreleasepool {
KBSkinTheme *t = [KBSkinManager shared].current;
UIImage *img = nil;
BOOL isDefaultTheme = [self kb_isDefaultKeyboardTheme:t];
BOOL isDarkMode = [self kb_isDarkModeActive];
NSString *skinId = t.skinId ?: @"";
NSString *themeKey =
[NSString stringWithFormat:@"%@|default=%d|dark=%d", skinId,
isDefaultTheme, isDarkMode];
BOOL themeChanged =
(self.kb_lastAppliedThemeKey.length == 0 ||
![self.kb_lastAppliedThemeKey isEqualToString:themeKey]);
if (themeChanged) {
self.kb_lastAppliedThemeKey = themeKey;
}
CGSize size = self.bgImageView.bounds.size;
if (isDefaultTheme) {
if (isDarkMode) {
// 使使
//
img = nil;
self.bgImageView.image = nil;
[self.kb_defaultGradientLayer removeFromSuperlayer];
self.kb_defaultGradientLayer = nil;
// 使
if (@available(iOS 13.0, *)) {
// iOS 使 (RGB: 44, 44, 46 in sRGB, #2C2C2E)
// 使
UIColor *kbBgColor =
[UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle ==
UIUserInterfaceStyleDark) {
//
return [UIColor colorWithRed:43.0 / 255.0
green:43.0 / 255.0
blue:43.0 / 255.0
alpha:1.0];
} else {
return [UIColor colorWithRed:209.0 / 255.0
green:211.0 / 255.0
blue:219.0 / 255.0
alpha:1.0];
}
}];
self.contentView.backgroundColor = kbBgColor;
self.bgImageView.backgroundColor = kbBgColor;
} else {
UIColor *darkColor = [UIColor colorWithRed:43.0 / 255.0
green:43.0 / 255.0
blue:43.0 / 255.0
alpha:1.0];
self.contentView.backgroundColor = darkColor;
self.bgImageView.backgroundColor = darkColor;
}
} else {
// 使
if (size.width <= 0 || size.height <= 0) {
[self.view layoutIfNeeded];
size = self.bgImageView.bounds.size;
}
if (size.width <= 0 || size.height <= 0) {
size = self.view.bounds.size;
}
if (size.width <= 0 || size.height <= 0) {
size = [UIScreen mainScreen].bounds.size;
}
UIColor *topColor = [UIColor colorWithHex:0xDEDFE4];
UIColor *bottomColor = [UIColor colorWithHex:0xD1D3DB];
UIColor *resolvedTopColor = topColor;
UIColor *resolvedBottomColor = bottomColor;
if (@available(iOS 13.0, *)) {
resolvedTopColor =
[topColor resolvedColorWithTraitCollection:self.traitCollection];
resolvedBottomColor =
[bottomColor resolvedColorWithTraitCollection:self.traitCollection];
}
CAGradientLayer *layer = self.kb_defaultGradientLayer;
if (!layer) {
layer = [CAGradientLayer layer];
layer.startPoint = CGPointMake(0.5, 0.0);
layer.endPoint = CGPointMake(0.5, 1.0);
[self.bgImageView.layer insertSublayer:layer atIndex:0];
self.kb_defaultGradientLayer = layer;
}
layer.colors =
@[ (id)resolvedTopColor.CGColor, (id)resolvedBottomColor.CGColor ];
layer.frame = (CGRect){CGPointZero, size};
img = nil;
self.bgImageView.image = nil;
self.contentView.backgroundColor = [UIColor clearColor];
self.bgImageView.backgroundColor = [UIColor clearColor];
}
NSLog(@"===");
} else {
// 使
self.contentView.backgroundColor = [UIColor clearColor];
self.bgImageView.backgroundColor = [UIColor clearColor];
[self.kb_defaultGradientLayer removeFromSuperlayer];
self.kb_defaultGradientLayer = nil;
img = [[KBSkinManager shared] currentBackgroundImage];
}
NSLog(@"⌨️[Keyboard] apply theme id=%@ hasBg=%d", t.skinId, (img != nil));
[self kb_logSkinDiagnosticsWithTheme:t backgroundImage:img];
self.bgImageView.image = img;
//
if (themeChanged &&
[self.keyBoardMainView respondsToSelector:@selector(kb_applyTheme)]) {
// method declared in KBKeyBoardMainView.h
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.keyBoardMainView performSelector:@selector(kb_applyTheme)];
#pragma clang diagnostic pop
}
// 访 self.functionView
KBFunctionView *functionView = [self kb_functionViewIfCreated];
if (themeChanged && functionView &&
[functionView respondsToSelector:@selector(kb_applyTheme)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[functionView performSelector:@selector(kb_applyTheme)];
#pragma clang diagnostic pop
}
}
}
- (BOOL)kb_isDefaultKeyboardTheme:(KBSkinTheme *)theme {
NSString *skinId = theme.skinId ?: @"";
if (skinId.length == 0 || [skinId isEqualToString:@"default"]) {
return YES;
}
if ([skinId isEqualToString:kKBDefaultSkinIdLight]) {
return YES;
}
return [skinId isEqualToString:kKBDefaultSkinIdDark];
}
- (BOOL)kb_isDarkModeActive {
if (@available(iOS 13.0, *)) {
return self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark;
}
return NO;
}
- (NSString *)kb_defaultSkinIdForCurrentStyle {
return [self kb_isDarkModeActive] ? kKBDefaultSkinIdDark
: kKBDefaultSkinIdLight;
}
- (NSString *)kb_defaultSkinZipNameForCurrentStyle {
return [self kb_isDarkModeActive] ? kKBDefaultSkinZipNameDark
: kKBDefaultSkinZipNameLight;
}
- (UIImage *)kb_defaultGradientImageWithSize:(CGSize)size
topColor:(UIColor *)topColor
bottomColor:(UIColor *)bottomColor {
if (size.width <= 0 || size.height <= 0) {
return nil;
}
//
if (self.kb_cachedGradientImage &&
CGSizeEqualToSize(self.kb_cachedGradientSize, size)) {
return self.kb_cachedGradientImage;
}
UIColor *resolvedTopColor = topColor;
UIColor *resolvedBottomColor = bottomColor;
if (@available(iOS 13.0, *)) {
resolvedTopColor =
[topColor resolvedColorWithTraitCollection:self.traitCollection];
resolvedBottomColor =
[bottomColor resolvedColorWithTraitCollection:self.traitCollection];
}
CAGradientLayer *layer = [CAGradientLayer layer];
layer.frame = CGRectMake(0, 0, size.width, size.height);
layer.startPoint = CGPointMake(0.5, 0.0);
layer.endPoint = CGPointMake(0.5, 1.0);
layer.colors =
@[ (id)resolvedTopColor.CGColor, (id)resolvedBottomColor.CGColor ];
UIGraphicsBeginImageContextWithOptions(size, YES, 0);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.kb_cachedGradientImage = image;
self.kb_cachedGradientSize = size;
return image;
}
- (void)kb_logSkinDiagnosticsWithTheme:(KBSkinTheme *)theme
backgroundImage:(UIImage *)image {
#if DEBUG
NSString *skinId = theme.skinId ?: @"";
NSString *name = theme.name ?: @"";
NSMutableArray<NSString *> *roots = [NSMutableArray array];
NSURL *containerURL = [[NSFileManager defaultManager]
containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (containerURL.path.length > 0) {
[roots addObject:containerURL.path];
}
NSString *cacheRoot = NSSearchPathForDirectoriesInDomains(
NSCachesDirectory, NSUserDomainMask, YES)
.firstObject;
if (cacheRoot.length > 0) {
[roots addObject:cacheRoot];
}
NSFileManager *fm = [NSFileManager defaultManager];
NSMutableArray<NSString *> *lines = [NSMutableArray array];
for (NSString *root in roots) {
NSString *iconsDir = [[root stringByAppendingPathComponent:@"Skins"]
stringByAppendingPathComponent:skinId];
iconsDir = [iconsDir stringByAppendingPathComponent:@"icons"];
BOOL isDir = NO;
BOOL exists = [fm fileExistsAtPath:iconsDir isDirectory:&isDir] && isDir;
NSArray *contents =
exists ? [fm contentsOfDirectoryAtPath:iconsDir error:nil] : nil;
NSUInteger count = contents.count;
BOOL hasQ =
exists &&
[fm fileExistsAtPath:[iconsDir
stringByAppendingPathComponent:@"key_q.png"]];
BOOL hasQUp =
exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:
@"key_q_up.png"]];
BOOL hasDel =
exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:
@"key_del.png"]];
BOOL hasShift =
exists &&
[fm fileExistsAtPath:[iconsDir
stringByAppendingPathComponent:@"key_up.png"]];
BOOL hasShiftUpper =
exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:
@"key_up_upper.png"]];
NSString *line = [NSString
stringWithFormat:@"root=%@ icons=%@ exist=%d count=%tu key_q=%d "
@"key_q_up=%d key_del=%d key_up=%d key_up_upper=%d",
root, iconsDir, exists, count, hasQ, hasQUp, hasDel,
hasShift, hasShiftUpper];
[lines addObject:line];
}
NSLog(@"[Keyboard] theme id=%@ name=%@ hasBg=%d\n%@", skinId, name,
(image != nil), [lines componentsJoinedByString:@"\n"]);
#endif
}
- (void)kb_consumePendingShopSkin {
KBWeakSelf [KBSkinInstallBridge
consumePendingRequestFromBundle:NSBundle.mainBundle
completion:^(BOOL success,
NSError *_Nullable error) {
if (!success) {
if (error) {
NSLog(@"[Keyboard] skin request failed: %@",
error);
[KBHUD
showInfo:
KBLocalized(
@"皮肤资源准备失败,请稍后再试")];
}
return;
}
[weakSelf kb_applyTheme];
[KBHUD showInfo:KBLocalized(
@"皮肤已更新,立即体验吧")];
}];
}
- (void)kb_applyDefaultSkinIfNeeded {
NSDictionary *pending = [KBSkinInstallBridge pendingRequestPayload];
if (pending.count > 0) {
return;
}
NSString *currentId = [KBSkinManager shared].current.skinId ?: @"";
BOOL isDefault =
(currentId.length == 0 || [currentId isEqualToString:@"default"]);
BOOL isLightDefault = [currentId isEqualToString:kKBDefaultSkinIdLight];
BOOL isDarkDefault = [currentId isEqualToString:kKBDefaultSkinIdDark];
if (!isDefault && !isLightDefault && !isDarkDefault) {
//
return;
}
NSString *targetId = [self kb_defaultSkinIdForCurrentStyle];
NSString *targetZip = [self kb_defaultSkinZipNameForCurrentStyle];
if (currentId.length > 0 && [currentId isEqualToString:targetId]) {
return;
}
NSError *applyError = nil;
if ([KBSkinInstallBridge applyInstalledSkinWithId:targetId error:&applyError]) {
return;
}
[KBSkinInstallBridge publishBundleSkinRequestWithId:targetId
name:targetId
zipName:targetZip
iconShortNames:nil];
[KBSkinInstallBridge
consumePendingRequestFromBundle:NSBundle.mainBundle
completion:^(__unused BOOL success,
__unused NSError *_Nullable error) {
//
}];
}
@end

View File

@@ -0,0 +1,151 @@
//
// KeyboardViewController+UI.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBChatMessage.h"
#import "KBChatPanelView.h"
#import "KBFunctionView.h"
#import "KBKeyBoardMainView.h"
#import "KBKeyboardSubscriptionView.h"
#import "KBSettingView.h"
#import "Masonry.h"
@implementation KeyboardViewController (UI)
- (void)setupUI {
self.view.translatesAutoresizingMaskIntoConstraints = NO;
//
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth];
CGFloat keyboardBaseHeight = [self kb_keyboardBaseHeightForWidth:portraitWidth];
CGFloat screenWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
// FIX: iOS 26
// iOS 26 self.view view
// view
// UI
// 0
// viewWillAppear:
// iOS 18
NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:0];
NSLayoutConstraint *w =
[self.view.widthAnchor constraintEqualToConstant:screenWidth];
self.kb_heightConstraint = h;
self.kb_widthConstraint = w;
h.priority = UILayoutPriorityRequired;
w.priority = UILayoutPriorityRequired;
[NSLayoutConstraint activateConstraints:@[ h, w ]];
// UIInputView
if ([self.view isKindOfClass:[UIInputView class]]) {
UIInputView *iv = (UIInputView *)self.view;
if ([iv respondsToSelector:@selector(setAllowsSelfSizing:)]) {
iv.allowsSelfSizing = NO;
}
}
//
[self.view addSubview:self.contentView];
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.bottom.equalTo(self.view);
self.contentWidthConstraint = make.width.mas_equalTo(portraitWidth);
self.contentHeightConstraint = make.height.mas_equalTo(keyboardHeight);
}];
//
[self.contentView addSubview:self.bgImageView];
[self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
[self.contentView addSubview:self.keyBoardMainView];
[self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.contentView);
self.keyBoardMainHeightConstraint =
make.height.mas_equalTo(keyboardBaseHeight);
}];
//
self.contentView.hidden = YES;
}
#pragma mark - Lazy
- (nullable KBFunctionView *)kb_functionViewIfCreated {
return _functionView;
}
- (UIView *)contentView {
if (!_contentView) {
_contentView = [[UIView alloc] init];
_contentView.backgroundColor = [UIColor clearColor];
}
return _contentView;
}
- (UIImageView *)bgImageView {
if (!_bgImageView) {
_bgImageView = [[UIImageView alloc] init];
_bgImageView.contentMode = UIViewContentModeScaleAspectFill;
_bgImageView.clipsToBounds = YES;
}
return _bgImageView;
}
- (KBKeyBoardMainView *)keyBoardMainView {
if (!_keyBoardMainView) {
_keyBoardMainView = [[KBKeyBoardMainView alloc] init];
_keyBoardMainView.delegate = self;
}
return _keyBoardMainView;
}
- (KBFunctionView *)functionView {
if (!_functionView) {
_functionView = [[KBFunctionView alloc] init];
_functionView.delegate = self; // Bar
}
return _functionView;
}
- (KBSettingView *)settingView {
if (!_settingView) {
_settingView = [[KBSettingView alloc] init];
}
return _settingView;
}
- (KBChatPanelView *)chatPanelView {
if (!_chatPanelView) {
NSLog(@"[Keyboard] ⚠️ chatPanelView 被创建!");
_chatPanelView = [[KBChatPanelView alloc] init];
_chatPanelView.delegate = self;
}
return _chatPanelView;
}
- (NSMutableArray<KBChatMessage *> *)chatMessages {
if (!_chatMessages) {
_chatMessages = [NSMutableArray array];
}
return _chatMessages;
}
- (KBKeyboardSubscriptionView *)subscriptionView {
if (!_subscriptionView) {
_subscriptionView = [[KBKeyboardSubscriptionView alloc] init];
_subscriptionView.delegate = self;
_subscriptionView.hidden = YES;
_subscriptionView.alpha = 0.0;
}
return _subscriptionView;
}
@end

View File

@@ -0,0 +1,46 @@
//
// KBEmojiDataProvider.h
// CustomKeyboard
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString * const KBEmojiRecentsDidChangeNotification;
@class KBEmojiCategory, KBEmojiItem;
@interface KBEmojiItem : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *value;
@property (nonatomic, copy, readonly) NSString *name;
- (instancetype)initWithValue:(NSString *)value name:(NSString *)name;
@end
@interface KBEmojiCategory : NSObject
@property (nonatomic, copy, readonly) NSString *identifier;
@property (nonatomic, copy, readonly) NSString *displayTitle;
@property (nonatomic, copy, readonly) NSString *iconSymbol;
@property (nonatomic, assign, readonly, getter=isDynamic) BOOL dynamic;
@property (nonatomic, copy, readonly) NSArray<KBEmojiItem *> *items;
@end
@interface KBEmojiDataProvider : NSObject
+ (instancetype)shared;
/// 所有分类(按系统顺序),包含“常用”。
@property (nonatomic, copy, readonly) NSArray<KBEmojiCategory *> *categories;
/// 记录一次 emoji 选择,并刷新“常用”分类。
- (void)recordEmojiSelection:(NSString *)emoji;
/// 重新加载 JSON若首次调用
- (void)reloadIfNeeded;
/// 更新当前语言对应的分类标题。
- (void)refreshLocalizedTitles;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,270 @@
//
// KBEmojiDataProvider.m
// CustomKeyboard
//
#import "KBEmojiDataProvider.h"
#import "KBLocalizationManager.h"
#import "KBConfig.h"
NSString * const KBEmojiRecentsDidChangeNotification = @"KBEmojiRecentsDidChangeNotification";
static NSString * const kKBEmojiJSONFileName = @"emoji_categories";
static NSString * const kKBEmojiRecentsStoreKey = @"KBEmojiRecentEmojis";
static NSString * const kKBEmojiRecentsCategoryId = @"recents";
static const NSUInteger kKBEmojiRecentsLimit = 32;
#pragma mark - Model Implementations
@interface KBEmojiItem ()
@property (nonatomic, copy, readwrite) NSString *value;
@property (nonatomic, copy, readwrite) NSString *name;
@end
@implementation KBEmojiItem
- (instancetype)initWithValue:(NSString *)value name:(NSString *)name {
if (self = [super init]) {
_value = [value copy];
_name = [name copy];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
KBEmojiItem *item = [[[self class] allocWithZone:zone] initWithValue:self.value name:self.name];
return item;
}
@end
@interface KBEmojiCategory ()
@property (nonatomic, copy, readwrite) NSString *identifier;
@property (nonatomic, copy) NSDictionary<NSString *, NSString *> *titleMap;
@property (nonatomic, copy, readwrite) NSString *displayTitle;
@property (nonatomic, copy, readwrite) NSString *iconSymbol;
@property (nonatomic, assign, readwrite, getter=isDynamic) BOOL dynamic;
@property (nonatomic, copy, readwrite) NSArray<KBEmojiItem *> *items;
@end
@implementation KBEmojiCategory
- (void)refreshDisplayTitleForLanguage:(NSString *)lang {
if (lang.length == 0) {
lang = KBLanguageCodeEnglish;
}
NSString *title = self.titleMap[lang];
if (title.length == 0) {
if ([lang.lowercaseString hasPrefix:@"zh"]) {
title = self.titleMap[@"zh-Hans"] ?: self.titleMap[@"zh-hans"];
}
}
if (title.length == 0) {
title = self.titleMap[@"en"];
}
if (title.length == 0) {
title = self.titleMap.allValues.firstObject;
}
self.displayTitle = title ?: @"";
}
@end
#pragma mark - Data Provider
@interface KBEmojiDataProvider ()
@property (nonatomic, copy) NSArray<KBEmojiCategory *> *categoriesInternal;
@property (nonatomic, strong) NSMutableDictionary<NSString *, KBEmojiItem *> *itemLookup;
@property (nonatomic, strong) NSMutableOrderedSet<NSString *> *recentValues;
@end
@implementation KBEmojiDataProvider
+ (instancetype)shared {
static KBEmojiDataProvider *m;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
m = [KBEmojiDataProvider new];
[[NSNotificationCenter defaultCenter] addObserver:m
selector:@selector(onLocalizationChanged:)
name:KBLocalizationDidChangeNotification
object:nil];
});
return m;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (NSArray<KBEmojiCategory *> *)categories {
[self reloadIfNeeded];
return self.categoriesInternal ?: @[];
}
- (void)reloadIfNeeded {
if (self.categoriesInternal.count > 0) { return; }
[self loadEmojiJSON];
[self refreshLocalizedTitles];
[self loadRecentsFromStore];
[self rebuildRecentsCategory];
}
- (void)loadEmojiJSON {
NSString *path = [[NSBundle mainBundle] pathForResource:kKBEmojiJSONFileName ofType:@"json"];
if (path.length == 0) {
return;
}
NSData *data = [NSData dataWithContentsOfFile:path];
if (data.length == 0) { return; }
NSError *err = nil;
NSDictionary *root = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
if (!root || err) {
NSLog(@"[Emoji] failed to parse json: %@", err);
return;
}
NSArray *catArray = root[@"categories"];
if (![catArray isKindOfClass:NSArray.class]) {
return;
}
NSMutableArray<KBEmojiCategory *> *tmpCats = [NSMutableArray arrayWithCapacity:catArray.count];
self.itemLookup = [NSMutableDictionary dictionary];
for (NSDictionary *catDict in catArray) {
if (![catDict isKindOfClass:NSDictionary.class]) continue;
KBEmojiCategory *category = [KBEmojiCategory new];
category.identifier = catDict[@"id"] ?: @"";
NSDictionary *titleMap = catDict[@"title"];
if ([titleMap isKindOfClass:NSDictionary.class]) {
category.titleMap = titleMap;
} else {
category.titleMap = @{};
}
NSString *iconKey = catDict[@"icon"];
category.iconSymbol = [self symbolForIconKey:iconKey];
NSString *type = catDict[@"type"];
category.dynamic = [type.lowercaseString isEqualToString:@"dynamic"];
NSArray *emojiArray = catDict[@"emojis"];
NSMutableArray<KBEmojiItem *> *items = [NSMutableArray arrayWithCapacity:[emojiArray count]];
if ([emojiArray isKindOfClass:NSArray.class]) {
for (NSDictionary *emojiDict in emojiArray) {
if (![emojiDict isKindOfClass:NSDictionary.class]) continue;
NSString *value = emojiDict[@"value"];
if (value.length == 0) continue;
NSString *name = emojiDict[@"name"] ?: @"";
KBEmojiItem *item = [[KBEmojiItem alloc] initWithValue:value name:name];
[items addObject:item];
if (value.length > 0) {
self.itemLookup[value] = item;
}
}
}
category.items = items.copy;
[tmpCats addObject:category];
}
self.categoriesInternal = tmpCats.copy;
}
- (NSString *)symbolForIconKey:(NSString *)key {
static NSDictionary<NSString *, NSString *> *map;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
map = @{
@"emoji_tab_recent": @"🕘",
@"emoji_tab_people": @"😊",
@"emoji_tab_nature": @"🌿",
@"emoji_tab_food": @"🍔",
@"emoji_tab_activity": @"🏀",
@"emoji_tab_travel": @"✈️",
@"emoji_tab_objects": @"💡",
@"emoji_tab_symbols": @"♾",
@"emoji_tab_flags": @"🏳️"
};
});
NSString *symbol = map[key];
return symbol.length ? symbol : @"●";
}
- (void)refreshLocalizedTitles {
NSString *lang = [KBLocalizationManager shared].currentLanguageCode ?: KBLanguageCodeEnglish;
for (KBEmojiCategory *cat in self.categoriesInternal) {
[cat refreshDisplayTitleForLanguage:lang];
}
}
- (void)onLocalizationChanged:(__unused NSNotification *)note {
[self refreshLocalizedTitles];
[[NSNotificationCenter defaultCenter] postNotificationName:KBEmojiRecentsDidChangeNotification object:nil];
}
- (void)recordEmojiSelection:(NSString *)emoji {
if (emoji.length == 0) return;
[self reloadIfNeeded];
if (!self.recentValues) {
self.recentValues = [NSMutableOrderedSet orderedSet];
}
[self.recentValues removeObject:emoji];
[self.recentValues insertObject:emoji atIndex:0];
while (self.recentValues.count > kKBEmojiRecentsLimit) {
[self.recentValues removeObjectAtIndex:self.recentValues.count - 1];
}
[self saveRecentsToStore];
[self rebuildRecentsCategory];
[[NSNotificationCenter defaultCenter] postNotificationName:KBEmojiRecentsDidChangeNotification object:nil];
}
- (void)loadRecentsFromStore {
NSUserDefaults *defs = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
if (!defs) { defs = NSUserDefaults.standardUserDefaults; }
NSArray *stored = [defs objectForKey:kKBEmojiRecentsStoreKey];
NSMutableOrderedSet *set = [NSMutableOrderedSet orderedSet];
if ([stored isKindOfClass:NSArray.class]) {
for (id obj in stored) {
if (![obj isKindOfClass:NSString.class]) continue;
NSString *str = (NSString *)obj;
if (str.length == 0) continue;
[set addObject:str];
if (set.count >= kKBEmojiRecentsLimit) break;
}
}
self.recentValues = set;
}
- (void)saveRecentsToStore {
if (!self.recentValues) return;
NSArray *arr = self.recentValues.array;
NSUserDefaults *defs = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
if (!defs) { defs = NSUserDefaults.standardUserDefaults; }
[defs setObject:arr forKey:kKBEmojiRecentsStoreKey];
[defs synchronize];
}
- (void)rebuildRecentsCategory {
KBEmojiCategory *recent = [self categoryForIdentifier:kKBEmojiRecentsCategoryId];
if (!recent) return;
NSArray<NSString *> *values = self.recentValues.array ?: @[];
NSMutableArray<KBEmojiItem *> *items = [NSMutableArray arrayWithCapacity:values.count];
for (NSString *value in values) {
KBEmojiItem *item = self.itemLookup[value];
if (!item) {
item = [[KBEmojiItem alloc] initWithValue:value name:@""];
}
[items addObject:item];
}
recent.items = items.copy;
}
- (KBEmojiCategory *)categoryForIdentifier:(NSString *)identifier {
if (identifier.length == 0) return nil;
for (KBEmojiCategory *cat in self.categoriesInternal) {
if ([cat.identifier isEqualToString:identifier]) {
return cat;
}
}
return nil;
}
@end

View File

@@ -0,0 +1,43 @@
//
// KBFullAccessManager.h
// 统一封装:检测并管理键盘扩展的“允许完全访问”状态
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, KBFullAccessState) {
KBFullAccessStateUnknown = 0, // 无法确定(降级处理为未开启)
KBFullAccessStateDenied, // 未开启完全访问
KBFullAccessStateGranted // 已开启完全访问
};
/// 状态变更通知(仅扩展进程内广播)
extern NSNotificationName const KBFullAccessChangedNotification;
/// 键盘扩展“完全访问”状态管理
@interface KBFullAccessManager : NSObject
+ (instancetype)shared;
/// 绑定当前的 UIInputViewController用于调用系统私有选择器 hasFullAccess按字符串反射避免编译期引用
- (void)bindInputController:(UIInputViewController *)ivc;
/// 当前状态(内部做缓存;如需强制刷新,调用 refresh
- (KBFullAccessState)currentState;
/// 便捷判断
- (BOOL)hasFullAccess;
/// 立即刷新一次状态(若状态有变化会发送 KBFullAccessChangedNotification
- (void)refresh;
/// 若未开启,则在传入视图上展示引导弹层(使用现有的 KBFullAccessGuideView返回是否已开启
- (BOOL)ensureFullAccessOrGuideInView:(UIView *)parent;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,100 @@
//
// KBFullAccessManager.m
//
// 访
// 1) UIInputViewController hasFullAccess API
// 2) Unknown Denied
//
#import "KBFullAccessManager.h"
#import <objc/message.h>
#if __has_include("KBNetworkManager.h")
#import "KBNetworkManager.h"
#endif
#if __has_include("KBKeyboardPermissionManager.h")
#import "KBKeyboardPermissionManager.h"
#endif
NSNotificationName const KBFullAccessChangedNotification = @"KBFullAccessChangedNotification";
@interface KBFullAccessManager ()
@property (nonatomic, weak) UIInputViewController *ivc;
@property (nonatomic, assign) KBFullAccessState state;
@end
@implementation KBFullAccessManager
+ (instancetype)shared {
static KBFullAccessManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ m = [KBFullAccessManager new]; });
return m;
}
- (instancetype)init {
if (self = [super init]) {
_state = KBFullAccessStateUnknown;
}
return self;
}
- (void)bindInputController:(UIInputViewController *)ivc {
self.ivc = ivc;
[self refresh];
}
- (KBFullAccessState)currentState { return _state; }
- (BOOL)hasFullAccess { return self.state == KBFullAccessStateGranted; }
- (void)refresh {
KBFullAccessState newState = [self p_detectFullAccessState];
if (newState != self.state) {
self.state = newState;
[[NSNotificationCenter defaultCenter] postNotificationName:KBFullAccessChangedNotification object:nil];
[self p_applySideEffects];
}
}
- (BOOL)ensureFullAccessOrGuideInView:(UIView *)parent {
[self refresh];
if (self.state == KBFullAccessStateGranted) return YES;
#if __has_include("KBFullAccessGuideView.h")
// App
Class guideCls = NSClassFromString(@"KBFullAccessGuideView");
if (guideCls && [guideCls respondsToSelector:NSSelectorFromString(@"showInView:")]) {
SEL sel = NSSelectorFromString(@"showInView:");
((void (*)(id, SEL, UIView *))objc_msgSend)(guideCls, sel, parent);
}
#endif
return NO;
}
#pragma mark - Detect
// hasFullAccess Unknown
- (KBFullAccessState)p_detectFullAccessState {
UIInputViewController *ivc = self.ivc;
if (!ivc) return KBFullAccessStateUnknown;
SEL sel = NSSelectorFromString(@"hasFullAccess");
if ([ivc respondsToSelector:sel]) {
BOOL granted = ((BOOL (*)(id, SEL))objc_msgSend)(ivc, sel);
return granted ? KBFullAccessStateGranted : KBFullAccessStateDenied;
}
// Unknown
return KBFullAccessStateUnknown;
}
#pragma mark - Side Effects
- (void)p_applySideEffects {
#if __has_include("KBNetworkManager.h")
// 访
[KBNetworkManager shared].enabled = (self.state == KBFullAccessStateGranted);
#endif
#if __has_include("KBKeyboardPermissionManager.h")
// App访App
[[KBKeyboardPermissionManager shared] reportFullAccessFromExtension:(self.state == KBFullAccessStateGranted)];
#endif
}
@end

View File

@@ -0,0 +1,23 @@
//
// KBSuggestionEngine.h
// CustomKeyboard
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// Simple local suggestion engine (prefix match + lightweight ranking).
@interface KBSuggestionEngine : NSObject
+ (instancetype)shared;
/// Returns suggestions for prefix (lowercase expected), limited by count.
- (NSArray<NSString *> *)suggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit;
/// Record a selection to slightly boost ranking next time.
- (void)recordSelection:(NSString *)word;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,167 @@
//
// KBSuggestionEngine.m
// CustomKeyboard
//
#import "KBSuggestionEngine.h"
#import "KBConfig.h"
@interface KBSuggestionEngine ()
@property (nonatomic, copy) NSArray<NSString *> *words;
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *selectionCounts;
@property (nonatomic, strong) NSSet<NSString *> *priorityWords;
@end
@implementation KBSuggestionEngine
+ (instancetype)shared {
static KBSuggestionEngine *engine;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
engine = [[KBSuggestionEngine alloc] init];
});
return engine;
}
- (instancetype)init {
if (self = [super init]) {
_selectionCounts = [NSMutableDictionary dictionary];
NSArray<NSString *> *defaults = [self.class kb_defaultWords];
_priorityWords = [NSSet setWithArray:defaults];
_words = [self kb_loadWords];
}
return self;
}
- (NSArray<NSString *> *)suggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
if (prefix.length == 0 || limit == 0) { return @[]; }
NSString *lower = prefix.lowercaseString;
NSMutableArray<NSString *> *matches = [NSMutableArray array];
for (NSString *word in self.words) {
if ([word hasPrefix:lower]) {
[matches addObject:word];
if (matches.count >= limit * 3) {
// Avoid scanning too many matches for long lists.
break;
}
}
}
if (matches.count == 0) { return @[]; }
[matches sortUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
NSInteger ca = self.selectionCounts[a].integerValue;
NSInteger cb = self.selectionCounts[b].integerValue;
if (ca != cb) {
return (cb > ca) ? NSOrderedAscending : NSOrderedDescending;
}
BOOL pa = [self.priorityWords containsObject:a];
BOOL pb = [self.priorityWords containsObject:b];
if (pa != pb) {
return pa ? NSOrderedAscending : NSOrderedDescending;
}
return [a compare:b];
}];
if (matches.count > limit) {
return [matches subarrayWithRange:NSMakeRange(0, limit)];
}
return matches.copy;
}
- (void)recordSelection:(NSString *)word {
if (word.length == 0) { return; }
NSString *key = word.lowercaseString;
NSInteger count = self.selectionCounts[key].integerValue + 1;
self.selectionCounts[key] = @(count);
}
#pragma mark - Defaults
- (NSArray<NSString *> *)kb_loadWords {
NSMutableOrderedSet<NSString *> *set = [[NSMutableOrderedSet alloc] init];
[set addObjectsFromArray:[self.class kb_defaultWords]];
NSArray<NSString *> *paths = [self kb_wordListPaths];
for (NSString *path in paths) {
if (path.length == 0) { continue; }
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
if (content.length == 0) { continue; }
NSArray<NSString *> *lines = [content componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (NSString *line in lines) {
NSString *word = [self kb_sanitizedWordFromLine:line];
if (word.length == 0) { continue; }
[set addObject:word];
}
}
NSArray<NSString *> *result = set.array ?: @[];
return result;
}
- (NSArray<NSString *> *)kb_wordListPaths {
NSMutableArray<NSString *> *paths = [NSMutableArray array];
// 1) App Group override (allows server-downloaded large list).
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (containerURL.path.length > 0) {
NSString *groupPath = [[containerURL path] stringByAppendingPathComponent:@"kb_words.txt"];
[paths addObject:groupPath];
}
// 2) Bundle fallback.
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"kb_words" ofType:@"txt"];
if (bundlePath.length > 0) {
[paths addObject:bundlePath];
}
return paths;
}
- (NSString *)kb_sanitizedWordFromLine:(NSString *)line {
NSString *trimmed = [[line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString];
if (trimmed.length == 0) { return @""; }
static NSCharacterSet *letters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
letters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyz"];
});
for (NSUInteger i = 0; i < trimmed.length; i++) {
if (![letters characterIsMember:[trimmed characterAtIndex:i]]) {
return @"";
}
}
return trimmed;
}
+ (NSArray<NSString *> *)kb_defaultWords {
return @[
@"a", @"an", @"and", @"are", @"as", @"at",
@"app", @"ap", @"apple", @"apply", @"april", @"application",
@"about", @"above", @"after", @"again", @"against", @"all",
@"am", @"among", @"amount", @"any", @"around",
@"be", @"because", @"been", @"before", @"being", @"below",
@"best", @"between", @"both", @"but", @"by",
@"can", @"could", @"come", @"common", @"case",
@"do", @"does", @"down", @"day",
@"each", @"early", @"end", @"even", @"every",
@"for", @"from", @"first", @"found", @"free",
@"get", @"good", @"great", @"go",
@"have", @"has", @"had", @"help", @"how",
@"in", @"is", @"it", @"if", @"into",
@"just", @"keep", @"kind", @"know",
@"like", @"look", @"long", @"last",
@"make", @"more", @"most", @"my",
@"new", @"no", @"not", @"now",
@"of", @"on", @"one", @"or", @"other", @"our", @"out",
@"people", @"place", @"please",
@"quick", @"quite",
@"right", @"read", @"real",
@"see", @"say", @"some", @"such", @"so",
@"the", @"to", @"this", @"that", @"them", @"then", @"there", @"they", @"these", @"time",
@"use", @"up", @"under",
@"very",
@"we", @"with", @"what", @"when", @"where", @"who", @"why", @"will", @"would",
@"you", @"your"
];
}
@end

View File

@@ -1,26 +0,0 @@
//
// MASCompositeConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASConstraint.h"
#import "MASUtilities.h"
/**
* A group of MASConstraint objects
*/
@interface MASCompositeConstraint : MASConstraint
/**
* Creates a composite with a predefined array of children
*
* @param children child MASConstraints
*
* @return a composite constraint
*/
- (id)initWithChildren:(NSArray *)children;
@end

View File

@@ -1,183 +0,0 @@
//
// MASCompositeConstraint.m
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASCompositeConstraint.h"
#import "MASConstraint+Private.h"
@interface MASCompositeConstraint () <MASConstraintDelegate>
@property (nonatomic, strong) id mas_key;
@property (nonatomic, strong) NSMutableArray *childConstraints;
@end
@implementation MASCompositeConstraint
- (id)initWithChildren:(NSArray *)children {
self = [super init];
if (!self) return nil;
_childConstraints = [children mutableCopy];
for (MASConstraint *constraint in _childConstraints) {
constraint.delegate = self;
}
return self;
}
#pragma mark - MASConstraintDelegate
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.childConstraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.childConstraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
id<MASConstraintDelegate> strongDelegate = self.delegate;
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
newConstraint.delegate = self;
[self.childConstraints addObject:newConstraint];
return newConstraint;
}
#pragma mark - NSLayoutConstraint multiplier proxies
- (MASConstraint * (^)(CGFloat))multipliedBy {
return ^id(CGFloat multiplier) {
for (MASConstraint *constraint in self.childConstraints) {
constraint.multipliedBy(multiplier);
}
return self;
};
}
- (MASConstraint * (^)(CGFloat))dividedBy {
return ^id(CGFloat divider) {
for (MASConstraint *constraint in self.childConstraints) {
constraint.dividedBy(divider);
}
return self;
};
}
#pragma mark - MASLayoutPriority proxy
- (MASConstraint * (^)(MASLayoutPriority))priority {
return ^id(MASLayoutPriority priority) {
for (MASConstraint *constraint in self.childConstraints) {
constraint.priority(priority);
}
return self;
};
}
#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
#pragma mark - attribute chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
return self;
}
#pragma mark - Animator proxy
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
- (MASConstraint *)animator {
for (MASConstraint *constraint in self.childConstraints) {
[constraint animator];
}
return self;
}
#endif
#pragma mark - debug helpers
- (MASConstraint * (^)(id))key {
return ^id(id key) {
self.mas_key = key;
int i = 0;
for (MASConstraint *constraint in self.childConstraints) {
constraint.key([NSString stringWithFormat:@"%@[%d]", key, i++]);
}
return self;
};
}
#pragma mark - NSLayoutConstraint constant setters
- (void)setInsets:(MASEdgeInsets)insets {
for (MASConstraint *constraint in self.childConstraints) {
constraint.insets = insets;
}
}
- (void)setInset:(CGFloat)inset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.inset = inset;
}
}
- (void)setOffset:(CGFloat)offset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.offset = offset;
}
}
- (void)setSizeOffset:(CGSize)sizeOffset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.sizeOffset = sizeOffset;
}
}
- (void)setCenterOffset:(CGPoint)centerOffset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.centerOffset = centerOffset;
}
}
#pragma mark - MASConstraint
- (void)activate {
for (MASConstraint *constraint in self.childConstraints) {
[constraint activate];
}
}
- (void)deactivate {
for (MASConstraint *constraint in self.childConstraints) {
[constraint deactivate];
}
}
- (void)install {
for (MASConstraint *constraint in self.childConstraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
}
- (void)uninstall {
for (MASConstraint *constraint in self.childConstraints) {
[constraint uninstall];
}
}
@end

View File

@@ -1,66 +0,0 @@
//
// MASConstraint+Private.h
// Masonry
//
// Created by Nick Tymchenko on 29/04/14.
// Copyright (c) 2014 cloudling. All rights reserved.
//
#import "MASConstraint.h"
@protocol MASConstraintDelegate;
@interface MASConstraint ()
/**
* Whether or not to check for an existing constraint instead of adding constraint
*/
@property (nonatomic, assign) BOOL updateExisting;
/**
* Usually MASConstraintMaker but could be a parent MASConstraint
*/
@property (nonatomic, weak) id<MASConstraintDelegate> delegate;
/**
* Based on a provided value type, is equal to calling:
* NSNumber - setOffset:
* NSValue with CGPoint - setPointOffset:
* NSValue with CGSize - setSizeOffset:
* NSValue with MASEdgeInsets - setInsets:
*/
- (void)setLayoutConstantWithValue:(NSValue *)value;
@end
@interface MASConstraint (Abstract)
/**
* Sets the constraint relation to given NSLayoutRelation
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation;
/**
* Override to set a custom chaining behaviour
*/
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
@end
@protocol MASConstraintDelegate <NSObject>
/**
* Notifies the delegate when the constraint needs to be replaced with another constraint. For example
* A MASViewConstraint may turn into a MASCompositeConstraint when an array is passed to one of the equality blocks
*/
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint;
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
@end

View File

@@ -1,272 +0,0 @@
//
// MASConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 22/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASUtilities.h"
/**
* Enables Constraints to be created with chainable syntax
* Constraint can represent single NSLayoutConstraint (MASViewConstraint)
* or a group of NSLayoutConstraints (MASComposisteConstraint)
*/
@interface MASConstraint : NSObject
// Chaining Support
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (MASConstraint * (^)(MASEdgeInsets insets))insets;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (MASConstraint * (^)(CGFloat inset))inset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeWidth, NSLayoutAttributeHeight
*/
- (MASConstraint * (^)(CGSize offset))sizeOffset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
*/
- (MASConstraint * (^)(CGPoint offset))centerOffset;
/**
* Modifies the NSLayoutConstraint constant
*/
- (MASConstraint * (^)(CGFloat offset))offset;
/**
* Modifies the NSLayoutConstraint constant based on a value type
*/
- (MASConstraint * (^)(NSValue *value))valueOffset;
/**
* Sets the NSLayoutConstraint multiplier property
*/
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;
/**
* Sets the NSLayoutConstraint multiplier to 1.0/dividedBy
*/
- (MASConstraint * (^)(CGFloat divider))dividedBy;
/**
* Sets the NSLayoutConstraint priority to a float or MASLayoutPriority
*/
- (MASConstraint * (^)(MASLayoutPriority priority))priority;
/**
* Sets the NSLayoutConstraint priority to MASLayoutPriorityLow
*/
- (MASConstraint * (^)(void))priorityLow;
/**
* Sets the NSLayoutConstraint priority to MASLayoutPriorityMedium
*/
- (MASConstraint * (^)(void))priorityMedium;
/**
* Sets the NSLayoutConstraint priority to MASLayoutPriorityHigh
*/
- (MASConstraint * (^)(void))priorityHigh;
/**
* Sets the constraint relation to NSLayoutRelationEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))equalTo;
/**
* Sets the constraint relation to NSLayoutRelationGreaterThanOrEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
/**
* Sets the constraint relation to NSLayoutRelationLessThanOrEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;
/**
* Optional semantic property which has no effect but improves the readability of constraint
*/
- (MASConstraint *)with;
/**
* Optional semantic property which has no effect but improves the readability of constraint
*/
- (MASConstraint *)and;
/**
* Creates a new MASCompositeConstraint with the called attribute and reciever
*/
- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASConstraint *)firstBaseline;
- (MASConstraint *)lastBaseline;
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
- (MASConstraint *)leftMargin;
- (MASConstraint *)rightMargin;
- (MASConstraint *)topMargin;
- (MASConstraint *)bottomMargin;
- (MASConstraint *)leadingMargin;
- (MASConstraint *)trailingMargin;
- (MASConstraint *)centerXWithinMargins;
- (MASConstraint *)centerYWithinMargins;
#endif
/**
* Sets the constraint debug name
*/
- (MASConstraint * (^)(id key))key;
// NSLayoutConstraint constant Setters
// for use outside of mas_updateConstraints/mas_makeConstraints blocks
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (void)setInsets:(MASEdgeInsets)insets;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (void)setInset:(CGFloat)inset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeWidth, NSLayoutAttributeHeight
*/
- (void)setSizeOffset:(CGSize)sizeOffset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
*/
- (void)setCenterOffset:(CGPoint)centerOffset;
/**
* Modifies the NSLayoutConstraint constant
*/
- (void)setOffset:(CGFloat)offset;
// NSLayoutConstraint Installation support
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
/**
* Whether or not to go through the animator proxy when modifying the constraint
*/
@property (nonatomic, copy, readonly) MASConstraint *animator;
#endif
/**
* Activates an NSLayoutConstraint if it's supported by an OS.
* Invokes install otherwise.
*/
- (void)activate;
/**
* Deactivates previously installed/activated NSLayoutConstraint.
*/
- (void)deactivate;
/**
* Creates a NSLayoutConstraint and adds it to the appropriate view.
*/
- (void)install;
/**
* Removes previously installed NSLayoutConstraint
*/
- (void)uninstall;
@end
/**
* Convenience auto-boxing macros for MASConstraint methods.
*
* Defining MAS_SHORTHAND_GLOBALS will turn on auto-boxing for default syntax.
* A potential drawback of this is that the unprefixed macros will appear in global scope.
*/
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__)
#define offset(...) mas_offset(__VA_ARGS__)
#endif
@interface MASConstraint (AutoboxingSupport)
/**
* Aliases to corresponding relation methods (for shorthand macros)
* Also needed to aid autocompletion
*/
- (MASConstraint * (^)(id attr))mas_equalTo;
- (MASConstraint * (^)(id attr))mas_greaterThanOrEqualTo;
- (MASConstraint * (^)(id attr))mas_lessThanOrEqualTo;
/**
* A dummy method to aid autocompletion
*/
- (MASConstraint * (^)(id offset))mas_offset;
@end

View File

@@ -1,301 +0,0 @@
//
// MASConstraint.m
// Masonry
//
// Created by Nick Tymchenko on 1/20/14.
//
#import "MASConstraint.h"
#import "MASConstraint+Private.h"
#define MASMethodNotImplemented() \
@throw [NSException exceptionWithName:NSInternalInconsistencyException \
reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
userInfo:nil]
@implementation MASConstraint
#pragma mark - Init
- (id)init {
NSAssert(![self isMemberOfClass:[MASConstraint class]], @"MASConstraint is an abstract class, you should not instantiate it directly.");
return [super init];
}
#pragma mark - NSLayoutRelation proxies
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (MASConstraint * (^)(id))mas_equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (MASConstraint * (^)(id))greaterThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationGreaterThanOrEqual);
};
}
- (MASConstraint * (^)(id))mas_greaterThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationGreaterThanOrEqual);
};
}
- (MASConstraint * (^)(id))lessThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationLessThanOrEqual);
};
}
- (MASConstraint * (^)(id))mas_lessThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationLessThanOrEqual);
};
}
#pragma mark - MASLayoutPriority proxies
- (MASConstraint * (^)(void))priorityLow {
return ^id{
self.priority(MASLayoutPriorityDefaultLow);
return self;
};
}
- (MASConstraint * (^)(void))priorityMedium {
return ^id{
self.priority(MASLayoutPriorityDefaultMedium);
return self;
};
}
- (MASConstraint * (^)(void))priorityHigh {
return ^id{
self.priority(MASLayoutPriorityDefaultHigh);
return self;
};
}
#pragma mark - NSLayoutConstraint constant proxies
- (MASConstraint * (^)(MASEdgeInsets))insets {
return ^id(MASEdgeInsets insets){
self.insets = insets;
return self;
};
}
- (MASConstraint * (^)(CGFloat))inset {
return ^id(CGFloat inset){
self.inset = inset;
return self;
};
}
- (MASConstraint * (^)(CGSize))sizeOffset {
return ^id(CGSize offset) {
self.sizeOffset = offset;
return self;
};
}
- (MASConstraint * (^)(CGPoint))centerOffset {
return ^id(CGPoint offset) {
self.centerOffset = offset;
return self;
};
}
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}
- (MASConstraint * (^)(NSValue *value))valueOffset {
return ^id(NSValue *offset) {
NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
[self setLayoutConstantWithValue:offset];
return self;
};
}
- (MASConstraint * (^)(id offset))mas_offset {
// Will never be called due to macro
return nil;
}
#pragma mark - NSLayoutConstraint constant setter
- (void)setLayoutConstantWithValue:(NSValue *)value {
if ([value isKindOfClass:NSNumber.class]) {
self.offset = [(NSNumber *)value doubleValue];
} else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
CGPoint point;
[value getValue:&point];
self.centerOffset = point;
} else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
CGSize size;
[value getValue:&size];
self.sizeOffset = size;
} else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets insets;
[value getValue:&insets];
self.insets = insets;
} else {
NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
}
}
#pragma mark - Semantic properties
- (MASConstraint *)with {
return self;
}
- (MASConstraint *)and {
return self;
}
#pragma mark - Chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
MASMethodNotImplemented();
}
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
- (MASConstraint *)leading {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading];
}
- (MASConstraint *)trailing {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing];
}
- (MASConstraint *)width {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}
- (MASConstraint *)height {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
- (MASConstraint *)centerX {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}
- (MASConstraint *)centerY {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY];
}
- (MASConstraint *)baseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline];
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASConstraint *)firstBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASConstraint *)lastBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLastBaseline];
}
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
- (MASConstraint *)leftMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeftMargin];
}
- (MASConstraint *)rightMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRightMargin];
}
- (MASConstraint *)topMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTopMargin];
}
- (MASConstraint *)bottomMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottomMargin];
}
- (MASConstraint *)leadingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeadingMargin];
}
- (MASConstraint *)trailingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailingMargin];
}
- (MASConstraint *)centerXWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}
- (MASConstraint *)centerYWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
#endif
#pragma mark - Abstract
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy { MASMethodNotImplemented(); }
- (MASConstraint * (^)(CGFloat divider))dividedBy { MASMethodNotImplemented(); }
- (MASConstraint * (^)(MASLayoutPriority priority))priority { MASMethodNotImplemented(); }
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
- (MASConstraint * (^)(id key))key { MASMethodNotImplemented(); }
- (void)setInsets:(MASEdgeInsets __unused)insets { MASMethodNotImplemented(); }
- (void)setInset:(CGFloat __unused)inset { MASMethodNotImplemented(); }
- (void)setSizeOffset:(CGSize __unused)sizeOffset { MASMethodNotImplemented(); }
- (void)setCenterOffset:(CGPoint __unused)centerOffset { MASMethodNotImplemented(); }
- (void)setOffset:(CGFloat __unused)offset { MASMethodNotImplemented(); }
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
- (MASConstraint *)animator { MASMethodNotImplemented(); }
#endif
- (void)activate { MASMethodNotImplemented(); }
- (void)deactivate { MASMethodNotImplemented(); }
- (void)install { MASMethodNotImplemented(); }
- (void)uninstall { MASMethodNotImplemented(); }
@end

View File

@@ -1,146 +0,0 @@
//
// MASConstraintMaker.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASConstraint.h"
#import "MASUtilities.h"
typedef NS_OPTIONS(NSInteger, MASAttribute) {
MASAttributeLeft = 1 << NSLayoutAttributeLeft,
MASAttributeRight = 1 << NSLayoutAttributeRight,
MASAttributeTop = 1 << NSLayoutAttributeTop,
MASAttributeBottom = 1 << NSLayoutAttributeBottom,
MASAttributeLeading = 1 << NSLayoutAttributeLeading,
MASAttributeTrailing = 1 << NSLayoutAttributeTrailing,
MASAttributeWidth = 1 << NSLayoutAttributeWidth,
MASAttributeHeight = 1 << NSLayoutAttributeHeight,
MASAttributeCenterX = 1 << NSLayoutAttributeCenterX,
MASAttributeCenterY = 1 << NSLayoutAttributeCenterY,
MASAttributeBaseline = 1 << NSLayoutAttributeBaseline,
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
MASAttributeFirstBaseline = 1 << NSLayoutAttributeFirstBaseline,
MASAttributeLastBaseline = 1 << NSLayoutAttributeLastBaseline,
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
MASAttributeLeftMargin = 1 << NSLayoutAttributeLeftMargin,
MASAttributeRightMargin = 1 << NSLayoutAttributeRightMargin,
MASAttributeTopMargin = 1 << NSLayoutAttributeTopMargin,
MASAttributeBottomMargin = 1 << NSLayoutAttributeBottomMargin,
MASAttributeLeadingMargin = 1 << NSLayoutAttributeLeadingMargin,
MASAttributeTrailingMargin = 1 << NSLayoutAttributeTrailingMargin,
MASAttributeCenterXWithinMargins = 1 << NSLayoutAttributeCenterXWithinMargins,
MASAttributeCenterYWithinMargins = 1 << NSLayoutAttributeCenterYWithinMargins,
#endif
};
/**
* Provides factory methods for creating MASConstraints.
* Constraints are collected until they are ready to be installed
*
*/
@interface MASConstraintMaker : NSObject
/**
* The following properties return a new MASViewConstraint
* with the first item set to the makers associated view and the appropriate MASViewAttribute
*/
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@property (nonatomic, strong, readonly) MASConstraint *firstBaseline;
@property (nonatomic, strong, readonly) MASConstraint *lastBaseline;
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;
#endif
/**
* Returns a block which creates a new MASCompositeConstraint with the first item set
* to the makers associated view and children corresponding to the set bits in the
* MASAttribute parameter. Combine multiple attributes via binary-or.
*/
@property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);
/**
* Creates a MASCompositeConstraint with type MASCompositeConstraintTypeEdges
* which generates the appropriate MASViewConstraint children (top, left, bottom, right)
* with the first item set to the makers associated view
*/
@property (nonatomic, strong, readonly) MASConstraint *edges;
/**
* Creates a MASCompositeConstraint with type MASCompositeConstraintTypeSize
* which generates the appropriate MASViewConstraint children (width, height)
* with the first item set to the makers associated view
*/
@property (nonatomic, strong, readonly) MASConstraint *size;
/**
* Creates a MASCompositeConstraint with type MASCompositeConstraintTypeCenter
* which generates the appropriate MASViewConstraint children (centerX, centerY)
* with the first item set to the makers associated view
*/
@property (nonatomic, strong, readonly) MASConstraint *center;
/**
* Whether or not to check for an existing constraint instead of adding constraint
*/
@property (nonatomic, assign) BOOL updateExisting;
/**
* Whether or not to remove existing constraints prior to installing
*/
@property (nonatomic, assign) BOOL removeExisting;
/**
* initialises the maker with a default view
*
* @param view any MASConstraint are created with this view as the first item
*
* @return a new MASConstraintMaker
*/
- (id)initWithView:(MAS_VIEW *)view;
/**
* Calls install method on any MASConstraints which have been created by this maker
*
* @return an array of all the installed MASConstraints
*/
- (NSArray *)install;
- (MASConstraint * (^)(dispatch_block_t))group;
@end

View File

@@ -1,273 +0,0 @@
//
// MASConstraintMaker.m
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASConstraintMaker.h"
#import "MASViewConstraint.h"
#import "MASCompositeConstraint.h"
#import "MASConstraint+Private.h"
#import "MASViewAttribute.h"
#import "View+MASAdditions.h"
@interface MASConstraintMaker () <MASConstraintDelegate>
@property (nonatomic, weak) MAS_VIEW *view;
@property (nonatomic, strong) NSMutableArray *constraints;
@end
@implementation MASConstraintMaker
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
- (NSArray *)install {
if (self.removeExisting) {
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for (MASConstraint *constraint in installedConstraints) {
[constraint uninstall];
}
}
NSArray *constraints = self.constraints.copy;
for (MASConstraint *constraint in constraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[self.constraints removeAllObjects];
return constraints;
}
#pragma mark - MASConstraintDelegate
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.constraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs {
__unused MASAttribute anyAttribute = (MASAttributeLeft | MASAttributeRight | MASAttributeTop | MASAttributeBottom | MASAttributeLeading
| MASAttributeTrailing | MASAttributeWidth | MASAttributeHeight | MASAttributeCenterX
| MASAttributeCenterY | MASAttributeBaseline
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
| MASAttributeFirstBaseline | MASAttributeLastBaseline
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
| MASAttributeLeftMargin | MASAttributeRightMargin | MASAttributeTopMargin | MASAttributeBottomMargin
| MASAttributeLeadingMargin | MASAttributeTrailingMargin | MASAttributeCenterXWithinMargins
| MASAttributeCenterYWithinMargins
#endif
);
NSAssert((attrs & anyAttribute) != 0, @"You didn't pass any attribute to make.attributes(...)");
NSMutableArray *attributes = [NSMutableArray array];
if (attrs & MASAttributeLeft) [attributes addObject:self.view.mas_left];
if (attrs & MASAttributeRight) [attributes addObject:self.view.mas_right];
if (attrs & MASAttributeTop) [attributes addObject:self.view.mas_top];
if (attrs & MASAttributeBottom) [attributes addObject:self.view.mas_bottom];
if (attrs & MASAttributeLeading) [attributes addObject:self.view.mas_leading];
if (attrs & MASAttributeTrailing) [attributes addObject:self.view.mas_trailing];
if (attrs & MASAttributeWidth) [attributes addObject:self.view.mas_width];
if (attrs & MASAttributeHeight) [attributes addObject:self.view.mas_height];
if (attrs & MASAttributeCenterX) [attributes addObject:self.view.mas_centerX];
if (attrs & MASAttributeCenterY) [attributes addObject:self.view.mas_centerY];
if (attrs & MASAttributeBaseline) [attributes addObject:self.view.mas_baseline];
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
if (attrs & MASAttributeFirstBaseline) [attributes addObject:self.view.mas_firstBaseline];
if (attrs & MASAttributeLastBaseline) [attributes addObject:self.view.mas_lastBaseline];
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
if (attrs & MASAttributeLeftMargin) [attributes addObject:self.view.mas_leftMargin];
if (attrs & MASAttributeRightMargin) [attributes addObject:self.view.mas_rightMargin];
if (attrs & MASAttributeTopMargin) [attributes addObject:self.view.mas_topMargin];
if (attrs & MASAttributeBottomMargin) [attributes addObject:self.view.mas_bottomMargin];
if (attrs & MASAttributeLeadingMargin) [attributes addObject:self.view.mas_leadingMargin];
if (attrs & MASAttributeTrailingMargin) [attributes addObject:self.view.mas_trailingMargin];
if (attrs & MASAttributeCenterXWithinMargins) [attributes addObject:self.view.mas_centerXWithinMargins];
if (attrs & MASAttributeCenterYWithinMargins) [attributes addObject:self.view.mas_centerYWithinMargins];
#endif
NSMutableArray *children = [NSMutableArray arrayWithCapacity:attributes.count];
for (MASViewAttribute *a in attributes) {
[children addObject:[[MASViewConstraint alloc] initWithFirstViewAttribute:a]];
}
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
constraint.delegate = self;
[self.constraints addObject:constraint];
return constraint;
}
#pragma mark - standard Attributes
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
- (MASConstraint *)leading {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading];
}
- (MASConstraint *)trailing {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing];
}
- (MASConstraint *)width {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}
- (MASConstraint *)height {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
- (MASConstraint *)centerX {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}
- (MASConstraint *)centerY {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY];
}
- (MASConstraint *)baseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline];
}
- (MASConstraint *(^)(MASAttribute))attributes {
return ^(MASAttribute attrs){
return [self addConstraintWithAttributes:attrs];
};
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASConstraint *)firstBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASConstraint *)lastBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLastBaseline];
}
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
- (MASConstraint *)leftMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeftMargin];
}
- (MASConstraint *)rightMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRightMargin];
}
- (MASConstraint *)topMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTopMargin];
}
- (MASConstraint *)bottomMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottomMargin];
}
- (MASConstraint *)leadingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeadingMargin];
}
- (MASConstraint *)trailingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailingMargin];
}
- (MASConstraint *)centerXWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}
- (MASConstraint *)centerYWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
#endif
#pragma mark - composite Attributes
- (MASConstraint *)edges {
return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}
- (MASConstraint *)size {
return [self addConstraintWithAttributes:MASAttributeWidth | MASAttributeHeight];
}
- (MASConstraint *)center {
return [self addConstraintWithAttributes:MASAttributeCenterX | MASAttributeCenterY];
}
#pragma mark - grouping
- (MASConstraint *(^)(dispatch_block_t group))group {
return ^id(dispatch_block_t group) {
NSInteger previousCount = self.constraints.count;
group();
NSArray *children = [self.constraints subarrayWithRange:NSMakeRange(previousCount, self.constraints.count - previousCount)];
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
constraint.delegate = self;
return constraint;
};
}
@end

View File

@@ -1,22 +0,0 @@
//
// MASLayoutConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "MASUtilities.h"
/**
* When you are debugging or printing the constraints attached to a view this subclass
* makes it easier to identify which constraints have been created via Masonry
*/
@interface MASLayoutConstraint : NSLayoutConstraint
/**
* a key to associate with this constraint
*/
@property (nonatomic, strong) id mas_key;
@end

View File

@@ -1,13 +0,0 @@
//
// MASLayoutConstraint.m
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "MASLayoutConstraint.h"
@implementation MASLayoutConstraint
@end

View File

@@ -1,136 +0,0 @@
//
// MASUtilities.h
// Masonry
//
// Created by Jonas Budelmann on 19/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE || TARGET_OS_TV
#import <UIKit/UIKit.h>
#define MAS_VIEW UIView
#define MAS_VIEW_CONTROLLER UIViewController
#define MASEdgeInsets UIEdgeInsets
typedef UILayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
#elif TARGET_OS_MAC
#import <AppKit/AppKit.h>
#define MAS_VIEW NSView
#define MASEdgeInsets NSEdgeInsets
typedef NSLayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = NSLayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = NSLayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDragThatCanResizeWindow = NSLayoutPriorityDragThatCanResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 501;
static const MASLayoutPriority MASLayoutPriorityWindowSizeStayPut = NSLayoutPriorityWindowSizeStayPut;
static const MASLayoutPriority MASLayoutPriorityDragThatCannotResizeWindow = NSLayoutPriorityDragThatCannotResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = NSLayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeCompression = NSLayoutPriorityFittingSizeCompression;
#endif
/**
* Allows you to attach keys to objects matching the variable names passed.
*
* view1.mas_key = @"view1", view2.mas_key = @"view2";
*
* is equivalent to:
*
* MASAttachKeys(view1, view2);
*/
#define MASAttachKeys(...) \
{ \
NSDictionary *keyPairs = NSDictionaryOfVariableBindings(__VA_ARGS__); \
for (id key in keyPairs.allKeys) { \
id obj = keyPairs[key]; \
NSAssert([obj respondsToSelector:@selector(setMas_key:)], \
@"Cannot attach mas_key to %@", obj); \
[obj setMas_key:key]; \
} \
}
/**
* Used to create object hashes
* Based on http://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html
*/
#define MAS_NSUINT_BIT (CHAR_BIT * sizeof(NSUInteger))
#define MAS_NSUINTROTATE(val, howmuch) ((((NSUInteger)val) << howmuch) | (((NSUInteger)val) >> (MAS_NSUINT_BIT - howmuch)))
/**
* Given a scalar or struct value, wraps it in NSValue
* Based on EXPObjectify: https://github.com/specta/expecta
*/
static inline id _MASBoxValue(const char *type, ...) {
va_list v;
va_start(v, type);
id obj = nil;
if (strcmp(type, @encode(id)) == 0) {
id actual = va_arg(v, id);
obj = actual;
} else if (strcmp(type, @encode(CGPoint)) == 0) {
CGPoint actual = (CGPoint)va_arg(v, CGPoint);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(CGSize)) == 0) {
CGSize actual = (CGSize)va_arg(v, CGSize);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(double)) == 0) {
double actual = (double)va_arg(v, double);
obj = [NSNumber numberWithDouble:actual];
} else if (strcmp(type, @encode(float)) == 0) {
float actual = (float)va_arg(v, double);
obj = [NSNumber numberWithFloat:actual];
} else if (strcmp(type, @encode(int)) == 0) {
int actual = (int)va_arg(v, int);
obj = [NSNumber numberWithInt:actual];
} else if (strcmp(type, @encode(long)) == 0) {
long actual = (long)va_arg(v, long);
obj = [NSNumber numberWithLong:actual];
} else if (strcmp(type, @encode(long long)) == 0) {
long long actual = (long long)va_arg(v, long long);
obj = [NSNumber numberWithLongLong:actual];
} else if (strcmp(type, @encode(short)) == 0) {
short actual = (short)va_arg(v, int);
obj = [NSNumber numberWithShort:actual];
} else if (strcmp(type, @encode(char)) == 0) {
char actual = (char)va_arg(v, int);
obj = [NSNumber numberWithChar:actual];
} else if (strcmp(type, @encode(bool)) == 0) {
bool actual = (bool)va_arg(v, int);
obj = [NSNumber numberWithBool:actual];
} else if (strcmp(type, @encode(unsigned char)) == 0) {
unsigned char actual = (unsigned char)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedChar:actual];
} else if (strcmp(type, @encode(unsigned int)) == 0) {
unsigned int actual = (unsigned int)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedInt:actual];
} else if (strcmp(type, @encode(unsigned long)) == 0) {
unsigned long actual = (unsigned long)va_arg(v, unsigned long);
obj = [NSNumber numberWithUnsignedLong:actual];
} else if (strcmp(type, @encode(unsigned long long)) == 0) {
unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
obj = [NSNumber numberWithUnsignedLongLong:actual];
} else if (strcmp(type, @encode(unsigned short)) == 0) {
unsigned short actual = (unsigned short)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedShort:actual];
}
va_end(v);
return obj;
}
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))

View File

@@ -1,49 +0,0 @@
//
// MASViewAttribute.h
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASUtilities.h"
/**
* An immutable tuple which stores the view and the related NSLayoutAttribute.
* Describes part of either the left or right hand side of a constraint equation
*/
@interface MASViewAttribute : NSObject
/**
* The view which the reciever relates to. Can be nil if item is not a view.
*/
@property (nonatomic, weak, readonly) MAS_VIEW *view;
/**
* The item which the reciever relates to.
*/
@property (nonatomic, weak, readonly) id item;
/**
* The attribute which the reciever relates to
*/
@property (nonatomic, assign, readonly) NSLayoutAttribute layoutAttribute;
/**
* Convenience initializer.
*/
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
/**
* The designated initializer.
*/
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute;
/**
* Determine whether the layoutAttribute is a size attribute
*
* @return YES if layoutAttribute is equal to NSLayoutAttributeWidth or NSLayoutAttributeHeight
*/
- (BOOL)isSizeAttribute;
@end

View File

@@ -1,46 +0,0 @@
//
// MASViewAttribute.m
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASViewAttribute.h"
@implementation MASViewAttribute
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
return self;
}
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [super init];
if (!self) return nil;
_view = view;
_item = item;
_layoutAttribute = layoutAttribute;
return self;
}
- (BOOL)isSizeAttribute {
return self.layoutAttribute == NSLayoutAttributeWidth
|| self.layoutAttribute == NSLayoutAttributeHeight;
}
- (BOOL)isEqual:(MASViewAttribute *)viewAttribute {
if ([viewAttribute isKindOfClass:self.class]) {
return self.view == viewAttribute.view
&& self.layoutAttribute == viewAttribute.layoutAttribute;
}
return [super isEqual:viewAttribute];
}
- (NSUInteger)hash {
return MAS_NSUINTROTATE([self.view hash], MAS_NSUINT_BIT / 2) ^ self.layoutAttribute;
}
@end

View File

@@ -1,48 +0,0 @@
//
// MASViewConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASViewAttribute.h"
#import "MASConstraint.h"
#import "MASLayoutConstraint.h"
#import "MASUtilities.h"
/**
* A single constraint.
* Contains the attributes neccessary for creating a NSLayoutConstraint and adding it to the appropriate view
*/
@interface MASViewConstraint : MASConstraint <NSCopying>
/**
* First item/view and first attribute of the NSLayoutConstraint
*/
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
/**
* Second item/view and second attribute of the NSLayoutConstraint
*/
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
/**
* initialises the MASViewConstraint with the first part of the equation
*
* @param firstViewAttribute view.mas_left, view.mas_width etc.
*
* @return a new view constraint
*/
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute;
/**
* Returns all MASViewConstraints installed with this view as a first item.
*
* @param view A view to retrieve constraints for.
*
* @return An array of MASViewConstraints.
*/
+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view;
@end

View File

@@ -1,401 +0,0 @@
//
// MASViewConstraint.m
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASViewConstraint.h"
#import "MASConstraint+Private.h"
#import "MASCompositeConstraint.h"
#import "MASLayoutConstraint.h"
#import "View+MASAdditions.h"
#import <objc/runtime.h>
@interface MAS_VIEW (MASConstraints)
@property (nonatomic, readonly) NSMutableSet *mas_installedConstraints;
@end
@implementation MAS_VIEW (MASConstraints)
static char kInstalledConstraintsKey;
- (NSMutableSet *)mas_installedConstraints {
NSMutableSet *constraints = objc_getAssociatedObject(self, &kInstalledConstraintsKey);
if (!constraints) {
constraints = [NSMutableSet set];
objc_setAssociatedObject(self, &kInstalledConstraintsKey, constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return constraints;
}
@end
@interface MASViewConstraint ()
@property (nonatomic, strong, readwrite) MASViewAttribute *secondViewAttribute;
@property (nonatomic, weak) MAS_VIEW *installedView;
@property (nonatomic, weak) MASLayoutConstraint *layoutConstraint;
@property (nonatomic, assign) NSLayoutRelation layoutRelation;
@property (nonatomic, assign) MASLayoutPriority layoutPriority;
@property (nonatomic, assign) CGFloat layoutMultiplier;
@property (nonatomic, assign) CGFloat layoutConstant;
@property (nonatomic, assign) BOOL hasLayoutRelation;
@property (nonatomic, strong) id mas_key;
@property (nonatomic, assign) BOOL useAnimator;
@end
@implementation MASViewConstraint
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
self = [super init];
if (!self) return nil;
_firstViewAttribute = firstViewAttribute;
self.layoutPriority = MASLayoutPriorityRequired;
self.layoutMultiplier = 1;
return self;
}
#pragma mark - NSCoping
- (id)copyWithZone:(NSZone __unused *)zone {
MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:self.firstViewAttribute];
constraint.layoutConstant = self.layoutConstant;
constraint.layoutRelation = self.layoutRelation;
constraint.layoutPriority = self.layoutPriority;
constraint.layoutMultiplier = self.layoutMultiplier;
constraint.delegate = self.delegate;
return constraint;
}
#pragma mark - Public
+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view {
return [view.mas_installedConstraints allObjects];
}
#pragma mark - Private
- (void)setLayoutConstant:(CGFloat)layoutConstant {
_layoutConstant = layoutConstant;
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
if (self.useAnimator) {
[self.layoutConstraint.animator setConstant:layoutConstant];
} else {
self.layoutConstraint.constant = layoutConstant;
}
#else
self.layoutConstraint.constant = layoutConstant;
#endif
}
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
_layoutRelation = layoutRelation;
self.hasLayoutRelation = YES;
}
- (BOOL)supportsActiveProperty {
return [self.layoutConstraint respondsToSelector:@selector(isActive)];
}
- (BOOL)isActive {
BOOL active = YES;
if ([self supportsActiveProperty]) {
active = [self.layoutConstraint isActive];
}
return active;
}
- (BOOL)hasBeenInstalled {
return (self.layoutConstraint != nil) && [self isActive];
}
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
_secondViewAttribute = secondViewAttribute;
} else {
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
}
#pragma mark - NSLayoutConstraint multiplier proxies
- (MASConstraint * (^)(CGFloat))multipliedBy {
return ^id(CGFloat multiplier) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint multiplier after it has been installed");
self.layoutMultiplier = multiplier;
return self;
};
}
- (MASConstraint * (^)(CGFloat))dividedBy {
return ^id(CGFloat divider) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint multiplier after it has been installed");
self.layoutMultiplier = 1.0/divider;
return self;
};
}
#pragma mark - MASLayoutPriority proxy
- (MASConstraint * (^)(MASLayoutPriority))priority {
return ^id(MASLayoutPriority priority) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint priority after it has been installed");
self.layoutPriority = priority;
return self;
};
}
#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
NSMutableArray *children = NSMutableArray.new;
for (id attr in attribute) {
MASViewConstraint *viewConstraint = [self copy];
viewConstraint.layoutRelation = relation;
viewConstraint.secondViewAttribute = attr;
[children addObject:viewConstraint];
}
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self.delegate;
[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
} else {
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}
#pragma mark - Semantic properties
- (MASConstraint *)with {
return self;
}
- (MASConstraint *)and {
return self;
}
#pragma mark - attribute chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
#pragma mark - Animator proxy
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
- (MASConstraint *)animator {
self.useAnimator = YES;
return self;
}
#endif
#pragma mark - debug helpers
- (MASConstraint * (^)(id))key {
return ^id(id key) {
self.mas_key = key;
return self;
};
}
#pragma mark - NSLayoutConstraint constant setters
- (void)setInsets:(MASEdgeInsets)insets {
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute) {
case NSLayoutAttributeLeft:
case NSLayoutAttributeLeading:
self.layoutConstant = insets.left;
break;
case NSLayoutAttributeTop:
self.layoutConstant = insets.top;
break;
case NSLayoutAttributeBottom:
self.layoutConstant = -insets.bottom;
break;
case NSLayoutAttributeRight:
case NSLayoutAttributeTrailing:
self.layoutConstant = -insets.right;
break;
default:
break;
}
}
- (void)setInset:(CGFloat)inset {
[self setInsets:(MASEdgeInsets){.top = inset, .left = inset, .bottom = inset, .right = inset}];
}
- (void)setOffset:(CGFloat)offset {
self.layoutConstant = offset;
}
- (void)setSizeOffset:(CGSize)sizeOffset {
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute) {
case NSLayoutAttributeWidth:
self.layoutConstant = sizeOffset.width;
break;
case NSLayoutAttributeHeight:
self.layoutConstant = sizeOffset.height;
break;
default:
break;
}
}
- (void)setCenterOffset:(CGPoint)centerOffset {
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute) {
case NSLayoutAttributeCenterX:
self.layoutConstant = centerOffset.x;
break;
case NSLayoutAttributeCenterY:
self.layoutConstant = centerOffset.y;
break;
default:
break;
}
}
#pragma mark - MASConstraint
- (void)activate {
[self install];
}
- (void)deactivate {
[self uninstall];
}
- (void)install {
if (self.hasBeenInstalled) {
return;
}
if ([self supportsActiveProperty] && self.layoutConstraint) {
self.layoutConstraint.active = YES;
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
// alignment attributes must have a secondViewAttribute
// therefore we assume that is refering to superview
// eg make.left.equalTo(@10)
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
if (self.secondViewAttribute.view) {
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
NSAssert(closestCommonSuperview,
@"couldn't find a common superview for %@ and %@",
self.firstViewAttribute.view, self.secondViewAttribute.view);
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
self.installedView = self.firstViewAttribute.view;
} else {
self.installedView = self.firstViewAttribute.view.superview;
}
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
- (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint {
// check if any constraints are the same apart from the only mutable property constant
// go through constraints in reverse as we do not want to match auto-resizing or interface builder constraints
// and they are likely to be added first.
for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) {
if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue;
if (existingConstraint.firstItem != layoutConstraint.firstItem) continue;
if (existingConstraint.secondItem != layoutConstraint.secondItem) continue;
if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue;
if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue;
if (existingConstraint.relation != layoutConstraint.relation) continue;
if (existingConstraint.multiplier != layoutConstraint.multiplier) continue;
if (existingConstraint.priority != layoutConstraint.priority) continue;
return (id)existingConstraint;
}
return nil;
}
- (void)uninstall {
if ([self supportsActiveProperty]) {
self.layoutConstraint.active = NO;
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
return;
}
[self.installedView removeConstraint:self.layoutConstraint];
self.layoutConstraint = nil;
self.installedView = nil;
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}
@end

View File

@@ -1,29 +0,0 @@
//
// Masonry.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for Masonry.
FOUNDATION_EXPORT double MasonryVersionNumber;
//! Project version string for Masonry.
FOUNDATION_EXPORT const unsigned char MasonryVersionString[];
#import "MASUtilities.h"
#import "View+MASAdditions.h"
#import "View+MASShorthandAdditions.h"
#import "ViewController+MASAdditions.h"
#import "NSArray+MASAdditions.h"
#import "NSArray+MASShorthandAdditions.h"
#import "MASConstraint.h"
#import "MASCompositeConstraint.h"
#import "MASViewAttribute.h"
#import "MASViewConstraint.h"
#import "MASConstraintMaker.h"
#import "MASLayoutConstraint.h"
#import "NSLayoutConstraint+MASDebugAdditions.h"

View File

@@ -1,72 +0,0 @@
//
// NSArray+MASAdditions.h
//
//
// Created by Daniel Hammond on 11/26/13.
//
//
#import "MASUtilities.h"
#import "MASConstraintMaker.h"
#import "MASViewAttribute.h"
typedef NS_ENUM(NSUInteger, MASAxisType) {
MASAxisTypeHorizontal,
MASAxisTypeVertical
};
@interface NSArray (MASAdditions)
/**
* Creates a MASConstraintMaker with each view in the callee.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing on each view
*
* @param block scope within which you can build up the constraints which you wish to apply to each view.
*
* @return Array of created MASConstraints
*/
- (NSArray *)mas_makeConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with each view in the callee.
* Any constraints defined are added to each view or the appropriate superview once the block has finished executing on each view.
* If an existing constraint exists then it will be updated instead.
*
* @param block scope within which you can build up the constraints which you wish to apply to each view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_updateConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with each view in the callee.
* Any constraints defined are added to each view or the appropriate superview once the block has finished executing on each view.
* All constraints previously installed for the views will be removed.
*
* @param block scope within which you can build up the constraints which you wish to apply to each view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_remakeConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* distribute with fixed spacing
*
* @param axisType which axis to distribute items along
* @param fixedSpacing the spacing between each item
* @param leadSpacing the spacing before the first item and the container
* @param tailSpacing the spacing after the last item and the container
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
/**
* distribute with fixed item size
*
* @param axisType which axis to distribute items along
* @param fixedItemLength the fixed length of each item
* @param leadSpacing the spacing before the first item and the container
* @param tailSpacing the spacing after the last item and the container
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
@end

View File

@@ -1,162 +0,0 @@
//
// NSArray+MASAdditions.m
//
//
// Created by Daniel Hammond on 11/26/13.
//
//
#import "NSArray+MASAdditions.h"
#import "View+MASAdditions.h"
@implementation NSArray (MASAdditions)
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block {
NSMutableArray *constraints = [NSMutableArray array];
for (MAS_VIEW *view in self) {
NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
[constraints addObjectsFromArray:[view mas_makeConstraints:block]];
}
return constraints;
}
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block {
NSMutableArray *constraints = [NSMutableArray array];
for (MAS_VIEW *view in self) {
NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
[constraints addObjectsFromArray:[view mas_updateConstraints:block]];
}
return constraints;
}
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
NSMutableArray *constraints = [NSMutableArray array];
for (MAS_VIEW *view in self) {
NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
[constraints addObjectsFromArray:[view mas_remakeConstraints:block]];
}
return constraints;
}
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
if (self.count < 2) {
NSAssert(self.count>1,@"views to distribute need to bigger than one");
return;
}
MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
if (axisType == MASAxisTypeHorizontal) {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
if (prev) {
make.width.equalTo(prev);
make.left.equalTo(prev.mas_right).offset(fixedSpacing);
if (i == self.count - 1) {//last one
make.right.equalTo(tempSuperView).offset(-tailSpacing);
}
}
else {//first one
make.left.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
else {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
if (prev) {
make.height.equalTo(prev);
make.top.equalTo(prev.mas_bottom).offset(fixedSpacing);
if (i == self.count - 1) {//last one
make.bottom.equalTo(tempSuperView).offset(-tailSpacing);
}
}
else {//first one
make.top.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
}
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
if (self.count < 2) {
NSAssert(self.count>1,@"views to distribute need to bigger than one");
return;
}
MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
if (axisType == MASAxisTypeHorizontal) {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@(fixedItemLength));
if (prev) {
if (i == self.count - 1) {//last one
make.right.equalTo(tempSuperView).offset(-tailSpacing);
}
else {
CGFloat offset = (1-(i/((CGFloat)self.count-1)))*(fixedItemLength+leadSpacing)-i*tailSpacing/(((CGFloat)self.count-1));
make.right.equalTo(tempSuperView).multipliedBy(i/((CGFloat)self.count-1)).with.offset(offset);
}
}
else {//first one
make.left.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
else {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(@(fixedItemLength));
if (prev) {
if (i == self.count - 1) {//last one
make.bottom.equalTo(tempSuperView).offset(-tailSpacing);
}
else {
CGFloat offset = (1-(i/((CGFloat)self.count-1)))*(fixedItemLength+leadSpacing)-i*tailSpacing/(((CGFloat)self.count-1));
make.bottom.equalTo(tempSuperView).multipliedBy(i/((CGFloat)self.count-1)).with.offset(offset);
}
}
else {//first one
make.top.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
}
- (MAS_VIEW *)mas_commonSuperviewOfViews
{
MAS_VIEW *commonSuperview = nil;
MAS_VIEW *previousView = nil;
for (id object in self) {
if ([object isKindOfClass:[MAS_VIEW class]]) {
MAS_VIEW *view = (MAS_VIEW *)object;
if (previousView) {
commonSuperview = [view mas_closestCommonSuperview:commonSuperview];
} else {
commonSuperview = view;
}
previousView = view;
}
}
NSAssert(commonSuperview, @"Can't constrain views that do not share a common superview. Make sure that all the views in this array have been added into the same view hierarchy.");
return commonSuperview;
}
@end

View File

@@ -1,41 +0,0 @@
//
// NSArray+MASShorthandAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 22/07/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "NSArray+MASAdditions.h"
#ifdef MAS_SHORTHAND
/**
* Shorthand array additions without the 'mas_' prefixes,
* only enabled if MAS_SHORTHAND is defined
*/
@interface NSArray (MASShorthandAdditions)
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *make))block;
@end
@implementation NSArray (MASShorthandAdditions)
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_makeConstraints:block];
}
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_updateConstraints:block];
}
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_remakeConstraints:block];
}
@end
#endif

View File

@@ -1,16 +0,0 @@
//
// NSLayoutConstraint+MASDebugAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "MASUtilities.h"
/**
* makes debug and log output of NSLayoutConstraints more readable
*/
@interface NSLayoutConstraint (MASDebugAdditions)
@end

View File

@@ -1,146 +0,0 @@
//
// NSLayoutConstraint+MASDebugAdditions.m
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "NSLayoutConstraint+MASDebugAdditions.h"
#import "MASConstraint.h"
#import "MASLayoutConstraint.h"
@implementation NSLayoutConstraint (MASDebugAdditions)
#pragma mark - description maps
+ (NSDictionary *)layoutRelationDescriptionsByValue {
static dispatch_once_t once;
static NSDictionary *descriptionMap;
dispatch_once(&once, ^{
descriptionMap = @{
@(NSLayoutRelationEqual) : @"==",
@(NSLayoutRelationGreaterThanOrEqual) : @">=",
@(NSLayoutRelationLessThanOrEqual) : @"<=",
};
});
return descriptionMap;
}
+ (NSDictionary *)layoutAttributeDescriptionsByValue {
static dispatch_once_t once;
static NSDictionary *descriptionMap;
dispatch_once(&once, ^{
descriptionMap = @{
@(NSLayoutAttributeTop) : @"top",
@(NSLayoutAttributeLeft) : @"left",
@(NSLayoutAttributeBottom) : @"bottom",
@(NSLayoutAttributeRight) : @"right",
@(NSLayoutAttributeLeading) : @"leading",
@(NSLayoutAttributeTrailing) : @"trailing",
@(NSLayoutAttributeWidth) : @"width",
@(NSLayoutAttributeHeight) : @"height",
@(NSLayoutAttributeCenterX) : @"centerX",
@(NSLayoutAttributeCenterY) : @"centerY",
@(NSLayoutAttributeBaseline) : @"baseline",
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@(NSLayoutAttributeFirstBaseline) : @"firstBaseline",
@(NSLayoutAttributeLastBaseline) : @"lastBaseline",
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
@(NSLayoutAttributeLeftMargin) : @"leftMargin",
@(NSLayoutAttributeRightMargin) : @"rightMargin",
@(NSLayoutAttributeTopMargin) : @"topMargin",
@(NSLayoutAttributeBottomMargin) : @"bottomMargin",
@(NSLayoutAttributeLeadingMargin) : @"leadingMargin",
@(NSLayoutAttributeTrailingMargin) : @"trailingMargin",
@(NSLayoutAttributeCenterXWithinMargins) : @"centerXWithinMargins",
@(NSLayoutAttributeCenterYWithinMargins) : @"centerYWithinMargins",
#endif
};
});
return descriptionMap;
}
+ (NSDictionary *)layoutPriorityDescriptionsByValue {
static dispatch_once_t once;
static NSDictionary *descriptionMap;
dispatch_once(&once, ^{
#if TARGET_OS_IPHONE || TARGET_OS_TV
descriptionMap = @{
@(MASLayoutPriorityDefaultHigh) : @"high",
@(MASLayoutPriorityDefaultLow) : @"low",
@(MASLayoutPriorityDefaultMedium) : @"medium",
@(MASLayoutPriorityRequired) : @"required",
@(MASLayoutPriorityFittingSizeLevel) : @"fitting size",
};
#elif TARGET_OS_MAC
descriptionMap = @{
@(MASLayoutPriorityDefaultHigh) : @"high",
@(MASLayoutPriorityDragThatCanResizeWindow) : @"drag can resize window",
@(MASLayoutPriorityDefaultMedium) : @"medium",
@(MASLayoutPriorityWindowSizeStayPut) : @"window size stay put",
@(MASLayoutPriorityDragThatCannotResizeWindow) : @"drag cannot resize window",
@(MASLayoutPriorityDefaultLow) : @"low",
@(MASLayoutPriorityFittingSizeCompression) : @"fitting size",
@(MASLayoutPriorityRequired) : @"required",
};
#endif
});
return descriptionMap;
}
#pragma mark - description override
+ (NSString *)descriptionForObject:(id)obj {
if ([obj respondsToSelector:@selector(mas_key)] && [obj mas_key]) {
return [NSString stringWithFormat:@"%@:%@", [obj class], [obj mas_key]];
}
return [NSString stringWithFormat:@"%@:%p", [obj class], obj];
}
- (NSString *)description {
NSMutableString *description = [[NSMutableString alloc] initWithString:@"<"];
[description appendString:[self.class descriptionForObject:self]];
[description appendFormat:@" %@", [self.class descriptionForObject:self.firstItem]];
if (self.firstAttribute != NSLayoutAttributeNotAnAttribute) {
[description appendFormat:@".%@", self.class.layoutAttributeDescriptionsByValue[@(self.firstAttribute)]];
}
[description appendFormat:@" %@", self.class.layoutRelationDescriptionsByValue[@(self.relation)]];
if (self.secondItem) {
[description appendFormat:@" %@", [self.class descriptionForObject:self.secondItem]];
}
if (self.secondAttribute != NSLayoutAttributeNotAnAttribute) {
[description appendFormat:@".%@", self.class.layoutAttributeDescriptionsByValue[@(self.secondAttribute)]];
}
if (self.multiplier != 1) {
[description appendFormat:@" * %g", self.multiplier];
}
if (self.secondAttribute == NSLayoutAttributeNotAnAttribute) {
[description appendFormat:@" %g", self.constant];
} else {
if (self.constant) {
[description appendFormat:@" %@ %g", (self.constant < 0 ? @"-" : @"+"), ABS(self.constant)];
}
}
if (self.priority != MASLayoutPriorityRequired) {
[description appendFormat:@" ^%@", self.class.layoutPriorityDescriptionsByValue[@(self.priority)] ?: [NSNumber numberWithDouble:self.priority]];
}
[description appendString:@">"];
return description;
}
@end

View File

@@ -1,111 +0,0 @@
//
// UIView+MASAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASUtilities.h"
#import "MASConstraintMaker.h"
#import "MASViewAttribute.h"
/**
* Provides constraint maker block
* and convience methods for creating MASViewAttribute which are view + NSLayoutAttribute pairs
*/
@interface MAS_VIEW (MASAdditions)
/**
* following properties return a new MASViewAttribute with current view and appropriate NSLayoutAttribute
*/
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_right;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leading;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_width;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_height;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;
@property (nonatomic, strong, readonly) MASViewAttribute *(^mas_attribute)(NSLayoutAttribute attr);
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@property (nonatomic, strong, readonly) MASViewAttribute *mas_firstBaseline;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_lastBaseline;
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerYWithinMargins;
#endif
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideTop API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideBottom API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideLeft API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideRight API_AVAILABLE(ios(11.0),tvos(11.0));
#endif
/**
* a key to associate with this view
*/
@property (nonatomic, strong) id mas_key;
/**
* Finds the closest common superview between this view and another view
*
* @param view other view
*
* @return returns nil if common superview could not be found
*/
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created MASConstraints
*/
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* If an existing constraint exists then it will be updated instead.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* All constraints previously installed for the view will be removed.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
@end

View File

@@ -1,186 +0,0 @@
//
// UIView+MASAdditions.m
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "View+MASAdditions.h"
#import <objc/runtime.h>
@implementation MAS_VIEW (MASAdditions)
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.updateExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.removeExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}
#pragma mark - NSLayoutAttribute properties
- (MASViewAttribute *)mas_left {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeft];
}
- (MASViewAttribute *)mas_top {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_right {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeRight];
}
- (MASViewAttribute *)mas_bottom {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_leading {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeading];
}
- (MASViewAttribute *)mas_trailing {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTrailing];
}
- (MASViewAttribute *)mas_width {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeWidth];
}
- (MASViewAttribute *)mas_height {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeHeight];
}
- (MASViewAttribute *)mas_centerX {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterX];
}
- (MASViewAttribute *)mas_centerY {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterY];
}
- (MASViewAttribute *)mas_baseline {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeBaseline];
}
- (MASViewAttribute *(^)(NSLayoutAttribute))mas_attribute
{
return ^(NSLayoutAttribute attr) {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:attr];
};
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASViewAttribute *)mas_firstBaseline {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASViewAttribute *)mas_lastBaseline {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLastBaseline];
}
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
- (MASViewAttribute *)mas_leftMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeftMargin];
}
- (MASViewAttribute *)mas_rightMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeRightMargin];
}
- (MASViewAttribute *)mas_topMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTopMargin];
}
- (MASViewAttribute *)mas_bottomMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeBottomMargin];
}
- (MASViewAttribute *)mas_leadingMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeadingMargin];
}
- (MASViewAttribute *)mas_trailingMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTrailingMargin];
}
- (MASViewAttribute *)mas_centerXWithinMargins {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}
- (MASViewAttribute *)mas_centerYWithinMargins {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
#endif
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)
- (MASViewAttribute *)mas_safeAreaLayoutGuide {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_safeAreaLayoutGuideTop {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_safeAreaLayoutGuideBottom {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_safeAreaLayoutGuideLeft {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeLeft];
}
- (MASViewAttribute *)mas_safeAreaLayoutGuideRight {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeRight];
}
#endif
#pragma mark - associated properties
- (id)mas_key {
return objc_getAssociatedObject(self, @selector(mas_key));
}
- (void)setMas_key:(id)key {
objc_setAssociatedObject(self, @selector(mas_key), key, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - heirachy
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
MAS_VIEW *closestCommonSuperview = nil;
MAS_VIEW *secondViewSuperview = view;
while (!closestCommonSuperview && secondViewSuperview) {
MAS_VIEW *firstViewSuperview = self;
while (!closestCommonSuperview && firstViewSuperview) {
if (secondViewSuperview == firstViewSuperview) {
closestCommonSuperview = secondViewSuperview;
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
return closestCommonSuperview;
}
@end

View File

@@ -1,133 +0,0 @@
//
// UIView+MASShorthandAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 22/07/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "View+MASAdditions.h"
#ifdef MAS_SHORTHAND
/**
* Shorthand view additions without the 'mas_' prefixes,
* only enabled if MAS_SHORTHAND is defined
*/
@interface MAS_VIEW (MASShorthandAdditions)
@property (nonatomic, strong, readonly) MASViewAttribute *left;
@property (nonatomic, strong, readonly) MASViewAttribute *top;
@property (nonatomic, strong, readonly) MASViewAttribute *right;
@property (nonatomic, strong, readonly) MASViewAttribute *bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *leading;
@property (nonatomic, strong, readonly) MASViewAttribute *trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *width;
@property (nonatomic, strong, readonly) MASViewAttribute *height;
@property (nonatomic, strong, readonly) MASViewAttribute *centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *baseline;
@property (nonatomic, strong, readonly) MASViewAttribute *(^attribute)(NSLayoutAttribute attr);
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@property (nonatomic, strong, readonly) MASViewAttribute *firstBaseline;
@property (nonatomic, strong, readonly) MASViewAttribute *lastBaseline;
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
@property (nonatomic, strong, readonly) MASViewAttribute *leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *centerYWithinMargins;
#endif
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideTop API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideBottom API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideLeft API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideRight API_AVAILABLE(ios(11.0),tvos(11.0));
#endif
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *make))block;
@end
#define MAS_ATTR_FORWARD(attr) \
- (MASViewAttribute *)attr { \
return [self mas_##attr]; \
}
@implementation MAS_VIEW (MASShorthandAdditions)
MAS_ATTR_FORWARD(top);
MAS_ATTR_FORWARD(left);
MAS_ATTR_FORWARD(bottom);
MAS_ATTR_FORWARD(right);
MAS_ATTR_FORWARD(leading);
MAS_ATTR_FORWARD(trailing);
MAS_ATTR_FORWARD(width);
MAS_ATTR_FORWARD(height);
MAS_ATTR_FORWARD(centerX);
MAS_ATTR_FORWARD(centerY);
MAS_ATTR_FORWARD(baseline);
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
MAS_ATTR_FORWARD(firstBaseline);
MAS_ATTR_FORWARD(lastBaseline);
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
MAS_ATTR_FORWARD(leftMargin);
MAS_ATTR_FORWARD(rightMargin);
MAS_ATTR_FORWARD(topMargin);
MAS_ATTR_FORWARD(bottomMargin);
MAS_ATTR_FORWARD(leadingMargin);
MAS_ATTR_FORWARD(trailingMargin);
MAS_ATTR_FORWARD(centerXWithinMargins);
MAS_ATTR_FORWARD(centerYWithinMargins);
#endif
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)
MAS_ATTR_FORWARD(safeAreaLayoutGuideTop);
MAS_ATTR_FORWARD(safeAreaLayoutGuideBottom);
MAS_ATTR_FORWARD(safeAreaLayoutGuideLeft);
MAS_ATTR_FORWARD(safeAreaLayoutGuideRight);
#endif
- (MASViewAttribute *(^)(NSLayoutAttribute))attribute {
return [self mas_attribute];
}
- (NSArray *)makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
return [self mas_makeConstraints:block];
}
- (NSArray *)updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
return [self mas_updateConstraints:block];
}
- (NSArray *)remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
return [self mas_remakeConstraints:block];
}
@end
#endif

Some files were not shown because too many files have changed in this diff Show More