Compare commits
29 Commits
9b92d81902
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 573c9a42ae | |||
| b019e1e494 | |||
| 3d5ad8a614 | |||
| 5f1c22c6a8 | |||
| ce6de76055 | |||
| 5eef31b8da | |||
| 6336feff27 | |||
| efb3925257 | |||
| c083f1d4b3 | |||
| 05804ba7d6 | |||
| 5138c1c11a | |||
| 07b2b9d459 | |||
| afcd412f11 | |||
| cbf2db649e | |||
| 7deeedec68 | |||
| 005349f8e6 | |||
| ceca780ab3 | |||
| ab5220e460 | |||
| f88a85a807 | |||
| df547a16dc | |||
| c03a625409 | |||
| b6d3207665 | |||
| 98dc810455 | |||
| 9d446b4bc3 | |||
| f20ec7f06c | |||
| 8e356973bd | |||
| 43e6133c21 | |||
| 2970f1514b | |||
| 42aca73588 |
@ -71,7 +71,7 @@ Swift · UIKit · SnapKit(代码布局,约束写在 `setupUI()` / `setupCons
|
|||||||
|
|
||||||
## 单元测试
|
## 单元测试
|
||||||
|
|
||||||
每增一功能必须附带单元测试且全部通过(`Cmd + U` / `xcodebuild test`),与功能同次提交。
|
每增一功能必须附带单元测试且全部通过,与功能同次提交。单元测试必须在已连接的 iPhone 真机上运行,不使用模拟器;支持使用与开发机处于同一 Wi-Fi 下并已启用无线连接的真机。可在 Xcode 中选择真机后执行 `Cmd + U`,或使用 `xcodebuild test -destination 'platform=iOS,id=<设备 UDID>'`。
|
||||||
|
|
||||||
| 层级 | 优先级 |
|
| 层级 | 优先级 |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
|
|||||||
169
APPSTORE_CACHE_KEY_MIGRATION.md
Normal file
169
APPSTORE_CACHE_KEY_MIGRATION.md
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
# AppStore 缓存 Key 升级对照
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
- 旧线上工程与重构版工程的主应用 Bundle ID 均为 `com.yuanzhixiang.suixinkan`。通过 App Store 覆盖升级时,系统会保留原应用容器,因此旧版 `UserDefaults` 会继续存在。
|
||||||
|
- 本次升级策略为:**强制重新登录、旧偏好全部重置、不迁移旧缓存**。
|
||||||
|
- 新 Key 只对齐 Android 的业务作用域;iOS 统一使用 `_` 作为分隔符,不复制 Android MMKV 历史实现中无分隔符拼接的写法。
|
||||||
|
- 本文中的“旧线上工程”为 `/Users/hanqiu/Desktop/android_project/suixinkan_ios`,“重构版工程”为 `/Users/hanqiu/Desktop/suixinkan`。
|
||||||
|
|
||||||
|
## Key 占位符
|
||||||
|
|
||||||
|
| 占位符 | 含义 |
|
||||||
|
|---|---|
|
||||||
|
| `O` | 旧版将 `[accountType, userId, storeId, scenicId]` 中的非空值用 `_` 拼接,再清洗非字母数字字符后得到的 accountKey |
|
||||||
|
| `P` | 本次实施前重构版使用的 `<userId>_<accountType>` 账号前缀;用户 ID 为空时可能退化为 `guest` |
|
||||||
|
| `A` | 本次实施后统一使用的 `<accountType>_<userId>` 账号前缀 |
|
||||||
|
| `S` | 景区 ID(scenicId) |
|
||||||
|
| `T` | 排队点位 ID(spotId) |
|
||||||
|
| `R` | 角色编码(roleCode) |
|
||||||
|
|
||||||
|
> `suixinkan.session.v1` 是旧工程中真实存在的单个 `UserDefaults` Key,其值为 JSON Data。表格中的 `suixinkan.session.v1[token]` 等写法仅表示该 JSON 内的字段,不是独立的 `UserDefaults` Key。
|
||||||
|
|
||||||
|
## 新旧工程统一业务 Key 对照
|
||||||
|
|
||||||
|
| 业务数据 | 旧线上工程 Key | 重构版实施前 Key | 统一后的 Key | 升级处理 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 登录会话 | `suixinkan.session.v1` JSON | 拆分为多个 `key_in_*` | 保持拆分结构 | 删除旧会话,强制登录 |
|
||||||
|
| Token | `suixinkan.session.v1[token]` | `key_in_token` | `key_in_token` | 首次升级清空 |
|
||||||
|
| 用户 ID | `suixinkan.session.v1[userId]` | `key_in_user_id` | `key_in_user_id` | 首次升级清空 |
|
||||||
|
| 用户名 | `suixinkan.session.v1[userName]` | `key_in_user_name` | `key_in_user_name` | 首次升级清空 |
|
||||||
|
| 头像 | `suixinkan.session.v1[avatar]` | `key_in_avatar` | `key_in_avatar` | 首次升级清空 |
|
||||||
|
| 手机号 | `suixinkan.session.v1[phone]` | `key_in_phone` | `key_in_phone` | 首次升级清空 |
|
||||||
|
| 账号类型 | `suixinkan.session.v1[accountType]` | `key_in_account_type` | `key_in_account_type` | 首次升级清空 |
|
||||||
|
| 账号类型显示名称 | `suixinkan.session.v1[accountDisplayName]` | `key_in_account_display_name` | `key_in_account_display_name` | 首次升级清空 |
|
||||||
|
| 角色 ID | `suixinkan.session.v1[roleId]` | `P_key_in_role_id` | `A_key_in_role_id` | 不迁移;当前仅用于 legacy 权限匹配兜底 |
|
||||||
|
| 角色编码 | 无独立 Key | `key_in_role_code` | `A_key_in_role_code` | 改为账号作用域 |
|
||||||
|
| 角色名称 | `suixinkan.session.v1[roleName]` | `key_in_role_name` | `A_key_in_role_name` | 改为账号作用域 |
|
||||||
|
| 权限列表 | 无 | `P_key_role_permission_list` | `A_key_role_permission_list` | 不迁移预发布数据 |
|
||||||
|
| 当前权限 | 无 | `P_key_in_permission` | `A_key_in_permission` | 不迁移预发布数据 |
|
||||||
|
| 角色景区列表 | 无 | `P_key_current_role_scenic_list` | `A_key_current_role_scenic_list` | 不迁移预发布数据 |
|
||||||
|
| 当前景区 ID | `suixinkan.session.v1[scenicId]` | `key_in_current_scenic_id` | `A_key_in_current_scenic_id` | 改为账号作用域 |
|
||||||
|
| 当前景区名称 | `suixinkan.session.v1[scenicName]` | `key_in_current_scenic_name` | `A_key_in_current_scenic_name` | 改为账号作用域 |
|
||||||
|
| 当前门店 ID | `suixinkan.session.v1[storeId]` | `key_in_current_store_id` | `A_scenic_S_scenic_store_id` | 改为账号+景区作用域 |
|
||||||
|
| 当前门店名称 | `suixinkan.session.v1[storeName]` | 无 | 不再持久化 | 删除旧值 |
|
||||||
|
| 常用菜单 | `home.common.menu.uris` | `P_role_R_common_uris` | `A_role_R_common_uris` | 不迁移旧菜单;仅统一作用域,Android 精确后缀不同 |
|
||||||
|
| 收款语音 | `payment.voice.broadcast.enabled` | `P_key_is_open_receive_voice` | `A_key_is_open_receive_voice` | 重置为默认值 |
|
||||||
|
| 在线状态 | 无 | `P_key_online_status` | `A_key_online_status` | 改为账号作用域 |
|
||||||
|
| 上次位置上报时间 | 无 | `P_key_last_location_report_time` | `A_key_last_location_report_time` | 改为账号作用域 |
|
||||||
|
| 定位提醒分钟数 | 无 | `P_key_location_reminder_minutes` | `A_key_location_reminder_minutes` | 改为账号作用域 |
|
||||||
|
| 当前排队点位 ID | `account_O_scenic_S_scenic_queue_selected_spot_id` | `P__scenic_queue_punch_spot_id` | `A_scenic_S_scenic_queue_punch_spot_id` | 不迁移 |
|
||||||
|
| 当前排队点位名称 | `account_O_scenic_S_scenic_queue_selected_spot_name` | `P__scenic_queue_punch_spot_name` | `A_scenic_S_spot_T_scenic_queue_punch_spot_name` | 不迁移 |
|
||||||
|
| 排队播报备注 | `account_O_scenic_S_scenic_queue_custom_tts_text_spot_T` | `P__scenic_queue_remark` | `A_scenic_S_spot_T_scenic_queue_remark` | 不迁移 |
|
||||||
|
| 排队设置快照 | `account_O_scenic_S_scenic_queue_settings_snapshot_spot_T` | `P__scenic_queue_settings_snapshot` | `A_scenic_S_spot_T_scenic_queue_settings_snapshot` | 不迁移 |
|
||||||
|
| 排队预设语音 | `account_O_scenic_S_scenic_queue_preset_voices_spot_T` | `P__scenic_queue_preset_voices_v1` | `A_scenic_S_spot_T_scenic_queue_preset_voices_v1` | 不迁移 |
|
||||||
|
| 离线 TTS | 无 | `P__scenic_queue_offline_tts_v1` | `A_scenic_S_scenic_queue_offline_tts_v1` | 默认值对齐 Android 为 `true` |
|
||||||
|
|
||||||
|
## 首次升级需要清理的旧 Key
|
||||||
|
|
||||||
|
已新增整型版本标记 `suixinkan.cache.schema.version`。当版本小于目标缓存版本时执行一次清理,全部清理完成后再写入目标版本,避免每次启动重复清空用户重新登录后产生的数据。
|
||||||
|
|
||||||
|
### 会话与推送
|
||||||
|
|
||||||
|
- `suixinkan.session.v1`
|
||||||
|
- `apns_device_token`
|
||||||
|
- `apns_uploaded_token`
|
||||||
|
|
||||||
|
同时清空重构版预发布阶段可能存在的登录与账号字段,以确保升级后强制重新登录:
|
||||||
|
|
||||||
|
- `key_in_token`
|
||||||
|
- `key_last_login_username`
|
||||||
|
- `key_in_user_id`
|
||||||
|
- `key_in_user_name`
|
||||||
|
- `key_in_real_name`
|
||||||
|
- `key_in_avatar`
|
||||||
|
- `key_in_phone`
|
||||||
|
- `key_in_account_type`
|
||||||
|
- `key_in_account_display_name`
|
||||||
|
- `key_in_role_id`
|
||||||
|
- `key_in_role_code`
|
||||||
|
- `key_in_role_name`
|
||||||
|
- `key_in_current_scenic_id`
|
||||||
|
- `key_in_current_scenic_name`
|
||||||
|
- `key_in_current_store_id`
|
||||||
|
|
||||||
|
`key_privacy_agreement_accepted` 是否清理应服从“旧偏好全部重置”的既定策略,因此本次也应清理。
|
||||||
|
|
||||||
|
同时清理早期重构实现可能写入的以下未分域业务字段:
|
||||||
|
|
||||||
|
- `key_role_permission_list`
|
||||||
|
- `key_in_permission`
|
||||||
|
- `key_current_role_scenic_list`
|
||||||
|
- `key_online_status`
|
||||||
|
- `key_last_location_report_time`
|
||||||
|
- `key_location_reminder_minutes`
|
||||||
|
- `key_is_open_receive_voice`
|
||||||
|
|
||||||
|
### 首页与收款
|
||||||
|
|
||||||
|
- `home.common.menu.uris`
|
||||||
|
- `home.common.menu.android.baseline.v2`
|
||||||
|
- `home.routing.unknown.records`
|
||||||
|
- `payment.voice.broadcast.enabled`
|
||||||
|
|
||||||
|
### 排队全局偏好
|
||||||
|
|
||||||
|
- `scenic_queue_tts_enabled`
|
||||||
|
- `scenic_queue_background_poll_enabled`
|
||||||
|
- `scenic_queue_selected_spot_id`
|
||||||
|
- `scenic_queue_selected_spot_name`
|
||||||
|
- `scenic_queue_custom_tts_text`
|
||||||
|
- `scenic_queue_photo_estimate_seconds`
|
||||||
|
- `scenic_queue_broadcast_interval_seconds`
|
||||||
|
- `scenic_queue_countdown_threshold_seconds`
|
||||||
|
- `scenic_queue_show_start_shooting_button`
|
||||||
|
- `scenic_queue_auto_call_ahead_count`
|
||||||
|
- `scenic_queue_quick_call_button_enabled`
|
||||||
|
- `scenic_queue_prepare_call_button_enabled`
|
||||||
|
- `scenic_queue_config_logs`
|
||||||
|
- `scenic_queue_settings_snapshot`
|
||||||
|
- `scenic_queue_preset_voices`
|
||||||
|
|
||||||
|
### 排队动态缓存
|
||||||
|
|
||||||
|
清理符合以下已知格式的旧 Key,不应扩大为删除整个 `UserDefaults` domain:
|
||||||
|
|
||||||
|
- `account_O_scenic_S_scenic_queue_selected_spot_id`
|
||||||
|
- `account_O_scenic_S_scenic_queue_selected_spot_name`
|
||||||
|
- `account_O_scenic_S_scenic_queue_custom_tts_text_spot_T`
|
||||||
|
- `account_O_scenic_S_scenic_queue_settings_snapshot_spot_T`
|
||||||
|
- `account_O_scenic_S_scenic_queue_preset_voices_spot_T`
|
||||||
|
- `scenic_S_spot_T_scenic_queue_settings_snapshot`
|
||||||
|
- `scenic_S_scenic_queue_settings_snapshot`
|
||||||
|
- `scenic_S_spot_T_scenic_queue_preset_voices`
|
||||||
|
|
||||||
|
### 重构版预发布缓存
|
||||||
|
|
||||||
|
清理使用旧 `P` 前缀的以下 Key 家族:
|
||||||
|
|
||||||
|
- `P_key_in_role_id`
|
||||||
|
- `P_key_role_permission_list`
|
||||||
|
- `P_key_in_permission`
|
||||||
|
- `P_key_current_role_scenic_list`
|
||||||
|
- `P_key_online_status`
|
||||||
|
- `P_key_last_location_report_time`
|
||||||
|
- `P_key_location_reminder_minutes`
|
||||||
|
- `P_key_is_open_receive_voice`
|
||||||
|
- `P_role_R_common_uris`
|
||||||
|
- `P__scenic_queue_punch_spot_id`
|
||||||
|
- `P__scenic_queue_punch_spot_name`
|
||||||
|
- `P__scenic_queue_remark`
|
||||||
|
- `P__scenic_queue_settings_snapshot`
|
||||||
|
- `P__scenic_queue_offline_tts_v1`
|
||||||
|
- `P__scenic_queue_preset_voices_v1`
|
||||||
|
|
||||||
|
其中 `P__scenic_queue_*` 的双下划线来自当前 Key 生成器和自带前导下划线的后缀叠加,属于需要淘汰的错误格式。
|
||||||
|
|
||||||
|
## 不应清理的内容
|
||||||
|
|
||||||
|
- 禁止调用 `removePersistentDomain(forName:)` 清空应用的整个 `UserDefaults` domain,应只删除明确列出的 Key 和严格匹配的动态 Key。
|
||||||
|
- 不删除 `Documents/CloudDownloads`。该目录可能包含用户主动下载的文件,不属于普通缓存。
|
||||||
|
- 不删除 SDK 缓存、图片缓存或其他未列入本次升级范围的用户文件。
|
||||||
|
- 不做旧 `suixinkan.session.v1`、排队设置、常用菜单或收款偏好的解码与迁移。
|
||||||
|
|
||||||
|
## 发布前备注
|
||||||
|
|
||||||
|
- 旧线上版本为 `1.0.1 (2)`;当前覆盖升级版本已设置为 `1.1.4 (1010401)`。
|
||||||
|
- 重构版当前未保留旧工程的 APNs entitlement、远程通知后台能力、回调和设备 Token 上传流程。该问题不是缓存 Key 修改,但若线上仍需要推送,发布前必须单独恢复并验证。清理 `apns_device_token` 与 `apns_uploaded_token` 后,恢复推送时应重新绑定设备 Token。
|
||||||
|
- `Application Support/suixinkan/cloud_transfer_tasks.json` 当前为全账号共享文件,存在账号切换后读取其他账号任务的风险。该问题不是 `UserDefaults` Key 迁移的一部分,应作为独立发布风险处理。
|
||||||
|
- 本方案已在重构版中实施:应用首次创建 `AppStore` 时按 `suixinkan.cache.schema.version` 执行一次定向清理,并切换到表格中的统一 Key;不删除未列入范围的 `UserDefaults` 或用户文件。
|
||||||
1
Podfile
1
Podfile
@ -8,6 +8,7 @@ target 'suixinkan' do
|
|||||||
pod 'UMCommon'
|
pod 'UMCommon'
|
||||||
pod 'UMDevice'
|
pod 'UMDevice'
|
||||||
pod 'UMAPM'
|
pod 'UMAPM'
|
||||||
|
pod 'IQKeyboardManagerSwift'
|
||||||
# 微信 Open SDK:仅接入分享能力,不调用支付 API。
|
# 微信 Open SDK:仅接入分享能力,不调用支付 API。
|
||||||
pod 'WechatOpenSDK-XCFramework', '~> 2.0.4'
|
pod 'WechatOpenSDK-XCFramework', '~> 2.0.4'
|
||||||
|
|
||||||
|
|||||||
59
Podfile.lock
59
Podfile.lock
@ -6,6 +6,46 @@ PODS:
|
|||||||
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
||||||
- AMapSearch-NO-IDFA (9.8.0):
|
- AMapSearch-NO-IDFA (9.8.0):
|
||||||
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
||||||
|
- IQKeyboardCore (1.0.9)
|
||||||
|
- IQKeyboardManagerSwift (8.0.3):
|
||||||
|
- IQKeyboardManagerSwift/Appearance (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/Core (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/IQKeyboardReturnManager (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/IQKeyboardToolbarManager (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/IQTextView (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/Resign (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/Appearance (8.0.3):
|
||||||
|
- IQKeyboardManagerSwift/Core
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQKeyboardManagerSwift/Core (8.0.3):
|
||||||
|
- IQKeyboardNotification
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQKeyboardManagerSwift/IQKeyboardReturnManager (8.0.3):
|
||||||
|
- IQKeyboardReturnManager
|
||||||
|
- IQKeyboardManagerSwift/IQKeyboardToolbarManager (8.0.3):
|
||||||
|
- IQKeyboardManagerSwift/Core
|
||||||
|
- IQKeyboardToolbarManager
|
||||||
|
- IQKeyboardManagerSwift/IQTextView (8.0.3):
|
||||||
|
- IQTextView
|
||||||
|
- IQKeyboardManagerSwift/Resign (8.0.3):
|
||||||
|
- IQKeyboardManagerSwift/Core
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQKeyboardNotification (1.0.7)
|
||||||
|
- IQKeyboardReturnManager (1.0.6):
|
||||||
|
- IQKeyboardCore
|
||||||
|
- IQKeyboardToolbar (1.1.5):
|
||||||
|
- IQKeyboardToolbar/Core (= 1.1.5)
|
||||||
|
- IQKeyboardToolbar/Core (1.1.5):
|
||||||
|
- IQKeyboardCore
|
||||||
|
- IQKeyboardToolbar/Placeholderable
|
||||||
|
- IQKeyboardToolbar/Placeholderable (1.1.5)
|
||||||
|
- IQKeyboardToolbarManager (1.1.6):
|
||||||
|
- IQKeyboardToolbar
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQTextInputViewNotification (1.0.9):
|
||||||
|
- IQKeyboardCore
|
||||||
|
- IQTextView (1.0.5):
|
||||||
|
- IQKeyboardToolbar/Placeholderable
|
||||||
- UMAPM (2.0.7):
|
- UMAPM (2.0.7):
|
||||||
- UMCommon
|
- UMCommon
|
||||||
- UMCommon (7.5.11):
|
- UMCommon (7.5.11):
|
||||||
@ -17,6 +57,7 @@ DEPENDENCIES:
|
|||||||
- AMap3DMap-NO-IDFA
|
- AMap3DMap-NO-IDFA
|
||||||
- AMapLocation-NO-IDFA
|
- AMapLocation-NO-IDFA
|
||||||
- AMapSearch-NO-IDFA
|
- AMapSearch-NO-IDFA
|
||||||
|
- IQKeyboardManagerSwift
|
||||||
- UMAPM
|
- UMAPM
|
||||||
- UMCommon
|
- UMCommon
|
||||||
- UMDevice
|
- UMDevice
|
||||||
@ -28,6 +69,14 @@ SPEC REPOS:
|
|||||||
- AMapFoundation-NO-IDFA
|
- AMapFoundation-NO-IDFA
|
||||||
- AMapLocation-NO-IDFA
|
- AMapLocation-NO-IDFA
|
||||||
- AMapSearch-NO-IDFA
|
- AMapSearch-NO-IDFA
|
||||||
|
- IQKeyboardCore
|
||||||
|
- IQKeyboardManagerSwift
|
||||||
|
- IQKeyboardNotification
|
||||||
|
- IQKeyboardReturnManager
|
||||||
|
- IQKeyboardToolbar
|
||||||
|
- IQKeyboardToolbarManager
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQTextView
|
||||||
- UMAPM
|
- UMAPM
|
||||||
- UMCommon
|
- UMCommon
|
||||||
- UMDevice
|
- UMDevice
|
||||||
@ -38,11 +87,19 @@ SPEC CHECKSUMS:
|
|||||||
AMapFoundation-NO-IDFA: a2e3c895398d7ee757278e1a0a8f9359da4b146e
|
AMapFoundation-NO-IDFA: a2e3c895398d7ee757278e1a0a8f9359da4b146e
|
||||||
AMapLocation-NO-IDFA: 7cd8fc837ea41edfbf4d937cd20572e277b77d18
|
AMapLocation-NO-IDFA: 7cd8fc837ea41edfbf4d937cd20572e277b77d18
|
||||||
AMapSearch-NO-IDFA: c0afd2a69a076d4228becda4401dbe4a279a03ef
|
AMapSearch-NO-IDFA: c0afd2a69a076d4228becda4401dbe4a279a03ef
|
||||||
|
IQKeyboardCore: 250a88dfc713b2ecbe1dce250811d054fc016959
|
||||||
|
IQKeyboardManagerSwift: 7f6a9a1e2285732d3ff5f485ff921215253ea77e
|
||||||
|
IQKeyboardNotification: 3b9bc812a5af2cf97df08a363d9daa9fc0dc1848
|
||||||
|
IQKeyboardReturnManager: fcbf51fc68d7536fc1fbcca5231c4e82576b12ac
|
||||||
|
IQKeyboardToolbar: b0f26e43b9e806e961fde2344fe379ae193236b9
|
||||||
|
IQKeyboardToolbarManager: 242e858cc74e06e00aa989bea1355b24d4397854
|
||||||
|
IQTextInputViewNotification: 3b9fb27a16e7ee8958cc9092cfb07a1a9e1fd559
|
||||||
|
IQTextView: ae13b4922f22e6f027f62c557d9f4f236b19d5c7
|
||||||
UMAPM: ba9fbebe8dd2048b251c332ab0afc86e1ba5ea63
|
UMAPM: ba9fbebe8dd2048b251c332ab0afc86e1ba5ea63
|
||||||
UMCommon: d652b3b372a801b36db203f5b546ab3e15676898
|
UMCommon: d652b3b372a801b36db203f5b546ab3e15676898
|
||||||
UMDevice: 20b7b3c37a36b2c5c7ca8b5a54386ff9ed2b3b19
|
UMDevice: 20b7b3c37a36b2c5c7ca8b5a54386ff9ed2b3b19
|
||||||
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
|
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
|
||||||
|
|
||||||
PODFILE CHECKSUM: f48a71990c19e75f00e16701179b4f160e74986f
|
PODFILE CHECKSUM: 0fd38f741cbaae39c8e7a70fa233f651d3b34d0f
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
14
Pods/IQKeyboardCore/IQKeyboardCore/Assets/PrivacyInfo.xcprivacy
generated
Normal file
14
Pods/IQKeyboardCore/IQKeyboardCore/Assets/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
41
Pods/IQKeyboardCore/IQKeyboardCore/Classes/Constants/IQKeyboardConstants.swift
generated
Normal file
41
Pods/IQKeyboardCore/IQKeyboardCore/Classes/Constants/IQKeyboardConstants.swift
generated
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardConstants.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardCore
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
`IQEnableModeDefault`
|
||||||
|
Pick default settings.
|
||||||
|
|
||||||
|
`IQEnableModeEnabled`
|
||||||
|
setting is enabled.
|
||||||
|
|
||||||
|
`IQEnableModeDisabled`
|
||||||
|
setting is disabled.
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@objc public enum IQEnableMode: Int {
|
||||||
|
case `default`
|
||||||
|
case enabled
|
||||||
|
case disabled
|
||||||
|
}
|
||||||
65
Pods/IQKeyboardCore/IQKeyboardCore/Classes/IQKeyboardExtended.swift
generated
Normal file
65
Pods/IQKeyboardCore/IQKeyboardCore/Classes/IQKeyboardExtended.swift
generated
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardExtended.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardCore
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/// Wrapper for IQKeyboardManager compatible types. This type provides an extension point for
|
||||||
|
/// convenience methods in IQKeyboardManager.
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
public struct IQKeyboardExtension<Base: AnyObject> {
|
||||||
|
public private(set) weak var base: Base?
|
||||||
|
fileprivate init(_ base: Base) {
|
||||||
|
self.base = base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable identifier_name
|
||||||
|
/// Represents an object type that is compatible with IQKeyboardManager. You can use `iq` property to get a
|
||||||
|
/// value in the namespace of IQKeyboardManager.
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
public protocol IQKeyboardExtended {
|
||||||
|
/// Type being extended.
|
||||||
|
associatedtype Base: AnyObject
|
||||||
|
|
||||||
|
/// Instance IQKeyboardManager extension point.
|
||||||
|
@MainActor
|
||||||
|
var iq: IQKeyboardExtension<Base> { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
public extension IQKeyboardExtended where Self: UIView {
|
||||||
|
|
||||||
|
/// Instance IQKeyboardManager extension point.
|
||||||
|
@MainActor
|
||||||
|
var iq: IQKeyboardExtension<Self> {
|
||||||
|
get { IQKeyboardExtension(self) }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
|
// swiftlint:enable identifier_name
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
extension UIView: IQKeyboardExtended {}
|
||||||
99
Pods/IQKeyboardCore/IQKeyboardCore/Classes/IQTextInputView.swift
generated
Normal file
99
Pods/IQKeyboardCore/IQKeyboardCore/Classes/IQTextInputView.swift
generated
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
//
|
||||||
|
// IQTextInputView.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardCore
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public protocol IQTextInputView where Self: UIView, Self: UITextInputTraits {
|
||||||
|
|
||||||
|
#if compiler(>=5.7) // Xcode 14
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
@objc var iqIsFindInteractionEnabled: Bool { get }
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
@objc var iqFindInteraction: UIFindInteraction? { get }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@objc var returnKeyType: UIReturnKeyType { get set }
|
||||||
|
@objc var keyboardAppearance: UIKeyboardAppearance { get set }
|
||||||
|
|
||||||
|
@objc var iqIsEnabled: Bool { get }
|
||||||
|
|
||||||
|
@objc var inputAccessoryView: UIView? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension UITextField: IQTextInputView {
|
||||||
|
|
||||||
|
#if compiler(>=5.7) // Xcode 14
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
public var iqIsFindInteractionEnabled: Bool { false }
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
public var iqFindInteraction: UIFindInteraction? { nil }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public var iqIsEnabled: Bool { isEnabled }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension UITextView: IQTextInputView {
|
||||||
|
|
||||||
|
#if compiler(>=5.7) // Xcode 14
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
public var iqIsFindInteractionEnabled: Bool { isFindInteractionEnabled }
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
public var iqFindInteraction: UIFindInteraction? { findInteraction }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public var iqIsEnabled: Bool { isEditable }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension UISearchBar: IQTextInputView {
|
||||||
|
|
||||||
|
#if compiler(>=5.7) // Xcode 14
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
public var iqIsFindInteractionEnabled: Bool { false }
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
public var iqFindInteraction: UIFindInteraction? { nil }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public var iqIsEnabled: Bool {
|
||||||
|
#if compiler(>=5.8) // Xcode 14.4
|
||||||
|
if #available(iOS 16.4, *) {
|
||||||
|
return isEnabled
|
||||||
|
} else {
|
||||||
|
return searchTextField.isEnabled
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return searchTextField.isEnabled
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
180
Pods/IQKeyboardCore/IQKeyboardCore/Classes/UIKitExtensions/UIView+Hierarchy.swift
generated
Normal file
180
Pods/IQKeyboardCore/IQKeyboardCore/Classes/UIKitExtensions/UIView+Hierarchy.swift
generated
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
//
|
||||||
|
// UIView+Hierarchy.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardCore
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/**
|
||||||
|
UIView hierarchy category.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQKeyboardExtension where Base: UIView {
|
||||||
|
|
||||||
|
// MARK: viewControllers
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the UIViewController object that manages the receiver.
|
||||||
|
*/
|
||||||
|
func viewContainingController() -> UIViewController? {
|
||||||
|
|
||||||
|
var nextResponder: UIResponder? = base
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
nextResponder = nextResponder?.next
|
||||||
|
|
||||||
|
if let viewController: UIViewController = nextResponder as? UIViewController {
|
||||||
|
return viewController
|
||||||
|
}
|
||||||
|
|
||||||
|
} while nextResponder != nil
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "This has been removed because it no longer needed")
|
||||||
|
func topMostController() -> UIViewController? { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQKeyboardExtension where Base: UIView {
|
||||||
|
|
||||||
|
// MARK: Superviews
|
||||||
|
/**
|
||||||
|
Returns the superView of provided class type.
|
||||||
|
|
||||||
|
@param classType class type of the object which is to be search in above hierarchy and return
|
||||||
|
|
||||||
|
@param belowView view object in upper hierarchy where method should stop searching and return nil
|
||||||
|
*/
|
||||||
|
func superviewOf<T: UIView>(type classType: T.Type, belowView: UIView? = nil) -> T? {
|
||||||
|
|
||||||
|
var superView: UIView? = base?.superview
|
||||||
|
|
||||||
|
while let aSuperview: UIView = superView {
|
||||||
|
|
||||||
|
if aSuperview.isKind(of: classType) {
|
||||||
|
|
||||||
|
// If it's UIScrollView, then validating for special cases
|
||||||
|
if aSuperview is UIScrollView {
|
||||||
|
|
||||||
|
let classNameString: String = "\(type(of: aSuperview.self))"
|
||||||
|
|
||||||
|
// If it's not UITableViewWrapperView class,
|
||||||
|
// this is internal class which is actually manage in UITableview.
|
||||||
|
// The speciality of this class is that it's superview is UITableView.
|
||||||
|
// If it's not UITableViewCellScrollView class,
|
||||||
|
// this is internal class which is actually manage in UITableviewCell.
|
||||||
|
// The speciality of this class is that it's superview is UITableViewCell.
|
||||||
|
// If it's not _UIQueuingScrollView class,
|
||||||
|
// actually we validate for _ prefix which usually used by Apple internal classes
|
||||||
|
if !(aSuperview.superview is UITableView),
|
||||||
|
!(aSuperview.superview is UITableViewCell),
|
||||||
|
!classNameString.hasPrefix("_") {
|
||||||
|
return superView as? T
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return superView as? T
|
||||||
|
}
|
||||||
|
} else if aSuperview == belowView {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
superView = aSuperview.superview
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Special TextFields
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns searchBar if receiver object is UISearchBarTextField, otherwise return nil.
|
||||||
|
*/
|
||||||
|
func textFieldSearchBar() -> UISearchBar? {
|
||||||
|
|
||||||
|
if base is UISearchTextField,
|
||||||
|
var textInputViewController = base?.iq.viewContainingController() {
|
||||||
|
if let navController: UINavigationController = textInputViewController as? UINavigationController,
|
||||||
|
let topController: UIViewController = navController.topViewController {
|
||||||
|
textInputViewController = topController
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not adjusting for searchTextField inside searchController.
|
||||||
|
if textInputViewController.navigationItem.searchController?.searchBar.searchTextField == base {
|
||||||
|
return textInputViewController.navigationItem.searchController?.searchBar
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var responder: UIResponder? = base?.next
|
||||||
|
|
||||||
|
while let bar: UIResponder = responder {
|
||||||
|
|
||||||
|
if let searchBar: UISearchBar = bar as? UISearchBar {
|
||||||
|
return searchBar
|
||||||
|
} else if bar is UIViewController {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
responder = bar.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns YES if the receiver object is UIAlertSheetTextField, otherwise return NO.
|
||||||
|
*/
|
||||||
|
func isAlertViewTextField() -> Bool {
|
||||||
|
|
||||||
|
var alertViewController: UIResponder? = viewContainingController()
|
||||||
|
|
||||||
|
var isAlertViewTextField: Bool = false
|
||||||
|
|
||||||
|
while let controller: UIResponder = alertViewController, !isAlertViewTextField {
|
||||||
|
|
||||||
|
if controller is UIAlertController {
|
||||||
|
isAlertViewTextField = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
alertViewController = controller.next
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAlertViewTextField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension UIView {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.viewContainingController()")
|
||||||
|
func viewContainingController() -> UIViewController? { nil }
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "This has been removed because it no longer needed")
|
||||||
|
func topMostController() -> UIViewController? { nil }
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.superviewOf(type:belowView:)")
|
||||||
|
func superviewOfClassType(_ classType: UIView.Type, belowView: UIView? = nil) -> UIView? { nil }
|
||||||
|
}
|
||||||
21
Pods/IQKeyboardCore/LICENSE
generated
Normal file
21
Pods/IQKeyboardCore/LICENSE
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Mohd Iftekhar Qurashi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
75
Pods/IQKeyboardCore/README.md
generated
Normal file
75
Pods/IQKeyboardCore/README.md
generated
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# IQKeyboardCore
|
||||||
|
|
||||||
|
[](https://travis-ci.org/hackiftekhar/IQKeyboardCore)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardCore)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardCore)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardCore)
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
IQKeyboardCore is available through [CocoaPods](https://cocoapods.org). To install
|
||||||
|
it, simply add the following line to your Podfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pod 'IQKeyboardCore'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
IQKeyboardCore is not intended to use independently. It's just a helper and extension for most of the IQKeyboard related libraries
|
||||||
|
|
||||||
|
This contains IQTextInputView protocol
|
||||||
|
```swift
|
||||||
|
@objc public protocol IQTextInputView where Self: UIView, Self: UITextInputTraits {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
UITextField, UITextView and UISearchBar are the known classes who adopted this protocol within the library
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@objc extension UITextField: IQTextInputView {...}
|
||||||
|
|
||||||
|
@objc extension UITextView: IQTextInputView {...}
|
||||||
|
|
||||||
|
@objc extension UISearchBar: IQTextInputView {...}
|
||||||
|
```
|
||||||
|
|
||||||
|
This library also contains IQEnableMode which is used by other libraries
|
||||||
|
```swift
|
||||||
|
@objc public enum IQEnableMode: Int {
|
||||||
|
case `default`
|
||||||
|
case enabled
|
||||||
|
case disabled
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There are other extension functions which are available on UIView
|
||||||
|
```swift
|
||||||
|
public extension IQKeyboardExtension where Base: UIView {
|
||||||
|
func viewContainingController() -> UIViewController?
|
||||||
|
func superviewOf<T: UIView>(type classType: T.Type, belowView: UIView? = nil) -> T?
|
||||||
|
func textFieldSearchBar() -> UISearchBar?
|
||||||
|
func isAlertViewTextField() -> Bool
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Above extension functions can be used like below
|
||||||
|
```swift
|
||||||
|
view.iq.viewContainingController()
|
||||||
|
view.iq.superviewOf(type: UIScrollView.self)
|
||||||
|
view.iq.textFieldSearchBar()
|
||||||
|
view.iq.isAlertViewTextField()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
hackiftekhar, ideviftekhar@gmail.com
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
IQKeyboardCore is available under the MIT license. See the LICENSE file for more info.
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardAppearanceConfiguration.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardAppearanceConfiguration: NSObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Override the keyboardAppearance for all textInputView. Default is NO.
|
||||||
|
*/
|
||||||
|
public var overrideAppearance: Bool = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
If overrideKeyboardAppearance is YES, then all the textInputView keyboardAppearance is set using this property.
|
||||||
|
*/
|
||||||
|
public var appearance: UIKeyboardAppearance = .default
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "IQKeyboardAppearanceConfiguration")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardConfiguration: NSObject {}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardAppearanceManager+Internal.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardAppearanceManager {
|
||||||
|
|
||||||
|
func removeTextInputViewObserver() {
|
||||||
|
textInputViewObserver.unsubscribe(identifier: "IQKeyboardAppearanceManager")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTextInputViewObserver() {
|
||||||
|
textInputViewObserver.subscribe(identifier: "IQKeyboardAppearanceManager",
|
||||||
|
changeHandler: { [weak self] event, textInputView in
|
||||||
|
guard let self = self else { return }
|
||||||
|
switch event {
|
||||||
|
case .beginEditing:
|
||||||
|
guard self.keyboardConfiguration.overrideAppearance,
|
||||||
|
textInputView.keyboardAppearance != self.keyboardConfiguration.appearance else { return }
|
||||||
|
|
||||||
|
textInputView.keyboardAppearance = self.keyboardConfiguration.appearance
|
||||||
|
textInputView.reloadInputViews()
|
||||||
|
case .endEditing:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Appearance/IQKeyboardAppearanceManager.swift
generated
Normal file
44
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Appearance/IQKeyboardAppearanceManager.swift
generated
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardAppearanceManager.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQTextInputViewNotification
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers internal final class IQKeyboardAppearanceManager: NSObject {
|
||||||
|
|
||||||
|
let textInputViewObserver: IQTextInputViewNotification = .init()
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configuration related to keyboard appearance
|
||||||
|
*/
|
||||||
|
var keyboardConfiguration: IQKeyboardAppearanceConfiguration = .init()
|
||||||
|
|
||||||
|
public override init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
// Registering one time only
|
||||||
|
addTextInputViewObserver()
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Appearance/IQKeyboardManager+Appearance.swift
generated
Normal file
55
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Appearance/IQKeyboardManager+Appearance.swift
generated
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+Appearance.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var appearanceManager: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
internal var appearanceManager: IQKeyboardAppearanceManager {
|
||||||
|
if let object = objc_getAssociatedObject(self, &AssociatedKeys.appearanceManager)
|
||||||
|
as? IQKeyboardAppearanceManager {
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
let object: IQKeyboardAppearanceManager = .init()
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.appearanceManager,
|
||||||
|
object, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configuration related to keyboard appearance
|
||||||
|
*/
|
||||||
|
var keyboardConfiguration: IQKeyboardAppearanceConfiguration {
|
||||||
|
get { appearanceManager.keyboardConfiguration }
|
||||||
|
set { appearanceManager.keyboardConfiguration = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+Appearance_Deprecated.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "keyboardConfiguration.overrideAppearance")
|
||||||
|
var overrideKeyboardAppearance: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "keyboardConfiguration.appearance")
|
||||||
|
var keyboardAppearance: UIKeyboardAppearance {
|
||||||
|
get { .default }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
@ -0,0 +1,273 @@
|
|||||||
|
//
|
||||||
|
// IQActiveConfiguration.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
import IQKeyboardNotification
|
||||||
|
import IQTextInputViewNotification
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal final class IQActiveConfiguration: NSObject {
|
||||||
|
|
||||||
|
private let keyboardObserver: IQKeyboardNotification = .init()
|
||||||
|
private let textInputViewObserver: IQTextInputViewNotification = .init()
|
||||||
|
|
||||||
|
private var changeObservers: [AnyHashable: ConfigurationCompletion] = [:]
|
||||||
|
var cancellable: Set<AnyCancellable> = []
|
||||||
|
|
||||||
|
enum Event: Int {
|
||||||
|
case hide
|
||||||
|
case show
|
||||||
|
case change
|
||||||
|
|
||||||
|
var name: String {
|
||||||
|
switch self {
|
||||||
|
case .hide:
|
||||||
|
return "hide"
|
||||||
|
case .show:
|
||||||
|
return "show"
|
||||||
|
case .change:
|
||||||
|
return "change"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var lastEvent: Event = .hide
|
||||||
|
|
||||||
|
var rootConfiguration: IQRootControllerConfiguration?
|
||||||
|
|
||||||
|
var isReady: Bool {
|
||||||
|
if textInputViewInfo != nil,
|
||||||
|
let rootConfiguration = rootConfiguration {
|
||||||
|
return rootConfiguration.isReady
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
addKeyboardObserver()
|
||||||
|
addTextInputViewObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendEvent() {
|
||||||
|
|
||||||
|
guard let rootConfiguration = rootConfiguration,
|
||||||
|
rootConfiguration.isReady else { return }
|
||||||
|
|
||||||
|
if keyboardInfo.isVisible {
|
||||||
|
if lastEvent == .hide {
|
||||||
|
self.notify(event: .show, keyboardInfo: keyboardInfo, textInputViewInfo: textInputViewInfo)
|
||||||
|
} else {
|
||||||
|
self.notify(event: .change, keyboardInfo: keyboardInfo, textInputViewInfo: textInputViewInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if lastEvent != .hide {
|
||||||
|
if rootConfiguration.beginOrientation == rootConfiguration.currentOrientation {
|
||||||
|
|
||||||
|
// If interactive pop gesture is active then it manipulate viewController.view's frame
|
||||||
|
// To overcome with this, we have to do this workaround.
|
||||||
|
if rootConfiguration.isInteractiveGestureActive,
|
||||||
|
let rootController: UIViewController = rootConfiguration.rootController {
|
||||||
|
|
||||||
|
self.cancellable.forEach { $0.cancel() }
|
||||||
|
self.cancellable.removeAll()
|
||||||
|
|
||||||
|
// Saving current keyboard info and textInputView
|
||||||
|
let keyboardInfo = keyboardObserver.keyboardInfo
|
||||||
|
let textInputViewInfo = textInputViewObserver.textInputViewInfo
|
||||||
|
|
||||||
|
// Start observing frame changes.
|
||||||
|
// If pop successful, then we'll not get callbacks here again
|
||||||
|
// If user cancels the pop, then we'll get frame as .zero at some time
|
||||||
|
// Also the interactiveGesture becomes inactive (genuinely it's state is .possible)
|
||||||
|
// At this moment.
|
||||||
|
rootController.view.publisher(for: \.frame)
|
||||||
|
.removeDuplicates()
|
||||||
|
.sink(receiveValue: { [weak self] frame in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard frame.origin == .zero,
|
||||||
|
!rootConfiguration.isInteractiveGestureActive else { return }
|
||||||
|
|
||||||
|
self.cancellable.forEach { $0.cancel() }
|
||||||
|
self.cancellable.removeAll()
|
||||||
|
|
||||||
|
// Restore keyboard info and textInputViewInfo
|
||||||
|
self.notify(event: .change, keyboardInfo: keyboardInfo, textInputViewInfo: textInputViewInfo)
|
||||||
|
}).store(in: &cancellable)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
self.notify(event: .hide, keyboardInfo: keyboardInfo, textInputViewInfo: textInputViewInfo)
|
||||||
|
self.rootConfiguration = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if rootConfiguration.hasChanged {
|
||||||
|
animate(alongsideTransition: {
|
||||||
|
rootConfiguration.restore()
|
||||||
|
}, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateRootController(textInputView: IQTextInputView?) {
|
||||||
|
|
||||||
|
guard let textInputView: UIView = textInputView,
|
||||||
|
let controller: UIViewController = textInputView.iq.parentContainerViewController() else {
|
||||||
|
if let rootConfiguration = rootConfiguration,
|
||||||
|
rootConfiguration.hasChanged {
|
||||||
|
animate(alongsideTransition: {
|
||||||
|
rootConfiguration.restore()
|
||||||
|
}, completion: nil)
|
||||||
|
}
|
||||||
|
rootConfiguration = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let newConfiguration = IQRootControllerConfiguration(rootController: controller)
|
||||||
|
|
||||||
|
guard newConfiguration.rootController?.view.window != rootConfiguration?.rootController?.view.window ||
|
||||||
|
newConfiguration.beginOrientation != rootConfiguration?.beginOrientation else { return }
|
||||||
|
|
||||||
|
if rootConfiguration?.rootController != newConfiguration.rootController {
|
||||||
|
|
||||||
|
// If there was an old configuration but things are changed
|
||||||
|
if let rootConfiguration = rootConfiguration,
|
||||||
|
rootConfiguration.hasChanged {
|
||||||
|
animate(alongsideTransition: {
|
||||||
|
rootConfiguration.restore()
|
||||||
|
}, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootConfiguration = newConfiguration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
extension IQActiveConfiguration {
|
||||||
|
|
||||||
|
var keyboardInfo: IQKeyboardInfo {
|
||||||
|
return keyboardObserver.keyboardInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addKeyboardObserver() {
|
||||||
|
keyboardObserver.subscribe(identifier: "IQActiveConfiguration", changeHandler: { [weak self] _, endFrame in
|
||||||
|
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
guard self.keyboardObserver.oldKeyboardInfo.endFrame.height != endFrame.height else { return }
|
||||||
|
|
||||||
|
if let info = self.textInputViewInfo, self.keyboardInfo.isVisible {
|
||||||
|
if let rootConfiguration = self.rootConfiguration {
|
||||||
|
let beginIsPortrait: Bool = rootConfiguration.beginOrientation.isPortrait
|
||||||
|
let currentIsPortrait: Bool = rootConfiguration.currentOrientation.isPortrait
|
||||||
|
if beginIsPortrait != currentIsPortrait {
|
||||||
|
self.updateRootController(textInputView: info.textInputView)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.updateRootController(textInputView: info.textInputView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sendEvent()
|
||||||
|
|
||||||
|
// If interactive pop gesture is active then we don't want to remove this textField
|
||||||
|
if endFrame.height == 0,
|
||||||
|
!(self.rootConfiguration?.isInteractiveGestureActive ?? false) {
|
||||||
|
self.updateRootController(textInputView: nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public func animate(alongsideTransition transition: @escaping () -> Void, completion: (() -> Void)? = nil) {
|
||||||
|
keyboardObserver.animate(alongsideTransition: transition, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
extension IQActiveConfiguration {
|
||||||
|
|
||||||
|
var textInputView: (any IQTextInputView)? {
|
||||||
|
guard let textInputView: UIView = textInputViewObserver.textInputView,
|
||||||
|
textInputView.iq.isAlertViewTextField() == false else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return textInputViewObserver.textInputView
|
||||||
|
}
|
||||||
|
|
||||||
|
var textInputViewInfo: IQTextInputViewInfo? {
|
||||||
|
guard let textInputView: UIView = textInputView,
|
||||||
|
textInputView.iq.isAlertViewTextField() == false else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return textInputViewObserver.textInputViewInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addTextInputViewObserver() {
|
||||||
|
textInputViewObserver.subscribe(identifier: "IQActiveConfiguration",
|
||||||
|
changeHandler: { [weak self] event, textInputView in
|
||||||
|
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
guard (textInputView as UIView).iq.isAlertViewTextField() == false else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if event == .beginEditing {
|
||||||
|
self.updateRootController(textInputView: textInputView)
|
||||||
|
self.sendEvent()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
extension IQActiveConfiguration {
|
||||||
|
|
||||||
|
typealias ConfigurationCompletion = (_ event: Event,
|
||||||
|
_ keyboardInfo: IQKeyboardInfo,
|
||||||
|
_ textInputViewInfo: IQTextInputViewInfo?) -> Void
|
||||||
|
|
||||||
|
func subscribe(identifier: AnyHashable, changeHandler: @escaping ConfigurationCompletion) {
|
||||||
|
changeObservers[identifier] = changeHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsubscribe(identifier: AnyHashable) {
|
||||||
|
changeObservers[identifier] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notify(event: Event, keyboardInfo: IQKeyboardInfo, textInputViewInfo: IQTextInputViewInfo?) {
|
||||||
|
lastEvent = event
|
||||||
|
|
||||||
|
for block in changeObservers.values {
|
||||||
|
block(event, keyboardInfo, textInputViewInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
//
|
||||||
|
// IQRootControllerConfiguration.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal struct IQRootControllerConfiguration {
|
||||||
|
|
||||||
|
weak var rootController: UIViewController?
|
||||||
|
let beginOrigin: CGPoint
|
||||||
|
let beginSafeAreaInsets: UIEdgeInsets
|
||||||
|
let beginOrientation: UIInterfaceOrientation
|
||||||
|
|
||||||
|
init(rootController: UIViewController) {
|
||||||
|
self.rootController = rootController
|
||||||
|
beginOrigin = rootController.view.frame.origin
|
||||||
|
beginSafeAreaInsets = rootController.view.safeAreaInsets
|
||||||
|
|
||||||
|
let interfaceOrientation: UIInterfaceOrientation
|
||||||
|
if let scene = rootController.view.window?.windowScene {
|
||||||
|
interfaceOrientation = scene.interfaceOrientation
|
||||||
|
} else {
|
||||||
|
interfaceOrientation = .unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
beginOrientation = interfaceOrientation
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentOrientation: UIInterfaceOrientation {
|
||||||
|
let interfaceOrientation: UIInterfaceOrientation
|
||||||
|
if let scene = rootController?.view.window?.windowScene {
|
||||||
|
interfaceOrientation = scene.interfaceOrientation
|
||||||
|
} else {
|
||||||
|
interfaceOrientation = .unknown
|
||||||
|
}
|
||||||
|
return interfaceOrientation
|
||||||
|
}
|
||||||
|
|
||||||
|
var isReady: Bool {
|
||||||
|
return rootController?.view.window != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasChanged: Bool {
|
||||||
|
let origin: CGPoint = rootController?.view.frame.origin ?? .zero
|
||||||
|
return !origin.equalTo(beginOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isInteractiveGestureActive: Bool {
|
||||||
|
guard let rootController: UIViewController = rootController,
|
||||||
|
let navigationController: UINavigationController = rootController.navigationController,
|
||||||
|
let interactiveGestureRecognizer = navigationController.interactivePopGestureRecognizer else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch interactiveGestureRecognizer.state {
|
||||||
|
case .began, .changed:
|
||||||
|
return true
|
||||||
|
case .possible, .ended, .cancelled, .failed, .recognized:
|
||||||
|
// swiftlint:disable:next no_fallthrough_only
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@discardableResult
|
||||||
|
func restore() -> Bool {
|
||||||
|
guard let rootController: UIViewController = rootController,
|
||||||
|
!rootController.view.frame.origin.equalTo(beginOrigin) else { return false }
|
||||||
|
// Setting it's new frame
|
||||||
|
var rect: CGRect = rootController.view.frame
|
||||||
|
rect.origin = beginOrigin
|
||||||
|
rootController.view.frame = rect
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// IQScrollViewConfiguration.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal struct IQScrollViewConfiguration {
|
||||||
|
let scrollView: UIScrollView
|
||||||
|
let startingContentOffset: CGPoint
|
||||||
|
let startingScrollIndicatorInsets: UIEdgeInsets
|
||||||
|
let startingContentInset: UIEdgeInsets
|
||||||
|
|
||||||
|
private let canRestoreContentOffset: Bool
|
||||||
|
|
||||||
|
init(scrollView: UIScrollView, canRestoreContentOffset: Bool) {
|
||||||
|
self.scrollView = scrollView
|
||||||
|
self.canRestoreContentOffset = canRestoreContentOffset
|
||||||
|
|
||||||
|
startingContentOffset = scrollView.contentOffset
|
||||||
|
startingContentInset = scrollView.contentInset
|
||||||
|
startingScrollIndicatorInsets = scrollView.verticalScrollIndicatorInsets
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasChanged: Bool {
|
||||||
|
if scrollView.contentInset != self.startingContentInset {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if canRestoreContentOffset,
|
||||||
|
scrollView.iq.restoreContentOffset,
|
||||||
|
!scrollView.contentOffset.equalTo(startingContentOffset) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func restore(for textInputView: (some IQTextInputView)?) -> Bool {
|
||||||
|
var success: Bool = false
|
||||||
|
|
||||||
|
if scrollView.contentInset != self.startingContentInset {
|
||||||
|
scrollView.contentInset = self.startingContentInset
|
||||||
|
scrollView.layoutIfNeeded() // (Bug ID: #1996)
|
||||||
|
success = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if scrollView.verticalScrollIndicatorInsets != self.startingScrollIndicatorInsets {
|
||||||
|
scrollView.verticalScrollIndicatorInsets = self.startingScrollIndicatorInsets
|
||||||
|
}
|
||||||
|
|
||||||
|
if canRestoreContentOffset,
|
||||||
|
scrollView.iq.restoreContentOffset,
|
||||||
|
!scrollView.contentOffset.equalTo(startingContentOffset) {
|
||||||
|
|
||||||
|
// (Bug ID: #1365, #1508, #1541)
|
||||||
|
let stackView: UIStackView?
|
||||||
|
if let textInputView: UIView = textInputView {
|
||||||
|
stackView = textInputView.iq.superviewOf(type: UIStackView.self,
|
||||||
|
belowView: scrollView)
|
||||||
|
} else {
|
||||||
|
stackView = nil
|
||||||
|
}
|
||||||
|
// (Bug ID: #1901, #1996)
|
||||||
|
let animatedContentOffset: Bool = stackView != nil ||
|
||||||
|
scrollView is UICollectionView ||
|
||||||
|
scrollView is UITableView
|
||||||
|
|
||||||
|
if animatedContentOffset {
|
||||||
|
scrollView.setContentOffset(startingContentOffset, animated: UIView.areAnimationsEnabled)
|
||||||
|
} else {
|
||||||
|
scrollView.contentOffset = startingContentOffset
|
||||||
|
}
|
||||||
|
success = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+Debug.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// MARK: Debugging & Developer options
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var isDebuggingEnabled: Int = 0
|
||||||
|
static var logIndentation: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDebuggingEnabled: Bool {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &AssociatedKeys.isDebuggingEnabled) as? Bool ?? false
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.isDebuggingEnabled,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var logIndentation: Int {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &AssociatedKeys.logIndentation) as? Int ?? 0
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.logIndentation,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func showLog(_ logString: String, indentation: Int = 0) {
|
||||||
|
|
||||||
|
guard isDebuggingEnabled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if indentation < 0 {
|
||||||
|
logIndentation = max(0, logIndentation + indentation)
|
||||||
|
}
|
||||||
|
|
||||||
|
var preLog: String = "IQKeyboardManager"
|
||||||
|
for _ in 0 ... logIndentation {
|
||||||
|
preLog += "|\t"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(preLog + logString)
|
||||||
|
|
||||||
|
if indentation > 0 {
|
||||||
|
logIndentation += indentation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,169 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+Deprecated.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
// swiftlint:disable line_length
|
||||||
|
// swiftlint:disable type_name
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "keyboardDistance")
|
||||||
|
var keyboardDistanceFromTextField: CGFloat {
|
||||||
|
get { fatalError() }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "This feature has been removed due to few compatibility problems")
|
||||||
|
func registerTextFieldViewClass(_ aClass: UIView.Type,
|
||||||
|
didBeginEditingNotificationName: String,
|
||||||
|
didEndEditingNotificationName: String) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "This feature has been removed due to few compatibility problems")
|
||||||
|
func unregisterTextFieldViewClass(_ aClass: UIView.Type,
|
||||||
|
didBeginEditingNotificationName: String,
|
||||||
|
didEndEditingNotificationName: String) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
typealias SizeBlock = (_ size: CGSize) -> Void
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "Please use `IQKeyboardNotification` independently from https://github.com/hackiftekhar/IQKeyboardNotification")
|
||||||
|
func registerKeyboardSizeChange(identifier: AnyHashable, sizeHandler: @escaping SizeBlock) {}
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "Please use `IQKeyboardNotification` independently from https://github.com/hackiftekhar/IQKeyboardNotification")
|
||||||
|
func unregisterKeyboardSizeChange(identifier: AnyHashable) {}
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "Please use `IQKeyboardNotification` independently from https://github.com/hackiftekhar/IQKeyboardNotification")
|
||||||
|
var keyboardShowing: Bool { false }
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "Please use `IQKeyboardNotification` independently from https://github.com/hackiftekhar/IQKeyboardNotification")
|
||||||
|
var keyboardFrame: CGRect { .zero }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "IQKeyboardReturnManager", message: "Please use `IQKeyboardReturnManager` independently from https://github.com/hackiftekhar/IQKeyboardReturnManager")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardReturnKeyHandler: NSObject {}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "IQKeyboardNotification", message: "Please use `IQKeyboardNotification` independently from https://github.com/hackiftekhar/IQKeyboardNotification")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardListener: NSObject {}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "IQTextInputViewNotification", message: "Please use `IQTextInputViewNotification` independently from https://github.com/hackiftekhar/IQTextInputViewNotification")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQTextFieldViewListener: NSObject {}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "IQDeepResponderContainerView", message: "Please use `IQDeepResponderContainerView` class which is now part of `IQKeyboardToolbarManager` from https://github.com/hackiftekhar/IQKeyboardToolbarManager.")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers open class IQPreviousNextView: UIView {}
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "Please use `IQKeyboardToolbar` independently https://github.com/hackiftekhar/IQKeyboardToolbar or through `IQKeyboardToolbarManager` from https://github.com/hackiftekhar/IQKeyboardToolbarManager")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQToolbarPlaceholderConfigurationDeprecated: NSObject {
|
||||||
|
public var showPlaceholder: Bool = true
|
||||||
|
public var font: UIFont?
|
||||||
|
public var color: UIColor?
|
||||||
|
public var buttonColor: UIColor?
|
||||||
|
public override var accessibilityLabel: String? { didSet { } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@available(*, unavailable, message: "Please use `IQKeyboardToolbar` independently https://github.com/hackiftekhar/IQKeyboardToolbar or through `IQKeyboardToolbarManager` from https://github.com/hackiftekhar/IQKeyboardToolbarManager")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQBarButtonItemConfigurationDeprecated: NSObject {
|
||||||
|
|
||||||
|
public init(systemItem: UIBarButtonItem.SystemItem, action: Selector? = nil) {
|
||||||
|
self.systemItem = systemItem
|
||||||
|
self.image = nil
|
||||||
|
self.title = nil
|
||||||
|
self.action = action
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(image: UIImage, action: Selector? = nil) {
|
||||||
|
self.systemItem = nil
|
||||||
|
self.image = image
|
||||||
|
self.title = nil
|
||||||
|
self.action = action
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(title: String, action: Selector? = nil) {
|
||||||
|
self.systemItem = nil
|
||||||
|
self.image = nil
|
||||||
|
self.title = title
|
||||||
|
self.action = action
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
public let systemItem: UIBarButtonItem.SystemItem?
|
||||||
|
public let image: UIImage?
|
||||||
|
public let title: String?
|
||||||
|
public var action: Selector?
|
||||||
|
public override var accessibilityLabel: String? { didSet { } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "Please use `IQKeyboardToolbarManager` independently from https://github.com/hackiftekhar/IQKeyboardToolbarManager")
|
||||||
|
@objc public enum IQAutoToolbarManageBehaviorDeprecated: Int {
|
||||||
|
case bySubviews
|
||||||
|
case byTag
|
||||||
|
case byPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "Please use `IQKeyboardToolbarManager` independently from https://github.com/hackiftekhar/IQKeyboardToolbarManager")
|
||||||
|
@objc public enum IQPreviousNextDisplayModeDeprecated: Int {
|
||||||
|
case `default`
|
||||||
|
case alwaysHide
|
||||||
|
case alwaysShow
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "Please use `IQKeyboardToolbarManager` independently from https://github.com/hackiftekhar/IQKeyboardToolbarManager")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQToolbarConfiguration: NSObject {
|
||||||
|
|
||||||
|
public var useTextInputViewTintColor: Bool = false
|
||||||
|
public var tintColor: UIColor?
|
||||||
|
public var barTintColor: UIColor?
|
||||||
|
public var previousNextDisplayMode: IQPreviousNextDisplayModeDeprecated = .default
|
||||||
|
public var manageBehavior: IQAutoToolbarManageBehaviorDeprecated = .bySubviews
|
||||||
|
public var previousBarButtonConfiguration: IQBarButtonItemConfigurationDeprecated?
|
||||||
|
public var nextBarButtonConfiguration: IQBarButtonItemConfigurationDeprecated?
|
||||||
|
public var doneBarButtonConfiguration: IQBarButtonItemConfigurationDeprecated?
|
||||||
|
public let placeholderConfiguration: IQToolbarPlaceholderConfigurationDeprecated = .init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:enable line_length
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
|
// swiftlint:enable type_name
|
||||||
@ -0,0 +1,126 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+ActiveConfiguration.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
// MARK: UIKeyboard Notifications
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardManager {
|
||||||
|
|
||||||
|
func addActiveConfigurationObserver() {
|
||||||
|
activeConfiguration.subscribe(identifier: "IQKeyboardManager", changeHandler: { [weak self] event, _, _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
switch event {
|
||||||
|
case .show:
|
||||||
|
self.handleKeyboardTextInputViewVisible()
|
||||||
|
case .change:
|
||||||
|
self.handleKeyboardTextInputViewChanged()
|
||||||
|
case .hide:
|
||||||
|
self.handleKeyboardTextInputViewHide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleKeyboardTextInputViewVisible() {
|
||||||
|
|
||||||
|
setupTextInputView()
|
||||||
|
|
||||||
|
if privateIsEnabled() {
|
||||||
|
adjustPosition()
|
||||||
|
} else {
|
||||||
|
restorePosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleKeyboardTextInputViewChanged() {
|
||||||
|
|
||||||
|
setupTextInputView()
|
||||||
|
|
||||||
|
if privateIsEnabled() {
|
||||||
|
adjustPosition()
|
||||||
|
} else {
|
||||||
|
restorePosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleKeyboardTextInputViewHide() {
|
||||||
|
|
||||||
|
self.restorePosition()
|
||||||
|
self.banishTextInputViewSetup()
|
||||||
|
|
||||||
|
self.lastScrollViewConfiguration = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardManager {
|
||||||
|
|
||||||
|
func setupTextInputView() {
|
||||||
|
|
||||||
|
guard let textInputView = activeConfiguration.textInputView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let startingConfiguration = startingTextViewConfiguration,
|
||||||
|
startingConfiguration.hasChanged {
|
||||||
|
|
||||||
|
if startingConfiguration.scrollView.contentInset != startingConfiguration.startingContentInset {
|
||||||
|
showLog("""
|
||||||
|
Restoring textView.contentInset to: \(startingConfiguration.startingContentInset)
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
startingConfiguration.restore(for: textInputView)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
startingTextViewConfiguration = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func banishTextInputViewSetup() {
|
||||||
|
|
||||||
|
guard let textInputView = activeConfiguration.textInputView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let startingConfiguration = startingTextViewConfiguration,
|
||||||
|
startingConfiguration.hasChanged {
|
||||||
|
|
||||||
|
if startingConfiguration.scrollView.contentInset != startingConfiguration.startingContentInset {
|
||||||
|
showLog("""
|
||||||
|
Restoring textView.contentInset to: \(startingConfiguration.startingContentInset)
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
startingConfiguration.restore(for: textInputView)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
startingTextViewConfiguration = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+Internal.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardManager {
|
||||||
|
|
||||||
|
func privateIsEnabled() -> Bool {
|
||||||
|
|
||||||
|
guard let textInputView: any IQTextInputView = activeConfiguration.textInputView else {
|
||||||
|
return isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
switch textInputView.internalEnableMode {
|
||||||
|
case .default:
|
||||||
|
guard var controller = (textInputView as UIView).iq.viewContainingController() else {
|
||||||
|
return isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if textInputView is UISearchTextField {
|
||||||
|
if let navController: UINavigationController = controller as? UINavigationController,
|
||||||
|
let topController: UIViewController = navController.topViewController {
|
||||||
|
controller = topController
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not adjusting for searchTextField inside searchController.
|
||||||
|
if controller.navigationItem.searchController?.searchBar.searchTextField == textInputView {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If viewController is in enabledDistanceHandlingClasses, then assuming it's enabled.
|
||||||
|
let isWithEnabledClass: Bool = enabledDistanceHandlingClasses.contains(where: { controller.isKind(of: $0) })
|
||||||
|
var isEnabled: Bool = isEnabled || isWithEnabledClass
|
||||||
|
|
||||||
|
if isEnabled {
|
||||||
|
// If viewController is in disabledDistanceHandlingClasses,
|
||||||
|
// then assuming it's disabled.
|
||||||
|
if disabledDistanceHandlingClasses.contains(where: { controller.isKind(of: $0) }) {
|
||||||
|
isEnabled = false
|
||||||
|
} else {
|
||||||
|
// Special Controllers
|
||||||
|
let classNameString: String = "\(type(of: controller.self))"
|
||||||
|
|
||||||
|
// _UIAlertControllerTextFieldViewController
|
||||||
|
if classNameString.contains("UIAlertController"),
|
||||||
|
classNameString.hasSuffix("TextFieldViewController") {
|
||||||
|
isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isEnabled
|
||||||
|
case .enabled:
|
||||||
|
return true
|
||||||
|
case .disabled:
|
||||||
|
return false
|
||||||
|
@unknown default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
fileprivate extension IQTextInputView {
|
||||||
|
var internalEnableMode: IQEnableMode {
|
||||||
|
return iq.enableMode
|
||||||
|
}
|
||||||
|
}
|
||||||
798
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager+Position.swift
generated
Normal file
798
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager+Position.swift
generated
Normal file
@ -0,0 +1,798 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+Position.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
// swiftlint:disable file_length
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
private typealias IQLayoutGuide = (top: CGFloat, bottom: CGFloat)
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var movedDistance: Int = 0
|
||||||
|
static var movedDistanceChanged: Int = 0
|
||||||
|
static var lastScrollViewConfiguration: Int = 0
|
||||||
|
static var startingTextViewConfiguration: Int = 0
|
||||||
|
static var activeConfiguration: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
moved distance to the top used to maintain distance between keyboard and textInputView.
|
||||||
|
Most of the time this will be a positive value.
|
||||||
|
*/
|
||||||
|
private(set) var movedDistance: CGFloat {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &AssociatedKeys.movedDistance) as? CGFloat ?? 0.0
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.movedDistance, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
movedDistanceChanged?(movedDistance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Will be called then movedDistance will be changed
|
||||||
|
*/
|
||||||
|
var movedDistanceChanged: ((CGFloat) -> Void)? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &AssociatedKeys.movedDistanceChanged) as? ((CGFloat) -> Void)
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.movedDistanceChanged,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
movedDistanceChanged?(movedDistance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Variable to save lastScrollView that was scrolled. */
|
||||||
|
@nonobjc
|
||||||
|
internal var lastScrollViewConfiguration: IQScrollViewConfiguration? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self,
|
||||||
|
&AssociatedKeys.lastScrollViewConfiguration) as? IQScrollViewConfiguration
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.lastScrollViewConfiguration,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** used to adjust contentInset of UITextView. */
|
||||||
|
@nonobjc
|
||||||
|
internal var startingTextViewConfiguration: IQScrollViewConfiguration? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self,
|
||||||
|
&AssociatedKeys.startingTextViewConfiguration) as? IQScrollViewConfiguration
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.startingTextViewConfiguration,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func applicationDidBecomeActive(_ notification: Notification) {
|
||||||
|
|
||||||
|
guard privateIsEnabled(),
|
||||||
|
activeConfiguration.keyboardInfo.isVisible,
|
||||||
|
activeConfiguration.isReady else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adjustPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjusting RootViewController's frame according to interface orientation. */
|
||||||
|
// swiftlint:disable function_body_length
|
||||||
|
internal func adjustPosition() {
|
||||||
|
|
||||||
|
guard UIApplication.shared.applicationState == .active,
|
||||||
|
let textInputView: any IQTextInputView = activeConfiguration.textInputView,
|
||||||
|
let superview: UIView = textInputView.superview,
|
||||||
|
let rootConfiguration = activeConfiguration.rootConfiguration,
|
||||||
|
let rootController: UIViewController = rootConfiguration.rootController,
|
||||||
|
let window: UIWindow = rootController.view.window else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
|
||||||
|
|
||||||
|
defer {
|
||||||
|
showLog("<<<<< \(#function) ended <<<<<", indentation: -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let textInputViewRectInWindow: CGRect = superview.convert(textInputView.frame, to: window)
|
||||||
|
let textInputViewRectInRootSuperview: CGRect = superview.convert(textInputView.frame,
|
||||||
|
to: rootController.view.superview)
|
||||||
|
|
||||||
|
// Getting RootViewOrigin.
|
||||||
|
let rootViewOrigin: CGPoint = rootController.view.frame.origin
|
||||||
|
|
||||||
|
let keyboardDistance: CGFloat = getSpecialTextInputViewDistance(textInputView: textInputView)
|
||||||
|
|
||||||
|
let kbSize: CGSize = Self.getKeyboardSize(keyboardDistance: keyboardDistance,
|
||||||
|
keyboardFrame: activeConfiguration.keyboardInfo.endFrame,
|
||||||
|
safeAreaInsets: rootConfiguration.beginSafeAreaInsets,
|
||||||
|
windowFrame: window.frame)
|
||||||
|
let originalKbSize: CGSize = activeConfiguration.keyboardInfo.endFrame.size
|
||||||
|
|
||||||
|
let isScrollableTextInputView: Bool
|
||||||
|
|
||||||
|
if let textInputView: UIScrollView = textInputView as? UITextView {
|
||||||
|
isScrollableTextInputView = textInputView.isScrollEnabled
|
||||||
|
} else {
|
||||||
|
isScrollableTextInputView = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let layoutGuide: IQLayoutGuide = Self.getLayoutGuides(rootController: rootController, window: window,
|
||||||
|
isScrollableTextInputView: isScrollableTextInputView)
|
||||||
|
|
||||||
|
var superScrollView: UIScrollView?
|
||||||
|
var superView: UIScrollView? = (textInputView as UIView).iq.superviewOf(type: UIScrollView.self)
|
||||||
|
|
||||||
|
// Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
|
||||||
|
while let view: UIScrollView = superView {
|
||||||
|
|
||||||
|
if view.isScrollEnabled, !view.iq.ignoreScrollingAdjustment {
|
||||||
|
superScrollView = view
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
// Getting it's superScrollView. // (Enhancement ID: #21, #24)
|
||||||
|
superView = view.iq.superviewOf(type: UIScrollView.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move positive = textInputView is hidden.
|
||||||
|
// Move negative = textInputView is showing.
|
||||||
|
// Calculating move position.
|
||||||
|
var moveUp: CGFloat = Self.getMoveUpDistance(keyboardSize: kbSize,
|
||||||
|
layoutGuide: layoutGuide,
|
||||||
|
textInputViewRectInRootSuperview: textInputViewRectInRootSuperview,
|
||||||
|
textInputViewRectInWindow: textInputViewRectInWindow,
|
||||||
|
windowFrame: window.frame)
|
||||||
|
|
||||||
|
showLog("Need to move: \(moveUp), will be moving \(moveUp < 0 ? "down" : "up")")
|
||||||
|
|
||||||
|
|
||||||
|
setupActiveScrollViewConfiguration(superScrollView: superScrollView, textInputView: textInputView)
|
||||||
|
|
||||||
|
// Special case for ScrollView.
|
||||||
|
// If we found lastScrollView then setting it's contentOffset to show textInputView.
|
||||||
|
if let lastScrollViewConfiguration: IQScrollViewConfiguration = lastScrollViewConfiguration {
|
||||||
|
adjustScrollViewContentOffsets(moveUp: &moveUp, textInputView: textInputView,
|
||||||
|
lastScrollViewConfiguration: lastScrollViewConfiguration,
|
||||||
|
rootSuperview: rootController.view.superview, layoutGuide: layoutGuide,
|
||||||
|
textInputViewRectInRootSuperview: textInputViewRectInRootSuperview,
|
||||||
|
isScrollableTextInputView: isScrollableTextInputView, window: window,
|
||||||
|
kbSize: kbSize, keyboardDistance: keyboardDistance,
|
||||||
|
rootBeginSafeAreaInsets: rootConfiguration.beginSafeAreaInsets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for UITextView
|
||||||
|
// (Readjusting textInputView.contentInset when textInputView hight is too big to fit on screen)
|
||||||
|
// _lastScrollView If not having inside any scrollView, now contentInset manages the full screen textInputView.
|
||||||
|
// If is a UITextView type
|
||||||
|
if isScrollableTextInputView, let textInputView = textInputView as? UITextView {
|
||||||
|
|
||||||
|
adjustTextInputViewContentInset(window: window, originalKbSize: originalKbSize,
|
||||||
|
rootSuperview: rootController.view.superview,
|
||||||
|
layoutGuide: layoutGuide,
|
||||||
|
textInputView: textInputView)
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustRootController(moveUp: moveUp, rootViewOrigin: rootViewOrigin, originalKbSize: originalKbSize,
|
||||||
|
rootController: rootController, rootBeginOrigin: rootConfiguration.beginOrigin)
|
||||||
|
}
|
||||||
|
// swiftlint:enable function_body_length
|
||||||
|
|
||||||
|
internal func restorePosition() {
|
||||||
|
|
||||||
|
// Setting rootViewController frame to it's original position. // (Bug ID: #18)
|
||||||
|
guard let configuration: IQRootControllerConfiguration = activeConfiguration.rootConfiguration else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
|
||||||
|
|
||||||
|
defer {
|
||||||
|
showLog("<<<<< \(#function) ended <<<<<", indentation: -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
if configuration.hasChanged {
|
||||||
|
let classNameString: String = "\(type(of: configuration.rootController.self))"
|
||||||
|
self.showLog("Restoring \(classNameString) origin to: \(configuration.beginOrigin)")
|
||||||
|
}
|
||||||
|
configuration.restore()
|
||||||
|
|
||||||
|
// Animating content if needed (Bug ID: #204)
|
||||||
|
if self.layoutIfNeededOnUpdate {
|
||||||
|
// Animating content (Bug ID: #160)
|
||||||
|
configuration.rootController?.view.setNeedsLayout()
|
||||||
|
configuration.rootController?.view.layoutIfNeeded()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Restoring the contentOffset of the lastScrollView
|
||||||
|
if let lastConfiguration: IQScrollViewConfiguration = lastScrollViewConfiguration {
|
||||||
|
let textInputView: (any IQTextInputView)? = activeConfiguration.textInputView
|
||||||
|
|
||||||
|
restoreScrollViewConfigurationIfChanged(configuration: lastConfiguration, textInputView: textInputView)
|
||||||
|
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
// This is temporary solution. Have to implement the save and restore scrollView state
|
||||||
|
self.restoreScrollViewContentOffset(superScrollView: lastConfiguration.scrollView,
|
||||||
|
textInputView: textInputView)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
self.movedDistance = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable function_parameter_count
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private extension IQKeyboardManager {
|
||||||
|
|
||||||
|
func getSpecialTextInputViewDistance(textInputView: some IQTextInputView) -> CGFloat {
|
||||||
|
// Maintain keyboardDistance
|
||||||
|
let specialKeyboardDistance: CGFloat
|
||||||
|
|
||||||
|
if let searchBar: UISearchBar = textInputView.iq.textFieldSearchBar() {
|
||||||
|
specialKeyboardDistance = searchBar.iq.distanceFromKeyboard
|
||||||
|
} else {
|
||||||
|
specialKeyboardDistance = textInputView.iq.distanceFromKeyboard
|
||||||
|
}
|
||||||
|
|
||||||
|
if specialKeyboardDistance == UIView.defaultKeyboardDistance {
|
||||||
|
return keyboardDistance
|
||||||
|
} else {
|
||||||
|
return specialKeyboardDistance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func getKeyboardSize(keyboardDistance: CGFloat, keyboardFrame: CGRect,
|
||||||
|
safeAreaInsets: UIEdgeInsets, windowFrame: CGRect) -> CGSize {
|
||||||
|
let kbSize: CGSize
|
||||||
|
var kbFrame: CGRect = keyboardFrame
|
||||||
|
|
||||||
|
kbFrame.origin.y -= keyboardDistance
|
||||||
|
kbFrame.size.height += keyboardDistance
|
||||||
|
|
||||||
|
kbFrame.origin.y -= safeAreaInsets.bottom
|
||||||
|
kbFrame.size.height += safeAreaInsets.bottom
|
||||||
|
|
||||||
|
// (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
|
||||||
|
// Calculating actual keyboard covered size respect to window,
|
||||||
|
// keyboard frame may be different when hardware keyboard is attached
|
||||||
|
let intersectRect: CGRect = kbFrame.intersection(windowFrame)
|
||||||
|
|
||||||
|
if intersectRect.isNull {
|
||||||
|
kbSize = CGSize(width: kbFrame.size.width, height: 0)
|
||||||
|
} else {
|
||||||
|
kbSize = intersectRect.size
|
||||||
|
}
|
||||||
|
return kbSize
|
||||||
|
}
|
||||||
|
|
||||||
|
static private func getLayoutGuides(rootController: UIViewController, window: UIWindow,
|
||||||
|
isScrollableTextInputView: Bool) -> IQLayoutGuide {
|
||||||
|
let navigationBarAreaHeight: CGFloat
|
||||||
|
if let navigationController: UINavigationController = rootController.navigationController {
|
||||||
|
navigationBarAreaHeight = navigationController.navigationBar.frame.maxY
|
||||||
|
} else {
|
||||||
|
let statusBarHeight: CGFloat = window.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
|
||||||
|
navigationBarAreaHeight = statusBarHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
let directionalLayoutMargin: NSDirectionalEdgeInsets = rootController.view.directionalLayoutMargins
|
||||||
|
let topLayoutGuide: CGFloat = CGFloat.maximum(navigationBarAreaHeight, directionalLayoutMargin.top)
|
||||||
|
|
||||||
|
// Validation of textInputView for case where there is a tab bar
|
||||||
|
// at the bottom or running on iPhone X and textInputView is at the bottom.
|
||||||
|
let bottomLayoutGuide: CGFloat = isScrollableTextInputView ? 0 : directionalLayoutMargin.bottom
|
||||||
|
return (topLayoutGuide, bottomLayoutGuide)
|
||||||
|
}
|
||||||
|
|
||||||
|
static private func getMoveUpDistance(keyboardSize: CGSize,
|
||||||
|
layoutGuide: IQLayoutGuide,
|
||||||
|
textInputViewRectInRootSuperview: CGRect,
|
||||||
|
textInputViewRectInWindow: CGRect,
|
||||||
|
windowFrame: CGRect) -> CGFloat {
|
||||||
|
|
||||||
|
// Move positive = textInputView is hidden.
|
||||||
|
// Move negative = textInputView is showing.
|
||||||
|
// Calculating move position.
|
||||||
|
let visibleHeight: CGFloat = windowFrame.height-keyboardSize.height
|
||||||
|
|
||||||
|
let topMovement: CGFloat = textInputViewRectInRootSuperview.minY-layoutGuide.top
|
||||||
|
let bottomMovement: CGFloat = textInputViewRectInWindow.maxY - visibleHeight + layoutGuide.bottom
|
||||||
|
var moveUp: CGFloat = CGFloat.minimum(topMovement, bottomMovement)
|
||||||
|
moveUp = CGFloat(Int(moveUp))
|
||||||
|
return moveUp
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupActiveScrollViewConfiguration(superScrollView: UIScrollView?, textInputView: some IQTextInputView) {
|
||||||
|
// If there was a lastScrollView. // (Bug ID: #34)
|
||||||
|
guard let lastConfiguration: IQScrollViewConfiguration = lastScrollViewConfiguration else {
|
||||||
|
if let superScrollView: UIScrollView = superScrollView {
|
||||||
|
// If there was no lastScrollView and we found a current scrollView. then setting it as lastScrollView.
|
||||||
|
let configuration = IQScrollViewConfiguration(scrollView: superScrollView,
|
||||||
|
canRestoreContentOffset: true)
|
||||||
|
self.lastScrollViewConfiguration = configuration
|
||||||
|
showLog("""
|
||||||
|
Saving ScrollView New contentInset: \(configuration.startingContentInset)
|
||||||
|
and contentOffset: \(configuration.startingContentOffset)
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can't find current superScrollView, then setting lastScrollView to it's original form.
|
||||||
|
if superScrollView == nil {
|
||||||
|
restoreScrollViewConfigurationIfChanged(configuration: lastConfiguration,
|
||||||
|
textInputView: textInputView)
|
||||||
|
self.lastScrollViewConfiguration = nil
|
||||||
|
} else if superScrollView != lastConfiguration.scrollView {
|
||||||
|
// If both scrollView's are different,
|
||||||
|
// then reset lastScrollView to it's original frame and setting current scrollView as last scrollView.
|
||||||
|
restoreScrollViewConfigurationIfChanged(configuration: lastConfiguration,
|
||||||
|
textInputView: textInputView)
|
||||||
|
|
||||||
|
if let superScrollView = superScrollView {
|
||||||
|
let configuration = IQScrollViewConfiguration(scrollView: superScrollView,
|
||||||
|
canRestoreContentOffset: true)
|
||||||
|
self.lastScrollViewConfiguration = configuration
|
||||||
|
showLog("""
|
||||||
|
Saving ScrollView New contentInset: \(configuration.startingContentInset)
|
||||||
|
and contentOffset: \(configuration.startingContentOffset)
|
||||||
|
""")
|
||||||
|
} else {
|
||||||
|
self.lastScrollViewConfiguration = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Else the case where superScrollView == lastScrollView means we are on same scrollView
|
||||||
|
// after switching to different textInputView. So doing nothing, going ahead
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreScrollViewConfigurationIfChanged(configuration: IQScrollViewConfiguration,
|
||||||
|
textInputView: (some IQTextInputView)?) {
|
||||||
|
guard configuration.hasChanged else { return }
|
||||||
|
if configuration.scrollView.contentInset != configuration.startingContentInset {
|
||||||
|
showLog("Restoring contentInset to: \(configuration.startingContentInset)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.scrollView.iq.restoreContentOffset,
|
||||||
|
!configuration.scrollView.contentOffset.equalTo(configuration.startingContentOffset) {
|
||||||
|
showLog("Restoring contentOffset to: \(configuration.startingContentOffset)")
|
||||||
|
}
|
||||||
|
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
configuration.restore(for: textInputView)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable function_body_length
|
||||||
|
private func adjustScrollViewContentOffsets(moveUp: inout CGFloat, textInputView: some IQTextInputView,
|
||||||
|
lastScrollViewConfiguration: IQScrollViewConfiguration,
|
||||||
|
rootSuperview: UIView?,
|
||||||
|
layoutGuide: IQLayoutGuide,
|
||||||
|
textInputViewRectInRootSuperview: CGRect,
|
||||||
|
isScrollableTextInputView: Bool, window: UIWindow,
|
||||||
|
kbSize: CGSize, keyboardDistance: CGFloat,
|
||||||
|
rootBeginSafeAreaInsets: UIEdgeInsets) {
|
||||||
|
// Saving
|
||||||
|
var lastView: UIView = textInputView
|
||||||
|
var superScrollView: UIScrollView? = lastScrollViewConfiguration.scrollView
|
||||||
|
|
||||||
|
while let scrollView: UIScrollView = superScrollView {
|
||||||
|
|
||||||
|
var isContinue: Bool = false
|
||||||
|
|
||||||
|
if moveUp > 0 {
|
||||||
|
isContinue = moveUp > (-scrollView.contentOffset.y - scrollView.contentInset.top)
|
||||||
|
|
||||||
|
} else if let tableView: UITableView = scrollView.iq.superviewOf(type: UITableView.self) {
|
||||||
|
// Special treatment for UITableView due to their cell reusing logic
|
||||||
|
|
||||||
|
isContinue = scrollView.contentOffset.y > 0
|
||||||
|
|
||||||
|
Self.handleTableViewCase(moveUp: &moveUp, isContinue: isContinue, textInputView: textInputView,
|
||||||
|
tableView: tableView, rootSuperview: rootSuperview, layoutGuide: layoutGuide)
|
||||||
|
} else if let collectionView = scrollView.iq.superviewOf(type: UICollectionView.self) {
|
||||||
|
// Special treatment for UICollectionView due to their cell reusing logic
|
||||||
|
|
||||||
|
isContinue = scrollView.contentOffset.y > 0
|
||||||
|
|
||||||
|
Self.handleCollectionViewCase(moveUp: &moveUp, isContinue: isContinue,
|
||||||
|
textInputView: textInputView, collectionView: collectionView,
|
||||||
|
rootSuperview: rootSuperview, layoutGuide: layoutGuide)
|
||||||
|
} else {
|
||||||
|
isContinue = textInputViewRectInRootSuperview.minY < layoutGuide.top
|
||||||
|
|
||||||
|
if isContinue {
|
||||||
|
moveUp = CGFloat.minimum(0, textInputViewRectInRootSuperview.minY - layoutGuide.top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looping in upper hierarchy until we don't found any scrollView
|
||||||
|
// in it's upper hierarchy till UIWindow object.
|
||||||
|
if isContinue {
|
||||||
|
|
||||||
|
var tempScrollView: UIScrollView? = scrollView.iq.superviewOf(type: UIScrollView.self)
|
||||||
|
var nextScrollView: UIScrollView?
|
||||||
|
while let view: UIScrollView = tempScrollView {
|
||||||
|
|
||||||
|
if view.isScrollEnabled, !view.iq.ignoreScrollingAdjustment {
|
||||||
|
nextScrollView = view
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
tempScrollView = view.iq.superviewOf(type: UIScrollView.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting lastViewRect.
|
||||||
|
if let lastViewRect: CGRect = lastView.superview?.convert(lastView.frame, to: scrollView) {
|
||||||
|
|
||||||
|
// Calculating the expected Y offset from move and scrollView's contentOffset.
|
||||||
|
let minimumMovement: CGFloat = CGFloat.minimum(scrollView.contentOffset.y, -moveUp)
|
||||||
|
var suggestedOffsetY: CGFloat = scrollView.contentOffset.y - minimumMovement
|
||||||
|
|
||||||
|
// Rearranging the expected Y offset according to the view.
|
||||||
|
suggestedOffsetY = CGFloat.minimum(suggestedOffsetY, lastViewRect.minY)
|
||||||
|
|
||||||
|
updateSuggestedOffsetYAndMoveUp(suggestedOffsetY: &suggestedOffsetY, moveUp: &moveUp,
|
||||||
|
isScrollableTextInputView: isScrollableTextInputView,
|
||||||
|
nextScrollView: nextScrollView, textInputView: textInputView,
|
||||||
|
window: window, layoutGuide: layoutGuide,
|
||||||
|
scrollViewContentOffset: scrollView.contentOffset)
|
||||||
|
|
||||||
|
let newContentOffset: CGPoint = CGPoint(x: scrollView.contentOffset.x, y: suggestedOffsetY)
|
||||||
|
|
||||||
|
if !scrollView.contentOffset.equalTo(newContentOffset) {
|
||||||
|
|
||||||
|
updateScrollViewContentOffset(scrollView: scrollView, newContentOffset: newContentOffset,
|
||||||
|
moveUp: moveUp, textInputView: textInputView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting next lastView & superScrollView.
|
||||||
|
lastView = scrollView
|
||||||
|
superScrollView = nextScrollView
|
||||||
|
} else {
|
||||||
|
moveUp = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustScrollViewContentInset(lastScrollViewConfiguration: lastScrollViewConfiguration, window: window,
|
||||||
|
kbSize: kbSize, keyboardDistance: keyboardDistance,
|
||||||
|
rootBeginSafeAreaInsets: rootBeginSafeAreaInsets)
|
||||||
|
}
|
||||||
|
// swiftlint:enable function_body_length
|
||||||
|
|
||||||
|
private static func handleTableViewCase(moveUp: inout CGFloat, isContinue: Bool,
|
||||||
|
textInputView: some IQTextInputView, tableView: UITableView,
|
||||||
|
rootSuperview: UIView?, layoutGuide: IQLayoutGuide) {
|
||||||
|
guard isContinue,
|
||||||
|
let tableCell: UITableViewCell = textInputView.iq.superviewOf(type: UITableViewCell.self),
|
||||||
|
let indexPath: IndexPath = tableView.indexPath(for: tableCell),
|
||||||
|
let previousIndexPath: IndexPath = tableView.previousIndexPath(of: indexPath) else { return }
|
||||||
|
|
||||||
|
let previousCellRect: CGRect = tableView.rectForRow(at: previousIndexPath)
|
||||||
|
guard !previousCellRect.isEmpty else { return }
|
||||||
|
|
||||||
|
let previousCellRectInRootSuperview: CGRect = tableView.convert(previousCellRect,
|
||||||
|
to: rootSuperview)
|
||||||
|
|
||||||
|
moveUp = CGFloat.minimum(0, previousCellRectInRootSuperview.maxY - layoutGuide.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func handleCollectionViewCase(moveUp: inout CGFloat, isContinue: Bool,
|
||||||
|
textInputView: some IQTextInputView, collectionView: UICollectionView,
|
||||||
|
rootSuperview: UIView?,
|
||||||
|
layoutGuide: IQLayoutGuide) {
|
||||||
|
guard isContinue,
|
||||||
|
let collectionCell = textInputView.iq.superviewOf(type: UICollectionViewCell.self),
|
||||||
|
let indexPath: IndexPath = collectionView.indexPath(for: collectionCell),
|
||||||
|
let previousIndexPath: IndexPath = collectionView.previousIndexPath(of: indexPath),
|
||||||
|
let attributes = collectionView.layoutAttributesForItem(at: previousIndexPath) else { return }
|
||||||
|
|
||||||
|
let previousCellRect: CGRect = attributes.frame
|
||||||
|
guard !previousCellRect.isEmpty else { return }
|
||||||
|
let previousCellRectInRootSuperview: CGRect = collectionView.convert(previousCellRect,
|
||||||
|
to: rootSuperview)
|
||||||
|
|
||||||
|
moveUp = CGFloat.minimum(0, previousCellRectInRootSuperview.maxY - layoutGuide.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateSuggestedOffsetYAndMoveUp(suggestedOffsetY: inout CGFloat, moveUp: inout CGFloat,
|
||||||
|
isScrollableTextInputView: Bool, nextScrollView: UIScrollView?,
|
||||||
|
textInputView: some IQTextInputView, window: UIWindow,
|
||||||
|
layoutGuide: IQLayoutGuide,
|
||||||
|
scrollViewContentOffset: CGPoint) {
|
||||||
|
// If is a UITextView type
|
||||||
|
// nextScrollView == nil
|
||||||
|
// If processing scrollView is last scrollView in upper hierarchy
|
||||||
|
// (there is no other scrollView in upper hierarchy.)
|
||||||
|
//
|
||||||
|
// suggestedOffsetY >= 0
|
||||||
|
// suggestedOffsetY must be >= 0 in order to keep distance from navigationBar (Bug ID: #92)
|
||||||
|
guard isScrollableTextInputView,
|
||||||
|
nextScrollView == nil,
|
||||||
|
suggestedOffsetY >= 0,
|
||||||
|
let superview: UIView = textInputView.superview else {
|
||||||
|
// Subtracting the Y offset from the move variable,
|
||||||
|
// because we are going to change scrollView's contentOffset.y to suggestedOffsetY.
|
||||||
|
moveUp -= (suggestedOffsetY-scrollViewContentOffset.y)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentTextInputViewRect: CGRect = superview.convert(textInputView.frame,
|
||||||
|
to: window)
|
||||||
|
|
||||||
|
// Calculating expected fix distance which needs to be managed from navigation bar
|
||||||
|
let expectedFixDistance: CGFloat = currentTextInputViewRect.minY - layoutGuide.top
|
||||||
|
|
||||||
|
// Now if expectedOffsetY (scrollView.contentOffset.y + expectedFixDistance)
|
||||||
|
// is lower than current suggestedOffsetY, which means we're in a position where
|
||||||
|
// navigationBar up and hide, then reducing suggestedOffsetY with expectedOffsetY
|
||||||
|
// (scrollView.contentOffset.y + expectedFixDistance)
|
||||||
|
let expectedOffsetY: CGFloat = scrollViewContentOffset.y + expectedFixDistance
|
||||||
|
suggestedOffsetY = CGFloat.minimum(suggestedOffsetY, expectedOffsetY)
|
||||||
|
|
||||||
|
// Setting move to 0 because now we don't want to move any view anymore
|
||||||
|
// (All will be managed by our contentInset logic.
|
||||||
|
moveUp = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateScrollViewContentOffset(scrollView: UIScrollView, newContentOffset: CGPoint,
|
||||||
|
moveUp: CGFloat, textInputView: some IQTextInputView) {
|
||||||
|
showLog("""
|
||||||
|
old contentOffset: \(scrollView.contentOffset)
|
||||||
|
new contentOffset: \(newContentOffset)
|
||||||
|
""")
|
||||||
|
showLog("Remaining Move: \(moveUp)")
|
||||||
|
|
||||||
|
// Getting problem while using `setContentOffset:animated:`, So I used animation API.
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
|
||||||
|
// (Bug ID: #1365, #1508, #1541)
|
||||||
|
let stackView: UIStackView? = textInputView.iq.superviewOf(type: UIStackView.self,
|
||||||
|
belowView: scrollView)
|
||||||
|
// (Bug ID: #1901, #1996)
|
||||||
|
let animatedContentOffset: Bool = stackView != nil ||
|
||||||
|
scrollView is UICollectionView ||
|
||||||
|
scrollView is UITableView
|
||||||
|
|
||||||
|
if animatedContentOffset {
|
||||||
|
scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
|
||||||
|
} else {
|
||||||
|
scrollView.contentOffset = newContentOffset
|
||||||
|
}
|
||||||
|
}, completion: {
|
||||||
|
|
||||||
|
if scrollView is UITableView || scrollView is UICollectionView {
|
||||||
|
// Skip reloading input views during interactive navigation gesture to prevent toolbar flash (Issue #2102)
|
||||||
|
if self.activeConfiguration.rootConfiguration?.isInteractiveGestureActive == false {
|
||||||
|
// This will update the next/previous states
|
||||||
|
textInputView.reloadInputViews()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustScrollViewContentInset(lastScrollViewConfiguration: IQScrollViewConfiguration,
|
||||||
|
window: UIWindow, kbSize: CGSize, keyboardDistance: CGFloat,
|
||||||
|
rootBeginSafeAreaInsets: UIEdgeInsets) {
|
||||||
|
|
||||||
|
let lastScrollView = lastScrollViewConfiguration.scrollView
|
||||||
|
|
||||||
|
guard let lastScrollViewRect: CGRect = lastScrollView.superview?.convert(lastScrollView.frame, to: window),
|
||||||
|
!lastScrollView.iq.ignoreContentInsetAdjustment else { return }
|
||||||
|
|
||||||
|
// Updating contentInset
|
||||||
|
var bottomInset: CGFloat = (kbSize.height)-(window.frame.height-lastScrollViewRect.maxY)
|
||||||
|
let keyboardAndSafeArea: CGFloat = keyboardDistance + rootBeginSafeAreaInsets.bottom
|
||||||
|
var bottomScrollIndicatorInset: CGFloat = bottomInset - keyboardAndSafeArea
|
||||||
|
|
||||||
|
// Update the insets so that the scrollView doesn't shift incorrectly
|
||||||
|
// when the offset is near the bottom of the scroll view.
|
||||||
|
bottomInset = CGFloat.maximum(lastScrollViewConfiguration.startingContentInset.bottom, bottomInset)
|
||||||
|
let startingScrollInset: UIEdgeInsets = lastScrollViewConfiguration.startingScrollIndicatorInsets
|
||||||
|
bottomScrollIndicatorInset = CGFloat.maximum(startingScrollInset.bottom,
|
||||||
|
bottomScrollIndicatorInset)
|
||||||
|
|
||||||
|
bottomInset -= lastScrollView.safeAreaInsets.bottom
|
||||||
|
bottomScrollIndicatorInset -= lastScrollView.safeAreaInsets.bottom
|
||||||
|
|
||||||
|
var movedInsets: UIEdgeInsets = lastScrollView.contentInset
|
||||||
|
movedInsets.bottom = bottomInset
|
||||||
|
|
||||||
|
guard lastScrollView.contentInset != movedInsets else { return }
|
||||||
|
showLog("""
|
||||||
|
old ContentInset: \(lastScrollView.contentInset) new ContentInset: \(movedInsets)
|
||||||
|
""")
|
||||||
|
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
lastScrollView.contentInset = movedInsets
|
||||||
|
lastScrollView.layoutIfNeeded() // (Bug ID: #1996)
|
||||||
|
|
||||||
|
var newScrollIndicatorInset: UIEdgeInsets = lastScrollView.verticalScrollIndicatorInsets
|
||||||
|
|
||||||
|
newScrollIndicatorInset.bottom = bottomScrollIndicatorInset
|
||||||
|
lastScrollView.scrollIndicatorInsets = newScrollIndicatorInset
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func adjustTextInputViewContentInset(window: UIWindow, originalKbSize: CGSize,
|
||||||
|
rootSuperview: UIView?,
|
||||||
|
layoutGuide: IQLayoutGuide,
|
||||||
|
textInputView: UIScrollView) {
|
||||||
|
let keyboardYPosition: CGFloat = window.frame.height - originalKbSize.height
|
||||||
|
var rootSuperViewFrameInWindow: CGRect = window.frame
|
||||||
|
if let rootSuperview: UIView = rootSuperview {
|
||||||
|
rootSuperViewFrameInWindow = rootSuperview.convert(rootSuperview.bounds, to: window)
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyboardOverlapping: CGFloat = rootSuperViewFrameInWindow.maxY - keyboardYPosition
|
||||||
|
|
||||||
|
let availableHeight: CGFloat = rootSuperViewFrameInWindow.height-layoutGuide.top-keyboardOverlapping
|
||||||
|
let textInputViewHeight: CGFloat = CGFloat.minimum(textInputView.frame.height, availableHeight)
|
||||||
|
|
||||||
|
guard textInputView.frame.size.height-textInputView.contentInset.bottom>textInputViewHeight else { return }
|
||||||
|
// If frame is not change by library in past, then saving user textInputView properties (Bug ID: #92)
|
||||||
|
if startingTextViewConfiguration == nil {
|
||||||
|
startingTextViewConfiguration = IQScrollViewConfiguration(scrollView: textInputView,
|
||||||
|
canRestoreContentOffset: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var newContentInset: UIEdgeInsets = textInputView.contentInset
|
||||||
|
newContentInset.bottom = textInputView.frame.size.height-textInputViewHeight
|
||||||
|
newContentInset.bottom -= textInputView.safeAreaInsets.bottom
|
||||||
|
|
||||||
|
guard textInputView.contentInset != newContentInset else { return }
|
||||||
|
showLog("""
|
||||||
|
\(textInputView) Old textInputView.contentInset: \(textInputView.contentInset)
|
||||||
|
New textInputView.contentInset: \(newContentInset)
|
||||||
|
""")
|
||||||
|
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
|
||||||
|
textInputView.contentInset = newContentInset
|
||||||
|
textInputView.layoutIfNeeded() // (Bug ID: #1996)
|
||||||
|
textInputView.scrollIndicatorInsets = newContentInset
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustRootController(moveUp: CGFloat, rootViewOrigin: CGPoint, originalKbSize: CGSize,
|
||||||
|
rootController: UIViewController, rootBeginOrigin: CGPoint) {
|
||||||
|
// +Positive or zero.
|
||||||
|
var rootViewOrigin: CGPoint = rootViewOrigin
|
||||||
|
if moveUp >= 0 {
|
||||||
|
|
||||||
|
rootViewOrigin.y = CGFloat.maximum(rootViewOrigin.y - moveUp, CGFloat.minimum(0, -originalKbSize.height))
|
||||||
|
|
||||||
|
if !rootController.view.frame.origin.equalTo(rootViewOrigin) {
|
||||||
|
showLog("Moving Upward")
|
||||||
|
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
|
||||||
|
var rect: CGRect = rootController.view.frame
|
||||||
|
rect.origin = rootViewOrigin
|
||||||
|
rootController.view.frame = rect
|
||||||
|
|
||||||
|
// Animating content if needed (Bug ID: #204)
|
||||||
|
if self.layoutIfNeededOnUpdate {
|
||||||
|
// Animating content (Bug ID: #160)
|
||||||
|
rootController.view.setNeedsLayout()
|
||||||
|
rootController.view.layoutIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
let classNameString: String = "\(type(of: rootController.self))"
|
||||||
|
self.showLog("Set \(classNameString) origin to: \(rootViewOrigin)")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
movedDistance = rootBeginOrigin.y-rootViewOrigin.y
|
||||||
|
} else { // -Negative
|
||||||
|
let disturbDistance: CGFloat = rootViewOrigin.y-rootBeginOrigin.y
|
||||||
|
|
||||||
|
// disturbDistance Negative = frame disturbed.
|
||||||
|
// disturbDistance positive = frame not disturbed.
|
||||||
|
if disturbDistance <= 0 {
|
||||||
|
|
||||||
|
rootViewOrigin.y -= CGFloat.maximum(moveUp, disturbDistance)
|
||||||
|
|
||||||
|
if !rootController.view.frame.origin.equalTo(rootViewOrigin) {
|
||||||
|
showLog("Moving Downward")
|
||||||
|
// Setting adjusted rootViewRect
|
||||||
|
|
||||||
|
activeConfiguration.animate(alongsideTransition: {
|
||||||
|
|
||||||
|
var rect: CGRect = rootController.view.frame
|
||||||
|
rect.origin = rootViewOrigin
|
||||||
|
rootController.view.frame = rect
|
||||||
|
|
||||||
|
// Animating content if needed (Bug ID: #204)
|
||||||
|
if self.layoutIfNeededOnUpdate {
|
||||||
|
// Animating content (Bug ID: #160)
|
||||||
|
rootController.view.setNeedsLayout()
|
||||||
|
rootController.view.layoutIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
let classNameString: String = "\(type(of: rootController.self))"
|
||||||
|
self.showLog("Set \(classNameString) origin to: \(rootViewOrigin)")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
movedDistance = rootBeginOrigin.y-rootViewOrigin.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreScrollViewContentOffset(superScrollView: UIScrollView, textInputView: (some IQTextInputView)?) {
|
||||||
|
var superScrollView: UIScrollView? = superScrollView
|
||||||
|
while let scrollView: UIScrollView = superScrollView {
|
||||||
|
|
||||||
|
let width: CGFloat = CGFloat.maximum(scrollView.contentSize.width, scrollView.frame.width)
|
||||||
|
let height: CGFloat = CGFloat.maximum(scrollView.contentSize.height, scrollView.frame.height)
|
||||||
|
let contentSize: CGSize = CGSize(width: width, height: height)
|
||||||
|
|
||||||
|
let minimumY: CGFloat = contentSize.height - scrollView.frame.height
|
||||||
|
|
||||||
|
if minimumY < scrollView.contentOffset.y {
|
||||||
|
|
||||||
|
let newContentOffset: CGPoint = CGPoint(x: scrollView.contentOffset.x, y: minimumY)
|
||||||
|
if !scrollView.contentOffset.equalTo(newContentOffset) {
|
||||||
|
|
||||||
|
// (Bug ID: #1365, #1508, #1541)
|
||||||
|
let stackView: UIStackView?
|
||||||
|
if let textInputView: UIView = textInputView {
|
||||||
|
stackView = textInputView.iq.superviewOf(type: UIStackView.self,
|
||||||
|
belowView: scrollView)
|
||||||
|
} else {
|
||||||
|
stackView = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Bug ID: #1901, #1996)
|
||||||
|
let animatedContentOffset: Bool = stackView != nil ||
|
||||||
|
scrollView is UICollectionView ||
|
||||||
|
scrollView is UITableView
|
||||||
|
|
||||||
|
if animatedContentOffset {
|
||||||
|
scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
|
||||||
|
} else {
|
||||||
|
scrollView.contentOffset = newContentOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
showLog("Restoring contentOffset to: \(newContentOffset)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
superScrollView = scrollView.iq.superviewOf(type: UIScrollView.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable function_parameter_count
|
||||||
238
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager.swift
generated
Normal file
238
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager.swift
generated
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/**
|
||||||
|
IQKeyboardManager is a code-less drop-in universal library that automatically prevents issues
|
||||||
|
of the keyboard sliding up and covering UITextField/UITextView.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Simply enable the manager in your AppDelegate:
|
||||||
|
```swift
|
||||||
|
IQKeyboardManager.shared.isEnabled = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Thread Safety
|
||||||
|
All public APIs must be called from the main thread. The class is marked with @MainActor
|
||||||
|
to enforce this at compile time.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```swift
|
||||||
|
import IQKeyboardManagerSwift
|
||||||
|
|
||||||
|
@main
|
||||||
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
func application(_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
IQKeyboardManager.shared.isEnabled = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardManager: NSObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the default singleton instance.
|
||||||
|
*/
|
||||||
|
@MainActor
|
||||||
|
public static let shared: IQKeyboardManager = .init()
|
||||||
|
|
||||||
|
internal var activeConfiguration: IQActiveConfiguration = .init()
|
||||||
|
|
||||||
|
// MARK: UIKeyboard handling
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enable/disable managing distance between keyboard and textInputView.
|
||||||
|
Default is YES(Enabled when class loads in `+(void)load` method).
|
||||||
|
*/
|
||||||
|
public var isEnabled: Bool = false {
|
||||||
|
didSet {
|
||||||
|
guard isEnabled != oldValue else { return }
|
||||||
|
// If not enable, enable it.
|
||||||
|
if isEnabled {
|
||||||
|
// If keyboard is currently showing.
|
||||||
|
if activeConfiguration.keyboardInfo.isVisible {
|
||||||
|
adjustPosition()
|
||||||
|
} else {
|
||||||
|
restorePosition()
|
||||||
|
}
|
||||||
|
showLog("Enabled")
|
||||||
|
} else { // If not disable, disable it.
|
||||||
|
restorePosition()
|
||||||
|
showLog("Disabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sets the default distance between the keyboard and the active text input view.
|
||||||
|
|
||||||
|
This distance is applied to all text inputs unless overridden by setting
|
||||||
|
`view.iq.distanceFromKeyboard` for specific views.
|
||||||
|
|
||||||
|
- Precondition: Value must be non-negative. Negative values will be logged as warnings.
|
||||||
|
- Default: `10.0` points
|
||||||
|
- Note: This is a global setting. Use `view.iq.distanceFromKeyboard` for per-view customization.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```swift
|
||||||
|
// Set global distance
|
||||||
|
IQKeyboardManager.shared.keyboardDistance = 20.0
|
||||||
|
|
||||||
|
// Override for specific text field
|
||||||
|
myTextField.iq.distanceFromKeyboard = 30.0
|
||||||
|
```
|
||||||
|
|
||||||
|
- SeeAlso: `UIView.iq.distanceFromKeyboard` for per-view customization
|
||||||
|
*/
|
||||||
|
public var keyboardDistance: CGFloat = 10.0 {
|
||||||
|
didSet {
|
||||||
|
if keyboardDistance < 0 {
|
||||||
|
showLog("⚠️ keyboardDistance shouldn't be negative.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// MARK: UIAnimation handling
|
||||||
|
|
||||||
|
/**
|
||||||
|
If YES, then calls 'setNeedsLayout' and 'layoutIfNeeded' on any frame update of to viewController's view.
|
||||||
|
*/
|
||||||
|
public var layoutIfNeededOnUpdate: Bool = false
|
||||||
|
|
||||||
|
// MARK: Class Level disabling methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
Classes that should have keyboard distance handling disabled.
|
||||||
|
|
||||||
|
When a view controller is of one of these types, keyboard distance handling
|
||||||
|
is disabled regardless of the `isEnabled` property.
|
||||||
|
|
||||||
|
- Precondition: All classes must be subclasses of `UIViewController`
|
||||||
|
- Default: `[UITableViewController.self, UIInputViewController.self, UIAlertController.self]`
|
||||||
|
- Note: This takes precedence over `enabledDistanceHandlingClasses`. If a class appears
|
||||||
|
in both arrays, it will be disabled.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```swift
|
||||||
|
// Disable for custom view controller
|
||||||
|
IQKeyboardManager.shared.disabledDistanceHandlingClasses.append(MyCustomViewController.self)
|
||||||
|
|
||||||
|
// Disable for multiple controllers
|
||||||
|
IQKeyboardManager.shared.disabledDistanceHandlingClasses += [
|
||||||
|
LoginViewController.self,
|
||||||
|
SignupViewController.self
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- SeeAlso: `enabledDistanceHandlingClasses` for force-enabling specific classes
|
||||||
|
*/
|
||||||
|
public var disabledDistanceHandlingClasses: [UIViewController.Type] = [
|
||||||
|
UITableViewController.self,
|
||||||
|
UIInputViewController.self,
|
||||||
|
UIAlertController.self
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enable distance handling within the scope of enabled distance handling viewControllers classes.
|
||||||
|
Within this scope, 'enabled' property is ignored. Class should be kind of UIViewController.
|
||||||
|
If same Class is added in disabledDistanceHandlingClasses list,
|
||||||
|
then enabledDistanceHandlingClasses will be ignored.
|
||||||
|
*/
|
||||||
|
public var enabledDistanceHandlingClasses: [UIViewController.Type] = []
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
|
||||||
|
// MARK: Initialization/De-initialization
|
||||||
|
|
||||||
|
/* Singleton Object Initialization. */
|
||||||
|
private override init() {
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addActiveConfigurationObserver()
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)),
|
||||||
|
name: UIApplication.didBecomeActiveNotification, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
// Disable the keyboard manager.
|
||||||
|
isEnabled = false
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Public Methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
Manually triggers a position adjustment for the active text input view.
|
||||||
|
|
||||||
|
Call this method when you've made external changes to the view hierarchy that
|
||||||
|
might affect keyboard positioning (e.g., programmatically changing view frames,
|
||||||
|
adding/removing views, changing constraints).
|
||||||
|
|
||||||
|
This method is safe to call even when no text input is active or the keyboard
|
||||||
|
is hidden - it will simply return early.
|
||||||
|
|
||||||
|
## When to Call
|
||||||
|
- After programmatically modifying view frames
|
||||||
|
- After adding or removing views from the hierarchy
|
||||||
|
- After changing Auto Layout constraints that affect the active text field's position
|
||||||
|
- When orientation changes occur outside the normal notification flow
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```swift
|
||||||
|
// After programmatic layout changes
|
||||||
|
myView.frame = newFrame
|
||||||
|
IQKeyboardManager.shared.reloadLayoutIfNeeded()
|
||||||
|
|
||||||
|
// After constraint updates
|
||||||
|
NSLayoutConstraint.activate(newConstraints)
|
||||||
|
view.layoutIfNeeded()
|
||||||
|
IQKeyboardManager.shared.reloadLayoutIfNeeded()
|
||||||
|
```
|
||||||
|
|
||||||
|
- Note: This method only has an effect when:
|
||||||
|
- `isEnabled` is `true`
|
||||||
|
- A text input view is currently active
|
||||||
|
- The keyboard is visible
|
||||||
|
- The root configuration is ready
|
||||||
|
- SeeAlso: `isEnabled` for enabling/disabling the manager
|
||||||
|
*/
|
||||||
|
public func reloadLayoutIfNeeded() {
|
||||||
|
|
||||||
|
guard privateIsEnabled(),
|
||||||
|
activeConfiguration.keyboardInfo.isVisible,
|
||||||
|
activeConfiguration.isReady else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adjustPosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
//
|
||||||
|
// UIScrollView+IQKeyboardManagerExtension.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var ignoreScrollingAdjustment: Int = 0
|
||||||
|
static var ignoreContentInsetAdjustment: Int = 0
|
||||||
|
static var restoreContentOffset: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQKeyboardExtension where Base: UIScrollView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
If YES, then scrollview will ignore scrolling (simply not scroll it) for adjusting textInputView position.
|
||||||
|
Default is NO.
|
||||||
|
*/
|
||||||
|
var ignoreScrollingAdjustment: Bool {
|
||||||
|
get {
|
||||||
|
if let base = base {
|
||||||
|
return objc_getAssociatedObject(base, &AssociatedKeys.ignoreScrollingAdjustment) as? Bool ?? false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
if let base = base {
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.ignoreScrollingAdjustment,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
If YES, then scrollview will ignore content inset adjustment (simply not updating it) when keyboard is shown.
|
||||||
|
Default is NO.
|
||||||
|
*/
|
||||||
|
var ignoreContentInsetAdjustment: Bool {
|
||||||
|
get {
|
||||||
|
if let base = base {
|
||||||
|
return objc_getAssociatedObject(base, &AssociatedKeys.ignoreContentInsetAdjustment) as? Bool ?? false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
if let base = base {
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.ignoreContentInsetAdjustment,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
If we should restore scrollView contentOffset to it's initial position
|
||||||
|
*/
|
||||||
|
var restoreContentOffset: Bool {
|
||||||
|
get {
|
||||||
|
if let base = base {
|
||||||
|
return objc_getAssociatedObject(base, &AssociatedKeys.restoreContentOffset) as? Bool ?? false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
if let base = base {
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.restoreContentOffset,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension UIScrollView {
|
||||||
|
@available(*, unavailable, renamed: "iq.ignoreScrollingAdjustment")
|
||||||
|
var shouldIgnoreScrollingAdjustment: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.ignoreContentInsetAdjustment")
|
||||||
|
var shouldIgnoreContentInsetAdjustment: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.restoreContentOffset")
|
||||||
|
var shouldRestoreScrollViewContentOffset: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// UIScrollView+IQKeyboardManagerExtensionObjc.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// MARK: For ObjectiveC Compatibility
|
||||||
|
|
||||||
|
// swiftlint:disable identifier_name
|
||||||
|
@objc public extension UIScrollView {
|
||||||
|
|
||||||
|
var iq_ignoreScrollingAdjustment: Bool {
|
||||||
|
get { iq.ignoreScrollingAdjustment }
|
||||||
|
set { iq.ignoreScrollingAdjustment = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var iq_ignoreContentInsetAdjustment: Bool {
|
||||||
|
get { iq.ignoreContentInsetAdjustment }
|
||||||
|
set { iq.ignoreContentInsetAdjustment = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var iq_restoreContentOffset: Bool {
|
||||||
|
get { iq.restoreContentOffset }
|
||||||
|
set { iq.restoreContentOffset = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable identifier_name
|
||||||
@ -0,0 +1,158 @@
|
|||||||
|
//
|
||||||
|
// UIView+IQKeyboardManagerExtension.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var distanceFromKeyboard: Int = 0
|
||||||
|
static var enableMode: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension UIView {
|
||||||
|
nonisolated static let defaultKeyboardDistance: CGFloat = .greatestFiniteMagnitude
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@available(*, unavailable, renamed: "UIView.defaultKeyboardDistance")
|
||||||
|
nonisolated public let kIQUseDefaultKeyboardDistance: CGFloat = .greatestFiniteMagnitude
|
||||||
|
|
||||||
|
/**
|
||||||
|
Extension providing keyboard management functionality to UIView instances.
|
||||||
|
|
||||||
|
This extension allows per-view configuration of keyboard behavior, including
|
||||||
|
custom distance and enable/disable modes.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```swift
|
||||||
|
// Set custom distance for a text field
|
||||||
|
textField.iq.distanceFromKeyboard = 30.0
|
||||||
|
|
||||||
|
// Disable keyboard management for a specific text field
|
||||||
|
textField.iq.enableMode = .disabled
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQKeyboardExtension where Base: IQTextInputView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Custom distance from keyboard for this specific text input view.
|
||||||
|
|
||||||
|
If set to `UIView.defaultKeyboardDistance`, the global `keyboardDistance`
|
||||||
|
value from `IQKeyboardManager.shared.keyboardDistance` will be used.
|
||||||
|
Otherwise, this value takes precedence.
|
||||||
|
|
||||||
|
- Default: `UIView.defaultKeyboardDistance` (uses global setting)
|
||||||
|
- Note: Value cannot be negative. Negative values may cause unexpected behavior.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```swift
|
||||||
|
// Use global setting
|
||||||
|
textField.iq.distanceFromKeyboard = UIView.defaultKeyboardDistance
|
||||||
|
|
||||||
|
// Use custom distance
|
||||||
|
textField.iq.distanceFromKeyboard = 50.0
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
var distanceFromKeyboard: CGFloat {
|
||||||
|
get {
|
||||||
|
if let base = base {
|
||||||
|
if let value = objc_getAssociatedObject(base, &AssociatedKeys.distanceFromKeyboard) as? CGFloat {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UIView.defaultKeyboardDistance
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
if let base = base {
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.distanceFromKeyboard,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newValue < 0 {
|
||||||
|
IQKeyboardManager.shared.showLog("Warning: distanceFromKeyboard shouldn't be negative.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enable mode for this specific text input view.
|
||||||
|
|
||||||
|
Controls whether keyboard management is enabled, disabled, or uses the default
|
||||||
|
global setting for this view.
|
||||||
|
|
||||||
|
- `.default`: Use global `IQKeyboardManager.shared.isEnabled` setting
|
||||||
|
- `.enabled`: Force enable keyboard management for this view
|
||||||
|
- `.disabled`: Force disable keyboard management for this view
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```swift
|
||||||
|
// Use global setting
|
||||||
|
textField.iq.enableMode = .default
|
||||||
|
|
||||||
|
// Always enable for this field
|
||||||
|
textField.iq.enableMode = .enabled
|
||||||
|
|
||||||
|
// Always disable for this field
|
||||||
|
textField.iq.enableMode = .disabled
|
||||||
|
```
|
||||||
|
|
||||||
|
- SeeAlso: `IQKeyboardManager.isEnabled` for global control
|
||||||
|
*/
|
||||||
|
var enableMode: IQEnableMode {
|
||||||
|
get {
|
||||||
|
if let base = base {
|
||||||
|
return objc_getAssociatedObject(base, &AssociatedKeys.enableMode) as? IQEnableMode ?? .default
|
||||||
|
}
|
||||||
|
return .default
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
if let base = base {
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.enableMode, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension UIView {
|
||||||
|
@available(*, unavailable, renamed: "iq.distanceFromKeyboard")
|
||||||
|
var keyboardDistanceFromTextField: CGFloat {
|
||||||
|
get { 0 }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.enableMode")
|
||||||
|
var enableMode: IQEnableMode {
|
||||||
|
get { .default }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// UIView+IQKeyboardManagerExtensionObjc.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
// MARK: For ObjectiveC Compatibility
|
||||||
|
|
||||||
|
// swiftlint:disable identifier_name
|
||||||
|
@objc public extension UITextField {
|
||||||
|
|
||||||
|
var iq_distanceFromKeyboard: CGFloat {
|
||||||
|
get { iq.distanceFromKeyboard }
|
||||||
|
set { iq.distanceFromKeyboard = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var iq_enableMode: IQEnableMode {
|
||||||
|
get { iq.enableMode }
|
||||||
|
set { iq.enableMode = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public extension UITextView {
|
||||||
|
|
||||||
|
var iq_distanceFromKeyboard: CGFloat {
|
||||||
|
get { iq.distanceFromKeyboard }
|
||||||
|
set { iq.distanceFromKeyboard = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var iq_enableMode: IQEnableMode {
|
||||||
|
get { iq.enableMode }
|
||||||
|
set { iq.enableMode = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable identifier_name
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// UICollectionView+IndexPaths.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension UICollectionView {
|
||||||
|
|
||||||
|
func previousIndexPath(of indexPath: IndexPath) -> IndexPath? {
|
||||||
|
var previousRow: Int = indexPath.row - 1
|
||||||
|
var previousSection: Int = indexPath.section
|
||||||
|
|
||||||
|
// Fixing indexPath
|
||||||
|
if previousRow < 0 {
|
||||||
|
previousSection -= 1
|
||||||
|
if previousSection >= 0 {
|
||||||
|
previousRow = self.numberOfItems(inSection: previousSection) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard previousRow >= 0, previousSection >= 0 else { return nil }
|
||||||
|
return IndexPath(item: previousRow, section: previousSection)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// UITableView+IndexPaths.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension UITableView {
|
||||||
|
|
||||||
|
func previousIndexPath(of indexPath: IndexPath) -> IndexPath? {
|
||||||
|
var previousRow: Int = indexPath.row - 1
|
||||||
|
var previousSection: Int = indexPath.section
|
||||||
|
|
||||||
|
// Fixing indexPath
|
||||||
|
if previousRow < 0 {
|
||||||
|
previousSection -= 1
|
||||||
|
if previousSection >= 0 {
|
||||||
|
let rowCount = self.numberOfRows(inSection: previousSection)
|
||||||
|
previousRow = rowCount > 0 ? rowCount - 1 : -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard previousRow >= 0, previousSection >= 0 else { return nil }
|
||||||
|
return IndexPath(row: previousRow, section: previousSection)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
//
|
||||||
|
// UIView+Parent.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
/**
|
||||||
|
UIView hierarchy category.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQKeyboardExtension where Base: UIView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the UIViewController object that is actually the parent of this object.
|
||||||
|
Most of the time it's the viewController object which actually contains it,
|
||||||
|
but result may be different if it's viewController is added as childViewController of another viewController.
|
||||||
|
*/
|
||||||
|
func parentContainerViewController() -> UIViewController? {
|
||||||
|
|
||||||
|
var matchController: UIViewController? = viewContainingController()
|
||||||
|
var parentContainerViewController: UIViewController?
|
||||||
|
|
||||||
|
if var navController: UINavigationController = matchController?.navigationController {
|
||||||
|
|
||||||
|
while let parentNav: UINavigationController = navController.navigationController {
|
||||||
|
navController = parentNav
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentController: UIViewController = navController
|
||||||
|
|
||||||
|
while let parent: UIViewController = parentController.parent,
|
||||||
|
!(parent is UINavigationController) &&
|
||||||
|
!(parent is UITabBarController) &&
|
||||||
|
!(parent is UISplitViewController) {
|
||||||
|
|
||||||
|
parentController = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
if navController == parentController {
|
||||||
|
parentContainerViewController = navController.topViewController
|
||||||
|
} else {
|
||||||
|
parentContainerViewController = parentController
|
||||||
|
}
|
||||||
|
} else if let tabController: UITabBarController = matchController?.tabBarController {
|
||||||
|
let selectedController = tabController.selectedViewController
|
||||||
|
if let navController: UINavigationController = selectedController as? UINavigationController {
|
||||||
|
parentContainerViewController = navController.topViewController
|
||||||
|
} else {
|
||||||
|
parentContainerViewController = tabController.selectedViewController
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while let parent: UIViewController = matchController?.parent,
|
||||||
|
!(parent is UINavigationController) &&
|
||||||
|
!(parent is UITabBarController) &&
|
||||||
|
!(parent is UISplitViewController) {
|
||||||
|
|
||||||
|
matchController = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
parentContainerViewController = matchController
|
||||||
|
}
|
||||||
|
|
||||||
|
if let controller: UIViewController = parentContainerViewController?.iq_parentContainerViewController() {
|
||||||
|
return controller
|
||||||
|
} else {
|
||||||
|
return parentContainerViewController
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension UIView {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.parentContainerViewController()")
|
||||||
|
func parentContainerViewController() -> UIViewController? { nil }
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// UIView+ParentObjc.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@objc public extension UIView {
|
||||||
|
|
||||||
|
func iq_parentContainerViewController() -> UIViewController? {
|
||||||
|
iq.parentContainerViewController()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// UIViewController+ParentContainer.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension UIViewController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
This method is provided to override by viewController's
|
||||||
|
if the library lifts a viewController which you doesn't want to lift.
|
||||||
|
This may happen if you have implemented side menu feature
|
||||||
|
in your app and the library try to lift the side menu controller.
|
||||||
|
Overriding this method in side menu class to return correct controller should fix the problem.
|
||||||
|
*/
|
||||||
|
open func iq_parentContainerViewController() -> UIViewController? {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension UIViewController {
|
||||||
|
@available(*, unavailable, renamed: "iq_parentContainerViewController()")
|
||||||
|
open func parentIQContainerViewController() -> UIViewController? {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+ToolbarManager.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardToolbarManager
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
// swiftlint:disable line_length
|
||||||
|
@available(*, deprecated,
|
||||||
|
message: "Please use `IQKeyboardToolbarManager` independently from https://github.com/hackiftekhar/IQKeyboardToolbarManager")
|
||||||
|
// swiftlint:enable line_length
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var toolbarManager: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
internal var toolbarManager: IQKeyboardToolbarManager {
|
||||||
|
IQKeyboardToolbarManager.shared
|
||||||
|
}
|
||||||
|
|
||||||
|
var enableToolbarDebugging: Bool {
|
||||||
|
get { toolbarManager.isDebuggingEnabled }
|
||||||
|
set { toolbarManager.isDebuggingEnabled = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Automatic add the toolbar functionality. Default is YES.
|
||||||
|
*/
|
||||||
|
var enableAutoToolbar: Bool {
|
||||||
|
get { toolbarManager.isEnabled }
|
||||||
|
set { toolbarManager.isEnabled = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configurations related to the toolbar display over the keyboard.
|
||||||
|
*/
|
||||||
|
var toolbarConfiguration: IQKeyboardToolbarConfiguration {
|
||||||
|
toolbarManager.toolbarConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: UISound handling
|
||||||
|
|
||||||
|
/**
|
||||||
|
If YES, then it plays inputClick sound on next/previous/done click.
|
||||||
|
*/
|
||||||
|
var playInputClicks: Bool {
|
||||||
|
get { toolbarManager.playInputClicks }
|
||||||
|
set { toolbarManager.playInputClicks = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Disable automatic toolbar creation within the scope of disabled toolbar viewControllers classes.
|
||||||
|
Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController.
|
||||||
|
*/
|
||||||
|
var disabledToolbarClasses: [UIViewController.Type] {
|
||||||
|
get { toolbarManager.disabledToolbarClasses }
|
||||||
|
set { toolbarManager.disabledToolbarClasses = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enable automatic toolbar creation within the scope of enabled toolbar viewControllers classes.
|
||||||
|
Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController.
|
||||||
|
If same Class is added in disabledToolbarClasses list, then enabledToolbarClasses will be ignore.
|
||||||
|
*/
|
||||||
|
var enabledToolbarClasses: [UIViewController.Type] {
|
||||||
|
get { toolbarManager.enabledToolbarClasses }
|
||||||
|
set { toolbarManager.enabledToolbarClasses = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Allowed subclasses of UIView to add all inner textField,
|
||||||
|
this will allow to navigate between textField contains in different superview.
|
||||||
|
Class should be kind of UIView.
|
||||||
|
*/
|
||||||
|
var deepResponderAllowedContainerClasses: [UIView.Type] {
|
||||||
|
get { toolbarManager.deepResponderAllowedContainerClasses }
|
||||||
|
set { toolbarManager.deepResponderAllowedContainerClasses = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** reloadInputViews to reload toolbar buttons enable/disable state on the fly Enhancement ID #434. */
|
||||||
|
func reloadInputViews() {
|
||||||
|
toolbarManager.reloadInputViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns YES if can navigate to previous responder textInputView, otherwise NO.
|
||||||
|
*/
|
||||||
|
var canGoPrevious: Bool {
|
||||||
|
toolbarManager.canGoPrevious
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns YES if can navigate to next responder textInputViews, otherwise NO.
|
||||||
|
*/
|
||||||
|
var canGoNext: Bool {
|
||||||
|
toolbarManager.canGoNext
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Navigate to previous responder textInputViews
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
func goPrevious() -> Bool {
|
||||||
|
toolbarManager.goPrevious()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Navigate to next responder textInputView.
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
func goNext() -> Bool {
|
||||||
|
toolbarManager.goNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,177 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+ToolbarManagerDeprecated.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardToolbarManager
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
// swiftlint:disable identifier_name
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "playInputClicks")
|
||||||
|
var shouldPlayInputClicks: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.manageBehavior")
|
||||||
|
var toolbarManageBehaviour: IQKeyboardToolbarManageBehavior {
|
||||||
|
get { .bySubviews }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.useTextInputViewTintColor")
|
||||||
|
var shouldToolbarUsesTextFieldTintColor: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.tintColor")
|
||||||
|
var toolbarTintColor: UIColor? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.barTintColor")
|
||||||
|
var toolbarBarTintColor: UIColor? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.previousNextDisplayMode")
|
||||||
|
var previousNextDisplayMode: IQPreviousNextDisplayMode {
|
||||||
|
get { .default }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "deepResponderAllowedContainerClasses")
|
||||||
|
var toolbarPreviousNextAllowedClasses: [UIView.Type] {
|
||||||
|
get { [] }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.image",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
|
||||||
|
var toolbarPreviousBarButtonItemImage: UIImage? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.title",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
|
||||||
|
var toolbarPreviousBarButtonItemText: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.accessibilityLabel",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
|
||||||
|
var toolbarPreviousBarButtonItemAccessibilityLabel: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.image",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
|
||||||
|
var toolbarNextBarButtonItemImage: UIImage? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.title",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
|
||||||
|
var toolbarNextBarButtonItemText: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.accessibilityLabel",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
|
||||||
|
var toolbarNextBarButtonItemAccessibilityLabel: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.image",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
|
||||||
|
var toolbarDoneBarButtonItemImage: UIImage? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.title",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
|
||||||
|
var toolbarDoneBarButtonItemText: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.accessibilityLabel",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
|
||||||
|
var toolbarDoneBarButtonItemAccessibilityLabel: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.accessibilityLabel")
|
||||||
|
var toolbarTitleBarButtonItemAccessibilityLabel: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.showPlaceholder")
|
||||||
|
var shouldShowToolbarPlaceholder: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.font")
|
||||||
|
var placeholderFont: UIFont? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.color")
|
||||||
|
var placeholderColor: UIColor? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.buttonColor")
|
||||||
|
var placeholderButtonColor: UIColor? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
|
// swiftlint:enable identifier_name
|
||||||
14
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/PrivacyInfo.xcprivacy
generated
Normal file
14
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
100
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/IQKeyboardManager+Resign.swift
generated
Normal file
100
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/IQKeyboardManager+Resign.swift
generated
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+Resign.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var resignHandler: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
internal var resignHandler: IQKeyboardResignHandler {
|
||||||
|
if let object = objc_getAssociatedObject(self, &AssociatedKeys.resignHandler)
|
||||||
|
as? IQKeyboardResignHandler {
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
let object: IQKeyboardResignHandler = .init()
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.resignHandler,
|
||||||
|
object, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resigns Keyboard on touching outside TextInputView. Default is NO.
|
||||||
|
*/
|
||||||
|
var resignOnTouchOutside: Bool {
|
||||||
|
get { resignHandler.resignOnTouchOutside }
|
||||||
|
set { resignHandler.resignOnTouchOutside = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** TapGesture to resign keyboard on view's touch.
|
||||||
|
It's a readonly property and exposed only for adding/removing dependencies
|
||||||
|
if your added gesture does have collision with this one
|
||||||
|
*/
|
||||||
|
var resignGesture: UITapGestureRecognizer {
|
||||||
|
get { resignHandler.resignGesture }
|
||||||
|
set { resignHandler.resignGesture = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Disabled classes to ignore resignOnTouchOutside' property, Class should be kind of UIViewController.
|
||||||
|
*/
|
||||||
|
var disabledTouchResignedClasses: [UIViewController.Type] {
|
||||||
|
get { resignHandler.disabledTouchResignedClasses }
|
||||||
|
set { resignHandler.disabledTouchResignedClasses = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enabled classes to forcefully enable 'resignOnTouchOutside' property.
|
||||||
|
Class should be kind of UIViewController
|
||||||
|
. If same Class is added in disabledTouchResignedClasses list, then enabledTouchResignedClasses will be ignored.
|
||||||
|
*/
|
||||||
|
var enabledTouchResignedClasses: [UIViewController.Type] {
|
||||||
|
get { resignHandler.enabledTouchResignedClasses }
|
||||||
|
set { resignHandler.enabledTouchResignedClasses = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
if resignOnTouchOutside is enabled then you can customize the behavior
|
||||||
|
to not recognize gesture touches on some specific view subclasses.
|
||||||
|
Class should be kind of UIView. Default is [UIControl, UINavigationBar]
|
||||||
|
*/
|
||||||
|
var touchResignedGestureIgnoreClasses: [UIView.Type] {
|
||||||
|
get { resignHandler.touchResignedGestureIgnoreClasses }
|
||||||
|
set { resignHandler.touchResignedGestureIgnoreClasses = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resigns currently first responder field.
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
func resignFirstResponder() -> Bool {
|
||||||
|
resignHandler.resignFirstResponder()
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/IQKeyboardManager+Resign_Deprecated.swift
generated
Normal file
37
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/IQKeyboardManager+Resign_Deprecated.swift
generated
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardManager+Resign_Deprecated.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "resignOnTouchOutside")
|
||||||
|
var shouldResignOnTouchOutside: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
105
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/IQKeyboardResignHandler+Internal.swift
generated
Normal file
105
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/IQKeyboardResignHandler+Internal.swift
generated
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardResignHandler+Internal.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardResignHandler {
|
||||||
|
|
||||||
|
func removeTextInputViewObserver() {
|
||||||
|
textInputViewObserver.unsubscribe(identifier: "IQKeyboardResignHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTextInputViewObserver() {
|
||||||
|
textInputViewObserver.subscribe(identifier: "IQKeyboardResignHandler",
|
||||||
|
changeHandler: { [weak self] event, textInputView in
|
||||||
|
guard let self = self else { return }
|
||||||
|
switch event {
|
||||||
|
case .beginEditing:
|
||||||
|
self.resignGesture.isEnabled = self.privateResignOnTouchOutside()
|
||||||
|
textInputView.window?.addGestureRecognizer(self.resignGesture)
|
||||||
|
case .endEditing:
|
||||||
|
textInputView.window?.removeGestureRecognizer(self.resignGesture)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func privateResignOnTouchOutside() -> Bool {
|
||||||
|
|
||||||
|
guard let textInputView: any IQTextInputView = textInputViewObserver.textInputView else {
|
||||||
|
return resignOnTouchOutside
|
||||||
|
}
|
||||||
|
|
||||||
|
switch textInputView.internalResignOnTouchOutsideMode {
|
||||||
|
case .default:
|
||||||
|
guard var controller = (textInputView as UIView).iq.viewContainingController() else {
|
||||||
|
return resignOnTouchOutside
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is searchBar textField embedded in Navigation Bar
|
||||||
|
if textInputView is UISearchTextField {
|
||||||
|
if let navController: UINavigationController = controller as? UINavigationController,
|
||||||
|
let topController: UIViewController = navController.topViewController,
|
||||||
|
controller.navigationItem.searchController?.searchBar.searchTextField == textInputView {
|
||||||
|
controller = topController
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If viewController is in enabledTouchResignedClasses, then assuming resignOnTouchOutside is enabled.
|
||||||
|
let isWithEnabledClass: Bool = enabledTouchResignedClasses.contains(where: { controller.isKind(of: $0) })
|
||||||
|
var isEnabled: Bool = resignOnTouchOutside || isWithEnabledClass
|
||||||
|
|
||||||
|
if isEnabled {
|
||||||
|
|
||||||
|
// If viewController is in disabledTouchResignedClasses,
|
||||||
|
// then assuming resignOnTouchOutside is disable.
|
||||||
|
if disabledTouchResignedClasses.contains(where: { controller.isKind(of: $0) }) {
|
||||||
|
isEnabled = false
|
||||||
|
} else {
|
||||||
|
let classNameString: String = "\(type(of: controller.self))"
|
||||||
|
|
||||||
|
// _UIAlertControllerTextFieldViewController
|
||||||
|
if classNameString.contains("UIAlertController"),
|
||||||
|
classNameString.hasSuffix("TextFieldViewController") {
|
||||||
|
isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isEnabled
|
||||||
|
case .enabled:
|
||||||
|
return true
|
||||||
|
case .disabled:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
fileprivate extension IQTextInputView {
|
||||||
|
var internalResignOnTouchOutsideMode: IQEnableMode {
|
||||||
|
iq.resignOnTouchOutsideMode
|
||||||
|
}
|
||||||
|
}
|
||||||
154
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/IQKeyboardResignHandler.swift
generated
Normal file
154
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/IQKeyboardResignHandler.swift
generated
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardResignHandler.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQTextInputViewNotification
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers internal final class IQKeyboardResignHandler: NSObject {
|
||||||
|
|
||||||
|
let textInputViewObserver: IQTextInputViewNotification = .init()
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resigns Keyboard on touching outside of TextInputView. Default is NO.
|
||||||
|
*/
|
||||||
|
public var resignOnTouchOutside: Bool = false {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
resignGesture.isEnabled = privateResignOnTouchOutside()
|
||||||
|
|
||||||
|
IQKeyboardManager.shared.showLog("resignOnTouchOutside: \(resignOnTouchOutside ? "Yes" : "No")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** TapGesture to resign keyboard on view's touch.
|
||||||
|
It's a readonly property and exposed only for adding/removing dependencies
|
||||||
|
if your added gesture does have collision with this one
|
||||||
|
*/
|
||||||
|
public var resignGesture: UITapGestureRecognizer = .init()
|
||||||
|
|
||||||
|
/**
|
||||||
|
Disabled classes to ignore resignOnTouchOutside' property, Class should be kind of UIViewController.
|
||||||
|
*/
|
||||||
|
public var disabledTouchResignedClasses: [UIViewController.Type] = [
|
||||||
|
UIAlertController.self,
|
||||||
|
UIInputViewController.self
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enabled classes to forcefully enable 'resignOnTouchOutside' property.
|
||||||
|
Class should be kind of UIViewController
|
||||||
|
. If same Class is added in disabledTouchResignedClasses list, then enabledTouchResignedClasses will be ignored.
|
||||||
|
*/
|
||||||
|
public var enabledTouchResignedClasses: [UIViewController.Type] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
if resignOnTouchOutside is enabled then you can customize the behavior
|
||||||
|
to not recognize gesture touches on some specific view subclasses.
|
||||||
|
Class should be kind of UIView. Default is [UIControl, UINavigationBar]
|
||||||
|
*/
|
||||||
|
public var touchResignedGestureIgnoreClasses: [UIView.Type] = [
|
||||||
|
UIControl.self,
|
||||||
|
UINavigationBar.self
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resigns currently first responder field.
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
public func resignFirstResponder() -> Bool {
|
||||||
|
|
||||||
|
guard let textInputView: any IQTextInputView = textInputViewObserver.textInputView else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resigning first responder
|
||||||
|
guard textInputView.resignFirstResponder() else {
|
||||||
|
IQKeyboardManager.shared.showLog("Warning: Refuses to resign first responder: \(textInputView)")
|
||||||
|
// If it refuses then becoming it as first responder again. (Bug ID: #96)
|
||||||
|
// If it refuses to resign then becoming it first responder again for getting notifications callback.
|
||||||
|
textInputView.becomeFirstResponder()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public override init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
resignGesture.addTarget(self, action: #selector(self.tapRecognized(_:)))
|
||||||
|
resignGesture.cancelsTouchesInView = false
|
||||||
|
resignGesture.delegate = self
|
||||||
|
resignGesture.isEnabled = false
|
||||||
|
|
||||||
|
addTextInputViewObserver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension IQKeyboardResignHandler: UIGestureRecognizerDelegate {
|
||||||
|
|
||||||
|
/** Resigning on tap gesture. (Enhancement ID: #14)*/
|
||||||
|
private func tapRecognized(_ gesture: UITapGestureRecognizer) {
|
||||||
|
|
||||||
|
if gesture.state == .ended {
|
||||||
|
|
||||||
|
// Resigning currently responder textInputView.
|
||||||
|
resignFirstResponder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Note: returning YES is guaranteed to allow simultaneous recognition.
|
||||||
|
returning NO is not guaranteed to prevent simultaneous recognition,
|
||||||
|
as the other gesture's delegate may return YES.
|
||||||
|
*/
|
||||||
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
|
||||||
|
shouldRecognizeSimultaneouslyWith
|
||||||
|
otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
To not detect touch events in a subclass of UIControl,
|
||||||
|
these may have added their own selector for specific work
|
||||||
|
*/
|
||||||
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
|
||||||
|
shouldReceive touch: UITouch) -> Bool {
|
||||||
|
// (Bug ID: #145)
|
||||||
|
// Should not recognize gesture if the clicked view is either UIControl or UINavigationBar(<Back button etc...)
|
||||||
|
|
||||||
|
for ignoreClass in touchResignedGestureIgnoreClasses where touch.view?.isKind(of: ignoreClass) ?? false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Issue #2109) Ignore Apple Pencil touches to prevent conflicts with floating keyboard on iPad
|
||||||
|
if touch.type == .pencil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
69
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/UIKItExtensions/UIView+Resign.swift
generated
Normal file
69
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/UIKItExtensions/UIView+Resign.swift
generated
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
//
|
||||||
|
// UIView+Resign.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var resignOnTouchOutsideMode: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
TextInputView category for managing touch resign
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQKeyboardExtension where Base: IQTextInputView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Override resigns Keyboard on touching outside of TextInputView behavior for this particular textInputView.
|
||||||
|
*/
|
||||||
|
var resignOnTouchOutsideMode: IQEnableMode {
|
||||||
|
get {
|
||||||
|
guard let base = base else {
|
||||||
|
return .default
|
||||||
|
}
|
||||||
|
return objc_getAssociatedObject(base, &AssociatedKeys.resignOnTouchOutsideMode) as? IQEnableMode ?? .default
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
if let base = base {
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.resignOnTouchOutsideMode,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension UIView {
|
||||||
|
@available(*, unavailable, renamed: "iq.resignOnTouchOutsideMode")
|
||||||
|
var shouldResignOnTouchOutsideMode: IQEnableMode {
|
||||||
|
get { .default }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
45
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/UIKItExtensions/UIView+ResignObjc.swift
generated
Normal file
45
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Resign/UIKItExtensions/UIView+ResignObjc.swift
generated
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// UIView+ResignObjc.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
// MARK: For ObjectiveC Compatibility
|
||||||
|
|
||||||
|
// swiftlint:disable identifier_name
|
||||||
|
@objc public extension UITextField {
|
||||||
|
|
||||||
|
var iq_resignOnTouchOutsideMode: IQEnableMode {
|
||||||
|
get { iq.resignOnTouchOutsideMode }
|
||||||
|
set { iq.resignOnTouchOutsideMode = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public extension UITextView {
|
||||||
|
|
||||||
|
var iq_resignOnTouchOutsideMode: IQEnableMode {
|
||||||
|
get { iq.resignOnTouchOutsideMode }
|
||||||
|
set { iq.resignOnTouchOutsideMode = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable identifier_name
|
||||||
21
Pods/IQKeyboardManagerSwift/LICENSE.md
generated
Normal file
21
Pods/IQKeyboardManagerSwift/LICENSE.md
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2013-2023 Iftekhar Qurashi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
254
Pods/IQKeyboardManagerSwift/README.md
generated
Normal file
254
Pods/IQKeyboardManagerSwift/README.md
generated
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/Social.png" alt="Icon"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[](https://github.com/hackiftekhar/IQKeyboardManager/blob/master/LICENSE.md)
|
||||||
|
[](https://travis-ci.org/hackiftekhar/IQKeyboardManager)
|
||||||
|

|
||||||
|
[](http://cocoadocs.org/docsets/IQKeyboardManagerSwift)
|
||||||
|
[](https://github.com/hackiftekhar/IQKeyboardManager/tags)
|
||||||
|
|
||||||
|
## IQKeyboardManager Objective-C version source code is moved to https://github.com/hackiftekhar/IQKeyboardManagerObjC
|
||||||
|
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
While developing iOS apps, we often run into issues where the iPhone keyboard slides up and covers the `UITextField/UITextView`. `IQKeyboardManager` allows you to prevent this issue of keyboard sliding up and covering `UITextField/UITextView` without needing you to write any code or make any additional setup. To use `IQKeyboardManager` you simply need to add source files to your project.
|
||||||
|
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
1. **One Line of Code** - Just enable and it works
|
||||||
|
2. **Works Automatically** - No manual setup required
|
||||||
|
3. **No More UIScrollView** - Automatically handles scroll views
|
||||||
|
4. **No More Subclasses** - Works with standard UIKit components
|
||||||
|
5. **No More Manual Work** - Handles all edge cases automatically
|
||||||
|
6. **Modular Architecture** - Include only what you need via subspecs
|
||||||
|
|
||||||
|
### What's Included
|
||||||
|
|
||||||
|
- ✅ Automatic keyboard avoidance for UITextField/UITextView
|
||||||
|
- ✅ Support for UIScrollView, UITableView, UICollectionView
|
||||||
|
- ✅ All interface orientations
|
||||||
|
- ✅ Configurable keyboard distance
|
||||||
|
- ✅ Class-level enable/disable control
|
||||||
|
|
||||||
|
### Optional Features (via Subspecs)
|
||||||
|
|
||||||
|
- 📦 Toolbar with Previous/Next/Done buttons
|
||||||
|
- 📦 Return key handling customization
|
||||||
|
- 📦 Tap-to-resign keyboard
|
||||||
|
- 📦 Keyboard appearance configuration
|
||||||
|
- 📦 UITextView with placeholder supportv
|
||||||
|
|
||||||
|
## Subspecs
|
||||||
|
|
||||||
|
Now IQKeyboardManagerSwift uses a modular architecture with subspecs.
|
||||||
|
By default, all subspecs are included, but you can include only what you need:
|
||||||
|
|
||||||
|
### Available Subspecs
|
||||||
|
|
||||||
|
- **Core** (always included): Basic keyboard distance management
|
||||||
|
- **Appearance**: Keyboard appearance configuration
|
||||||
|
- **IQKeyboardReturnManager**: Return key handling
|
||||||
|
- **IQKeyboardToolbarManager**: Toolbar functionality (Previous/Next/Done buttons)
|
||||||
|
- **IQTextView**: UITextView with placeholder support
|
||||||
|
- **Resign**: Tap-to-resign keyboard functionality
|
||||||
|
|
||||||
|
### Including Specific Subspecs
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Include toolbar example
|
||||||
|
pod 'IQKeyboardManagerSwift/IQKeyboardToolbarManager'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshot
|
||||||
|
[](http://youtu.be/6nhLw6hju2A)
|
||||||
|
[](http://youtu.be/6nhLw6hju2A)
|
||||||
|
[](http://youtu.be/6nhLw6hju2A)
|
||||||
|
[](http://youtu.be/6nhLw6hju2A)
|
||||||
|
[](http://youtu.be/6nhLw6hju2A)
|
||||||
|
|
||||||
|
## GIF animation
|
||||||
|
[](http://youtu.be/6nhLw6hju2A)
|
||||||
|
|
||||||
|
## Video
|
||||||
|
|
||||||
|
<a href="http://youtu.be/WAYc2Qj-OQg" target="_blank"><img src="http://img.youtube.com/vi/WAYc2Qj-OQg/0.jpg"
|
||||||
|
alt="IQKeyboardManager Demo Video" width="480" height="360" border="10" /></a>
|
||||||
|
|
||||||
|
## Tutorial video by @rebeloper ([#1135](https://github.com/hackiftekhar/IQKeyboardManager/issues/1135))
|
||||||
|
|
||||||
|
@rebeloper demonstrated two videos on how to implement **IQKeyboardManager** at it's core:
|
||||||
|
|
||||||
|
<a href="https://www.youtube.com/playlist?list=PL_csAAO9PQ8aTL87XnueOXi3RpWE2m_8v" target="_blank"><img src="https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/ThirdPartyYoutubeTutorial.jpg"
|
||||||
|
alt="Youtube Tutorial Playlist"/></a>
|
||||||
|
|
||||||
|
https://www.youtube.com/playlist?list=PL_csAAO9PQ8aTL87XnueOXi3RpWE2m_8v
|
||||||
|
|
||||||
|
## Warning
|
||||||
|
|
||||||
|
- **If you're planning to build SDK/library/framework and want to handle UITextField/UITextView with IQKeyboardManager then you're totally going the wrong way.** I would never suggest to add **IQKeyboardManager** as **dependency/adding/shipping** with any third-party library. Instead of adding **IQKeyboardManager** you should implement your own solution to achieve same kind of results. **IQKeyboardManager** is totally designed for projects to help developers for their convenience, it's not designed for **adding/dependency/shipping** with any **third-party library**, because **doing this could block adoption by other developers for their projects as well (who are not using IQKeyboardManager and have implemented their custom solution to handle UITextField/UITextView in the project).**
|
||||||
|
- If **IQKeyboardManager** conflicts with other **third-party library**, then it's **developer responsibility** to **enable/disable IQKeyboardManager** when **presenting/dismissing** third-party library UI. Third-party libraries are not responsible to handle IQKeyboardManager.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
| | Minimum iOS Target | Minimum Xcode Version |
|
||||||
|
|------------------------|--------------------|-----------------------|
|
||||||
|
| IQKeyboardManagerSwift | iOS 13.0 | Xcode 13 |
|
||||||
|
| Demo Project | | Xcode 15 |
|
||||||
|
|
||||||
|
#### Swift versions support
|
||||||
|
|
||||||
|
| Swift | Xcode | IQKeyboardManagerSwift |
|
||||||
|
|-------------------|-------|------------------------|
|
||||||
|
| 5.9, 5.8, 5.7 | 16 | >= 7.0.0 |
|
||||||
|
| 5.9, 5.8, 5.7, 5.6| 15 | >= 7.0.0 |
|
||||||
|
| 5.5, 5.4, 5.3, 5.2, 5.1, 5.0, 4.2| 11 | >= 6.5.7 |
|
||||||
|
| 5.1, 5.0, 4.2, 4.0, 3.2, 3.0| 11 | >= 6.5.0 |
|
||||||
|
| 5.0,4.2, 4.0, 3.2, 3.0| 10.2 | >= 6.2.1 |
|
||||||
|
| 4.2, 4.0, 3.2, 3.0| 10.0 | >= 6.0.4 |
|
||||||
|
| 4.0, 3.2, 3.0 | 9.0 | 5.0.0 |
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
==========================
|
||||||
|
|
||||||
|
#### CocoaPods
|
||||||
|
|
||||||
|
To install it, simply add the following line to your Podfile: ([#236](https://github.com/hackiftekhar/IQKeyboardManager/issues/236))
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pod 'IQKeyboardManagerSwift'
|
||||||
|
```
|
||||||
|
|
||||||
|
*Or you can choose the version you need based on Swift support table from [Requirements](README.md#requirements)*
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pod 'IQKeyboardManagerSwift', '8.0.0'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Carthage
|
||||||
|
|
||||||
|
To integrate `IQKeyboardManger` or `IQKeyboardManagerSwift` into your Xcode project using Carthage, add the following line to your `Cartfile`:
|
||||||
|
|
||||||
|
```ogdl
|
||||||
|
github "hackiftekhar/IQKeyboardManager"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `carthage update --use-xcframeworks` to build the frameworks and drag `IQKeyboardManagerSwift.xcframework` into your Xcode project based on your need. Make sure to add only one framework, not both.
|
||||||
|
|
||||||
|
#### Swift Package Manager (SPM)
|
||||||
|
|
||||||
|
To install `IQKeyboardManagerSwift` package via Xcode
|
||||||
|
|
||||||
|
* Go to File -> Swift Packages -> Add Package Dependency...
|
||||||
|
* Then search for https://github.com/hackiftekhar/IQKeyboardManager.git
|
||||||
|
* And choose the version you want
|
||||||
|
|
||||||
|
#### Source Code
|
||||||
|
|
||||||
|
***IQKeyboardManagerSwift:*** Source code installation is not supported (since 7.2.0) because now the library depends on some other independent libraries. Due to this you may face compilation issues.
|
||||||
|
|
||||||
|
#### Basic Usage
|
||||||
|
|
||||||
|
### Minimal Setup (Core Only)
|
||||||
|
|
||||||
|
In `AppDelegate.swift`, import and enable IQKeyboardManager:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import IQKeyboardManagerSwift
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
|
var window: UIWindow?
|
||||||
|
|
||||||
|
func application(_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
|
||||||
|
// Enable keyboard management
|
||||||
|
IQKeyboardManager.shared.isEnabled = true
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! The keyboard will now automatically adjust to avoid covering text fields.
|
||||||
|
|
||||||
|
### With Toolbar (Requires IQKeyboardToolbarManager Subspec)
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import IQKeyboardManagerSwift
|
||||||
|
|
||||||
|
func application(_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
|
||||||
|
// Enable keyboard management
|
||||||
|
IQKeyboardManager.shared.isEnabled = true
|
||||||
|
|
||||||
|
// Enable toolbar (@Deprecated: Please use IQKeyboardToolbarManager pod independently)
|
||||||
|
IQKeyboardManager.shared.enableAutoToolbar = true
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With All Features
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import IQKeyboardManagerSwift
|
||||||
|
|
||||||
|
func application(_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
|
||||||
|
// Core functionality
|
||||||
|
IQKeyboardManager.shared.isEnabled = true
|
||||||
|
IQKeyboardManager.shared.keyboardDistance = 20.0
|
||||||
|
|
||||||
|
// Toolbar (if using IQKeyboardToolbarManager subspec)
|
||||||
|
IQKeyboardManager.shared.enableAutoToolbar = true
|
||||||
|
|
||||||
|
// Tap to resign (if using Resign subspec)
|
||||||
|
IQKeyboardManager.shared.resignOnTouchOutside = true
|
||||||
|
|
||||||
|
// Appearance (if using Appearance subspec)
|
||||||
|
IQKeyboardManager.shared.keyboardConfiguration.overrideKeyboardAppearance = true
|
||||||
|
IQKeyboardManager.shared.keyboardConfiguration.keyboardAppearance = .dark
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Migration Guide
|
||||||
|
==========================
|
||||||
|
- [IQKeyboardManager 2.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/blob/master/Documentation/MIGRATION%20GUIDE%201.0%20TO%202.0.md)
|
||||||
|
- [IQKeyboardManager 3.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/blob/master/Documentation/MIGRATION%20GUIDE%202.0%20TO%203.0.md)
|
||||||
|
- [IQKeyboardManager 4.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/blob/master/Documentation/MIGRATION%20GUIDE%203.0%20TO%204.0.md)
|
||||||
|
- [IQKeyboardManager 5.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/blob/master/Documentation/MIGRATION%20GUIDE%204.0%20TO%205.0.md)
|
||||||
|
- [IQKeyboardManager 6.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/blob/master/Documentation/MIGRATION%20GUIDE%205.0%20TO%206.0.md)
|
||||||
|
- [IQKeyboardManager 7.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/blob/master/Documentation/MIGRATION%20GUIDE%206.0%20TO%207.0.md)
|
||||||
|
- [IQKeyboardManager 8.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/blob/master/Documentation/MIGRATION%20GUIDE%207.0%20TO%208.0.md)
|
||||||
|
|
||||||
|
Other Links
|
||||||
|
==========================
|
||||||
|
|
||||||
|
- [Known Issues](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Known-Issues)
|
||||||
|
- [Manual Management Tweaks](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Manual-Management)
|
||||||
|
- [Properties and functions usage](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Properties-&-Functions)
|
||||||
|
|
||||||
|
## Dependency Diagram
|
||||||
|
[](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/IQKeyboardManagerDependency.jpg)
|
||||||
|
|
||||||
|
LICENSE
|
||||||
|
---
|
||||||
|
Distributed under the MIT License.
|
||||||
|
|
||||||
|
Contributions
|
||||||
|
---
|
||||||
|
Any contribution is more than welcome! You can contribute through pull requests and issues on GitHub.
|
||||||
|
|
||||||
|
Author
|
||||||
|
---
|
||||||
|
If you wish to contact me, email at: hack.iftekhar@gmail.com
|
||||||
14
Pods/IQKeyboardNotification/IQKeyboardNotification/Assets/PrivacyInfo.xcprivacy
generated
Normal file
14
Pods/IQKeyboardNotification/IQKeyboardNotification/Assets/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
243
Pods/IQKeyboardNotification/IQKeyboardNotification/Classes/IQKeyboardInfo.swift
generated
Normal file
243
Pods/IQKeyboardNotification/IQKeyboardNotification/Classes/IQKeyboardInfo.swift
generated
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardInfo.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardNotification
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
public struct IQKeyboardInfo: Equatable {
|
||||||
|
nonisolated public static func == (lhs: Self, rhs: Self) -> Bool {
|
||||||
|
lhs.event == rhs.event &&
|
||||||
|
lhs.endFrame.equalTo(rhs.endFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public enum Event: Int, CaseIterable {
|
||||||
|
case willShow
|
||||||
|
case didShow
|
||||||
|
case willChangeFrame
|
||||||
|
case didChangeFrame
|
||||||
|
case willHide
|
||||||
|
case didHide
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
public var notification: Notification.Name {
|
||||||
|
switch self {
|
||||||
|
case .willShow:
|
||||||
|
return UIResponder.keyboardWillShowNotification
|
||||||
|
case .didShow:
|
||||||
|
return UIResponder.keyboardDidShowNotification
|
||||||
|
case .willChangeFrame:
|
||||||
|
return UIResponder.keyboardWillChangeFrameNotification
|
||||||
|
case .didChangeFrame:
|
||||||
|
return UIResponder.keyboardDidChangeFrameNotification
|
||||||
|
case .willHide:
|
||||||
|
return UIResponder.keyboardWillHideNotification
|
||||||
|
case .didHide:
|
||||||
|
return UIResponder.keyboardDidHideNotification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let event: Event
|
||||||
|
|
||||||
|
/// `keyboardIsLocalUserInfoKey`.
|
||||||
|
public let isLocal: Bool
|
||||||
|
|
||||||
|
/// `UIKeyboardFrameBeginUserInfoKey`.
|
||||||
|
public let beginFrame: CGRect
|
||||||
|
|
||||||
|
/// `UIKeyboardFrameEndUserInfoKey`.
|
||||||
|
public let endFrame: CGRect
|
||||||
|
|
||||||
|
/// `UIKeyboardAnimationDurationUserInfoKey`.
|
||||||
|
public let animationDuration: TimeInterval
|
||||||
|
|
||||||
|
/// `UIKeyboardAnimationCurveUserInfoKey`.
|
||||||
|
public let animationCurve: UIView.AnimationCurve
|
||||||
|
|
||||||
|
public var animationOptions: UIView.AnimationOptions {
|
||||||
|
return UIView.AnimationOptions(rawValue: UInt(animationCurve.rawValue << 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isVisible: Bool {
|
||||||
|
endFrame.height > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
internal init(notification: Notification?, event: Event) {
|
||||||
|
self.event = event
|
||||||
|
|
||||||
|
let screenBounds: CGRect
|
||||||
|
if let screen: UIScreen = notification?.object as? UIScreen {
|
||||||
|
screenBounds = screen.bounds
|
||||||
|
} else {
|
||||||
|
screenBounds = UIScreen.main.bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
if let info: [AnyHashable: Any] = notification?.userInfo {
|
||||||
|
|
||||||
|
if let value = info[UIResponder.keyboardIsLocalUserInfoKey] as? Bool {
|
||||||
|
isLocal = value
|
||||||
|
} else {
|
||||||
|
isLocal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting keyboard animation.
|
||||||
|
if let curveValue: Int = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int,
|
||||||
|
let curve: UIView.AnimationCurve = UIView.AnimationCurve(rawValue: curveValue) {
|
||||||
|
animationCurve = curve
|
||||||
|
} else {
|
||||||
|
animationCurve = .easeOut
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting keyboard animation duration
|
||||||
|
if let duration: TimeInterval = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval,
|
||||||
|
duration != 0.0 {
|
||||||
|
animationDuration = duration
|
||||||
|
} else {
|
||||||
|
animationDuration = 0.25
|
||||||
|
}
|
||||||
|
|
||||||
|
if let beginKeyboardFrame: CGRect = info[UIResponder.keyboardFrameBeginUserInfoKey] as? CGRect {
|
||||||
|
beginFrame = Self.getKeyboardFrame(of: beginKeyboardFrame, inScreenBounds: screenBounds)
|
||||||
|
} else {
|
||||||
|
beginFrame = CGRect(x: 0, y: screenBounds.height, width: screenBounds.width, height: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var endFrame: CGRect
|
||||||
|
if let endKeyboardFrame: CGRect = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
|
||||||
|
endFrame = Self.getKeyboardFrame(of: endKeyboardFrame, inScreenBounds: screenBounds)
|
||||||
|
} else {
|
||||||
|
endFrame = CGRect(x: 0, y: screenBounds.height, width: screenBounds.width, height: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With iOS 26, the endFrame returns height equal to the input accessory view height even when keyboard is dismissed.
|
||||||
|
// Unfortunately we have to assume this hack for iOS 26.
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
if #available(iOS 26.0, *) {
|
||||||
|
switch event {
|
||||||
|
case .willHide, .didHide:
|
||||||
|
endFrame.origin.y += endFrame.size.height
|
||||||
|
endFrame.size.height = 0
|
||||||
|
case .willShow, .didShow, .willChangeFrame, .didChangeFrame:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
self.endFrame = endFrame
|
||||||
|
} else {
|
||||||
|
isLocal = true
|
||||||
|
animationCurve = .easeOut
|
||||||
|
animationDuration = 0.25
|
||||||
|
beginFrame = CGRect(x: 0, y: screenBounds.height, width: screenBounds.width, height: 0)
|
||||||
|
endFrame = CGRect(x: 0, y: screenBounds.height, width: screenBounds.width, height: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
public func animate(alongsideTransition transition: @escaping () -> Void, completion: (() -> Void)? = nil) {
|
||||||
|
|
||||||
|
// if let timing = UIView.AnimationCurve.RawValue(exactly: animationCurve.rawValue),
|
||||||
|
// let curve = UIView.AnimationCurve(rawValue: timing) {
|
||||||
|
// let animator = UIViewPropertyAnimator(duration: animationDuration, curve: curve) {
|
||||||
|
// transition()
|
||||||
|
// }
|
||||||
|
// animator.addCompletion { _ in
|
||||||
|
// completion?()
|
||||||
|
// }
|
||||||
|
// animator.isUserInteractionEnabled = true
|
||||||
|
// animator.startAnimation()
|
||||||
|
// } else {
|
||||||
|
var animationOptions: UIView.AnimationOptions = self.animationOptions
|
||||||
|
animationOptions.formUnion(.allowUserInteraction)
|
||||||
|
animationOptions.formUnion(.beginFromCurrentState)
|
||||||
|
UIView.animate(withDuration: animationDuration, delay: 0,
|
||||||
|
options: animationOptions,
|
||||||
|
animations: transition,
|
||||||
|
completion: { _ in
|
||||||
|
completion?()
|
||||||
|
})
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
private extension IQKeyboardInfo {
|
||||||
|
|
||||||
|
static func getKeyboardFrame(of rect: CGRect, inScreenBounds screenBounds: CGRect) -> CGRect {
|
||||||
|
var finalFrame: CGRect = rect
|
||||||
|
// If this is floating keyboard
|
||||||
|
if finalFrame.width < screenBounds.width,
|
||||||
|
finalFrame.maxY < screenBounds.height {
|
||||||
|
finalFrame.size = CGSize(width: finalFrame.size.width, height: 0)
|
||||||
|
} else {
|
||||||
|
// (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
|
||||||
|
// Calculating actual keyboard covered size respect to window,
|
||||||
|
// keyboard frame may be different when hardware keyboard is attached
|
||||||
|
let keyboardHeight = CGFloat.maximum(screenBounds.height - finalFrame.minY, 0)
|
||||||
|
finalFrame.size = CGSize(width: finalFrame.size.width, height: keyboardHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@objcMembers public class IQKeyboardInfoObjC: NSObject {
|
||||||
|
private let wrappedValue: IQKeyboardInfo
|
||||||
|
|
||||||
|
public var event: IQKeyboardInfo.Event { wrappedValue.event }
|
||||||
|
|
||||||
|
public var isLocal: Bool { wrappedValue.isLocal }
|
||||||
|
|
||||||
|
public var beginFrame: CGRect { wrappedValue.beginFrame }
|
||||||
|
|
||||||
|
public var endFrame: CGRect { wrappedValue.endFrame }
|
||||||
|
|
||||||
|
public var animationDuration: TimeInterval { wrappedValue.animationDuration }
|
||||||
|
|
||||||
|
public var animationCurve: UIView.AnimationCurve { wrappedValue.animationCurve }
|
||||||
|
|
||||||
|
public var animationOptions: UIView.AnimationOptions { wrappedValue.animationOptions }
|
||||||
|
|
||||||
|
public var isVisible: Bool { wrappedValue.isVisible }
|
||||||
|
|
||||||
|
init(wrappedValue: IQKeyboardInfo){
|
||||||
|
self.wrappedValue = wrappedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
public func animate(alongsideTransition transition: @escaping () -> Void, completion: (() -> Void)? = nil) {
|
||||||
|
wrappedValue.animate(alongsideTransition: transition, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Deprecated
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
public extension IQKeyboardInfo {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "event")
|
||||||
|
var name: Event { event }
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "isVisible")
|
||||||
|
var keyboardShowing: Bool { isVisible }
|
||||||
|
}
|
||||||
|
|
||||||
172
Pods/IQKeyboardNotification/IQKeyboardNotification/Classes/IQKeyboardNotification.swift
generated
Normal file
172
Pods/IQKeyboardNotification/IQKeyboardNotification/Classes/IQKeyboardNotification.swift
generated
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardNotification.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardNotification
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardNotification: NSObject {
|
||||||
|
|
||||||
|
private var storage: Set<AnyCancellable> = []
|
||||||
|
|
||||||
|
private var eventObservers: [IQKeyboardInfo.Event: [AnyHashable: SizeCompletion]] = [:]
|
||||||
|
|
||||||
|
public private(set) var oldKeyboardInfo: IQKeyboardInfo
|
||||||
|
|
||||||
|
public private(set) var keyboardInfo: IQKeyboardInfo {
|
||||||
|
didSet {
|
||||||
|
guard keyboardInfo != oldValue else { return }
|
||||||
|
oldKeyboardInfo = oldValue
|
||||||
|
sendKeyboardInfo(info: keyboardInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isVisible: Bool {
|
||||||
|
keyboardInfo.isVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
public var frame: CGRect {
|
||||||
|
keyboardInfo.endFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
public override init() {
|
||||||
|
keyboardInfo = IQKeyboardInfo(notification: nil, event: .didHide)
|
||||||
|
oldKeyboardInfo = keyboardInfo
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
// Registering for keyboard notification.
|
||||||
|
for event in IQKeyboardInfo.Event.allCases {
|
||||||
|
NotificationCenter.default.publisher(for: event.notification)
|
||||||
|
.map({ IQKeyboardInfo(notification: $0, event: event) })
|
||||||
|
.sink(receiveValue: { [weak self] keyboardInfo in
|
||||||
|
self?.keyboardInfo = keyboardInfo
|
||||||
|
})
|
||||||
|
.store(in: &storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func animate(alongsideTransition transition: @escaping () -> Void, completion: (() -> Void)? = nil) {
|
||||||
|
keyboardInfo.animate(alongsideTransition: transition, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQKeyboardNotification {
|
||||||
|
|
||||||
|
typealias SizeCompletion = (_ event: IQKeyboardInfo.Event, _ endFrame: CGRect) -> Void
|
||||||
|
|
||||||
|
func subscribe(for events: [IQKeyboardInfo.Event],
|
||||||
|
identifier: AnyHashable, changeHandler: @escaping SizeCompletion) {
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
var existingObservers: [AnyHashable: SizeCompletion] = eventObservers[event] ?? [:]
|
||||||
|
existingObservers[identifier] = changeHandler
|
||||||
|
eventObservers[event] = existingObservers
|
||||||
|
}
|
||||||
|
|
||||||
|
// If current event is the one user is subscribed to, then call changeHandler immediately for the first time.
|
||||||
|
if events.contains(keyboardInfo.event) {
|
||||||
|
changeHandler(keyboardInfo.event, keyboardInfo.endFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsubscribe(for events: [IQKeyboardInfo.Event], identifier: AnyHashable) {
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
var existingObservers: [AnyHashable: SizeCompletion] = eventObservers[event] ?? [:]
|
||||||
|
existingObservers[identifier] = nil
|
||||||
|
eventObservers[event] = existingObservers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSubscribed(for event: IQKeyboardInfo.Event? = nil, identifier: AnyHashable) -> Bool {
|
||||||
|
if let event = event {
|
||||||
|
guard let observers = eventObservers[event], !observers.isEmpty else { return false }
|
||||||
|
return observers[identifier] != nil
|
||||||
|
} else {
|
||||||
|
|
||||||
|
for event in IQKeyboardInfo.Event.allCases {
|
||||||
|
let observers = eventObservers[event] ?? [:]
|
||||||
|
if observers[identifier] != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendKeyboardInfo(info: IQKeyboardInfo) {
|
||||||
|
|
||||||
|
guard let observers = eventObservers[info.event], !observers.isEmpty else { return }
|
||||||
|
|
||||||
|
let endFrame: CGRect = info.endFrame
|
||||||
|
|
||||||
|
for block in observers.values {
|
||||||
|
block(info.event, endFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardNotification {
|
||||||
|
|
||||||
|
var oldKeyboardInfoObjc: IQKeyboardInfoObjC { IQKeyboardInfoObjC(wrappedValue: oldKeyboardInfo) }
|
||||||
|
|
||||||
|
var keyboardInfoObjc: IQKeyboardInfoObjC { IQKeyboardInfoObjC(wrappedValue: keyboardInfo) }
|
||||||
|
|
||||||
|
func subscribe(identifier: AnyHashable, changeHandler: @escaping SizeCompletion) {
|
||||||
|
subscribe(for: IQKeyboardInfo.Event.allCases, identifier: identifier, changeHandler: changeHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsubscribe(identifier: AnyHashable) {
|
||||||
|
unsubscribe(for: IQKeyboardInfo.Event.allCases, identifier: identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSubscribed(identifier: AnyHashable) -> Bool {
|
||||||
|
isSubscribed(for: nil, identifier: identifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Deprecated
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@objc public extension IQKeyboardNotification {
|
||||||
|
|
||||||
|
// MARK: Deprecated
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "isVisible")
|
||||||
|
var keyboardShowing: Bool { isVisible }
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "subscribe(identifier:changeHandler:)")
|
||||||
|
func registerSizeChange(identifier: AnyHashable, changeHandler: @escaping SizeCompletion) {
|
||||||
|
subscribe(identifier: identifier, changeHandler: changeHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "unsubscribe(identifier:)")
|
||||||
|
func unregisterSizeChange(identifier: AnyHashable) {
|
||||||
|
unsubscribe(identifier: identifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
21
Pods/IQKeyboardNotification/LICENSE
generated
Normal file
21
Pods/IQKeyboardNotification/LICENSE
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Mohd Iftekhar Qurashi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
66
Pods/IQKeyboardNotification/README.md
generated
Normal file
66
Pods/IQKeyboardNotification/README.md
generated
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# IQKeyboardNotification
|
||||||
|
Lightweight library to observe keyboard events with ease.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/hackiftekhar/IQKeyboardNotification)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardNotification)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardNotification)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardNotification)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
IQKeyboardNotification is available through [CocoaPods](https://cocoapods.org). To install
|
||||||
|
it, simply add the following line to your Podfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pod 'IQKeyboardNotification'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To observe keyboard events, subscribe to the keyboard events:-
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import IQKeyboardNotification
|
||||||
|
|
||||||
|
class ViewController: UIViewController {
|
||||||
|
|
||||||
|
private let keyboard: IQKeyboardNotification = .init()
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
// Subscribe
|
||||||
|
keyboard.subscribe(identifier: "YOUR_UNIQUE_IDENTIFIER") { event, frame in
|
||||||
|
print(frame)
|
||||||
|
// Write your own logic here based on event and keyboard frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
// Unsubscribe
|
||||||
|
keyboard.unsubscribe(identifier: "YOUR_UNIQUE_IDENTIFIER")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Iftekhar Qurashi hack.iftekhar@gmail.com
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
IQKeyboardNotification is available under the MIT license. See the LICENSE file for more info.
|
||||||
14
Pods/IQKeyboardReturnManager/IQKeyboardReturnManager/Assets/PrivacyInfo.xcprivacy
generated
Normal file
14
Pods/IQKeyboardReturnManager/IQKeyboardReturnManager/Assets/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,286 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardReturnManager+UITextFieldDelegate.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardReturnManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// MARK: UITextFieldDelegate
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension IQKeyboardReturnManager: UITextFieldDelegate {
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
|
||||||
|
|
||||||
|
var returnValue: Bool = true
|
||||||
|
|
||||||
|
if delegate == nil,
|
||||||
|
let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate {
|
||||||
|
let selector = #selector((any UITextFieldDelegate).textFieldShouldBeginEditing(_:))
|
||||||
|
if textFieldDelegate.responds(to: selector) {
|
||||||
|
returnValue = textFieldDelegate.textFieldShouldBeginEditing?(textField) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if returnValue {
|
||||||
|
updateReturnKey(textInputView: textField)
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
|
||||||
|
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate {
|
||||||
|
let selector = #selector((any UITextFieldDelegate).textFieldShouldEndEditing(_:))
|
||||||
|
if textFieldDelegate.responds(to: selector) {
|
||||||
|
return textFieldDelegate.textFieldShouldEndEditing?(textField) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextFieldDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) {
|
||||||
|
aDelegate = model.textFieldDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textFieldDidBeginEditing?(textField)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextFieldDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) {
|
||||||
|
aDelegate = model.textFieldDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textFieldDidEndEditing?(textField)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextFieldDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) {
|
||||||
|
aDelegate = model.textFieldDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textFieldDidEndEditing?(textField, reason: reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField,
|
||||||
|
shouldChangeCharactersIn range: NSRange,
|
||||||
|
replacementString string: String) -> Bool {
|
||||||
|
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate {
|
||||||
|
let selector: Selector = #selector((any UITextFieldDelegate).textField(_:shouldChangeCharactersIn:
|
||||||
|
replacementString:))
|
||||||
|
if textFieldDelegate.responds(to: selector) {
|
||||||
|
return textFieldDelegate.textField?(textField,
|
||||||
|
shouldChangeCharactersIn: range,
|
||||||
|
replacementString: string) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextFieldDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) {
|
||||||
|
aDelegate = model.textFieldDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textFieldDidChangeSelection?(textField)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldShouldClear(_ textField: UITextField) -> Bool {
|
||||||
|
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate {
|
||||||
|
let selector: Selector = #selector((any UITextFieldDelegate).textFieldShouldClear(_:))
|
||||||
|
if textFieldDelegate.responds(to: selector) {
|
||||||
|
return textFieldDelegate.textFieldShouldClear?(textField) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
var isReturn: Bool = true
|
||||||
|
|
||||||
|
if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate {
|
||||||
|
let selector: Selector = #selector((any UITextFieldDelegate).textFieldShouldReturn(_:))
|
||||||
|
if textFieldDelegate.responds(to: selector) {
|
||||||
|
isReturn = textFieldDelegate.textFieldShouldReturn?(textField) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isReturn {
|
||||||
|
goToNextResponderOrResign(from: textField)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return goToNextResponderOrResign(from: textField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=5.7) // Xcode 14
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, editMenuForCharactersIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? {
|
||||||
|
|
||||||
|
guard delegate == nil else { return nil }
|
||||||
|
|
||||||
|
if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate {
|
||||||
|
|
||||||
|
let selector = #selector((any UITextFieldDelegate).textField(_:editMenuForCharactersIn:suggestedActions:))
|
||||||
|
if textFieldDelegate.responds(to: selector) {
|
||||||
|
return textFieldDelegate.textField?(textField, editMenuForCharactersIn: range, suggestedActions: suggestedActions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, willPresentEditMenuWith animator: any UIEditMenuInteractionAnimating) {
|
||||||
|
var aDelegate: (any UITextFieldDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) {
|
||||||
|
aDelegate = model.textFieldDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textField?(textField, willPresentEditMenuWith: animator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, willDismissEditMenuWith animator: any UIEditMenuInteractionAnimating) {
|
||||||
|
var aDelegate: (any UITextFieldDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) {
|
||||||
|
aDelegate = model.textFieldDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textField?(textField, willDismissEditMenuWith: animator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if compiler(>=6.0) // Xcode 16
|
||||||
|
@available(iOS 18.0, *)
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
#if compiler(>=6.1) // Xcode 16.3
|
||||||
|
@available(iOS 18.4, *)
|
||||||
|
func textField(_ textField: UITextField, insertInputSuggestion inputSuggestion: UIInputSuggestion) {
|
||||||
|
var aDelegate: (any UITextFieldDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) {
|
||||||
|
aDelegate = model.textFieldDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textField?(textField, insertInputSuggestion: inputSuggestion)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
@available(iOS 26.0, *)
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, shouldChangeCharactersInRanges ranges: [NSValue], replacementString string: String) -> Bool {
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate {
|
||||||
|
let selector: Selector = #selector((any UITextFieldDelegate).textField(_:shouldChangeCharactersInRanges:
|
||||||
|
replacementString:))
|
||||||
|
if textFieldDelegate.responds(to: selector) {
|
||||||
|
return textFieldDelegate.textField?(textField,
|
||||||
|
shouldChangeCharactersInRanges: ranges,
|
||||||
|
replacementString: string) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, editMenuForCharactersInRanges ranges: [NSValue], suggestedActions: [UIMenuElement]) -> UIMenu? {
|
||||||
|
|
||||||
|
guard delegate == nil else { return nil }
|
||||||
|
|
||||||
|
if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate {
|
||||||
|
|
||||||
|
let selector = #selector((any UITextFieldDelegate).textField(_:editMenuForCharactersInRanges:suggestedActions:))
|
||||||
|
if textFieldDelegate.responds(to: selector) {
|
||||||
|
return textFieldDelegate.textField?(textField, editMenuForCharactersInRanges: ranges, suggestedActions: suggestedActions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -0,0 +1,532 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardReturnManager+UITextViewDelegate.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardReturnManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// MARK: UITextViewDelegate
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension IQKeyboardReturnManager: UITextViewDelegate {
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
|
||||||
|
|
||||||
|
var returnValue: Bool = true
|
||||||
|
|
||||||
|
if delegate == nil,
|
||||||
|
let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(textView)?.textViewDelegate {
|
||||||
|
if textViewDelegate.responds(to: #selector((any UITextViewDelegate).textViewShouldBeginEditing(_:))) {
|
||||||
|
returnValue = textViewDelegate.textViewShouldBeginEditing?(textView) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if returnValue {
|
||||||
|
updateReturnKey(textInputView: textView)
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
|
||||||
|
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(textView)?.textViewDelegate {
|
||||||
|
if textViewDelegate.responds(to: #selector((any UITextViewDelegate).textViewShouldEndEditing(_:))) {
|
||||||
|
return textViewDelegate.textViewShouldEndEditing?(textView) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textViewDidBeginEditing?(textView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewDidEndEditing(_ textView: UITextView) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textViewDidEndEditing?(textView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView,
|
||||||
|
shouldChangeTextIn range: NSRange,
|
||||||
|
replacementText text: String) -> Bool {
|
||||||
|
|
||||||
|
var shouldChange = true
|
||||||
|
|
||||||
|
if delegate == nil {
|
||||||
|
|
||||||
|
if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(textView)?.textViewDelegate {
|
||||||
|
let selector = #selector((any UITextViewDelegate).textView(_:shouldChangeTextIn:replacementText:))
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
shouldChange = (textViewDelegate.textView?(textView,
|
||||||
|
shouldChangeTextIn: range,
|
||||||
|
replacementText: text)) ?? true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.dismissTextViewOnReturn, text == "\n" {
|
||||||
|
goToNextResponderOrResign(from: textView)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldChange
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewDidChange(_ textView: UITextView) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textViewDidChange?(textView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewDidChangeSelection(_ textView: UITextView) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textViewDidChangeSelection?(textView)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS, deprecated: 17.0)
|
||||||
|
func textView(_ aTextView: UITextView,
|
||||||
|
shouldInteractWith URL: URL,
|
||||||
|
in characterRange: NSRange,
|
||||||
|
interaction: UITextItemInteraction) -> Bool {
|
||||||
|
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate {
|
||||||
|
|
||||||
|
let selector: Selector = #selector(textView as (UITextView, URL, NSRange, UITextItemInteraction) -> Bool)
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
return textViewDelegate.textView?(aTextView,
|
||||||
|
shouldInteractWith: URL,
|
||||||
|
in: characterRange,
|
||||||
|
interaction: interaction) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS, deprecated: 17.0)
|
||||||
|
func textView(_ aTextView: UITextView,
|
||||||
|
shouldInteractWith textAttachment: NSTextAttachment,
|
||||||
|
in characterRange: NSRange,
|
||||||
|
interaction: UITextItemInteraction) -> Bool {
|
||||||
|
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate {
|
||||||
|
let selector: Selector = #selector(textView as
|
||||||
|
(UITextView, NSTextAttachment, NSRange, UITextItemInteraction)
|
||||||
|
-> Bool)
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
return textViewDelegate.textView?(aTextView,
|
||||||
|
shouldInteractWith: textAttachment,
|
||||||
|
in: characterRange,
|
||||||
|
interaction: interaction) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS, deprecated: 10.0)
|
||||||
|
func textView(_ aTextView: UITextView,
|
||||||
|
shouldInteractWith URL: URL,
|
||||||
|
in characterRange: NSRange) -> Bool {
|
||||||
|
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate {
|
||||||
|
|
||||||
|
let selector: Selector = #selector(textView as (UITextView, URL, NSRange) -> Bool)
|
||||||
|
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
return textViewDelegate.textView?(aTextView,
|
||||||
|
shouldInteractWith: URL,
|
||||||
|
in: characterRange) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS, deprecated: 10.0)
|
||||||
|
func textView(_ aTextView: UITextView,
|
||||||
|
shouldInteractWith textAttachment: NSTextAttachment,
|
||||||
|
in characterRange: NSRange) -> Bool {
|
||||||
|
|
||||||
|
guard delegate == nil else { return true }
|
||||||
|
|
||||||
|
if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate {
|
||||||
|
|
||||||
|
let selector: Selector = #selector(textView as (UITextView, NSTextAttachment, NSRange) -> Bool)
|
||||||
|
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
return textViewDelegate.textView?(aTextView,
|
||||||
|
shouldInteractWith: textAttachment,
|
||||||
|
in: characterRange) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=5.7) // Xcode 14
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
func textView(_ aTextView: UITextView,
|
||||||
|
editMenuForTextIn range: NSRange,
|
||||||
|
suggestedActions: [UIMenuElement]) -> UIMenu? {
|
||||||
|
|
||||||
|
guard delegate == nil else { return nil }
|
||||||
|
|
||||||
|
if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate {
|
||||||
|
|
||||||
|
let selector = #selector((any UITextViewDelegate).textView(_:editMenuForTextIn:suggestedActions:))
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
return textViewDelegate.textView?(aTextView,
|
||||||
|
editMenuForTextIn: range,
|
||||||
|
suggestedActions: suggestedActions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ aTextView: UITextView,
|
||||||
|
willPresentEditMenuWith animator: any UIEditMenuInteractionAnimating) {
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(aTextView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textView?(aTextView, willPresentEditMenuWith: animator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ aTextView: UITextView,
|
||||||
|
willDismissEditMenuWith animator: any UIEditMenuInteractionAnimating) {
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(aTextView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textView?(aTextView, willDismissEditMenuWith: animator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if compiler(>=5.9) // Xcode 15
|
||||||
|
@available(iOS 17.0, *)
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
func textView(_ aTextView: UITextView,
|
||||||
|
primaryActionFor textItem: UITextItem,
|
||||||
|
defaultAction: UIAction) -> UIAction? {
|
||||||
|
guard delegate == nil else { return nil }
|
||||||
|
|
||||||
|
if let textViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate {
|
||||||
|
let selector = #selector((any UITextViewDelegate).textView(_:primaryActionFor:defaultAction:))
|
||||||
|
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
return textViewDelegate.textView?(aTextView,
|
||||||
|
primaryActionFor: textItem,
|
||||||
|
defaultAction: defaultAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ aTextView: UITextView,
|
||||||
|
menuConfigurationFor textItem: UITextItem,
|
||||||
|
defaultMenu: UIMenu) -> UITextItem.MenuConfiguration? {
|
||||||
|
guard delegate == nil else { return nil }
|
||||||
|
|
||||||
|
if let textViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate {
|
||||||
|
let selector = #selector((any UITextViewDelegate).textView(_:menuConfigurationFor:defaultMenu:))
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
return textViewDelegate.textView?(aTextView,
|
||||||
|
menuConfigurationFor: textItem,
|
||||||
|
defaultMenu: defaultMenu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView,
|
||||||
|
textItemMenuWillDisplayFor textItem: UITextItem,
|
||||||
|
animator: any UIContextMenuInteractionAnimating) {
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textView?(textView, textItemMenuWillDisplayFor: textItem, animator: animator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView,
|
||||||
|
textItemMenuWillEndFor textItem: UITextItem,
|
||||||
|
animator: any UIContextMenuInteractionAnimating) {
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textView?(textView, textItemMenuWillEndFor: textItem, animator: animator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if compiler(>=6.0) // Xcode 16
|
||||||
|
@available(iOS 18.0, *)
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
func textViewWritingToolsWillBegin(_ textView: UITextView) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textViewWritingToolsWillBegin?(textView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewWritingToolsDidEnd(_ textView: UITextView) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textViewWritingToolsDidEnd?(textView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView,
|
||||||
|
writingToolsIgnoredRangesInEnclosingRange enclosingRange: NSRange) -> [NSValue] {
|
||||||
|
guard delegate == nil else { return [] }
|
||||||
|
|
||||||
|
if let textViewDelegate = textInputViewCachedInfo(textView)?.textViewDelegate {
|
||||||
|
let selector = #selector((any UITextViewDelegate).textView(_:writingToolsIgnoredRangesInEnclosingRange:))
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
return textViewDelegate.textView?(textView,
|
||||||
|
writingToolsIgnoredRangesInEnclosingRange: enclosingRange) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView, willBeginFormattingWith viewController: UITextFormattingViewController) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textView?(textView, willBeginFormattingWith: viewController)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView, didBeginFormattingWith viewController: UITextFormattingViewController) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textView?(textView, didBeginFormattingWith: viewController)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView, willEndFormattingWith viewController: UITextFormattingViewController) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textView?(textView, willEndFormattingWith: viewController)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView, didEndFormattingWith viewController: UITextFormattingViewController) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textView?(textView, didEndFormattingWith: viewController)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if compiler(>=6.1) // Xcode 16.3
|
||||||
|
@available(iOS 18.4, *)
|
||||||
|
func textView(_ textView: UITextView, insertInputSuggestion inputSuggestion: UIInputSuggestion) {
|
||||||
|
|
||||||
|
var aDelegate: (any UITextViewDelegate)? = delegate
|
||||||
|
|
||||||
|
if aDelegate == nil {
|
||||||
|
|
||||||
|
if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) {
|
||||||
|
aDelegate = model.textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aDelegate?.textView?(textView, insertInputSuggestion: inputSuggestion)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
@available(iOS 26.0, *)
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView, shouldChangeTextInRanges ranges: [NSValue], replacementText text: String) -> Bool {
|
||||||
|
|
||||||
|
var shouldChange = true
|
||||||
|
|
||||||
|
if delegate == nil {
|
||||||
|
|
||||||
|
if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(textView)?.textViewDelegate {
|
||||||
|
let selector = #selector((any UITextViewDelegate).textView(_:shouldChangeTextInRanges:replacementText:))
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
shouldChange = (textViewDelegate.textView?(textView,
|
||||||
|
shouldChangeTextInRanges: ranges,
|
||||||
|
replacementText: text)) ?? true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.dismissTextViewOnReturn, text == "\n" {
|
||||||
|
goToNextResponderOrResign(from: textView)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldChange
|
||||||
|
}
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView, editMenuForTextInRanges ranges: [NSValue], suggestedActions: [UIMenuElement]) -> UIMenu? {
|
||||||
|
|
||||||
|
guard delegate == nil else { return nil }
|
||||||
|
|
||||||
|
if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(textView)?.textViewDelegate {
|
||||||
|
|
||||||
|
let selector = #selector((any UITextViewDelegate).textView(_:editMenuForTextInRanges:suggestedActions:))
|
||||||
|
if textViewDelegate.responds(to: selector) {
|
||||||
|
return textViewDelegate.textView?(textView,
|
||||||
|
editMenuForTextInRanges: ranges,
|
||||||
|
suggestedActions: suggestedActions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
278
Pods/IQKeyboardReturnManager/IQKeyboardReturnManager/Classes/IQKeyboardReturnManager.swift
generated
Normal file
278
Pods/IQKeyboardReturnManager/IQKeyboardReturnManager/Classes/IQKeyboardReturnManager.swift
generated
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardReturnManager.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardReturnManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
/**
|
||||||
|
Manages the return key to work like next/done in a view hierarchy.
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardReturnManager: NSObject {
|
||||||
|
|
||||||
|
// MARK: Private variables
|
||||||
|
private var textInputViewInfoCache: [IQTextInputViewInfoModel] = []
|
||||||
|
|
||||||
|
// MARK: Settings
|
||||||
|
|
||||||
|
/**
|
||||||
|
Delegate of textInputView
|
||||||
|
*/
|
||||||
|
public weak var delegate: (any UITextFieldDelegate & UITextViewDelegate)?
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set the last textInputView return key type. Default is UIReturnKeyDefault.
|
||||||
|
*/
|
||||||
|
public var lastTextInputViewReturnKeyType: UIReturnKeyType = .default {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
if let activeModel = textInputViewInfoCache.first(where: {
|
||||||
|
guard let textInputView = $0.textInputView else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return textInputView.isFirstResponder
|
||||||
|
}), let view: any IQTextInputView = activeModel.textInputView {
|
||||||
|
updateReturnKey(textInputView: view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var dismissTextViewOnReturn: Bool = false
|
||||||
|
|
||||||
|
// MARK: Initialization/De-initialization
|
||||||
|
|
||||||
|
public override init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, message: "Please use addResponderSubviews(of:recursive:)")
|
||||||
|
public init(controller: UIViewController) {
|
||||||
|
super.init()
|
||||||
|
addResponderSubviews(of: controller.view, recursive: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
|
||||||
|
// for model in textInputViewInfoCache {
|
||||||
|
// model.restore()
|
||||||
|
// }
|
||||||
|
|
||||||
|
textInputViewInfoCache.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Registering/Unregistering textInputView
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Should pass TextInputView instance. Assign textInputView delegate to self, change it's returnKeyType.
|
||||||
|
|
||||||
|
@param textInputView TextInputView object to register.
|
||||||
|
*/
|
||||||
|
func add(textInputView: any IQTextInputView) {
|
||||||
|
|
||||||
|
let model = IQTextInputViewInfoModel(textInputView: textInputView)
|
||||||
|
textInputViewInfoCache.append(model)
|
||||||
|
|
||||||
|
if let view: UITextField = textInputView as? UITextField {
|
||||||
|
view.delegate = self
|
||||||
|
} else if let view: UITextView = textInputView as? UITextView {
|
||||||
|
view.delegate = self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Should pass TextInputView instance. Restore it's textInputView delegate and it's returnKeyType.
|
||||||
|
|
||||||
|
@param textInputView TextInputView object to unregister.
|
||||||
|
*/
|
||||||
|
func remove(textInputView: any IQTextInputView) {
|
||||||
|
|
||||||
|
guard let index: Int = textInputViewCachedInfoIndex(textInputView) else { return }
|
||||||
|
|
||||||
|
let model = textInputViewInfoCache.remove(at: index)
|
||||||
|
model.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Add all the TextInputView responderView's.
|
||||||
|
|
||||||
|
@param view object to register all it's responder subviews.
|
||||||
|
*/
|
||||||
|
func addResponderSubviews(of view: UIView, recursive: Bool) {
|
||||||
|
|
||||||
|
let textInputViews: [any IQTextInputView] = view.responderSubviews(recursive: recursive)
|
||||||
|
|
||||||
|
for view in textInputViews {
|
||||||
|
add(textInputView: view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Remove all the TextInputView responderView's.
|
||||||
|
|
||||||
|
@param view object to unregister all it's responder subviews.
|
||||||
|
*/
|
||||||
|
func removeResponderSubviews(of view: UIView, recursive: Bool) {
|
||||||
|
|
||||||
|
let textInputViews: [any IQTextInputView] = view.responderSubviews(recursive: recursive)
|
||||||
|
|
||||||
|
for view in textInputViews {
|
||||||
|
remove(textInputView: view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
@discardableResult
|
||||||
|
func goToNextResponderOrResign(from textInputView: any IQTextInputView) -> Bool {
|
||||||
|
|
||||||
|
guard let textInfoCache: IQTextInputViewInfoModel = nextResponderFromTextInputView(textInputView),
|
||||||
|
let textInputView = textInfoCache.textInputView else {
|
||||||
|
textInputView.resignFirstResponder()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
textInputView.becomeFirstResponder()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Internal Functions
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
func nextResponderFromTextInputView(_ textInputView: some IQTextInputView) -> IQTextInputViewInfoModel? {
|
||||||
|
guard let currentIndex: Int = textInputViewCachedInfoIndex(textInputView),
|
||||||
|
currentIndex < textInputViewInfoCache.count - 1 else { return nil }
|
||||||
|
|
||||||
|
let candidates = Array(textInputViewInfoCache[currentIndex+1..<textInputViewInfoCache.count])
|
||||||
|
|
||||||
|
return candidates.first {
|
||||||
|
guard let inputView = $0.textInputView,
|
||||||
|
inputView.isUserInteractionEnabled,
|
||||||
|
!inputView.isHidden, inputView.alpha != 0.0
|
||||||
|
else { return false }
|
||||||
|
|
||||||
|
return inputView.iqIsEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func textInputViewCachedInfoIndex(_ textInputView: some IQTextInputView) -> Int? {
|
||||||
|
return textInputViewInfoCache.firstIndex {
|
||||||
|
guard let inputView = $0.textInputView else { return false }
|
||||||
|
return inputView == textInputView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func textInputViewCachedInfo(_ textInputView: some IQTextInputView) -> IQTextInputViewInfoModel? {
|
||||||
|
guard let index: Int = textInputViewCachedInfoIndex(textInputView) else { return nil }
|
||||||
|
return textInputViewInfoCache[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateReturnKey(textInputView: some IQTextInputView) {
|
||||||
|
|
||||||
|
let returnKey: UIReturnKeyType
|
||||||
|
if nextResponderFromTextInputView(textInputView) != nil {
|
||||||
|
returnKey = .next
|
||||||
|
} else {
|
||||||
|
returnKey = lastTextInputViewReturnKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
if textInputView.returnKeyType != returnKey {
|
||||||
|
// If it's the last textInputView in responder view, else next
|
||||||
|
textInputView.returnKeyType = returnKey
|
||||||
|
textInputView.reloadInputViews()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Deprecated
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardReturnManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "lastTextInputViewReturnKeyType")
|
||||||
|
var lastTextFieldReturnKeyType: UIReturnKeyType {
|
||||||
|
get { .default }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "add(textInputView:)")
|
||||||
|
func addTextFieldView(_ textInputView: any IQTextInputView) { }
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "remove(textInputView:)")
|
||||||
|
func removeTextFieldView(_ textInputView: any IQTextInputView) { }
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "addResponderSubviews(of:recursive:)")
|
||||||
|
func addResponderFromView(_ view: UIView, recursive: Bool) { }
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "removeResponderSubviews(of:recursive:)")
|
||||||
|
func removeResponderFromView(_ view: UIView, recursive: Bool = true) { }
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
fileprivate extension UIView {
|
||||||
|
|
||||||
|
func responderSubviews(recursive: Bool) -> [any IQTextInputView] {
|
||||||
|
|
||||||
|
// Array of TextInputViews.
|
||||||
|
var textInputViews: [any IQTextInputView] = []
|
||||||
|
for view in subviews {
|
||||||
|
|
||||||
|
if let view = view as? IQTextInputView {
|
||||||
|
textInputViews.append(view)
|
||||||
|
}
|
||||||
|
// Sometimes there are hidden or disabled views and textInputView inside them still recorded,
|
||||||
|
// so we added some more validations here (Bug ID: #458)
|
||||||
|
// Uncommented else (Bug ID: #625)
|
||||||
|
else if recursive, !view.subviews.isEmpty {
|
||||||
|
let deepResponders = view.responderSubviews(recursive: recursive)
|
||||||
|
textInputViews.append(contentsOf: deepResponders)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// subviews are returning in opposite order. Sorting according the frames 'y'.
|
||||||
|
return textInputViews.sorted(by: { (view1: any IQTextInputView, view2: any IQTextInputView) -> Bool in
|
||||||
|
|
||||||
|
let frame1: CGRect = view1.convert(view1.bounds, to: self)
|
||||||
|
let frame2: CGRect = view2.convert(view2.bounds, to: self)
|
||||||
|
|
||||||
|
if frame1.minY != frame2.minY {
|
||||||
|
return frame1.minY < frame2.minY
|
||||||
|
} else {
|
||||||
|
return frame1.minX < frame2.minX
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Pods/IQKeyboardReturnManager/IQKeyboardReturnManager/Classes/IQTextInputViewInfoModel.swift
generated
Normal file
56
Pods/IQKeyboardReturnManager/IQKeyboardReturnManager/Classes/IQTextInputViewInfoModel.swift
generated
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// IQTextInputViewInfoModel.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardReturnManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal final class IQTextInputViewInfoModel: NSObject {
|
||||||
|
|
||||||
|
weak var textFieldDelegate: (any UITextFieldDelegate)?
|
||||||
|
weak var textViewDelegate: (any UITextViewDelegate)?
|
||||||
|
weak var textInputView: (any IQTextInputView)?
|
||||||
|
let originalReturnKeyType: UIReturnKeyType
|
||||||
|
|
||||||
|
init(textInputView: any IQTextInputView) {
|
||||||
|
self.textInputView = textInputView
|
||||||
|
self.originalReturnKeyType = textInputView.returnKeyType
|
||||||
|
if let textInputView = textInputView as? UITextField {
|
||||||
|
self.textFieldDelegate = textInputView.delegate
|
||||||
|
} else if let textInputView = textInputView as? UITextView {
|
||||||
|
self.textViewDelegate = textInputView.delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func restore() {
|
||||||
|
textInputView?.returnKeyType = originalReturnKeyType
|
||||||
|
if let textInputView = textInputView as? UITextField {
|
||||||
|
textInputView.delegate = textFieldDelegate
|
||||||
|
} else if let textInputView = textInputView as? UITextView {
|
||||||
|
textInputView.delegate = textViewDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Pods/IQKeyboardReturnManager/LICENSE
generated
Normal file
21
Pods/IQKeyboardReturnManager/LICENSE
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Mohd Iftekhar Qurashi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
68
Pods/IQKeyboardReturnManager/README.md
generated
Normal file
68
Pods/IQKeyboardReturnManager/README.md
generated
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# IQKeyboardReturnManager
|
||||||
|
|
||||||
|
[](https://travis-ci.org/hackiftekhar/IQKeyboardReturnManager)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardReturnManager)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardReturnManager)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardReturnManager)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
IQKeyboardReturnManager is available through [CocoaPods](https://cocoapods.org). To install
|
||||||
|
it, simply add the following line to your Podfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pod 'IQKeyboardReturnManager'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To handle keyboard return key automatically:-
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import IQKeyboardReturnManager
|
||||||
|
|
||||||
|
class ViewController: UIViewController {
|
||||||
|
|
||||||
|
let returnManager: IQKeyboardReturnManager = .init()
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// This will add all textInputView of the controller and start observing for textFieldShouldReturn events
|
||||||
|
returnManager.addResponderSubviews(of: self.view, recursive: true)
|
||||||
|
|
||||||
|
// If you would like to dismiss the UITextView on tapping on return then add this
|
||||||
|
returnManager.dismissTextViewOnReturn = true
|
||||||
|
|
||||||
|
// If you would like to change last textInputView return key type to done or something else, then add this
|
||||||
|
returnManager.lastTextInputViewReturnKeyType = .done
|
||||||
|
|
||||||
|
// If you would like to customize the navigation between textField by your own order then add them manually
|
||||||
|
returnManager.add(textInputView: textField1)
|
||||||
|
returnManager.add(textInputView: textField2)
|
||||||
|
returnManager.add(textInputView: textField3)
|
||||||
|
returnManager.add(textInputView: textField4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IQKeyboardReturnManager will forward all delegate callbacks to you, so you can customize the decisions
|
||||||
|
extension ViewController: UITextFieldDelegate {
|
||||||
|
@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {...}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Iftekhar Qurashi hack.iftekhar@gmail.com
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
IQKeyboardReturnManager is available under the MIT license. See the LICENSE file for more info.
|
||||||
14
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Assets/PrivacyInfo.xcprivacy
generated
Normal file
14
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Assets/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
150
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQBarButtonItem/IQBarButtonItem.swift
generated
Normal file
150
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQBarButtonItem/IQBarButtonItem.swift
generated
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
//
|
||||||
|
// IQBarButtonItem.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers open class IQBarButtonItem: UIBarButtonItem {
|
||||||
|
|
||||||
|
internal static let flexibleBarButtonItem: IQBarButtonItem = {
|
||||||
|
let barButton = IQBarButtonItem(barButtonSystemItem: .flexibleSpace,
|
||||||
|
target: nil, action: nil)
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
barButton.hidesSharedBackground = false
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return barButton
|
||||||
|
}()
|
||||||
|
|
||||||
|
/**
|
||||||
|
Fixed space bar button of toolbar.
|
||||||
|
*/
|
||||||
|
internal static let fixedSpaceBarButton: IQBarButtonItem = {
|
||||||
|
let barButton = IQBarButtonItem(barButtonSystemItem: .fixedSpace,
|
||||||
|
target: nil, action: nil)
|
||||||
|
barButton.isSystemItem = true
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
barButton.hidesSharedBackground = false
|
||||||
|
} else {
|
||||||
|
barButton.width = 6
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
barButton.width = 6
|
||||||
|
#endif
|
||||||
|
return barButton
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
public override init() {
|
||||||
|
super.init()
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
public required init?(coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func initialize() {
|
||||||
|
|
||||||
|
let states: [UIControl.State] = [.normal, .highlighted, .disabled, .focused]
|
||||||
|
|
||||||
|
for state in states {
|
||||||
|
|
||||||
|
setBackgroundImage(UIImage(), for: state, barMetrics: .default)
|
||||||
|
setBackgroundImage(UIImage(), for: state, style: .plain, barMetrics: .default)
|
||||||
|
setBackButtonBackgroundImage(UIImage(), for: state, barMetrics: .default)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTitlePositionAdjustment(UIOffset(), for: .default)
|
||||||
|
setBackgroundVerticalPositionAdjustment(0, for: .default)
|
||||||
|
setBackButtonBackgroundVerticalPositionAdjustment(0, for: .default)
|
||||||
|
}
|
||||||
|
|
||||||
|
override open var tintColor: UIColor? {
|
||||||
|
didSet {
|
||||||
|
|
||||||
|
var textAttributes: [NSAttributedString.Key: Any] = [:]
|
||||||
|
textAttributes[.foregroundColor] = tintColor
|
||||||
|
|
||||||
|
if let attributes: [NSAttributedString.Key: Any] = titleTextAttributes(for: .normal) {
|
||||||
|
for (key, value) in attributes {
|
||||||
|
textAttributes[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTitleTextAttributes(textAttributes, for: .normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Boolean to know if it's a system item or custom item,
|
||||||
|
we are having a limitation that we cannot override a designated initializer,
|
||||||
|
so we are manually setting this property once in initialization
|
||||||
|
*/
|
||||||
|
internal var isSystemItem: Bool = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
Additional target & action to do get callback action.
|
||||||
|
Note that setting custom target & selector doesn't affect native functionality,
|
||||||
|
this is just an additional target to get a callback.
|
||||||
|
|
||||||
|
@param target Target object.
|
||||||
|
@param action Target Selector.
|
||||||
|
*/
|
||||||
|
open func setTarget(_ target: AnyObject?, action: Selector?) {
|
||||||
|
if let target: AnyObject = target, let action: Selector = action {
|
||||||
|
invocation = IQInvocation(target: target, action: action)
|
||||||
|
} else {
|
||||||
|
invocation = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Customized Invocation to be called when button is pressed.
|
||||||
|
invocation is internally created using setTarget:action: method.
|
||||||
|
*/
|
||||||
|
open var invocation: IQInvocation? {
|
||||||
|
didSet {
|
||||||
|
// We have to put this condition here because if we override this function then
|
||||||
|
// We were getting "Cannot override '_' which has been marked unavailable" in Xcode 15
|
||||||
|
if let titleBarButton = self as? IQTitleBarButtonItem {
|
||||||
|
|
||||||
|
if let target = invocation?.target, let action = invocation?.action {
|
||||||
|
titleBarButton.isEnabled = true
|
||||||
|
titleBarButton.titleButton.isEnabled = true
|
||||||
|
titleBarButton.titleButton.addTarget(target, action: action, for: .touchUpInside)
|
||||||
|
} else {
|
||||||
|
titleBarButton.isEnabled = false
|
||||||
|
titleBarButton.titleButton.isEnabled = false
|
||||||
|
titleBarButton.titleButton.removeTarget(nil, action: nil, for: .touchUpInside)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQBarButtonItem/IQBarButtonItemConfiguration.swift
generated
Normal file
102
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQBarButtonItem/IQBarButtonItemConfiguration.swift
generated
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
//
|
||||||
|
// IQBarButtonItemConfiguration.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/**
|
||||||
|
IQBarButtonItemConfiguration for creating toolbar with bar button items
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQBarButtonItemConfiguration: NSObject {
|
||||||
|
|
||||||
|
public init(systemItem: UIBarButtonItem.SystemItem, action: Selector? = nil) {
|
||||||
|
self.systemItem = systemItem
|
||||||
|
self.image = nil
|
||||||
|
self.title = nil
|
||||||
|
self.action = action
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(image: UIImage, action: Selector? = nil) {
|
||||||
|
self.systemItem = nil
|
||||||
|
self.image = image
|
||||||
|
self.title = nil
|
||||||
|
self.action = action
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(title: String, action: Selector? = nil) {
|
||||||
|
self.systemItem = nil
|
||||||
|
self.image = nil
|
||||||
|
self.title = title
|
||||||
|
self.action = action
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
public let systemItem: UIBarButtonItem.SystemItem? // System Item to be used to instantiate bar button.
|
||||||
|
|
||||||
|
public let image: UIImage? // Image to show on bar button item if it's not a system item.
|
||||||
|
|
||||||
|
public let title: String? // Title to show on bar button item if it's not a system item.
|
||||||
|
|
||||||
|
public var action: Selector? // action for bar button item. Usually 'doneAction:(IQBarButtonItem*)item'.
|
||||||
|
|
||||||
|
public override var accessibilityLabel: String? { didSet { } } // Accessibility related labels
|
||||||
|
|
||||||
|
public func apply(on oldBarButtonItem: IQBarButtonItem, target: AnyObject?) -> IQBarButtonItem {
|
||||||
|
|
||||||
|
var newBarButtonItem: IQBarButtonItem = oldBarButtonItem
|
||||||
|
|
||||||
|
if systemItem == nil, !oldBarButtonItem.isSystemItem {
|
||||||
|
newBarButtonItem.title = title
|
||||||
|
newBarButtonItem.image = image
|
||||||
|
newBarButtonItem.target = target
|
||||||
|
newBarButtonItem.action = action
|
||||||
|
} else {
|
||||||
|
if let systemItem: UIBarButtonItem.SystemItem = systemItem {
|
||||||
|
newBarButtonItem = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: action)
|
||||||
|
newBarButtonItem.isSystemItem = true
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
newBarButtonItem.style = .plain
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} else if let image: UIImage = image {
|
||||||
|
newBarButtonItem = IQBarButtonItem(image: image, style: .plain, target: target, action: action)
|
||||||
|
} else {
|
||||||
|
newBarButtonItem = IQBarButtonItem(title: title, style: .plain, target: target, action: action)
|
||||||
|
}
|
||||||
|
|
||||||
|
newBarButtonItem.invocation = oldBarButtonItem.invocation
|
||||||
|
newBarButtonItem.isEnabled = oldBarButtonItem.isEnabled
|
||||||
|
newBarButtonItem.tag = oldBarButtonItem.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
newBarButtonItem.accessibilityLabel = accessibilityLabel
|
||||||
|
newBarButtonItem.accessibilityIdentifier = oldBarButtonItem.accessibilityLabel
|
||||||
|
|
||||||
|
return newBarButtonItem
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQBarButtonItem/IQInvocation.swift
generated
Normal file
42
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQBarButtonItem/IQInvocation.swift
generated
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// IQInvocation.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQInvocation: NSObject {
|
||||||
|
public weak var target: AnyObject?
|
||||||
|
public let action: Selector
|
||||||
|
|
||||||
|
public init(target: AnyObject, action: Selector) {
|
||||||
|
self.target = target
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
public func invoke(from: Any) {
|
||||||
|
|
||||||
|
guard let target: AnyObject = target else { return }
|
||||||
|
UIApplication.shared.sendAction(action, to: target, from: from, for: UIEvent())
|
||||||
|
}
|
||||||
|
}
|
||||||
166
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQBarButtonItem/IQTitleBarButtonItem.swift
generated
Normal file
166
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQBarButtonItem/IQTitleBarButtonItem.swift
generated
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
//
|
||||||
|
// IQTitleBarButtonItem.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers open class IQTitleBarButtonItem: IQBarButtonItem {
|
||||||
|
|
||||||
|
open var titleFont: UIFont? {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
if let titleFont: UIFont = titleFont {
|
||||||
|
titleButton.titleLabel?.font = titleFont
|
||||||
|
} else {
|
||||||
|
titleButton.titleLabel?.font = UIFont.systemFont(ofSize: 13)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open var title: String? {
|
||||||
|
didSet {
|
||||||
|
titleButton.setTitle(title, for: .normal)
|
||||||
|
updateAccessibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
titleColor to be used for displaying button text when displaying title (disabled state).
|
||||||
|
*/
|
||||||
|
open var titleColor: UIColor? {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
|
||||||
|
if let titleColor: UIColor = titleColor {
|
||||||
|
titleButton.setTitleColor(titleColor, for: .disabled)
|
||||||
|
} else {
|
||||||
|
titleButton.setTitleColor(UIColor.lightGray, for: .disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
selectableTitleColor to be used for displaying button text when button is enabled.
|
||||||
|
*/
|
||||||
|
open var selectableTitleColor: UIColor? {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
|
||||||
|
if let selectableTitleColor: UIColor = selectableTitleColor {
|
||||||
|
titleButton.setTitleColor(selectableTitleColor, for: .normal)
|
||||||
|
} else {
|
||||||
|
titleButton.setTitleColor(UIColor.systemBlue, for: .normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal let titleButton: UIButton = UIButton(type: .system)
|
||||||
|
private let _titleView: UIView = UIView()
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
public convenience init(title: String?) {
|
||||||
|
|
||||||
|
self.init(title: nil, style: .plain, target: nil, action: nil)
|
||||||
|
|
||||||
|
_titleView.backgroundColor = UIColor.clear
|
||||||
|
|
||||||
|
titleButton.isAccessibilityElement = false
|
||||||
|
titleButton.isEnabled = false
|
||||||
|
titleButton.titleLabel?.numberOfLines = 3
|
||||||
|
titleButton.setTitleColor(UIColor.lightGray, for: .disabled)
|
||||||
|
titleButton.setTitleColor(UIColor.systemBlue, for: .normal)
|
||||||
|
titleButton.backgroundColor = UIColor.clear
|
||||||
|
titleButton.titleLabel?.textAlignment = .center
|
||||||
|
titleButton.setTitle(title, for: .normal)
|
||||||
|
titleFont = UIFont.systemFont(ofSize: 13.0)
|
||||||
|
titleButton.titleLabel?.font = self.titleFont
|
||||||
|
_titleView.addSubview(titleButton)
|
||||||
|
|
||||||
|
let lowPriority: UILayoutPriority = UILayoutPriority(rawValue: UILayoutPriority.defaultLow.rawValue-1)
|
||||||
|
let highPriority: UILayoutPriority = UILayoutPriority(rawValue: UILayoutPriority.defaultHigh.rawValue-1)
|
||||||
|
|
||||||
|
_titleView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
_titleView.setContentHuggingPriority(lowPriority, for: .vertical)
|
||||||
|
_titleView.setContentHuggingPriority(lowPriority, for: .horizontal)
|
||||||
|
_titleView.setContentCompressionResistancePriority(highPriority, for: .vertical)
|
||||||
|
_titleView.setContentCompressionResistancePriority(highPriority, for: .horizontal)
|
||||||
|
|
||||||
|
titleButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
titleButton.setContentHuggingPriority(lowPriority, for: .vertical)
|
||||||
|
titleButton.setContentHuggingPriority(lowPriority, for: .horizontal)
|
||||||
|
titleButton.setContentCompressionResistancePriority(highPriority, for: .vertical)
|
||||||
|
titleButton.setContentCompressionResistancePriority(highPriority, for: .horizontal)
|
||||||
|
|
||||||
|
let top: NSLayoutConstraint = NSLayoutConstraint(item: titleButton, attribute: .top,
|
||||||
|
relatedBy: .equal,
|
||||||
|
toItem: _titleView, attribute: .top,
|
||||||
|
multiplier: 1, constant: 0)
|
||||||
|
let bottom: NSLayoutConstraint = NSLayoutConstraint(item: titleButton, attribute: .bottom,
|
||||||
|
relatedBy: .equal,
|
||||||
|
toItem: _titleView, attribute: .bottom,
|
||||||
|
multiplier: 1, constant: 0)
|
||||||
|
let leading: NSLayoutConstraint = NSLayoutConstraint(item: titleButton, attribute: .leading,
|
||||||
|
relatedBy: .equal,
|
||||||
|
toItem: _titleView, attribute: .leading,
|
||||||
|
multiplier: 1, constant: 0)
|
||||||
|
let trailing: NSLayoutConstraint = NSLayoutConstraint(item: titleButton, attribute: .trailing,
|
||||||
|
relatedBy: .equal,
|
||||||
|
toItem: _titleView, attribute: .trailing,
|
||||||
|
multiplier: 1, constant: 0)
|
||||||
|
|
||||||
|
_titleView.addConstraints([top, bottom, leading, trailing])
|
||||||
|
|
||||||
|
customView = _titleView
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateAccessibility() {
|
||||||
|
if title == nil || title?.isEmpty == true {
|
||||||
|
isAccessibilityElement = false
|
||||||
|
// Swift 6: Reference to static property 'none'
|
||||||
|
// is not concurrency-safe because it involves shared mutable state
|
||||||
|
// accessibilityTraits = .none
|
||||||
|
accessibilityTraits = .init(rawValue: 0)
|
||||||
|
} else if titleButton.isEnabled == true {
|
||||||
|
isAccessibilityElement = true
|
||||||
|
// Swift 6: Reference to static property 'button'
|
||||||
|
// is not concurrency-safe because it involves shared mutable state
|
||||||
|
// accessibilityTraits = .button
|
||||||
|
accessibilityTraits = .init(rawValue: 1)
|
||||||
|
} else {
|
||||||
|
isAccessibilityElement = true
|
||||||
|
|
||||||
|
// Swift 6: Reference to static property 'staticText'
|
||||||
|
// is not concurrency-safe because it involves shared mutable state
|
||||||
|
// accessibilityTraits = .staticText
|
||||||
|
accessibilityTraits = .init(rawValue: 64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
389
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQKeyboardExtension/UIView+IQKeyboardExtension.swift
generated
Normal file
389
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQKeyboardExtension/UIView+IQKeyboardExtension.swift
generated
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
//
|
||||||
|
// UIView+IQKeyboardExtension.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var toolbar: Int = 0
|
||||||
|
static var hidePlaceholder: Int = 0
|
||||||
|
static var placeholder: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQKeyboardExtension where Base: IQTextInputView {
|
||||||
|
|
||||||
|
// MARK: Toolbar
|
||||||
|
|
||||||
|
/**
|
||||||
|
Toolbar references for better customization control.
|
||||||
|
*/
|
||||||
|
var toolbar: IQKeyboardToolbar {
|
||||||
|
var toolbar: IQKeyboardToolbar? = base?.inputAccessoryView as? IQKeyboardToolbar
|
||||||
|
|
||||||
|
if toolbar == nil, let base = base {
|
||||||
|
toolbar = objc_getAssociatedObject(base, &AssociatedKeys.toolbar) as? IQKeyboardToolbar
|
||||||
|
}
|
||||||
|
|
||||||
|
if let toolbar: IQKeyboardToolbar = toolbar {
|
||||||
|
return toolbar
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let width: CGFloat = base?.window?.windowScene?.screen.bounds.width ?? 0
|
||||||
|
let height: CGFloat
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
height = 58
|
||||||
|
} else {
|
||||||
|
height = 44
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
height = 44
|
||||||
|
#endif
|
||||||
|
|
||||||
|
let frame = CGRect(origin: .zero, size: .init(width: width, height: height))
|
||||||
|
let newToolbar = IQKeyboardToolbar(frame: frame)
|
||||||
|
|
||||||
|
if let base = base {
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.toolbar, newToolbar, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newToolbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Toolbar title
|
||||||
|
|
||||||
|
/**
|
||||||
|
If `hideToolbarPlaceholder` is YES, then title will not be added to the toolbar. Default to NO.
|
||||||
|
*/
|
||||||
|
var hidePlaceholder: Bool {
|
||||||
|
get {
|
||||||
|
if let base = base {
|
||||||
|
return objc_getAssociatedObject(base, &AssociatedKeys.hidePlaceholder) as? Bool ?? false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
if let base = base {
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.hidePlaceholder,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
toolbar.titleBarButton.title = drawingPlaceholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
`toolbarPlaceholder` to override default `placeholder` text when drawing text on toolbar.
|
||||||
|
*/
|
||||||
|
var placeholder: String? {
|
||||||
|
get {
|
||||||
|
if let base = base {
|
||||||
|
return objc_getAssociatedObject(base, &AssociatedKeys.placeholder) as? String
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
if let base = base {
|
||||||
|
// swiftlint:disable line_length
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.placeholder, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
toolbar.titleBarButton.title = drawingPlaceholder
|
||||||
|
// swiftlint:enable line_length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
`drawingToolbarPlaceholder` will be actual text used to draw on toolbar.
|
||||||
|
This would either `placeholder` or `toolbarPlaceholder`.
|
||||||
|
*/
|
||||||
|
var drawingPlaceholder: String? {
|
||||||
|
|
||||||
|
guard !hidePlaceholder else { return nil }
|
||||||
|
|
||||||
|
if let placeholder = placeholder,
|
||||||
|
!placeholder.isEmpty {
|
||||||
|
return placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let placeholderable: any IQPlaceholderable = base as? (any IQPlaceholderable) else { return nil }
|
||||||
|
|
||||||
|
if let placeholder = placeholderable.attributedPlaceholder?.string,
|
||||||
|
!placeholder.isEmpty {
|
||||||
|
return placeholder
|
||||||
|
} else if let placeholder = placeholderable.placeholder {
|
||||||
|
return placeholder
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Common
|
||||||
|
|
||||||
|
func addToolbar(target: AnyObject?,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
title: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
guard let base = base else { return }
|
||||||
|
// Creating a toolBar for phoneNumber keyboard
|
||||||
|
let toolbar: IQKeyboardToolbar = toolbar
|
||||||
|
|
||||||
|
let items: [UIBarButtonItem] = Self.constructBarButtonItems(target: target, toolbar: toolbar,
|
||||||
|
previousConfiguration: previousConfiguration,
|
||||||
|
nextConfiguration: nextConfiguration,
|
||||||
|
rightConfiguration: rightConfiguration,
|
||||||
|
title: title,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
|
||||||
|
// Adding button to toolBar.
|
||||||
|
toolbar.items = items
|
||||||
|
|
||||||
|
switch base.keyboardAppearance {
|
||||||
|
case .dark:
|
||||||
|
toolbar.barStyle = .black
|
||||||
|
default:
|
||||||
|
toolbar.barStyle = .default
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting toolbar to keyboard.
|
||||||
|
let reloadInputViews: Bool = base.inputAccessoryView != toolbar
|
||||||
|
guard reloadInputViews else { return }
|
||||||
|
|
||||||
|
base.inputAccessoryView = toolbar
|
||||||
|
|
||||||
|
base.reloadInputViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Right
|
||||||
|
func addDone(target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
|
||||||
|
let title: String? = showPlaceholder ? drawingPlaceholder : nil
|
||||||
|
|
||||||
|
addDone(target: target, action: action,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDone(target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
|
||||||
|
let rightConfiguration: IQBarButtonItemConfiguration
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
rightConfiguration = IQBarButtonItemConfiguration(image: UIImage(systemName: "checkmark")!, action: action)
|
||||||
|
} else {
|
||||||
|
rightConfiguration = IQBarButtonItemConfiguration(systemItem: .done, action: action)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
rightConfiguration = IQBarButtonItemConfiguration(systemItem: .done, action: action)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
addToolbar(target: target, rightConfiguration: rightConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRightButton(target: AnyObject?,
|
||||||
|
configuration: IQBarButtonItemConfiguration,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
let title: String? = showPlaceholder ? drawingPlaceholder : nil
|
||||||
|
addRightButton(target: target, configuration: configuration, title: title,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRightButton(target: AnyObject?,
|
||||||
|
configuration: IQBarButtonItemConfiguration,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
addToolbar(target: target, rightConfiguration: configuration, title: title,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Right/Left
|
||||||
|
func addRightLeft(target: AnyObject?,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration, leftConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
let title: String? = showPlaceholder ? drawingPlaceholder : nil
|
||||||
|
addRightLeft(target: target,
|
||||||
|
rightConfiguration: rightConfiguration, leftConfiguration: leftConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRightLeft(target: AnyObject?,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration, leftConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
addToolbar(target: target,
|
||||||
|
previousConfiguration: leftConfiguration, rightConfiguration: rightConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Previous/Next/Right
|
||||||
|
|
||||||
|
func addPreviousNextRight(target: AnyObject?,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration?,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
|
||||||
|
let title: String? = showPlaceholder ? drawingPlaceholder : nil
|
||||||
|
addPreviousNextRight(target: target,
|
||||||
|
previousConfiguration: previousConfiguration, nextConfiguration: nextConfiguration,
|
||||||
|
rightConfiguration: rightConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPreviousNextRight(target: AnyObject?,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration?,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
|
||||||
|
addToolbar(target: target,
|
||||||
|
previousConfiguration: previousConfiguration, nextConfiguration: nextConfiguration,
|
||||||
|
rightConfiguration: rightConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPreviousNextDone(target: AnyObject?, previousAction: Selector, nextAction: Selector, doneAction: Selector,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
let title: String? = showPlaceholder ? drawingPlaceholder : nil
|
||||||
|
addPreviousNextDone(target: target, previousAction: previousAction, nextAction: nextAction,
|
||||||
|
doneAction: doneAction,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPreviousNextDone(target: AnyObject?,
|
||||||
|
previousAction: Selector, nextAction: Selector, doneAction: Selector,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
|
||||||
|
let chevronUp: UIImage = UIImage(systemName: "chevron.up") ?? UIImage()
|
||||||
|
let chevronDown: UIImage = UIImage(systemName: "chevron.down") ?? UIImage()
|
||||||
|
|
||||||
|
let previousConfiguration = IQBarButtonItemConfiguration(image: chevronUp,
|
||||||
|
action: previousAction)
|
||||||
|
let nextConfiguration = IQBarButtonItemConfiguration(image: chevronDown,
|
||||||
|
action: nextAction)
|
||||||
|
let rightConfiguration: IQBarButtonItemConfiguration
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
rightConfiguration = IQBarButtonItemConfiguration(image: UIImage(systemName: "checkmark")!, action: doneAction)
|
||||||
|
} else {
|
||||||
|
rightConfiguration = IQBarButtonItemConfiguration(systemItem: .done, action: doneAction)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
rightConfiguration = IQBarButtonItemConfiguration(systemItem: .done, action: doneAction)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
addToolbar(target: target, previousConfiguration: previousConfiguration,
|
||||||
|
nextConfiguration: nextConfiguration, rightConfiguration: rightConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private extension IQKeyboardExtension where Base: IQTextInputView {
|
||||||
|
|
||||||
|
private static func constructBarButtonItems(target: AnyObject?,
|
||||||
|
toolbar: IQKeyboardToolbar,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
title: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) -> [UIBarButtonItem] {
|
||||||
|
var items: [UIBarButtonItem] = []
|
||||||
|
|
||||||
|
// Leading group
|
||||||
|
do {
|
||||||
|
if let previousConfiguration: IQBarButtonItemConfiguration = previousConfiguration {
|
||||||
|
let prev: IQBarButtonItem = previousConfiguration.apply(on: toolbar.previousBarButton, target: target)
|
||||||
|
toolbar.previousBarButton = prev
|
||||||
|
items.append(prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if previousConfiguration != nil, nextConfiguration != nil {
|
||||||
|
items.append(IQBarButtonItem.fixedSpaceBarButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let nextConfiguration: IQBarButtonItemConfiguration = nextConfiguration {
|
||||||
|
let next: IQBarButtonItem = nextConfiguration.apply(on: toolbar.nextBarButton, target: target)
|
||||||
|
toolbar.nextBarButton = next
|
||||||
|
items.append(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !toolbar.additionalLeadingItems.isEmpty {
|
||||||
|
items.append(contentsOf: toolbar.additionalLeadingItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center Title bar button item
|
||||||
|
if let title = title, !title.isEmpty {
|
||||||
|
// Title button
|
||||||
|
toolbar.titleBarButton.title = title
|
||||||
|
toolbar.titleBarButton.accessibilityLabel = titleAccessibilityLabel
|
||||||
|
toolbar.titleBarButton.accessibilityIdentifier = titleAccessibilityLabel
|
||||||
|
toolbar.titleBarButton.customView?.frame = .zero
|
||||||
|
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
if !items.isEmpty {
|
||||||
|
items.append(IQBarButtonItem.flexibleBarButtonItem)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items.append(IQBarButtonItem.flexibleBarButtonItem)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
items.append(IQBarButtonItem.flexibleBarButtonItem)
|
||||||
|
#endif
|
||||||
|
items.append(toolbar.titleBarButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flexible space
|
||||||
|
items.append(IQBarButtonItem.flexibleBarButtonItem)
|
||||||
|
|
||||||
|
// Trailing group
|
||||||
|
do {
|
||||||
|
if !toolbar.additionalTrailingItems.isEmpty {
|
||||||
|
items.append(contentsOf: toolbar.additionalTrailingItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let rightConfiguration: IQBarButtonItemConfiguration = rightConfiguration {
|
||||||
|
|
||||||
|
let done: IQBarButtonItem = rightConfiguration.apply(on: toolbar.doneBarButton, target: target)
|
||||||
|
toolbar.doneBarButton = done
|
||||||
|
items.append(done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,228 @@
|
|||||||
|
//
|
||||||
|
// UIView+IQKeyboardExtensionDeprecated.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
// swiftlint:disable line_length
|
||||||
|
// swiftlint:disable function_parameter_count
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension UIView {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.toolbar")
|
||||||
|
var keyboardToolbar: IQKeyboardToolbar {
|
||||||
|
get { fatalError() }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.hidePlaceholder")
|
||||||
|
var shouldHideToolbarPlaceholder: Bool {
|
||||||
|
get { false }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.placeholder")
|
||||||
|
var toolbarPlaceholder: String? {
|
||||||
|
get { nil }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.drawingPlaceholder")
|
||||||
|
var drawingToolbarPlaceholder: String? {
|
||||||
|
get { nil }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addToolbar(target:previousConfiguration:nextConfiguration:rightConfiguration:title:titleAccessibilityLabel:)")
|
||||||
|
func addKeyboardToolbarWithTarget(target: AnyObject?,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil,
|
||||||
|
rightBarButtonConfiguration: IQBarButtonItemConfiguration?,
|
||||||
|
previousBarButtonConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextBarButtonConfiguration: IQBarButtonItemConfiguration? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addDone(target:action:showPlaceholder:titleAccessibilityLabel:)")
|
||||||
|
func addDoneOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
shouldShowPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addDone(target:action:title:titleAccessibilityLabel:)")
|
||||||
|
func addDoneOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightButton(target:configuration:showPlaceholder:titleAccessibilityLabel:)")
|
||||||
|
func addRightButtonOnKeyboardWithImage(_ image: UIImage,
|
||||||
|
target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
shouldShowPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightButton(target:configuration:title:titleAccessibilityLabel:)")
|
||||||
|
func addRightButtonOnKeyboardWithImage(_ image: UIImage,
|
||||||
|
target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightButton(target:configuration:showPlaceholder:titleAccessibilityLabel:)")
|
||||||
|
func addRightButtonOnKeyboardWithText(_ text: String,
|
||||||
|
target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
shouldShowPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightButton(target:configuration:title:titleAccessibilityLabel:)")
|
||||||
|
func addRightButtonOnKeyboardWithText(_ text: String,
|
||||||
|
target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:showPlaceholder:titleAccessibilityLabel:)")
|
||||||
|
func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
cancelAction: Selector,
|
||||||
|
doneAction: Selector,
|
||||||
|
shouldShowPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:showPlaceholder:titleAccessibilityLabel:)")
|
||||||
|
func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
leftButtonTitle: String,
|
||||||
|
rightButtonTitle: String,
|
||||||
|
leftButtonAction: Selector,
|
||||||
|
rightButtonAction: Selector,
|
||||||
|
shouldShowPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:showPlaceholder:titleAccessibilityLabel:)")
|
||||||
|
func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
leftButtonImage: UIImage,
|
||||||
|
rightButtonImage: UIImage,
|
||||||
|
leftButtonAction: Selector,
|
||||||
|
rightButtonAction: Selector,
|
||||||
|
shouldShowPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:title:titleAccessibilityLabel:)")
|
||||||
|
func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
cancelAction: Selector,
|
||||||
|
doneAction: Selector,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:title:titleAccessibilityLabel:)")
|
||||||
|
func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
leftButtonTitle: String,
|
||||||
|
rightButtonTitle: String,
|
||||||
|
leftButtonAction: Selector,
|
||||||
|
rightButtonAction: Selector,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:title:titleAccessibilityLabel:)")
|
||||||
|
func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
leftButtonImage: UIImage,
|
||||||
|
rightButtonImage: UIImage,
|
||||||
|
leftButtonAction: Selector,
|
||||||
|
rightButtonAction: Selector,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addPreviousNextDone(target:previousAction:nextAction:doneAction:showPlaceholder:titleAccessibilityLabel:)")
|
||||||
|
func addPreviousNextDone(_ target: AnyObject?,
|
||||||
|
previousAction: Selector,
|
||||||
|
nextAction: Selector,
|
||||||
|
doneAction: Selector,
|
||||||
|
shouldShowPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addPreviousNextDone(target:previousAction:nextAction:doneAction:title:titleAccessibilityLabel:)")
|
||||||
|
func addPreviousNextDoneOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
previousAction: Selector,
|
||||||
|
nextAction: Selector,
|
||||||
|
doneAction: Selector,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addPreviousNextRight(target:previousConfiguration:nextConfiguration:rightConfiguration:showPlaceholder:titleAccessibilityLabel:)")
|
||||||
|
func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
rightButtonImage: UIImage,
|
||||||
|
previousAction: Selector,
|
||||||
|
nextAction: Selector,
|
||||||
|
rightButtonAction: Selector,
|
||||||
|
shouldShowPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addPreviousNextRight(target:previousConfiguration:nextConfiguration:rightConfiguration:showPlaceholder:titleAccessibilityLabel:)")
|
||||||
|
func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
rightButtonTitle: String,
|
||||||
|
previousAction: Selector,
|
||||||
|
nextAction: Selector,
|
||||||
|
rightButtonAction: Selector,
|
||||||
|
shouldShowPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addPreviousNextRight(target:previousConfiguration:nextConfiguration:rightConfiguration:title:titleAccessibilityLabel:)")
|
||||||
|
func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
rightButtonImage: UIImage,
|
||||||
|
previousAction: Selector,
|
||||||
|
nextAction: Selector,
|
||||||
|
rightButtonAction: Selector,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "iq.addPreviousNextRight(target:previousConfiguration:nextConfiguration:rightConfiguration:title:titleAccessibilityLabel:)")
|
||||||
|
func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?,
|
||||||
|
rightButtonTitle: String,
|
||||||
|
previousAction: Selector,
|
||||||
|
nextAction: Selector,
|
||||||
|
rightButtonAction: Selector,
|
||||||
|
titleText: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
|
// swiftlint:enable line_length
|
||||||
|
// swiftlint:enable function_parameter_count
|
||||||
@ -0,0 +1,254 @@
|
|||||||
|
//
|
||||||
|
// UIView+IQKeyboardExtensionObjc.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// MARK: For ObjectiveC Compatibility
|
||||||
|
|
||||||
|
// swiftlint:disable identifier_name
|
||||||
|
@objc public extension UITextField {
|
||||||
|
|
||||||
|
var iq_toolbar: IQKeyboardToolbar { iq.toolbar }
|
||||||
|
|
||||||
|
var iq_hidePlaceholder: Bool {
|
||||||
|
get { iq.hidePlaceholder }
|
||||||
|
set(newValue) { iq.hidePlaceholder = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var iq_placeholder: String? {
|
||||||
|
get { iq.placeholder }
|
||||||
|
set(newValue) { iq.placeholder = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var iq_drawingPlaceholder: String? { iq.drawingPlaceholder }
|
||||||
|
|
||||||
|
func iq_addToolbar(target: AnyObject?,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
title: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addToolbar(target: target, previousConfiguration: previousConfiguration,
|
||||||
|
nextConfiguration: nextConfiguration,
|
||||||
|
rightConfiguration: rightConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addDone(target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addDone(target: target, action: action, showPlaceholder: showPlaceholder,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addDone(target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addDone(target: target, action: action, title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addRightButton(target: AnyObject?,
|
||||||
|
configuration: IQBarButtonItemConfiguration,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addRightButton(target: target, configuration: configuration, showPlaceholder: showPlaceholder,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addRightButton(target: AnyObject?,
|
||||||
|
configuration: IQBarButtonItemConfiguration,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addRightButton(target: target, configuration: configuration, title: title,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addRightLeft(target: AnyObject?,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
leftConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addRightLeft(target: target, rightConfiguration: rightConfiguration,
|
||||||
|
leftConfiguration: leftConfiguration, showPlaceholder: showPlaceholder,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addRightLeft(target: AnyObject?,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
leftConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addRightLeft(target: target, rightConfiguration: rightConfiguration,
|
||||||
|
leftConfiguration: leftConfiguration, title: title,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addPreviousNextRight(target: AnyObject?,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration?,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addPreviousNextRight(target: target, previousConfiguration: previousConfiguration,
|
||||||
|
nextConfiguration: nextConfiguration, rightConfiguration: rightConfiguration,
|
||||||
|
showPlaceholder: showPlaceholder, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addPreviousNextRight(target: AnyObject?,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration?,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addPreviousNextRight(target: target, previousConfiguration: previousConfiguration,
|
||||||
|
nextConfiguration: nextConfiguration,
|
||||||
|
rightConfiguration: rightConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addPreviousNextDone(target: AnyObject?, previousAction: Selector,
|
||||||
|
nextAction: Selector, doneAction: Selector,
|
||||||
|
showPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addPreviousNextDone(target: target, previousAction: previousAction,
|
||||||
|
nextAction: nextAction, doneAction: doneAction,
|
||||||
|
showPlaceholder: showPlaceholder,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addPreviousNextDone(target: AnyObject?,
|
||||||
|
previousAction: Selector, nextAction: Selector, doneAction: Selector,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addPreviousNextDone(target: target, previousAction: previousAction,
|
||||||
|
nextAction: nextAction, doneAction: doneAction,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public extension UITextView {
|
||||||
|
|
||||||
|
var iq_toolbar: IQKeyboardToolbar { iq.toolbar }
|
||||||
|
|
||||||
|
var iq_hidePlaceholder: Bool {
|
||||||
|
get { iq.hidePlaceholder }
|
||||||
|
set(newValue) { iq.hidePlaceholder = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var iq_placeholder: String? {
|
||||||
|
get { iq.placeholder }
|
||||||
|
set(newValue) { iq.placeholder = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var iq_drawingPlaceholder: String? { iq.drawingPlaceholder }
|
||||||
|
|
||||||
|
func iq_addToolbar(target: AnyObject?,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
title: String?,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addToolbar(target: target, previousConfiguration: previousConfiguration,
|
||||||
|
nextConfiguration: nextConfiguration, rightConfiguration: rightConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addDone(target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addDone(target: target, action: action, showPlaceholder: showPlaceholder,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addDone(target: AnyObject?,
|
||||||
|
action: Selector,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addDone(target: target, action: action, title: title,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addRightButton(target: AnyObject?,
|
||||||
|
configuration: IQBarButtonItemConfiguration,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addRightButton(target: target, configuration: configuration,
|
||||||
|
showPlaceholder: showPlaceholder, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addRightButton(target: AnyObject?,
|
||||||
|
configuration: IQBarButtonItemConfiguration,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addRightButton(target: target, configuration: configuration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addRightLeft(target: AnyObject?,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
leftConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addRightLeft(target: target, rightConfiguration: rightConfiguration,
|
||||||
|
leftConfiguration: leftConfiguration, showPlaceholder: showPlaceholder,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addRightLeft(target: AnyObject?,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
leftConfiguration: IQBarButtonItemConfiguration,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addRightLeft(target: target, rightConfiguration: rightConfiguration,
|
||||||
|
leftConfiguration: leftConfiguration, title: title,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addPreviousNextRight(target: AnyObject?,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration?,
|
||||||
|
showPlaceholder: Bool = false, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addPreviousNextRight(target: target, previousConfiguration: previousConfiguration,
|
||||||
|
nextConfiguration: nextConfiguration, rightConfiguration: rightConfiguration,
|
||||||
|
showPlaceholder: showPlaceholder, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addPreviousNextRight(target: AnyObject?,
|
||||||
|
previousConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
nextConfiguration: IQBarButtonItemConfiguration? = nil,
|
||||||
|
rightConfiguration: IQBarButtonItemConfiguration?,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addPreviousNextRight(target: target, previousConfiguration: previousConfiguration,
|
||||||
|
nextConfiguration: nextConfiguration,
|
||||||
|
rightConfiguration: rightConfiguration,
|
||||||
|
title: title, titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addPreviousNextDone(target: AnyObject?, previousAction: Selector, nextAction: Selector,
|
||||||
|
doneAction: Selector, showPlaceholder: Bool = false,
|
||||||
|
titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addPreviousNextDone(target: target, previousAction: previousAction,
|
||||||
|
nextAction: nextAction,
|
||||||
|
doneAction: doneAction, showPlaceholder: showPlaceholder,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iq_addPreviousNextDone(target: AnyObject?,
|
||||||
|
previousAction: Selector, nextAction: Selector, doneAction: Selector,
|
||||||
|
title: String?, titleAccessibilityLabel: String? = nil) {
|
||||||
|
iq.addPreviousNextDone(target: target, previousAction: previousAction,
|
||||||
|
nextAction: nextAction,
|
||||||
|
doneAction: doneAction, title: title,
|
||||||
|
titleAccessibilityLabel: titleAccessibilityLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable identifier_name
|
||||||
183
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQKeyboardToolbar.swift
generated
Normal file
183
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Classes/IQKeyboardToolbar.swift
generated
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbar.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/** @abstract IQKeyboardToolbar. */
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers open class IQKeyboardToolbar: UIToolbar {
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func initialize() {
|
||||||
|
|
||||||
|
sizeToFit()
|
||||||
|
|
||||||
|
autoresizingMask = .flexibleWidth
|
||||||
|
self.isTranslucent = true
|
||||||
|
self.barTintColor = nil
|
||||||
|
|
||||||
|
let positions: [UIBarPosition] = [.any, .bottom, .top, .topAttached]
|
||||||
|
|
||||||
|
for position in positions {
|
||||||
|
|
||||||
|
self.setBackgroundImage(nil, forToolbarPosition: position, barMetrics: .default)
|
||||||
|
self.setShadowImage(nil, forToolbarPosition: .any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background color
|
||||||
|
self.backgroundColor = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Additional bar buttons to show at the leading side.
|
||||||
|
*/
|
||||||
|
open var additionalLeadingItems: [UIBarButtonItem] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
Additional bar buttons to show at the trailing side.
|
||||||
|
*/
|
||||||
|
open var additionalTrailingItems: [UIBarButtonItem] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
Previous bar button of toolbar.
|
||||||
|
*/
|
||||||
|
private var privatePreviousBarButton: IQBarButtonItem?
|
||||||
|
open var previousBarButton: IQBarButtonItem {
|
||||||
|
get {
|
||||||
|
if privatePreviousBarButton == nil {
|
||||||
|
privatePreviousBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil)
|
||||||
|
}
|
||||||
|
return privatePreviousBarButton!
|
||||||
|
}
|
||||||
|
|
||||||
|
set (newValue) {
|
||||||
|
privatePreviousBarButton = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Next bar button of toolbar.
|
||||||
|
*/
|
||||||
|
private var privateNextBarButton: IQBarButtonItem?
|
||||||
|
open var nextBarButton: IQBarButtonItem {
|
||||||
|
get {
|
||||||
|
if privateNextBarButton == nil {
|
||||||
|
privateNextBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil)
|
||||||
|
}
|
||||||
|
return privateNextBarButton!
|
||||||
|
}
|
||||||
|
|
||||||
|
set (newValue) {
|
||||||
|
privateNextBarButton = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Title bar button of toolbar.
|
||||||
|
*/
|
||||||
|
private var privateTitleBarButton: IQTitleBarButtonItem?
|
||||||
|
open var titleBarButton: IQTitleBarButtonItem {
|
||||||
|
get {
|
||||||
|
if privateTitleBarButton == nil {
|
||||||
|
privateTitleBarButton = IQTitleBarButtonItem(title: nil)
|
||||||
|
}
|
||||||
|
return privateTitleBarButton!
|
||||||
|
}
|
||||||
|
|
||||||
|
set (newValue) {
|
||||||
|
privateTitleBarButton = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Done bar button of toolbar.
|
||||||
|
*/
|
||||||
|
private var privateDoneBarButton: IQBarButtonItem?
|
||||||
|
open var doneBarButton: IQBarButtonItem {
|
||||||
|
get {
|
||||||
|
if privateDoneBarButton == nil {
|
||||||
|
privateDoneBarButton = IQBarButtonItem(title: nil, style: .done, target: nil, action: nil)
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
privateDoneBarButton?.style = .plain
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return privateDoneBarButton!
|
||||||
|
}
|
||||||
|
|
||||||
|
set (newValue) {
|
||||||
|
privateDoneBarButton = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||||
|
var sizeThatFit: CGSize = super.sizeThatFits(size)
|
||||||
|
let height: CGFloat
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
height = 58
|
||||||
|
} else {
|
||||||
|
height = 44
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
height = 44
|
||||||
|
#endif
|
||||||
|
sizeThatFit.height = height
|
||||||
|
|
||||||
|
return sizeThatFit
|
||||||
|
}
|
||||||
|
|
||||||
|
override open var tintColor: UIColor! {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
guard let items: [UIBarButtonItem] = items else { return }
|
||||||
|
for item in items {
|
||||||
|
item.tintColor = tintColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension IQKeyboardToolbar: UIInputViewAudioFeedback {
|
||||||
|
|
||||||
|
open var enableInputClicksWhenVisible: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbarPlaceholderConfiguration.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// swiftlint:disable type_name
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardToolbarPlaceholderConfiguration: NSObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
If YES, then it add the textInputView's placeholder text on toolbar. Default is YES.
|
||||||
|
*/
|
||||||
|
public var showPlaceholder: Bool = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
Placeholder Font. Default is nil.
|
||||||
|
*/
|
||||||
|
public var font: UIFont?
|
||||||
|
|
||||||
|
/**
|
||||||
|
Placeholder Color. Default is nil. Which means lightGray
|
||||||
|
*/
|
||||||
|
public var color: UIColor?
|
||||||
|
|
||||||
|
/**
|
||||||
|
Placeholder Button Color when it's treated as button. Default is nil.
|
||||||
|
*/
|
||||||
|
public var buttonColor: UIColor?
|
||||||
|
|
||||||
|
/**
|
||||||
|
Placeholder accessibility Label
|
||||||
|
*/
|
||||||
|
public override var accessibilityLabel: String? { didSet { } }
|
||||||
|
}
|
||||||
|
// swiftlint:enable type_name
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "IQKeyboardToolbarPlaceholderConfiguration")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQToolbarPlaceholderConfiguration: NSObject {}
|
||||||
37
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Placeholderable/IQPlaceholderable.swift
generated
Normal file
37
Pods/IQKeyboardToolbar/IQKeyboardToolbar/Placeholderable/IQPlaceholderable.swift
generated
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// IQPlaceholderable.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public protocol IQPlaceholderable: AnyObject {
|
||||||
|
|
||||||
|
var placeholder: String? { get set }
|
||||||
|
var attributedPlaceholder: NSAttributedString? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension UITextField: IQPlaceholderable { }
|
||||||
21
Pods/IQKeyboardToolbar/LICENSE
generated
Normal file
21
Pods/IQKeyboardToolbar/LICENSE
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Mohd Iftekhar Qurashi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
85
Pods/IQKeyboardToolbar/README.md
generated
Normal file
85
Pods/IQKeyboardToolbar/README.md
generated
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# IQKeyboardToolbar
|
||||||
|
|
||||||
|
[](https://travis-ci.org/hackiftekhar/IQKeyboardToolbar)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardToolbar)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardToolbar)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardToolbar)
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
IQKeyboardToolbar is available through [CocoaPods](https://cocoapods.org). To install
|
||||||
|
it, simply add the following line to your Podfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pod 'IQKeyboardToolbar'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This library can be used to add toolbar to the keyboard
|
||||||
|
|
||||||
|
Previous Next and Done button
|
||||||
|
```swift
|
||||||
|
textField.iq.addPreviousNextDone(target: self,
|
||||||
|
previousAction: #selector(textField1PreviousAction),
|
||||||
|
nextAction: #selector(textField1NextAction),
|
||||||
|
doneAction: #selector(doneAction), showPlaceholder: true)
|
||||||
|
```
|
||||||
|
|
||||||
|
Previous Next and Right button with customized titles or images
|
||||||
|
```swift
|
||||||
|
textField.iq.addPreviousNextRight(target: self,
|
||||||
|
previousConfiguration: .init(title: "Prev", action: #selector(textView1PreviousAction)),
|
||||||
|
nextConfiguration: .init(title: "Next", action: #selector(textView1NextAction)),
|
||||||
|
rightConfiguration: .init(image: UIImage(systemName: "chevron.down")!, action: #selector(doneAction)),
|
||||||
|
title: "Text View 1")
|
||||||
|
```
|
||||||
|
|
||||||
|
Action button
|
||||||
|
```swift
|
||||||
|
textField.iq.addDone(target: self,
|
||||||
|
action: #selector(doneAction),
|
||||||
|
title: "Select Account")
|
||||||
|
textField.iq.toolbar.titleBarButton.setTarget(self,
|
||||||
|
action: #selector(selectAccount))
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional leading and trailing buttons
|
||||||
|
```swift
|
||||||
|
textField.iq.toolbar.additionalLeadingItems = [.init(barButtonSystemItem: .add, target: self, action: #selector(addAction))]
|
||||||
|
textField.iq.toolbar.additionalTrailingItems = [.init(barButtonSystemItem: .camera, target: self, action: #selector(cameraAction))]
|
||||||
|
textField.iq.addToolbar(target: self,
|
||||||
|
previousConfiguration: nil,
|
||||||
|
nextConfiguration: .init(title: "Next", action: #selector(doneAction)),
|
||||||
|
rightConfiguration: .init(title: "Finish", action: #selector(doneAction)),
|
||||||
|
title: "TextView 2")
|
||||||
|
```
|
||||||
|
|
||||||
|
Hide Placeholder
|
||||||
|
```swift
|
||||||
|
textField1.iq.hidePlaceholder = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Customized Placeholder
|
||||||
|
```swift
|
||||||
|
textField1.iq.placeholder = "My Own Placeholder"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Iftekhar Qurashi hack.iftekhar@gmail.com
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
IQKeyboardToolbar is available under the MIT license. See the LICENSE file for more info.
|
||||||
14
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Assets/PrivacyInfo.xcprivacy
generated
Normal file
14
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Assets/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbarConfiguration.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardToolbar
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardToolbarConfiguration: NSObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
If YES, then uses textInputView's tintColor property,
|
||||||
|
otherwise tint color is default. Default is NO.
|
||||||
|
*/
|
||||||
|
public var useTextInputViewTintColor: Bool = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
This is used for toolbar.tintColor when textInputView.keyboardAppearance is UIKeyboardAppearanceDefault.
|
||||||
|
If useTextInputViewTintColor is YES then this property is ignored. Default is nil and uses black color.
|
||||||
|
*/
|
||||||
|
public var tintColor: UIColor?
|
||||||
|
|
||||||
|
/**
|
||||||
|
This is used for toolbar.barTintColor. Default is nil.
|
||||||
|
*/
|
||||||
|
public var barTintColor: UIColor?
|
||||||
|
|
||||||
|
/**
|
||||||
|
IQPreviousNextDisplayModeDefault: Show NextPrevious when there are more than 1 textInputView otherwise hide.
|
||||||
|
IQPreviousNextDisplayModeAlwaysHide: Do not show NextPrevious buttons in any case.
|
||||||
|
IQPreviousNextDisplayModeAlwaysShow: Always show nextPrevious buttons,
|
||||||
|
if there are more than 1 textInputView then both buttons will be visible but will be shown as disabled.
|
||||||
|
*/
|
||||||
|
public var previousNextDisplayMode: IQPreviousNextDisplayMode = .default
|
||||||
|
|
||||||
|
/**
|
||||||
|
/**
|
||||||
|
IQAutoToolbarBySubviews: Creates Toolbar according to subview's hierarchy of TextInputView's in view.
|
||||||
|
IQAutoToolbarByTag: Creates Toolbar according to tag property of TextInputView's.
|
||||||
|
IQAutoToolbarByPosition: Creates Toolbar according to the y,x position
|
||||||
|
of textInputView in it's superview coordinate.
|
||||||
|
|
||||||
|
Default is IQAutoToolbarBySubviews.
|
||||||
|
*/
|
||||||
|
AutoToolbar managing behavior. Default is IQAutoToolbarBySubviews.
|
||||||
|
*/
|
||||||
|
public var manageBehavior: IQKeyboardToolbarManageBehavior = .bySubviews
|
||||||
|
|
||||||
|
/**
|
||||||
|
Buttons configuration displayed on the toolbar, the selector parameter is ignored in below configuration
|
||||||
|
*/
|
||||||
|
public var previousBarButtonConfiguration: IQBarButtonItemConfiguration?
|
||||||
|
public var nextBarButtonConfiguration: IQBarButtonItemConfiguration?
|
||||||
|
public var doneBarButtonConfiguration: IQBarButtonItemConfiguration?
|
||||||
|
|
||||||
|
public let placeholderConfiguration: IQKeyboardToolbarPlaceholderConfiguration = .init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardToolbarConfiguration {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "useTextInputViewTintColor")
|
||||||
|
var useTextFieldTintColor: Bool {
|
||||||
|
get { fatalError() }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbarConstants.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// MARK: IQKeyboardToolbarManageBehavior
|
||||||
|
|
||||||
|
/**
|
||||||
|
`IQAutoToolbarBySubviews`
|
||||||
|
Creates Toolbar according to subview's hierarchy of textInputView's in view.
|
||||||
|
|
||||||
|
`IQAutoToolbarByTag`
|
||||||
|
Creates Toolbar according to tag property of textInputView's.
|
||||||
|
|
||||||
|
`IQAutoToolbarByPosition`
|
||||||
|
Creates Toolbar according to the y,x position of textInputView in it's superview coordinate.
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@objc public enum IQKeyboardToolbarManageBehavior: Int {
|
||||||
|
case bySubviews
|
||||||
|
case byTag
|
||||||
|
case byPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "IQKeyboardToolbarManageBehavior")
|
||||||
|
@objc public enum IQAutoToolbarManageBehavior: Int {
|
||||||
|
case bySubviews
|
||||||
|
case byTag
|
||||||
|
case byPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
`IQPreviousNextDisplayModeDefault`
|
||||||
|
Show NextPrevious when there are more than 1 textInputView otherwise hide.
|
||||||
|
|
||||||
|
`IQPreviousNextDisplayModeAlwaysHide`
|
||||||
|
Do not show NextPrevious buttons in any case.
|
||||||
|
|
||||||
|
`IQPreviousNextDisplayModeAlwaysShow`
|
||||||
|
Always show nextPrevious buttons,
|
||||||
|
if there are more than 1 textInputView then both buttons will be visible but will be shown as disabled.
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@objc public enum IQPreviousNextDisplayMode: Int {
|
||||||
|
case `default`
|
||||||
|
case alwaysHide
|
||||||
|
case alwaysShow
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbarManager+Debug
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// MARK: Debugging & Developer options
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
func showLog(_ logString: String, indentation: Int = 0) {
|
||||||
|
|
||||||
|
guard isDebuggingEnabled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if indentation < 0 {
|
||||||
|
self.logIndentation = max(0, self.logIndentation + indentation)
|
||||||
|
}
|
||||||
|
|
||||||
|
var preLog: String = "IQKeyboardToolbarManager"
|
||||||
|
for _ in 0 ... self.logIndentation {
|
||||||
|
preLog += "|\t"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(preLog + logString)
|
||||||
|
|
||||||
|
if indentation > 0 {
|
||||||
|
self.logIndentation += indentation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/IQDeepResponderContainerView.swift
generated
Normal file
29
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/IQDeepResponderContainerView.swift
generated
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// IQDeepResponderContainerView.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers open class IQDeepResponderContainerView: UIView {
|
||||||
|
}
|
||||||
183
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/IQKeyboardToolbarManager+Deprecated.swift
generated
Normal file
183
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/IQKeyboardToolbarManager+Deprecated.swift
generated
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbarManager+Deprecated.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardToolbar
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
// swiftlint:disable identifier_name
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "isEnabled")
|
||||||
|
var enableAutoToolbar: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "playInputClicks")
|
||||||
|
var shouldPlayInputClicks: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.manageBehavior")
|
||||||
|
var toolbarManageBehaviour: IQAutoToolbarManageBehavior {
|
||||||
|
get { .bySubviews }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.useTextInputViewTintColor")
|
||||||
|
var shouldToolbarUsesTextFieldTintColor: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.tintColor")
|
||||||
|
var toolbarTintColor: UIColor? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.barTintColor")
|
||||||
|
var toolbarBarTintColor: UIColor? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.previousNextDisplayMode")
|
||||||
|
var previousNextDisplayMode: IQPreviousNextDisplayMode {
|
||||||
|
get { .default }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "deepResponderAllowedContainerClasses")
|
||||||
|
var toolbarPreviousNextAllowedClasses: [UIView.Type] {
|
||||||
|
get { [] }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardToolbarManager {
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.image",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
|
||||||
|
var toolbarPreviousBarButtonItemImage: UIImage? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.title",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
|
||||||
|
var toolbarPreviousBarButtonItemText: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.accessibilityLabel",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
|
||||||
|
var toolbarPreviousBarButtonItemAccessibilityLabel: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.image",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
|
||||||
|
var toolbarNextBarButtonItemImage: UIImage? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.title",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
|
||||||
|
var toolbarNextBarButtonItemText: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.accessibilityLabel",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
|
||||||
|
var toolbarNextBarButtonItemAccessibilityLabel: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.image",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
|
||||||
|
var toolbarDoneBarButtonItemImage: UIImage? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.title",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
|
||||||
|
var toolbarDoneBarButtonItemText: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.accessibilityLabel",
|
||||||
|
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
|
||||||
|
var toolbarDoneBarButtonItemAccessibilityLabel: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.accessibilityLabel")
|
||||||
|
var toolbarTitleBarButtonItemAccessibilityLabel: String? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.showPlaceholder")
|
||||||
|
var shouldShowToolbarPlaceholder: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.font")
|
||||||
|
var placeholderFont: UIFont? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.color")
|
||||||
|
var placeholderColor: UIColor? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.buttonColor")
|
||||||
|
var placeholderButtonColor: UIColor? {
|
||||||
|
get { nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
|
// swiftlint:enable identifier_name
|
||||||
148
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/IQKeyboardToolbarManager+Internal.swift
generated
Normal file
148
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/IQKeyboardToolbarManager+Internal.swift
generated
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbarManager+Internal.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQTextInputViewNotification
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
/** Get all textInputView siblings of textInputView. */
|
||||||
|
func responderViews(of textInputView: some IQTextInputView) -> [any IQTextInputView]? {
|
||||||
|
|
||||||
|
var superConsideredView: UIView?
|
||||||
|
|
||||||
|
// If find any consider responderView in it's upper hierarchy then will get deepResponderView.
|
||||||
|
for allowedClass in deepResponderAllowedContainerClasses {
|
||||||
|
superConsideredView = (textInputView as UIView).iq.superviewOf(type: allowedClass)
|
||||||
|
if superConsideredView != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let swiftUIHostingView: UIView? = Self.getSwiftUIHostingView(textInputView: textInputView)
|
||||||
|
|
||||||
|
// (Enhancement ID: #22)
|
||||||
|
// If there is a superConsideredView in view's hierarchy,
|
||||||
|
// then fetching all it's subview that responds.
|
||||||
|
// No sorting for superConsideredView, it's by subView position.
|
||||||
|
if let view: UIView = swiftUIHostingView {
|
||||||
|
return view.iq.deepResponderViews()
|
||||||
|
} else if let view: UIView = superConsideredView {
|
||||||
|
return view.iq.deepResponderViews()
|
||||||
|
} else { // Otherwise fetching all the siblings
|
||||||
|
|
||||||
|
let textInputViews: [any IQTextInputView] = textInputView.iq.responderSiblings()
|
||||||
|
|
||||||
|
// Sorting textInputViews according to behavior
|
||||||
|
switch toolbarConfiguration.manageBehavior {
|
||||||
|
// If autoToolbar behavior is bySubviews, then returning it.
|
||||||
|
case .bySubviews: return textInputViews
|
||||||
|
|
||||||
|
// If autoToolbar behavior is by tag, then sorting it according to tag property.
|
||||||
|
case .byTag: return textInputViews.sortedByTag()
|
||||||
|
|
||||||
|
// If autoToolbar behavior is by tag, then sorting it according to tag property.
|
||||||
|
case .byPosition: return textInputViews.sortedByPosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func privateIsEnableAutoToolbar(of textInputView: some IQTextInputView) -> Bool {
|
||||||
|
|
||||||
|
var isEnabled: Bool = isEnabled
|
||||||
|
|
||||||
|
guard var textInputViewController = (textInputView as UIView).iq.viewContainingController() else {
|
||||||
|
return isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if textInputView is UISearchTextField {
|
||||||
|
if let navController: UINavigationController = textInputViewController as? UINavigationController,
|
||||||
|
let topController: UIViewController = navController.topViewController {
|
||||||
|
textInputViewController = topController
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not adding toolbar for searchTextField inside searchController.
|
||||||
|
if textInputViewController.navigationItem.searchController?.searchBar.searchTextField == textInputView {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isEnabled, enabledToolbarClasses.contains(where: { textInputViewController.isKind(of: $0) }) {
|
||||||
|
isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isEnabled {
|
||||||
|
|
||||||
|
// If found any toolbar disabled classes then return.
|
||||||
|
if disabledToolbarClasses.contains(where: { textInputViewController.isKind(of: $0) }) {
|
||||||
|
isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special Controllers
|
||||||
|
if isEnabled {
|
||||||
|
|
||||||
|
let classNameString: String = "\(type(of: textInputViewController.self))"
|
||||||
|
|
||||||
|
// _UIAlertControllerTextFieldViewController
|
||||||
|
if classNameString.contains("UIAlertController"),
|
||||||
|
classNameString.hasSuffix("TextFieldViewController") {
|
||||||
|
isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
private static func getSwiftUIHostingView(textInputView: some IQTextInputView) -> UIView? {
|
||||||
|
var swiftUIHostingView: UIView?
|
||||||
|
let swiftUIHostingViewName: String = "UIHostingView<"
|
||||||
|
var superView: UIView? = textInputView.superview
|
||||||
|
while let aSuperview: UIView = superView {
|
||||||
|
|
||||||
|
let classNameString: String = {
|
||||||
|
var name: String = "\(type(of: aSuperview.self))"
|
||||||
|
if name.hasPrefix("_") {
|
||||||
|
name.removeFirst()
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}()
|
||||||
|
|
||||||
|
if classNameString.hasPrefix(swiftUIHostingViewName) {
|
||||||
|
swiftUIHostingView = aSuperview
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
superView = aSuperview.superview
|
||||||
|
}
|
||||||
|
return swiftUIHostingView
|
||||||
|
}
|
||||||
|
}
|
||||||
173
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/IQKeyboardToolbarManager.swift
generated
Normal file
173
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/IQKeyboardToolbarManager.swift
generated
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbarManager.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQTextInputViewNotification
|
||||||
|
import IQKeyboardCore
|
||||||
|
import IQKeyboardToolbar
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQKeyboardToolbarManager: NSObject {
|
||||||
|
|
||||||
|
private let textInputViewObserver: IQTextInputViewNotification = .init()
|
||||||
|
private var storage: Set<AnyCancellable> = []
|
||||||
|
|
||||||
|
internal var textInputView: (some IQTextInputView)? {
|
||||||
|
textInputViewObserver.textInputView
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the default singleton instance.
|
||||||
|
*/
|
||||||
|
@MainActor
|
||||||
|
@objc public static let shared: IQKeyboardToolbarManager = .init()
|
||||||
|
|
||||||
|
/**
|
||||||
|
Automatic add the toolbar functionality. Default is false.
|
||||||
|
*/
|
||||||
|
public var isEnabled: Bool = false {
|
||||||
|
didSet {
|
||||||
|
reloadInputViews()
|
||||||
|
showLog("isEnabled: \(isEnabled ? "Yes" : "NO")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isDebuggingEnabled: Bool = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configurations related to the toolbar display over the keyboard.
|
||||||
|
*/
|
||||||
|
public let toolbarConfiguration: IQKeyboardToolbarConfiguration = .init()
|
||||||
|
|
||||||
|
// MARK: UISound handling
|
||||||
|
|
||||||
|
/**
|
||||||
|
If YES, then it plays inputClick sound on next/previous/done click.
|
||||||
|
*/
|
||||||
|
public var playInputClicks: Bool = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
Disable automatic toolbar creation within the scope of disabled toolbar viewControllers classes.
|
||||||
|
Within this scope, 'isEnabled' property is ignored. Class should be kind of UIViewController.
|
||||||
|
*/
|
||||||
|
public var disabledToolbarClasses: [UIViewController.Type] = [
|
||||||
|
UIAlertController.self,
|
||||||
|
UIInputViewController.self
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enable automatic toolbar creation within the scope of enabled toolbar viewControllers classes.
|
||||||
|
Within this scope, 'isEnabled' property is ignored. Class should be kind of UIViewController.
|
||||||
|
If same Class is added in disabledToolbarClasses list, then enabledToolbarClasses will be ignore.
|
||||||
|
*/
|
||||||
|
public var enabledToolbarClasses: [UIViewController.Type] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
Allowed subclasses of UIView to add all inner textInputView,
|
||||||
|
this will allow to navigate between textInputView contains in different superview.
|
||||||
|
Class should be kind of UIView.
|
||||||
|
*/
|
||||||
|
public var deepResponderAllowedContainerClasses: [UIView.Type] = [
|
||||||
|
UITableView.self,
|
||||||
|
UICollectionView.self,
|
||||||
|
IQDeepResponderContainerView.self
|
||||||
|
]
|
||||||
|
|
||||||
|
internal var logIndentation = 0
|
||||||
|
|
||||||
|
internal override init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
addTextInputViewObserver()
|
||||||
|
addBecomeActiveObserver()
|
||||||
|
|
||||||
|
// (Bug ID: #550)
|
||||||
|
// Loading IQKeyboardToolbar, IQTitleBarButtonItem, IQBarButtonItem to fix first time keyboard appearance delay
|
||||||
|
// If you experience exception breakpoint issue at below line then try these solutions
|
||||||
|
// https://stackoverflow.com/questions/27375640/all-exception-break-point-is-stopping-for-no-reason-on-simulator
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let textInputView: UITextField = UITextField()
|
||||||
|
textInputView.iq.addDone(target: nil, action: #selector(self.doneAction(_:)))
|
||||||
|
textInputView.iq.addPreviousNextDone(target: nil,
|
||||||
|
previousAction: #selector(self.previousAction(_:)),
|
||||||
|
nextAction: #selector(self.nextAction(_:)),
|
||||||
|
doneAction: #selector(self.doneAction(_:)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
private func removeTextInputViewObserver() {
|
||||||
|
textInputViewObserver.unsubscribe(identifier: "IQKeyboardToolbarManager")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addTextInputViewObserver() {
|
||||||
|
textInputViewObserver.subscribe(identifier: "IQKeyboardToolbarManager",
|
||||||
|
changeHandler: { [weak self] event, textInputView in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard (textInputView as UIView).iq.isAlertViewTextField() == false else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch event {
|
||||||
|
case .beginEditing:
|
||||||
|
reloadInputViews()
|
||||||
|
case .endEditing:
|
||||||
|
removeToolbarIfRequired(of: textInputView)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
private func removeBecomeActiveObserver() {
|
||||||
|
storage.forEach { $0.cancel() }
|
||||||
|
storage.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addBecomeActiveObserver() {
|
||||||
|
let beginEditingNotificationNames: [Notification.Name] = [
|
||||||
|
UIApplication.didBecomeActiveNotification
|
||||||
|
]
|
||||||
|
|
||||||
|
for notificationName in beginEditingNotificationNames {
|
||||||
|
NotificationCenter.default.publisher(for: notificationName)
|
||||||
|
.sink(receiveValue: { [weak self] info in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
// In SwiftUI View's when app comes from background to foreground
|
||||||
|
// The inputAccessoryView was getting removed in iOS 26. So forcing to reload.
|
||||||
|
self.reloadInputViews()
|
||||||
|
})
|
||||||
|
.store(in: &storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,210 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbarManager+Action.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
import IQKeyboardToolbar
|
||||||
|
|
||||||
|
// MARK: Previous next button actions
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns YES if can navigate to previous responder textInputView, otherwise NO.
|
||||||
|
*/
|
||||||
|
var canGoPrevious: Bool {
|
||||||
|
// If it is not first textInputView. then it's previous object canBecomeFirstResponder.
|
||||||
|
guard let textInputView: any IQTextInputView = self.textInputView,
|
||||||
|
let textInputViews: [any IQTextInputView] = responderViews(of: textInputView),
|
||||||
|
let index: Int = textInputViews.firstIndex(where: { $0 == textInputView }),
|
||||||
|
index > 0 else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns YES if can navigate to next responder textInputViews, otherwise NO.
|
||||||
|
*/
|
||||||
|
var canGoNext: Bool {
|
||||||
|
// If it is not first textInputView. then it's previous object canBecomeFirstResponder.
|
||||||
|
guard let textInputView: any IQTextInputView = self.textInputView,
|
||||||
|
let textInputViews: [any IQTextInputView] = responderViews(of: textInputView),
|
||||||
|
let index: Int = textInputViews.firstIndex(where: { $0 == textInputView }),
|
||||||
|
index < textInputViews.count-1 else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Navigate to previous responder textInputViews
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
func goPrevious() -> Bool {
|
||||||
|
|
||||||
|
// If it is not first textInputView. then it's previous object becomeFirstResponder.
|
||||||
|
guard let textInputView: any IQTextInputView = self.textInputView,
|
||||||
|
let textInputViews: [any IQTextInputView] = responderViews(of: textInputView),
|
||||||
|
let index: Int = textInputViews.firstIndex(where: { $0 == textInputView }),
|
||||||
|
index > 0 else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextTextInputView: any IQTextInputView = textInputViews[index-1]
|
||||||
|
|
||||||
|
let isAcceptAsFirstResponder: Bool = nextTextInputView.becomeFirstResponder()
|
||||||
|
|
||||||
|
// If it refuses then becoming previous textInputView as first responder again. (Bug ID: #96)
|
||||||
|
if !isAcceptAsFirstResponder {
|
||||||
|
showLog("Refuses to become first responder: \(nextTextInputView)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAcceptAsFirstResponder
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Navigate to next responder textInputView.
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
func goNext() -> Bool {
|
||||||
|
|
||||||
|
// If it is not first textInputView. then it's previous object becomeFirstResponder.
|
||||||
|
guard let textInputView: any IQTextInputView = self.textInputView,
|
||||||
|
let textInputViews: [any IQTextInputView] = responderViews(of: textInputView),
|
||||||
|
let index: Int = textInputViews.firstIndex(where: { $0 == textInputView }),
|
||||||
|
index < textInputViews.count-1 else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextTextInputView: any IQTextInputView = textInputViews[index+1]
|
||||||
|
|
||||||
|
let isAcceptAsFirstResponder: Bool = nextTextInputView.becomeFirstResponder()
|
||||||
|
|
||||||
|
// If it refuses then becoming previous textInputView as first responder again. (Bug ID: #96)
|
||||||
|
if !isAcceptAsFirstResponder {
|
||||||
|
showLog("Refuses to become first responder: \(nextTextInputView)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAcceptAsFirstResponder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
/** previousAction. */
|
||||||
|
func previousAction(_ barButton: IQBarButtonItem) {
|
||||||
|
|
||||||
|
// If user wants to play input Click sound.
|
||||||
|
if playInputClicks {
|
||||||
|
// Play Input Click Sound.
|
||||||
|
UIDevice.current.playInputClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
guard canGoPrevious,
|
||||||
|
let textInputView: any IQTextInputView = self.textInputView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let isAcceptedAsFirstResponder: Bool = goPrevious()
|
||||||
|
|
||||||
|
if isAcceptedAsFirstResponder {
|
||||||
|
Self.sendInvokeAction(of: barButton, sender: textInputView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** nextAction. */
|
||||||
|
func nextAction(_ barButton: IQBarButtonItem) {
|
||||||
|
|
||||||
|
// If user wants to play input Click sound.
|
||||||
|
if playInputClicks {
|
||||||
|
// Play Input Click Sound.
|
||||||
|
UIDevice.current.playInputClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
guard canGoNext,
|
||||||
|
let textInputView: any IQTextInputView = self.textInputView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let isAcceptedAsFirstResponder: Bool = goNext()
|
||||||
|
|
||||||
|
if isAcceptedAsFirstResponder {
|
||||||
|
Self.sendInvokeAction(of: barButton, sender: textInputView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** doneAction. Resigning current textInputView. */
|
||||||
|
func doneAction(_ barButton: IQBarButtonItem) {
|
||||||
|
|
||||||
|
// If user wants to play input Click sound.
|
||||||
|
if playInputClicks {
|
||||||
|
// Play Input Click Sound.
|
||||||
|
UIDevice.current.playInputClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let textInputView: any IQTextInputView = self.textInputView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resign textInputView.
|
||||||
|
let isResignedFirstResponder: Bool = textInputView.resignFirstResponder()
|
||||||
|
|
||||||
|
if isResignedFirstResponder {
|
||||||
|
Self.sendInvokeAction(of: barButton, sender: textInputView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private extension IQKeyboardToolbarManager {
|
||||||
|
private static func sendInvokeAction(of barButton: IQBarButtonItem, sender: some IQTextInputView) {
|
||||||
|
// Handling search bar special case
|
||||||
|
if let searchBar: UISearchBar = sender.iq.textFieldSearchBar() {
|
||||||
|
switch barButton {
|
||||||
|
case sender.internalToolbar.nextBarButton:
|
||||||
|
searchBar.internalToolbar.nextBarButton.invocation?.invoke(from: searchBar)
|
||||||
|
case sender.internalToolbar.previousBarButton:
|
||||||
|
searchBar.internalToolbar.previousBarButton.invocation?.invoke(from: searchBar)
|
||||||
|
case sender.internalToolbar.doneBarButton:
|
||||||
|
searchBar.internalToolbar.doneBarButton.invocation?.invoke(from: searchBar)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
barButton.invocation?.invoke(from: sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
fileprivate extension IQTextInputView {
|
||||||
|
var internalToolbar: IQKeyboardToolbar {
|
||||||
|
return iq.toolbar
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,287 @@
|
|||||||
|
//
|
||||||
|
// IQKeyboardToolbarManager+Toolbar.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
import IQKeyboardToolbar
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQKeyboardToolbarManager {
|
||||||
|
/** reloadInputViews to reload toolbar buttons enable/disable state on the fly Enhancement ID #434. */
|
||||||
|
func reloadInputViews() {
|
||||||
|
|
||||||
|
guard let textInputView = textInputView else { return }
|
||||||
|
// If enabled then adding toolbar.
|
||||||
|
if privateIsEnableAutoToolbar(of: textInputView) {
|
||||||
|
self.addToolbarIfRequired(of: textInputView)
|
||||||
|
} else {
|
||||||
|
self.removeToolbarIfRequired(of: textInputView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Default tag for toolbar with Done button -1001
|
||||||
|
*/
|
||||||
|
private static let toolbarTag = -1001
|
||||||
|
|
||||||
|
// swiftlint:disable function_body_length
|
||||||
|
// swiftlint:disable cyclomatic_complexity
|
||||||
|
/**
|
||||||
|
Add toolbar if it is required to add on textInputViews and it's siblings.
|
||||||
|
*/
|
||||||
|
func addToolbarIfRequired(of textInputView: some IQTextInputView) {
|
||||||
|
|
||||||
|
// Either there is no inputAccessoryView or
|
||||||
|
// if accessoryView is not appropriate for current situation
|
||||||
|
// (There is Previous/Next/Done toolbar)
|
||||||
|
guard let siblings: [any IQTextInputView] = responderViews(of: textInputView), !siblings.isEmpty,
|
||||||
|
!Self.hasUserDefinedInputAccessoryView(textInputView: textInputView) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
|
||||||
|
defer {
|
||||||
|
showLog("<<<<< \(#function) ended <<<<<", indentation: -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
showLog("Found \(siblings.count) responder sibling(s)")
|
||||||
|
let rightConfiguration: IQBarButtonItemConfiguration = getRightConfiguration()
|
||||||
|
|
||||||
|
let isTableCollectionView: Bool
|
||||||
|
if (textInputView as UIView).iq.superviewOf(type: UITableView.self) != nil ||
|
||||||
|
(textInputView as UIView).iq.superviewOf(type: UICollectionView.self) != nil {
|
||||||
|
isTableCollectionView = true
|
||||||
|
} else {
|
||||||
|
isTableCollectionView = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousNextDisplayMode: IQPreviousNextDisplayMode = toolbarConfiguration.previousNextDisplayMode
|
||||||
|
|
||||||
|
let havePreviousNext: Bool
|
||||||
|
switch previousNextDisplayMode {
|
||||||
|
case .default:
|
||||||
|
// If the textInputView is part of UITableView/UICollectionView then we should be exposing previous/next too
|
||||||
|
// Because at this time we don't know previous or next cell if it contains another textInputView to move.
|
||||||
|
if isTableCollectionView {
|
||||||
|
havePreviousNext = true
|
||||||
|
} else if siblings.count <= 1 {
|
||||||
|
// If only one object is found, then adding only Done button.
|
||||||
|
havePreviousNext = false
|
||||||
|
} else {
|
||||||
|
havePreviousNext = true
|
||||||
|
}
|
||||||
|
case .alwaysShow:
|
||||||
|
havePreviousNext = true
|
||||||
|
case .alwaysHide:
|
||||||
|
havePreviousNext = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholderConfig: IQKeyboardToolbarPlaceholderConfiguration = toolbarConfiguration.placeholderConfiguration
|
||||||
|
let titleText: String? = placeholderConfig.showPlaceholder ? textInputView.iq.drawingPlaceholder : nil
|
||||||
|
if havePreviousNext {
|
||||||
|
let prevConfiguration: IQBarButtonItemConfiguration = getPreviousConfiguration()
|
||||||
|
let nextConfiguration: IQBarButtonItemConfiguration = getNextConfiguration()
|
||||||
|
|
||||||
|
textInputView.iq.addToolbar(target: self,
|
||||||
|
previousConfiguration: prevConfiguration,
|
||||||
|
nextConfiguration: nextConfiguration,
|
||||||
|
rightConfiguration: rightConfiguration, title: titleText,
|
||||||
|
titleAccessibilityLabel: placeholderConfig.accessibilityLabel)
|
||||||
|
if isTableCollectionView {
|
||||||
|
// (Bug ID: #56)
|
||||||
|
// In case of UITableView, the next/previous buttons should always be enabled.
|
||||||
|
textInputView.iq.toolbar.previousBarButton.isEnabled = true
|
||||||
|
textInputView.iq.toolbar.nextBarButton.isEnabled = true
|
||||||
|
} else {
|
||||||
|
// If first textInputView, then previous should not be enabled.
|
||||||
|
if let first = siblings.first {
|
||||||
|
textInputView.iq.toolbar.previousBarButton.isEnabled = first != textInputView
|
||||||
|
} else {
|
||||||
|
textInputView.iq.toolbar.previousBarButton.isEnabled = false
|
||||||
|
}
|
||||||
|
// If last textInputView, then next should not be enabled.
|
||||||
|
if let last = siblings.last {
|
||||||
|
textInputView.iq.toolbar.nextBarButton.isEnabled = last != textInputView
|
||||||
|
} else {
|
||||||
|
textInputView.iq.toolbar.nextBarButton.isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textInputView.iq.addToolbar(target: self, rightConfiguration: rightConfiguration,
|
||||||
|
title: titleText,
|
||||||
|
titleAccessibilityLabel: placeholderConfig.accessibilityLabel)
|
||||||
|
}
|
||||||
|
// (Bug ID: #78)
|
||||||
|
textInputView.inputAccessoryView?.tag = IQKeyboardToolbarManager.toolbarTag
|
||||||
|
|
||||||
|
Self.applyToolbarConfiguration(textInputView: textInputView, toolbarConfiguration: toolbarConfiguration)
|
||||||
|
}
|
||||||
|
// swiftlint:enable function_body_length
|
||||||
|
// swiftlint:enable cyclomatic_complexity
|
||||||
|
|
||||||
|
/** Remove any toolbar if it is IQKeyboardToolbar. */
|
||||||
|
func removeToolbarIfRequired(of textInputView: some IQTextInputView) { // (Bug ID: #18)
|
||||||
|
|
||||||
|
guard let toolbar: IQKeyboardToolbar = textInputView.inputAccessoryView as? IQKeyboardToolbar,
|
||||||
|
toolbar.tag == IQKeyboardToolbarManager.toolbarTag else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
|
||||||
|
|
||||||
|
defer {
|
||||||
|
showLog("<<<<< \(#function) ended <<<<<", indentation: -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
textInputView.inputAccessoryView = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private extension IQKeyboardToolbarManager {
|
||||||
|
|
||||||
|
static func hasUserDefinedInputAccessoryView(textInputView: some IQTextInputView) -> Bool {
|
||||||
|
guard let inputAccessoryView: UIView = textInputView.inputAccessoryView,
|
||||||
|
inputAccessoryView.tag != IQKeyboardToolbarManager.toolbarTag else { return false }
|
||||||
|
|
||||||
|
let swiftUIAccessoryNamePrefixes: [String] = [
|
||||||
|
"InputAccessoryHost<InputAccessoryBar>", // iOS 17 and below
|
||||||
|
"RootUIView", // iOS 18
|
||||||
|
"_UIInputViewContent", // iOS 18 system views
|
||||||
|
"_UIKeyboardInputAccessory" // Additional iOS 18 system view
|
||||||
|
]
|
||||||
|
let classNameString: String = "\(type(of: inputAccessoryView.classForCoder))"
|
||||||
|
|
||||||
|
// If it's SwiftUI accessory view but doesn't have a height (fake accessory view), then we should
|
||||||
|
// add our own accessoryView otherwise, keep the SwiftUI accessoryView since user has added it from code
|
||||||
|
let isSwiftUIAccessoryView = swiftUIAccessoryNamePrefixes.contains(where: { classNameString.hasPrefix($0) })
|
||||||
|
|
||||||
|
// A height of 0 or very small height typically indicates a system placeholder
|
||||||
|
guard isSwiftUIAccessoryView,
|
||||||
|
inputAccessoryView.subviews.isEmpty else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRightConfiguration() -> IQBarButtonItemConfiguration {
|
||||||
|
let rightConfiguration: IQBarButtonItemConfiguration
|
||||||
|
if let configuration: IQBarButtonItemConfiguration = toolbarConfiguration.doneBarButtonConfiguration {
|
||||||
|
rightConfiguration = configuration
|
||||||
|
rightConfiguration.action = #selector(self.doneAction(_:))
|
||||||
|
} else {
|
||||||
|
#if compiler(>=6.2) // Xcode 26
|
||||||
|
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
|
||||||
|
if #available(iOS 26.0, *), !requiresCompatibility {
|
||||||
|
rightConfiguration = IQBarButtonItemConfiguration(image: UIImage(systemName: "checkmark")!, action: #selector(self.doneAction(_:)))
|
||||||
|
} else {
|
||||||
|
rightConfiguration = IQBarButtonItemConfiguration(systemItem: .done, action: #selector(self.doneAction(_:)))
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
rightConfiguration = IQBarButtonItemConfiguration(systemItem: .done, action: #selector(self.doneAction(_:)))
|
||||||
|
#endif
|
||||||
|
rightConfiguration.accessibilityLabel = "Done"
|
||||||
|
}
|
||||||
|
return rightConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPreviousConfiguration() -> IQBarButtonItemConfiguration {
|
||||||
|
let prevConfiguration: IQBarButtonItemConfiguration
|
||||||
|
if let configuration: IQBarButtonItemConfiguration = toolbarConfiguration.previousBarButtonConfiguration {
|
||||||
|
configuration.action = #selector(self.previousAction(_:))
|
||||||
|
prevConfiguration = configuration
|
||||||
|
} else {
|
||||||
|
let chevronUp: UIImage = UIImage(systemName: "chevron.up") ?? UIImage()
|
||||||
|
prevConfiguration = IQBarButtonItemConfiguration(image: chevronUp,
|
||||||
|
action: #selector(self.previousAction(_:)))
|
||||||
|
prevConfiguration.accessibilityLabel = "Previous"
|
||||||
|
}
|
||||||
|
return prevConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNextConfiguration() -> IQBarButtonItemConfiguration {
|
||||||
|
let nextConfiguration: IQBarButtonItemConfiguration
|
||||||
|
if let configuration: IQBarButtonItemConfiguration = toolbarConfiguration.nextBarButtonConfiguration {
|
||||||
|
configuration.action = #selector(self.nextAction(_:))
|
||||||
|
nextConfiguration = configuration
|
||||||
|
} else {
|
||||||
|
let chevronDown: UIImage = UIImage(systemName: "chevron.down") ?? UIImage()
|
||||||
|
nextConfiguration = IQBarButtonItemConfiguration(image: chevronDown,
|
||||||
|
action: #selector(self.nextAction(_:)))
|
||||||
|
nextConfiguration.accessibilityLabel = "Next"
|
||||||
|
}
|
||||||
|
return nextConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
static func applyToolbarConfiguration(textInputView: some IQTextInputView,
|
||||||
|
toolbarConfiguration: IQKeyboardToolbarConfiguration) {
|
||||||
|
|
||||||
|
let toolbar: IQKeyboardToolbar = textInputView.iq.toolbar
|
||||||
|
|
||||||
|
// Setting toolbar tintColor // (Enhancement ID: #30)
|
||||||
|
if toolbarConfiguration.useTextInputViewTintColor {
|
||||||
|
toolbar.tintColor = textInputView.tintColor
|
||||||
|
} else {
|
||||||
|
toolbar.tintColor = toolbarConfiguration.tintColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bar style according to keyboard appearance
|
||||||
|
switch textInputView.keyboardAppearance {
|
||||||
|
case .dark:
|
||||||
|
toolbar.barStyle = .black
|
||||||
|
toolbar.barTintColor = nil
|
||||||
|
default:
|
||||||
|
toolbar.barStyle = .default
|
||||||
|
toolbar.barTintColor = toolbarConfiguration.barTintColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting toolbar title font. // (Enhancement ID: #30)
|
||||||
|
guard toolbarConfiguration.placeholderConfiguration.showPlaceholder,
|
||||||
|
!textInputView.iq.hidePlaceholder else {
|
||||||
|
toolbar.titleBarButton.title = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updating placeholder font to toolbar. //(Bug ID: #148, #272)
|
||||||
|
if toolbar.titleBarButton.title == nil ||
|
||||||
|
toolbar.titleBarButton.title != textInputView.iq.drawingPlaceholder {
|
||||||
|
toolbar.titleBarButton.title = textInputView.iq.drawingPlaceholder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting toolbar title font. // (Enhancement ID: #30)
|
||||||
|
toolbar.titleBarButton.titleFont = toolbarConfiguration.placeholderConfiguration.font
|
||||||
|
|
||||||
|
// Setting toolbar title color. // (Enhancement ID: #880)
|
||||||
|
toolbar.titleBarButton.titleColor = toolbarConfiguration.placeholderConfiguration.color
|
||||||
|
|
||||||
|
// Setting toolbar button title color. // (Enhancement ID: #880)
|
||||||
|
toolbar.titleBarButton.selectableTitleColor = toolbarConfiguration.placeholderConfiguration.buttonColor
|
||||||
|
}
|
||||||
|
}
|
||||||
58
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/UIKitExtensions/Array+Sort.swift
generated
Normal file
58
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/UIKitExtensions/Array+Sort.swift
generated
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// Array+Sort.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
/**
|
||||||
|
UIView.subviews sorting category.
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension Array where Element: IQTextInputView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the array by sorting the UIView's by their tag property.
|
||||||
|
*/
|
||||||
|
func sortedByTag() -> [Element] {
|
||||||
|
|
||||||
|
return sorted(by: { (obj1: Element, obj2: Element) -> Bool in
|
||||||
|
|
||||||
|
return (obj1.tag < obj2.tag)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the array by sorting the UIView's by their tag property.
|
||||||
|
*/
|
||||||
|
func sortedByPosition() -> [Element] {
|
||||||
|
|
||||||
|
return sorted(by: { (obj1: Element, obj2: Element) -> Bool in
|
||||||
|
if obj1.frame.minY != obj2.frame.minY {
|
||||||
|
return obj1.frame.minY < obj2.frame.minY
|
||||||
|
} else {
|
||||||
|
return obj1.frame.minX < obj2.frame.minX
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
179
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/UIKitExtensions/UIView+Responders.swift
generated
Normal file
179
Pods/IQKeyboardToolbarManager/IQKeyboardToolbarManager/Classes/UIKitExtensions/UIView+Responders.swift
generated
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
//
|
||||||
|
// UIView+Responders.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private struct AssociatedKeys {
|
||||||
|
static var ignoreSwitchingByNextPrevious: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
UIView category for managing textInputView
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQKeyboardExtension where Base: IQTextInputView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
If ignoreSwitchingByNextPrevious is true then library will ignore this textInputView
|
||||||
|
while moving to other textInputView using keyboard toolbar next previous buttons.
|
||||||
|
Default is false
|
||||||
|
*/
|
||||||
|
var ignoreSwitchingByNextPrevious: Bool {
|
||||||
|
get {
|
||||||
|
if let base = base {
|
||||||
|
return objc_getAssociatedObject(base, &AssociatedKeys.ignoreSwitchingByNextPrevious) as? Bool ?? false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
if let base = base {
|
||||||
|
objc_setAssociatedObject(base, &AssociatedKeys.ignoreSwitchingByNextPrevious,
|
||||||
|
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable unused_setter_value
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension UIView {
|
||||||
|
@available(*, unavailable, renamed: "iq.ignoreSwitchingByNextPrevious")
|
||||||
|
var ignoreSwitchingByNextPrevious: Bool {
|
||||||
|
get { false }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable unused_setter_value
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardExtension where Base: IQTextInputView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns all siblings of the receiver which canBecomeFirstResponder.
|
||||||
|
*/
|
||||||
|
func responderSiblings() -> [any IQTextInputView] {
|
||||||
|
|
||||||
|
// Getting all siblings
|
||||||
|
guard let siblings: [UIView] = base?.superview?.subviews else { return [] }
|
||||||
|
|
||||||
|
// Array of textInputView.
|
||||||
|
var textInputViews: [any IQTextInputView] = []
|
||||||
|
for view in siblings {
|
||||||
|
if let view = view as? any IQTextInputView,
|
||||||
|
view == base || !view.internalIgnoreSwitchingByNextPrevious,
|
||||||
|
view.internalCanBecomeFirstResponder() {
|
||||||
|
textInputViews.append(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return textInputViews
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
UIView category for managing textInputView
|
||||||
|
*/
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
internal extension IQKeyboardExtension where Base: UIView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns all deep subViews of the receiver which canBecomeFirstResponder.
|
||||||
|
*/
|
||||||
|
func deepResponderViews() -> [any IQTextInputView] {
|
||||||
|
|
||||||
|
guard let subviews: [UIView] = base?.subviews, !subviews.isEmpty else { return [] }
|
||||||
|
|
||||||
|
// Array of textInputViews.
|
||||||
|
var textInputViews: [any IQTextInputView] = []
|
||||||
|
|
||||||
|
for view in subviews {
|
||||||
|
|
||||||
|
if let view = view as? any IQTextInputView,
|
||||||
|
view == base || !view.internalIgnoreSwitchingByNextPrevious,
|
||||||
|
view.internalCanBecomeFirstResponder() {
|
||||||
|
textInputViews.append(view)
|
||||||
|
}
|
||||||
|
// Sometimes there are hidden or disabled views and textInputView inside them still recorded,
|
||||||
|
// so we added some more validations here (Bug ID: #458)
|
||||||
|
// Uncommented else (Bug ID: #625)
|
||||||
|
else if view.subviews.count != 0,
|
||||||
|
base?.isUserInteractionEnabled == true,
|
||||||
|
base?.isHidden == false, base?.alpha != 0.0 {
|
||||||
|
for deepView in view.iq.deepResponderViews() {
|
||||||
|
textInputViews.append(deepView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// subviews are returning in opposite order. Sorting according the frames 'y'.
|
||||||
|
return textInputViews.sorted(by: { (view1: any IQTextInputView, view2: any IQTextInputView) -> Bool in
|
||||||
|
|
||||||
|
let frame1: CGRect = view1.convert(view1.bounds, to: base)
|
||||||
|
let frame2: CGRect = view2.convert(view2.bounds, to: base)
|
||||||
|
|
||||||
|
if frame1.minY != frame2.minY {
|
||||||
|
return frame1.minY < frame2.minY
|
||||||
|
} else {
|
||||||
|
return frame1.minX < frame2.minX
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
private extension IQKeyboardExtension where Base: IQTextInputView {
|
||||||
|
|
||||||
|
func canBecomeFirstResponder() -> Bool {
|
||||||
|
|
||||||
|
var canBecomeFirstResponder: Bool = base?.iqIsEnabled == true
|
||||||
|
|
||||||
|
if canBecomeFirstResponder {
|
||||||
|
canBecomeFirstResponder = base?.isUserInteractionEnabled == true &&
|
||||||
|
base?.isHidden == false &&
|
||||||
|
base?.alpha != 0.0 &&
|
||||||
|
!isAlertViewTextField() &&
|
||||||
|
textFieldSearchBar() == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return canBecomeFirstResponder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
fileprivate extension IQTextInputView {
|
||||||
|
var internalIgnoreSwitchingByNextPrevious: Bool {
|
||||||
|
return iq.ignoreSwitchingByNextPrevious
|
||||||
|
}
|
||||||
|
func internalCanBecomeFirstResponder() -> Bool {
|
||||||
|
return iq.canBecomeFirstResponder()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// UIView+RespondersObjc.swift
|
||||||
|
// https://github.com/hackiftekhar/IQKeyboardToolbar
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// MARK: For ObjectiveC Compatibility
|
||||||
|
|
||||||
|
// swiftlint:disable identifier_name
|
||||||
|
@objc public extension UITextField {
|
||||||
|
|
||||||
|
var iq_ignoreSwitchingByNextPrevious: Bool {
|
||||||
|
get { iq.ignoreSwitchingByNextPrevious }
|
||||||
|
set { iq.ignoreSwitchingByNextPrevious = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public extension UITextView {
|
||||||
|
|
||||||
|
var iq_ignoreSwitchingByNextPrevious: Bool {
|
||||||
|
get { iq.ignoreSwitchingByNextPrevious }
|
||||||
|
set { iq.ignoreSwitchingByNextPrevious = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable identifier_name
|
||||||
21
Pods/IQKeyboardToolbarManager/LICENSE
generated
Normal file
21
Pods/IQKeyboardToolbarManager/LICENSE
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Mohd Iftekhar Qurashi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
134
Pods/IQKeyboardToolbarManager/README.md
generated
Normal file
134
Pods/IQKeyboardToolbarManager/README.md
generated
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
# IQKeyboardToolbarManager
|
||||||
|
|
||||||
|
[](https://travis-ci.org/hackiftekhar/IQKeyboardToolbarManager)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardToolbarManager)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardToolbarManager)
|
||||||
|
[](https://cocoapods.org/pods/IQKeyboardToolbarManager)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Swift Package Manager (Recommended)
|
||||||
|
|
||||||
|
IQKeyboardToolbarManager is available through [Swift Package Manager](https://swift.org/package-manager/).
|
||||||
|
|
||||||
|
**Requirements:** iOS 13.0+, Swift 5.7+
|
||||||
|
|
||||||
|
#### Using Xcode:
|
||||||
|
1. In Xcode, go to `File` → `Add Package Dependencies...`
|
||||||
|
2. Enter the repository URL: `https://github.com/hackiftekhar/IQKeyboardToolbarManager`
|
||||||
|
3. Select the version rule (e.g., "Up to Next Major Version")
|
||||||
|
4. Click `Add Package`
|
||||||
|
5. Select the `IQKeyboardToolbarManager` library and click `Add Package`
|
||||||
|
|
||||||
|
#### Using Package.swift:
|
||||||
|
Add the following dependency to your `Package.swift` file:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/hackiftekhar/IQKeyboardToolbarManager", from: "1.1.3")
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add `IQKeyboardToolbarManager` to your target dependencies:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
.target(
|
||||||
|
name: "YourTarget",
|
||||||
|
dependencies: ["IQKeyboardToolbarManager"]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### CocoaPods
|
||||||
|
|
||||||
|
IQKeyboardToolbarManager is also available through [CocoaPods](https://cocoapods.org). To install
|
||||||
|
it, simply add the following line to your Podfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pod 'IQKeyboardToolbarManager'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Carthage
|
||||||
|
|
||||||
|
Add the following line to your `Cartfile`:
|
||||||
|
|
||||||
|
```
|
||||||
|
github "hackiftekhar/IQKeyboardToolbarManager"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
After adding IQKeyboardToolbarManager to your project, import it and enable toolbar handling in AppDelegate:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardToolbarManager
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
IQKeyboardToolbarManager.shared.isEnabled = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Cuistomize Behavior
|
||||||
|
```swift
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.useTextInputViewTintColor = true
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.tintColor = UIColor.systemGreen
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.barTintColor = UIColor.systemYellow
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.previousNextDisplayMode = .alwaysShow
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.manageBehavior = .byPosition
|
||||||
|
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.previousBarButtonConfiguration = ... // BarButton configuration to change title, image or system image etc
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.nextBarButtonConfiguration = ... // BarButton configuration to change title, image or system image etc
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.doneBarButtonConfiguration = ... // BarButton configuration to change title, image or system image etc
|
||||||
|
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.placeholderConfiguration.showPlaceholder = false
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.placeholderConfiguration.font = UIFont.italicSystemFont(ofSize: 14)
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.placeholderConfiguration.color = UIColor.systemPurple
|
||||||
|
IQKeyboardToolbarManager.shared.toolbarConfiguration.placeholderConfiguration.buttonColor = UIColor.systemBrown // This is used only if placeholder is an action button
|
||||||
|
|
||||||
|
IQKeyboardToolbarManager.shared.playInputClicks = false
|
||||||
|
|
||||||
|
IQKeyboardToolbarManager.shared.disabledToolbarClasses.append(ChatViewController.self)
|
||||||
|
IQKeyboardToolbarManager.shared.enabledToolbarClasses.append(LoginViewController.self)
|
||||||
|
IQKeyboardToolbarManager.shared.deepResponderAllowedContainerClasses.append(UIStackView.self)
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful functions and variables
|
||||||
|
```swift
|
||||||
|
if IQKeyboardToolbarManager.shared.canGoPrevious {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
if IQKeyboardToolbarManager.shared.canGoNext {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
IQKeyboardToolbarManager.shared.goPrevious()
|
||||||
|
IQKeyboardToolbarManager.shared.goNext()
|
||||||
|
|
||||||
|
IQKeyboardToolbarManager.shared.reloadInputViews() // If some textInputView hierarchy are changed on the fly then use this to reload button states
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful functions and variables for TextInputView
|
||||||
|
```swift
|
||||||
|
textField.iq.ignoreSwitchingByNextPrevious = false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Iftekhar Qurashi hack.iftekhar@gmail.com
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
IQKeyboardToolbarManager is available under the MIT license. See the LICENSE file for more info.
|
||||||
14
Pods/IQTextInputViewNotification/IQTextInputViewNotification/Assets/PrivacyInfo.xcprivacy
generated
Normal file
14
Pods/IQTextInputViewNotification/IQTextInputViewNotification/Assets/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
91
Pods/IQTextInputViewNotification/IQTextInputViewNotification/Classes/IQTextInputViewInfo.swift
generated
Normal file
91
Pods/IQTextInputViewNotification/IQTextInputViewNotification/Classes/IQTextInputViewInfo.swift
generated
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// IQTextInputViewInfo.swift
|
||||||
|
// https://github.com/hackiftekhar/IQTextInputViewNotification
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
public import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public struct IQTextInputViewInfo: Equatable {
|
||||||
|
|
||||||
|
nonisolated public static func == (lhs: Self, rhs: Self) -> Bool {
|
||||||
|
return lhs.textInputView == rhs.textInputView &&
|
||||||
|
lhs.event == rhs.event
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
@objc public enum Event: Int {
|
||||||
|
case beginEditing
|
||||||
|
case endEditing
|
||||||
|
|
||||||
|
public var name: String {
|
||||||
|
switch self {
|
||||||
|
case .beginEditing:
|
||||||
|
return "BeginEditing"
|
||||||
|
case .endEditing:
|
||||||
|
return "EndEditing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let event: Event
|
||||||
|
|
||||||
|
public let textInputView: any IQTextInputView
|
||||||
|
|
||||||
|
internal init?(notification: Notification, event: Event) {
|
||||||
|
guard let view: any IQTextInputView = notification.object as? (any IQTextInputView) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.event = event
|
||||||
|
textInputView = view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Deprecated
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
public extension IQTextInputViewInfo {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "event")
|
||||||
|
var name: Event { event }
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "textInputView")
|
||||||
|
var textFieldView: any IQTextInputView { textInputView }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "IQTextInputViewInfo")
|
||||||
|
@MainActor
|
||||||
|
public struct IQTextFieldViewInfo: Equatable {}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@objcMembers public class IQTextInputViewInfoObjC: NSObject {
|
||||||
|
private let wrappedValue: IQTextInputViewInfo
|
||||||
|
|
||||||
|
public var event: IQTextInputViewInfo.Event { wrappedValue.event }
|
||||||
|
|
||||||
|
public var textInputView: any IQTextInputView { wrappedValue.textInputView }
|
||||||
|
|
||||||
|
init(wrappedValue: IQTextInputViewInfo){
|
||||||
|
self.wrappedValue = wrappedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
196
Pods/IQTextInputViewNotification/IQTextInputViewNotification/Classes/IQTextInputViewNotification.swift
generated
Normal file
196
Pods/IQTextInputViewNotification/IQTextInputViewNotification/Classes/IQTextInputViewNotification.swift
generated
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
//
|
||||||
|
// IQTextInputViewNotification.swift
|
||||||
|
// https://github.com/hackiftekhar/IQTextInputViewNotification
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
public import IQKeyboardCore
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQTextInputViewNotification: NSObject {
|
||||||
|
|
||||||
|
private var storage: Set<AnyCancellable> = []
|
||||||
|
|
||||||
|
private var textInputViewObservers: [AnyHashable: TextInputViewCompletion] = [:]
|
||||||
|
|
||||||
|
private var findInteractionTextInputViewInfo: IQTextInputViewInfo?
|
||||||
|
|
||||||
|
public private(set) var textInputViewInfo: IQTextInputViewInfo?
|
||||||
|
|
||||||
|
public var event: IQTextInputViewInfo.Event? {
|
||||||
|
textInputViewInfo?.event
|
||||||
|
}
|
||||||
|
|
||||||
|
public var textInputView: (any IQTextInputView)? {
|
||||||
|
return textInputViewInfo?.textInputView
|
||||||
|
}
|
||||||
|
|
||||||
|
public override init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
// Registering for TextInputView notification.
|
||||||
|
do {
|
||||||
|
let beginEditingNotificationNames: [Notification.Name] = [
|
||||||
|
UITextField.textDidBeginEditingNotification,
|
||||||
|
UITextView.textDidBeginEditingNotification
|
||||||
|
]
|
||||||
|
|
||||||
|
for notificationName in beginEditingNotificationNames {
|
||||||
|
NotificationCenter.default.publisher(for: notificationName)
|
||||||
|
.compactMap({ IQTextInputViewInfo(notification: $0, event: .beginEditing) })
|
||||||
|
.sink(receiveValue: { [weak self] info in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.didBeginEditing(info: info)
|
||||||
|
})
|
||||||
|
.store(in: &storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let endEditingNotificationNames: [Notification.Name] = [
|
||||||
|
UITextField.textDidEndEditingNotification,
|
||||||
|
UITextView.textDidEndEditingNotification
|
||||||
|
]
|
||||||
|
|
||||||
|
for notificationName in endEditingNotificationNames {
|
||||||
|
NotificationCenter.default.publisher(for: notificationName)
|
||||||
|
.compactMap({ IQTextInputViewInfo(notification: $0, event: .endEditing) })
|
||||||
|
.sink(receiveValue: { [weak self] info in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.didEndEditing(info: info)
|
||||||
|
})
|
||||||
|
.store(in: &storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func didBeginEditing(info: IQTextInputViewInfo) {
|
||||||
|
|
||||||
|
#if compiler(>=5.7) // Xcode 14
|
||||||
|
if #available(iOS 16.0, *),
|
||||||
|
let findInteractionTextInputViewInfo = findInteractionTextInputViewInfo,
|
||||||
|
findInteractionTextInputViewInfo.textInputView.iqFindInteraction?.isFindNavigatorVisible == true {
|
||||||
|
// // This means the this didBeginEditing call comes due to find interaction
|
||||||
|
textInputViewInfo = findInteractionTextInputViewInfo
|
||||||
|
sendEvent(info: findInteractionTextInputViewInfo)
|
||||||
|
} else if textInputViewInfo != info {
|
||||||
|
textInputViewInfo = info
|
||||||
|
findInteractionTextInputViewInfo = nil
|
||||||
|
sendEvent(info: info)
|
||||||
|
} else {
|
||||||
|
findInteractionTextInputViewInfo = nil
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if textInputViewInfo != info {
|
||||||
|
textInputViewInfo = info
|
||||||
|
findInteractionTextInputViewInfo = nil
|
||||||
|
sendEvent(info: info)
|
||||||
|
} else {
|
||||||
|
findInteractionTextInputViewInfo = nil
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private func didEndEditing(info: IQTextInputViewInfo) {
|
||||||
|
|
||||||
|
if textInputViewInfo != info {
|
||||||
|
#if compiler(>=5.7) // Xcode 14
|
||||||
|
if #available(iOS 16.0, *),
|
||||||
|
info.textInputView.iqIsFindInteractionEnabled {
|
||||||
|
findInteractionTextInputViewInfo = textInputViewInfo
|
||||||
|
} else {
|
||||||
|
findInteractionTextInputViewInfo = nil
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
findInteractionTextInputViewInfo = nil
|
||||||
|
#endif
|
||||||
|
textInputViewInfo = info
|
||||||
|
sendEvent(info: info)
|
||||||
|
textInputViewInfo = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQTextInputViewNotification {
|
||||||
|
|
||||||
|
typealias TextInputViewCompletion = (_ event: IQTextInputViewInfo.Event,
|
||||||
|
_ textInputView: any IQTextInputView) -> Void
|
||||||
|
|
||||||
|
func subscribe(identifier: AnyHashable, changeHandler: @escaping TextInputViewCompletion) {
|
||||||
|
textInputViewObservers[identifier] = changeHandler
|
||||||
|
|
||||||
|
if let textInputViewInfo = textInputViewInfo {
|
||||||
|
changeHandler(textInputViewInfo.event, textInputViewInfo.textInputView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsubscribe(identifier: AnyHashable) {
|
||||||
|
textInputViewObservers[identifier] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSubscribed(identifier: AnyHashable) -> Bool {
|
||||||
|
return textInputViewObservers[identifier] != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@nonobjc
|
||||||
|
private func sendEvent(info: IQTextInputViewInfo) {
|
||||||
|
|
||||||
|
for block in textInputViewObservers.values {
|
||||||
|
block(info.event, info.textInputView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Deprecated
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
public extension IQTextInputViewNotification {
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "textInputViewInfo")
|
||||||
|
var textFieldViewInfo: IQTextInputViewInfo? { textInputViewInfo }
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "textInputView")
|
||||||
|
var textFieldView: (some IQTextInputView)? { textInputView }
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "subscribe(identifier:changeHandler:)")
|
||||||
|
func registerTextFieldViewChange(identifier: AnyHashable, changeHandler: @escaping TextInputViewCompletion) {}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "unsubscribe(identifier:)")
|
||||||
|
func unregisterSizeChange(identifier: AnyHashable) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable, renamed: "IQTextInputViewNotification")
|
||||||
|
@MainActor
|
||||||
|
@objcMembers public final class IQTextFieldViewListener: NSObject {}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc public extension IQTextInputViewNotification {
|
||||||
|
|
||||||
|
var textInputViewInfoObjc: IQTextInputViewInfoObjC? {
|
||||||
|
guard let textInputViewInfo = textInputViewInfo else { return nil }
|
||||||
|
return IQTextInputViewInfoObjC(wrappedValue: textInputViewInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Pods/IQTextInputViewNotification/LICENSE
generated
Normal file
19
Pods/IQTextInputViewNotification/LICENSE
generated
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2024 hackiftekhar <ideviftekhar@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
66
Pods/IQTextInputViewNotification/README.md
generated
Normal file
66
Pods/IQTextInputViewNotification/README.md
generated
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# IQTextInputViewNotification
|
||||||
|
|
||||||
|
[](https://travis-ci.org/hackiftekhar/IQTextInputViewNotification)
|
||||||
|
[](https://cocoapods.org/pods/IQTextInputViewNotification)
|
||||||
|
[](https://cocoapods.org/pods/IQTextInputViewNotification)
|
||||||
|
[](https://cocoapods.org/pods/IQTextInputViewNotification)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
IQTextInputViewNotification is available through [CocoaPods](https://cocoapods.org). To install
|
||||||
|
it, simply add the following line to your Podfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pod 'IQTextInputViewNotification'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To observe textInputView becomeFirstResponder and resignFirstResponder changes, subscribe to the textInputView events:-
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import IQTextInputViewNotification
|
||||||
|
|
||||||
|
class ViewController: UIViewController {
|
||||||
|
|
||||||
|
private let textInputViewObserver: IQTextInputViewNotification = .init()
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
// Subscribe
|
||||||
|
textInputViewObserver.subscribe(identifier: "YOUR_UNIQUE_IDENTIFIER") {info in
|
||||||
|
print(info.event.name) // BeginEditing or EndEditing event
|
||||||
|
print(info.textInputView) // TextInputView which begin editing or end editing
|
||||||
|
// Write your own logic here based on event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
// Unsubscribe
|
||||||
|
textInputViewObserver.unsubscribe(identifier: "YOUR_UNIQUE_IDENTIFIER")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Iftekhar Qurashi hack.iftekhar@gmail.com
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
IQTextInputViewNotification is available under the MIT license. See the LICENSE file for more info.
|
||||||
14
Pods/IQTextView/IQTextView/Assets/PrivacyInfo.xcprivacy
generated
Normal file
14
Pods/IQTextView/IQTextView/Assets/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array/>
|
||||||
|
<key>NSPrivacyTracking</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
29
Pods/IQTextView/IQTextView/Classes/IQTextView+Placeholderable.swift
generated
Normal file
29
Pods/IQTextView/IQTextView/Classes/IQTextView+Placeholderable.swift
generated
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// IQTextView.swift
|
||||||
|
// https://github.com/hackiftekhar/IQTextView
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import IQKeyboardToolbar
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objc extension IQTextView: IQPlaceholderable { }
|
||||||
193
Pods/IQTextView/IQTextView/Classes/IQTextView.swift
generated
Normal file
193
Pods/IQTextView/IQTextView/Classes/IQTextView.swift
generated
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
//
|
||||||
|
// IQTextView.swift
|
||||||
|
// https://github.com/hackiftekhar/IQTextView
|
||||||
|
// Copyright (c) 2013-24 Iftekhar Qurashi.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/** @abstract UITextView with placeholder support */
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
@objcMembers open class IQTextView: UITextView {
|
||||||
|
|
||||||
|
required public init?(coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public init(frame: CGRect, textContainer: NSTextContainer?) {
|
||||||
|
super.init(frame: frame, textContainer: textContainer)
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setup() {
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder),
|
||||||
|
name: UITextView.textDidChangeNotification, object: self)
|
||||||
|
|
||||||
|
do {
|
||||||
|
placeholderLabel.frame = placeholderExpectedFrame
|
||||||
|
placeholderLabel.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
placeholderLabel.lineBreakMode = .byWordWrapping
|
||||||
|
placeholderLabel.numberOfLines = 0
|
||||||
|
placeholderLabel.font = self.font
|
||||||
|
placeholderLabel.textAlignment = self.textAlignment
|
||||||
|
placeholderLabel.backgroundColor = UIColor.clear
|
||||||
|
placeholderLabel.isAccessibilityElement = false
|
||||||
|
placeholderLabel.textColor = UIColor.placeholderText
|
||||||
|
self.addSubview(placeholderLabel)
|
||||||
|
refreshPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var placeholderInsets: UIEdgeInsets {
|
||||||
|
let top: CGFloat = self.textContainerInset.top
|
||||||
|
let left: CGFloat = self.textContainerInset.left + self.textContainer.lineFragmentPadding
|
||||||
|
let bottom: CGFloat = self.textContainerInset.bottom
|
||||||
|
let right: CGFloat = self.textContainerInset.right + self.textContainer.lineFragmentPadding
|
||||||
|
return UIEdgeInsets(top: top, left: left, bottom: bottom, right: right)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var placeholderExpectedFrame: CGRect {
|
||||||
|
let insets: UIEdgeInsets = self.placeholderInsets
|
||||||
|
let maxWidth: CGFloat = self.frame.width-insets.left-insets.right
|
||||||
|
let size: CGSize = CGSize(width: maxWidth, height: self.frame.height-insets.top-insets.bottom)
|
||||||
|
let expectedSize: CGSize = placeholderLabel.sizeThatFits(size)
|
||||||
|
|
||||||
|
return CGRect(x: insets.left, y: insets.top, width: maxWidth, height: expectedSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
public let placeholderLabel: UILabel = .init()
|
||||||
|
|
||||||
|
/** @abstract To set textView's placeholder text color. */
|
||||||
|
@IBInspectable open var placeholderTextColor: UIColor? {
|
||||||
|
|
||||||
|
get {
|
||||||
|
return placeholderLabel.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
set {
|
||||||
|
placeholderLabel.textColor = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @abstract To set textView's placeholder text. Default is nil. */
|
||||||
|
@IBInspectable open var placeholder: String? {
|
||||||
|
|
||||||
|
get {
|
||||||
|
return placeholderLabel.text
|
||||||
|
}
|
||||||
|
|
||||||
|
set {
|
||||||
|
placeholderLabel.text = newValue
|
||||||
|
refreshPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @abstract To set textView's placeholder attributed text. Default is nil. */
|
||||||
|
open var attributedPlaceholder: NSAttributedString? {
|
||||||
|
get {
|
||||||
|
return placeholderLabel.attributedText
|
||||||
|
}
|
||||||
|
|
||||||
|
set {
|
||||||
|
placeholderLabel.attributedText = newValue
|
||||||
|
refreshPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
placeholderLabel.frame = placeholderExpectedFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func refreshPlaceholder() {
|
||||||
|
|
||||||
|
let text: String = text ?? attributedText?.string ?? ""
|
||||||
|
placeholderLabel.alpha = text.isEmpty ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
open override var text: String! {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
refreshPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open override var attributedText: NSAttributedString! {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
refreshPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open override var font: UIFont? {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
|
||||||
|
if let font: UIFont = font {
|
||||||
|
placeholderLabel.font = font
|
||||||
|
} else {
|
||||||
|
placeholderLabel.font = UIFont.systemFont(ofSize: 12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open override var textAlignment: NSTextAlignment {
|
||||||
|
didSet {
|
||||||
|
placeholderLabel.textAlignment = textAlignment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open override var intrinsicContentSize: CGSize {
|
||||||
|
guard !hasText else {
|
||||||
|
return super.intrinsicContentSize
|
||||||
|
}
|
||||||
|
|
||||||
|
var newSize: CGSize = super.intrinsicContentSize
|
||||||
|
let placeholderInsets: UIEdgeInsets = self.placeholderInsets
|
||||||
|
newSize.height = placeholderExpectedFrame.height + placeholderInsets.top + placeholderInsets.bottom
|
||||||
|
|
||||||
|
return newSize
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func caretRect(for position: UITextPosition) -> CGRect {
|
||||||
|
var originalRect = super.caretRect(for: position)
|
||||||
|
|
||||||
|
// When placeholder is visible and text alignment is centered
|
||||||
|
guard placeholderLabel.alpha == 1 && self.textAlignment == .center else { return originalRect }
|
||||||
|
|
||||||
|
// Calculate the width of the placeholder text
|
||||||
|
let font = placeholderLabel.font ?? UIFont.systemFont(ofSize: UIFont.systemFontSize)
|
||||||
|
let textSize = placeholderLabel.text?.size(withAttributes: [.font: font]) ?? .zero
|
||||||
|
// Calculate the starting x position of the centered placeholder text
|
||||||
|
let centeredTextX = (self.bounds.size.width - textSize.width) / 2
|
||||||
|
// Update the caret position to match the starting x position of the centered text
|
||||||
|
originalRect.origin.x = centeredTextX
|
||||||
|
|
||||||
|
return originalRect
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Pods/IQTextView/LICENSE
generated
Normal file
21
Pods/IQTextView/LICENSE
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Mohd Iftekhar Qurashi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
48
Pods/IQTextView/README.md
generated
Normal file
48
Pods/IQTextView/README.md
generated
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# IQTextView
|
||||||
|
|
||||||
|
[](https://travis-ci.org/hackiftekhar/IQTextView)
|
||||||
|
[](https://cocoapods.org/pods/IQTextView)
|
||||||
|
[](https://cocoapods.org/pods/IQTextView)
|
||||||
|
[](https://cocoapods.org/pods/IQTextView)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
IQTextView is available through [CocoaPods](https://cocoapods.org). To install
|
||||||
|
it, simply add the following line to your Podfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pod 'IQTextView'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You can set any UITextView class to IQTextView and then you can change the placeholder and placeholderTextColor from the storyboard
|
||||||
|
|
||||||
|
To change them via code, you may have to set them programmatically
|
||||||
|
```swift
|
||||||
|
textView.placeholderTextColor = UIColor.lightGray
|
||||||
|
textView.placeholder = "Enter your message here..."
|
||||||
|
```
|
||||||
|
|
||||||
|
To work this with IQKeyboardToolbarManager to show placeholder in toolbar, you may have to confirm IQPlaceholderable manually in your code
|
||||||
|
```swift
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
@MainActor
|
||||||
|
extension IQTextView: IQPlaceholderable { }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Iftekhar Qurashi hack.iftekhar@gmail.com
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
IQTextView is available under the MIT license. See the LICENSE file for more info.
|
||||||
59
Pods/Manifest.lock
generated
59
Pods/Manifest.lock
generated
@ -6,6 +6,46 @@ PODS:
|
|||||||
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
||||||
- AMapSearch-NO-IDFA (9.8.0):
|
- AMapSearch-NO-IDFA (9.8.0):
|
||||||
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
||||||
|
- IQKeyboardCore (1.0.9)
|
||||||
|
- IQKeyboardManagerSwift (8.0.3):
|
||||||
|
- IQKeyboardManagerSwift/Appearance (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/Core (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/IQKeyboardReturnManager (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/IQKeyboardToolbarManager (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/IQTextView (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/Resign (= 8.0.3)
|
||||||
|
- IQKeyboardManagerSwift/Appearance (8.0.3):
|
||||||
|
- IQKeyboardManagerSwift/Core
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQKeyboardManagerSwift/Core (8.0.3):
|
||||||
|
- IQKeyboardNotification
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQKeyboardManagerSwift/IQKeyboardReturnManager (8.0.3):
|
||||||
|
- IQKeyboardReturnManager
|
||||||
|
- IQKeyboardManagerSwift/IQKeyboardToolbarManager (8.0.3):
|
||||||
|
- IQKeyboardManagerSwift/Core
|
||||||
|
- IQKeyboardToolbarManager
|
||||||
|
- IQKeyboardManagerSwift/IQTextView (8.0.3):
|
||||||
|
- IQTextView
|
||||||
|
- IQKeyboardManagerSwift/Resign (8.0.3):
|
||||||
|
- IQKeyboardManagerSwift/Core
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQKeyboardNotification (1.0.7)
|
||||||
|
- IQKeyboardReturnManager (1.0.6):
|
||||||
|
- IQKeyboardCore
|
||||||
|
- IQKeyboardToolbar (1.1.5):
|
||||||
|
- IQKeyboardToolbar/Core (= 1.1.5)
|
||||||
|
- IQKeyboardToolbar/Core (1.1.5):
|
||||||
|
- IQKeyboardCore
|
||||||
|
- IQKeyboardToolbar/Placeholderable
|
||||||
|
- IQKeyboardToolbar/Placeholderable (1.1.5)
|
||||||
|
- IQKeyboardToolbarManager (1.1.6):
|
||||||
|
- IQKeyboardToolbar
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQTextInputViewNotification (1.0.9):
|
||||||
|
- IQKeyboardCore
|
||||||
|
- IQTextView (1.0.5):
|
||||||
|
- IQKeyboardToolbar/Placeholderable
|
||||||
- UMAPM (2.0.7):
|
- UMAPM (2.0.7):
|
||||||
- UMCommon
|
- UMCommon
|
||||||
- UMCommon (7.5.11):
|
- UMCommon (7.5.11):
|
||||||
@ -17,6 +57,7 @@ DEPENDENCIES:
|
|||||||
- AMap3DMap-NO-IDFA
|
- AMap3DMap-NO-IDFA
|
||||||
- AMapLocation-NO-IDFA
|
- AMapLocation-NO-IDFA
|
||||||
- AMapSearch-NO-IDFA
|
- AMapSearch-NO-IDFA
|
||||||
|
- IQKeyboardManagerSwift
|
||||||
- UMAPM
|
- UMAPM
|
||||||
- UMCommon
|
- UMCommon
|
||||||
- UMDevice
|
- UMDevice
|
||||||
@ -28,6 +69,14 @@ SPEC REPOS:
|
|||||||
- AMapFoundation-NO-IDFA
|
- AMapFoundation-NO-IDFA
|
||||||
- AMapLocation-NO-IDFA
|
- AMapLocation-NO-IDFA
|
||||||
- AMapSearch-NO-IDFA
|
- AMapSearch-NO-IDFA
|
||||||
|
- IQKeyboardCore
|
||||||
|
- IQKeyboardManagerSwift
|
||||||
|
- IQKeyboardNotification
|
||||||
|
- IQKeyboardReturnManager
|
||||||
|
- IQKeyboardToolbar
|
||||||
|
- IQKeyboardToolbarManager
|
||||||
|
- IQTextInputViewNotification
|
||||||
|
- IQTextView
|
||||||
- UMAPM
|
- UMAPM
|
||||||
- UMCommon
|
- UMCommon
|
||||||
- UMDevice
|
- UMDevice
|
||||||
@ -38,11 +87,19 @@ SPEC CHECKSUMS:
|
|||||||
AMapFoundation-NO-IDFA: a2e3c895398d7ee757278e1a0a8f9359da4b146e
|
AMapFoundation-NO-IDFA: a2e3c895398d7ee757278e1a0a8f9359da4b146e
|
||||||
AMapLocation-NO-IDFA: 7cd8fc837ea41edfbf4d937cd20572e277b77d18
|
AMapLocation-NO-IDFA: 7cd8fc837ea41edfbf4d937cd20572e277b77d18
|
||||||
AMapSearch-NO-IDFA: c0afd2a69a076d4228becda4401dbe4a279a03ef
|
AMapSearch-NO-IDFA: c0afd2a69a076d4228becda4401dbe4a279a03ef
|
||||||
|
IQKeyboardCore: 250a88dfc713b2ecbe1dce250811d054fc016959
|
||||||
|
IQKeyboardManagerSwift: 7f6a9a1e2285732d3ff5f485ff921215253ea77e
|
||||||
|
IQKeyboardNotification: 3b9bc812a5af2cf97df08a363d9daa9fc0dc1848
|
||||||
|
IQKeyboardReturnManager: fcbf51fc68d7536fc1fbcca5231c4e82576b12ac
|
||||||
|
IQKeyboardToolbar: b0f26e43b9e806e961fde2344fe379ae193236b9
|
||||||
|
IQKeyboardToolbarManager: 242e858cc74e06e00aa989bea1355b24d4397854
|
||||||
|
IQTextInputViewNotification: 3b9fb27a16e7ee8958cc9092cfb07a1a9e1fd559
|
||||||
|
IQTextView: ae13b4922f22e6f027f62c557d9f4f236b19d5c7
|
||||||
UMAPM: ba9fbebe8dd2048b251c332ab0afc86e1ba5ea63
|
UMAPM: ba9fbebe8dd2048b251c332ab0afc86e1ba5ea63
|
||||||
UMCommon: d652b3b372a801b36db203f5b546ab3e15676898
|
UMCommon: d652b3b372a801b36db203f5b546ab3e15676898
|
||||||
UMDevice: 20b7b3c37a36b2c5c7ca8b5a54386ff9ed2b3b19
|
UMDevice: 20b7b3c37a36b2c5c7ca8b5a54386ff9ed2b3b19
|
||||||
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
|
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
|
||||||
|
|
||||||
PODFILE CHECKSUM: f48a71990c19e75f00e16701179b4f160e74986f
|
PODFILE CHECKSUM: 0fd38f741cbaae39c8e7a70fa233f651d3b34d0f
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
4498
Pods/Pods.xcodeproj/project.pbxproj
generated
4498
Pods/Pods.xcodeproj/project.pbxproj
generated
File diff suppressed because it is too large
Load Diff
26
Pods/Target Support Files/IQKeyboardCore/IQKeyboardCore-Info.plist
generated
Normal file
26
Pods/Target Support Files/IQKeyboardCore/IQKeyboardCore-Info.plist
generated
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?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>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>${PODS_DEVELOPMENT_LANGUAGE}</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>${EXECUTABLE_NAME}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>${PRODUCT_NAME}</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0.9</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string></string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
5
Pods/Target Support Files/IQKeyboardCore/IQKeyboardCore-dummy.m
generated
Normal file
5
Pods/Target Support Files/IQKeyboardCore/IQKeyboardCore-dummy.m
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
@interface PodsDummy_IQKeyboardCore : NSObject
|
||||||
|
@end
|
||||||
|
@implementation PodsDummy_IQKeyboardCore
|
||||||
|
@end
|
||||||
12
Pods/Target Support Files/IQKeyboardCore/IQKeyboardCore-prefix.pch
generated
Normal file
12
Pods/Target Support Files/IQKeyboardCore/IQKeyboardCore-prefix.pch
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#ifdef __OBJC__
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#else
|
||||||
|
#ifndef FOUNDATION_EXPORT
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
#define FOUNDATION_EXPORT extern "C"
|
||||||
|
#else
|
||||||
|
#define FOUNDATION_EXPORT extern
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user