Compare commits
38 Commits
9b92d81902
...
codex/push
| Author | SHA1 | Date | |
|---|---|---|---|
| 413a9603a4 | |||
| f87b13cc05 | |||
| 0e0415eec4 | |||
| 7ccbc5568d | |||
| 787a166263 | |||
| 74f7370aab | |||
| 1b2637f0ee | |||
| caeeb9a1cf | |||
| e7f1d777dd | |||
| 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` 或用户文件。
|
||||
20
Podfile
20
Podfile
@ -1,13 +1,24 @@
|
||||
platform :ios, '16.0'
|
||||
|
||||
project 'suixinkan.xcodeproj',
|
||||
'Debug' => :debug,
|
||||
'SimulatorDebug' => :debug,
|
||||
'Release' => :release
|
||||
|
||||
target 'suixinkan' do
|
||||
use_frameworks!
|
||||
pod 'AMapLocation-NO-IDFA'
|
||||
pod 'AMap3DMap-NO-IDFA'
|
||||
pod 'AMapSearch-NO-IDFA'
|
||||
# 高德 SDK 未提供 arm64-simulator slice,仅在真机配置中接入。
|
||||
pod 'AMapFoundation-NO-IDFA', :configurations => ['Debug', 'Release']
|
||||
pod 'AMapLocation-NO-IDFA', :configurations => ['Debug', 'Release']
|
||||
pod 'AMap3DMap-NO-IDFA', :configurations => ['Debug', 'Release']
|
||||
pod 'AMapSearch-NO-IDFA', :configurations => ['Debug', 'Release']
|
||||
pod 'UMCommon'
|
||||
pod 'UMDevice'
|
||||
pod 'UMAPM'
|
||||
# 极光推送:固定无 IDFA 的 JCore,避免引入 ATT 授权。
|
||||
pod 'JCore', '5.4.2-noidfa'
|
||||
pod 'JPush', '6.1.0'
|
||||
pod 'IQKeyboardManagerSwift'
|
||||
# 微信 Open SDK:仅接入分享能力,不调用支付 API。
|
||||
pod 'WechatOpenSDK-XCFramework', '~> 2.0.4'
|
||||
|
||||
@ -20,6 +31,9 @@ post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
|
||||
if target.name == 'Pods-suixinkanTests' && config.name == 'SimulatorDebug'
|
||||
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = ''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
69
Podfile.lock
69
Podfile.lock
@ -6,6 +6,49 @@ PODS:
|
||||
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
||||
- AMapSearch-NO-IDFA (9.8.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
|
||||
- JCore (5.4.2-noidfa)
|
||||
- JPush (6.1.0):
|
||||
- JCore (> 5.1.1)
|
||||
- UMAPM (2.0.7):
|
||||
- UMCommon
|
||||
- UMCommon (7.5.11):
|
||||
@ -15,8 +58,12 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- AMap3DMap-NO-IDFA
|
||||
- AMapFoundation-NO-IDFA
|
||||
- AMapLocation-NO-IDFA
|
||||
- AMapSearch-NO-IDFA
|
||||
- IQKeyboardManagerSwift
|
||||
- JCore (= 5.4.2-noidfa)
|
||||
- JPush (= 6.1.0)
|
||||
- UMAPM
|
||||
- UMCommon
|
||||
- UMDevice
|
||||
@ -28,6 +75,16 @@ SPEC REPOS:
|
||||
- AMapFoundation-NO-IDFA
|
||||
- AMapLocation-NO-IDFA
|
||||
- AMapSearch-NO-IDFA
|
||||
- IQKeyboardCore
|
||||
- IQKeyboardManagerSwift
|
||||
- IQKeyboardNotification
|
||||
- IQKeyboardReturnManager
|
||||
- IQKeyboardToolbar
|
||||
- IQKeyboardToolbarManager
|
||||
- IQTextInputViewNotification
|
||||
- IQTextView
|
||||
- JCore
|
||||
- JPush
|
||||
- UMAPM
|
||||
- UMCommon
|
||||
- UMDevice
|
||||
@ -38,11 +95,21 @@ SPEC CHECKSUMS:
|
||||
AMapFoundation-NO-IDFA: a2e3c895398d7ee757278e1a0a8f9359da4b146e
|
||||
AMapLocation-NO-IDFA: 7cd8fc837ea41edfbf4d937cd20572e277b77d18
|
||||
AMapSearch-NO-IDFA: c0afd2a69a076d4228becda4401dbe4a279a03ef
|
||||
IQKeyboardCore: 250a88dfc713b2ecbe1dce250811d054fc016959
|
||||
IQKeyboardManagerSwift: 7f6a9a1e2285732d3ff5f485ff921215253ea77e
|
||||
IQKeyboardNotification: 3b9bc812a5af2cf97df08a363d9daa9fc0dc1848
|
||||
IQKeyboardReturnManager: fcbf51fc68d7536fc1fbcca5231c4e82576b12ac
|
||||
IQKeyboardToolbar: b0f26e43b9e806e961fde2344fe379ae193236b9
|
||||
IQKeyboardToolbarManager: 242e858cc74e06e00aa989bea1355b24d4397854
|
||||
IQTextInputViewNotification: 3b9fb27a16e7ee8958cc9092cfb07a1a9e1fd559
|
||||
IQTextView: ae13b4922f22e6f027f62c557d9f4f236b19d5c7
|
||||
JCore: 696e0ac22f417bb0ac4729fc41bc2941aef14e2b
|
||||
JPush: 891c497687be788b094bde368d51289355ee1112
|
||||
UMAPM: ba9fbebe8dd2048b251c332ab0afc86e1ba5ea63
|
||||
UMCommon: d652b3b372a801b36db203f5b546ab3e15676898
|
||||
UMDevice: 20b7b3c37a36b2c5c7ca8b5a54386ff9ed2b3b19
|
||||
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
|
||||
|
||||
PODFILE CHECKSUM: f48a71990c19e75f00e16701179b4f160e74986f
|
||||
PODFILE CHECKSUM: 5abf70ade4a9c4b66b64877fdea91f1d086bcaf5
|
||||
|
||||
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.
|
||||
1
Pods/JCore/README.md
generated
Normal file
1
Pods/JCore/README.md
generated
Normal file
@ -0,0 +1 @@
|
||||
# jcore-sdk
|
||||
44
Pods/JCore/jcore-noidfa-ios-5.4.2.xcframework/Info.plist
generated
Normal file
44
Pods/JCore/jcore-noidfa-ios-5.4.2.xcframework/Info.plist
generated
Normal file
@ -0,0 +1,44 @@
|
||||
<?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>AvailableLibraries</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>HeadersPath</key>
|
||||
<string>Headers</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>libJCore.a</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>HeadersPath</key>
|
||||
<string>Headers</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64_x86_64-simulator</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>libJCore.a</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
<string>x86_64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
<key>SupportedPlatformVariant</key>
|
||||
<string>simulator</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XFWK</string>
|
||||
<key>XCFrameworkFormatVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
19
Pods/JCore/jcore-noidfa-ios-5.4.2.xcframework/ios-arm64/Headers/JGCOREAPI.h
generated
Normal file
19
Pods/JCore/jcore-noidfa-ios-5.4.2.xcframework/ios-arm64/Headers/JGCOREAPI.h
generated
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// JGCOREService.h
|
||||
// JCore
|
||||
//
|
||||
// Created by Shuni Huang on 2024/3/19.
|
||||
// Copyright © 2024 jiguang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface JGCOREAPI : NSObject
|
||||
|
||||
+ (void)ci:(BOOL)enable;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
27
Pods/JCore/jcore-noidfa-ios-5.4.2.xcframework/ios-arm64/Headers/JGInforCollectionAuth.h
generated
Normal file
27
Pods/JCore/jcore-noidfa-ios-5.4.2.xcframework/ios-arm64/Headers/JGInforCollectionAuth.h
generated
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// JGInforCollectionAuth.h
|
||||
// JCore
|
||||
//
|
||||
// Created by 豆瓣 on 2021/10/27.
|
||||
// Copyright © 2021 jiguang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 合规接口,是否授权极光采集一定的设备信息
|
||||
@interface JGInforCollectionAuthItems : NSObject
|
||||
/// 是否授权,默认YES
|
||||
@property(nonatomic,assign)BOOL isAuth;
|
||||
@end
|
||||
|
||||
@interface JGInforCollectionAuth : NSObject
|
||||
|
||||
/// 设备信息采集授权接口(合规接口)
|
||||
/// 请务必在调用初始化、功能性接口前调用此接口进行合规授权
|
||||
/// @param authBlock auth:YES 则极光认为您同意极光采集一定的设备信息
|
||||
+(void)JCollectionAuth:(void(^_Nullable)(JGInforCollectionAuthItems *authInfo))authBlock;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
64
Pods/JCore/jcore-noidfa-ios-5.4.2.xcframework/ios-arm64/PrivacyInfo.xcprivacy
generated
Normal file
64
Pods/JCore/jcore-noidfa-ios-5.4.2.xcframework/ios-arm64/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,64 @@
|
||||
<?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>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeCrashData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypePreciseLocation</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user