@xrnjs/app-template 0.0.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/$package.json +30 -36
  2. package/android/app/build.gradle +56 -73
  3. package/android/app/src/debug/java/com/xrn/template/ReactNativeFlipper.java +23 -44
  4. package/android/app/src/main/assets/xrn-manifest.json +1 -1
  5. package/android/app/src/main/java/com/xrn/template/MainApplication.kt +54 -65
  6. package/android/app/src/main/java/com/xrn/template/multibundle/CodePushBundleActivity.kt +65 -0
  7. package/android/app/src/main/java/com/xrn/template/multibundle/CodePushJSBundleLoader.kt +18 -0
  8. package/android/app/src/main/java/com/xrn/template/multibundle/CodePushUtils.kt +263 -0
  9. package/android/app/src/main/java/com/xrn/template/multibundle/XBundleTool.kt +9 -0
  10. package/android/app/src/main/java/com/xrn/template/multibundle/XRNReactHostDelegate.kt +56 -0
  11. package/android/app/src/main/java/com/xrn/template/navigation/XBundleActivity.kt +10 -7
  12. package/android/app/src/main/res/raw/bundle_config.json +2 -6
  13. package/android/build.gradle +40 -39
  14. package/android/buildSrc/build.gradle.kts +15 -0
  15. package/android/gradle/wrapper/gradle-wrapper.properties +3 -1
  16. package/android/gradle.properties +17 -10
  17. package/android/settings.gradle +40 -12
  18. package/harmony/.clang-tidy +1 -1
  19. package/harmony/.clangd +3 -1
  20. package/harmony/AppScope/app.json5 +1 -1
  21. package/harmony/build-profile.json5 +8 -8
  22. package/harmony/entry/oh-package-lock.json5 +283 -262
  23. package/harmony/entry/oh-package.json5 +35 -36
  24. package/harmony/entry/src/main/cpp/CMakeLists.txt +37 -17
  25. package/harmony/entry/src/main/cpp/PackageProvider.cpp +20 -2
  26. package/harmony/entry/src/main/cpp/generated/BundleNavigation.cpp +3 -0
  27. package/harmony/entry/src/main/cpp/generated/RNOHGeneratedPackage.h +31 -87
  28. package/harmony/entry/src/main/cpp/generated/RTNCodePush.cpp +7 -1
  29. package/harmony/entry/src/main/cpp/generated/XRNBundleModule.cpp +9 -0
  30. package/harmony/entry/src/main/cpp/generated/XRNDebugToolsModule.cpp +10 -0
  31. package/harmony/entry/src/main/cpp/generated/{ImageResizer.cpp → XRNImageView.cpp} +6 -3
  32. package/harmony/entry/src/main/cpp/generated/{ImageResizer.h → XRNImageView.h} +2 -2
  33. package/harmony/entry/src/main/cpp/generated/{RNCWebViewComponentDescriptor.h → XRNImageViewComponentDescriptor.h} +11 -10
  34. package/harmony/entry/src/main/cpp/generated/XRNImageViewJSIBinder.h +58 -0
  35. package/harmony/entry/src/main/cpp/generated/XRNKeyboard.cpp +23 -0
  36. package/harmony/entry/src/main/cpp/generated/{RNLocalize.h → XRNKeyboard.h} +2 -2
  37. package/harmony/entry/src/main/cpp/generated/XRNNavigation.cpp +5 -0
  38. package/harmony/entry/src/main/cpp/generated/{RNCWebViewModule.cpp → XRNNetworkModule.cpp} +2 -4
  39. package/harmony/entry/src/main/cpp/generated/{RNCWebViewModule.h → XRNNetworkModule.h} +2 -2
  40. package/harmony/entry/src/main/ets/BundleHelper.ets +41 -45
  41. package/harmony/entry/src/main/ets/MainAbilityStage.ets +9 -4
  42. package/harmony/entry/src/main/ets/entryability/EntryAbility.ets +1 -1
  43. package/harmony/entry/src/main/ets/navDestination/BizModuleContainer.ets +2 -2
  44. package/harmony/entry/src/main/ets/navDestination/MainModuleContainer.ets +2 -2
  45. package/harmony/entry/src/main/ets/navDestination/Splash.ets +1 -2
  46. package/harmony/entry/src/main/ets/pages/Index.ets +4 -4
  47. package/harmony/entry/src/main/resources/rawfile/bundle_config.json5 +2 -6
  48. package/harmony/entry/src/main/resources/rawfile/xrn-manifest.json +1 -0
  49. package/harmony/oh-package-lock.json5 +2 -1
  50. package/harmony/oh-package.json5 +12 -4
  51. package/index.ts +0 -0
  52. package/ios/BundleEventCenter/Hooks/XRNCustomKeyborad+XTHooks.h +16 -0
  53. package/ios/BundleEventCenter/Hooks/XRNCustomKeyborad+XTHooks.mm +40 -0
  54. package/ios/BundleEventCenter/Hooks/XTIQKeyboardManagerBridge.h +15 -0
  55. package/ios/BundleEventCenter/Hooks/XTIQKeyboardManagerBridge.m +22 -0
  56. package/ios/BundleEventCenter/XTBundleProvider.m +98 -72
  57. package/ios/BundleEventCenter/XTBundleViewControllerFactory.m +4 -15
  58. package/ios/BundleEventCenter/bundleController/XTBundleViewController.mm +43 -0
  59. package/ios/BundleEventCenter/bundleController/XTMainBundleViewController.mm +48 -0
  60. package/ios/BundleEventCenter/bundleController/xtBundles.plist +1 -1
  61. package/ios/Podfile +234 -151
  62. package/ios/Podfile.lock +2810 -594
  63. package/ios/RNDebug/DebugPlugin/XTPluginManage.m +5 -5
  64. package/ios/RNDebug/MotroTools/XTMetroAutoConnector.h +21 -0
  65. package/ios/RNDebug/MotroTools/XTMetroAutoConnector.m +124 -0
  66. package/ios/RNDebug/MotroTools/XTReloadCommandHelper.h +30 -0
  67. package/ios/RNDebug/MotroTools/XTReloadCommandHelper.mm +128 -0
  68. package/ios/RNDebug/Views/XTScanQRPlugin.swift +9 -4
  69. package/ios/XRNTemplate/AppDelegate.h +3 -3
  70. package/ios/XRNTemplate/AppDelegate.mm +40 -102
  71. package/ios/XRNTemplate/Info.plist +4 -4
  72. package/ios/XRNTemplate/PrivacyInfo.xcprivacy +48 -0
  73. package/ios/XRNTemplate-Bridging-Header.h +2 -1
  74. package/ios/XRNTemplate.xcodeproj/project.pbxproj +68 -77
  75. package/ios/ios_patches/fixed_glog_config.h +195 -0
  76. package/ios/ios_patches/xt_IQKeyboardManager+Position.swift +1341 -0
  77. package/ios/ios_patches/xt_IQKeyboardManager.swift +454 -0
  78. package/ios/subBundles/.gitkeep +0 -0
  79. package/main-bundle/babel.config.js +2 -2
  80. package/main-bundle/package.json +11 -20
  81. package/main-bundle/yarn.lock +14655 -9651
  82. package/metro.config.js +21 -0
  83. package/package.json +2 -2
  84. package/react-native.config.js +3 -45
  85. package/src/.gitkeep +0 -0
  86. package/sub-bundle/babel.config.js +2 -2
  87. package/sub-bundle/package.json +11 -20
  88. package/sub-bundle/react-native.config.js +4 -0
  89. package/sub-bundle/yarn.lock +14655 -9651
  90. package/xrn.config.json +12 -12
  91. package/android/app/src/main/java/com/xrn/template/multibundle/XDevSupportParams.kt +0 -6
  92. package/android/app/src/main/java/com/xrn/template/multibundle/XRNHostParams.kt +0 -82
  93. package/harmony/entry/src/main/cpp/generated/ConfigNativeModule.cpp +0 -21
  94. package/harmony/entry/src/main/cpp/generated/ConfigNativeModule.h +0 -21
  95. package/harmony/entry/src/main/cpp/generated/GetRandomValuesNativeModule.cpp +0 -21
  96. package/harmony/entry/src/main/cpp/generated/GetRandomValuesNativeModule.h +0 -21
  97. package/harmony/entry/src/main/cpp/generated/NativeHarmonyTouchId.cpp +0 -22
  98. package/harmony/entry/src/main/cpp/generated/NativeHarmonyTouchId.h +0 -21
  99. package/harmony/entry/src/main/cpp/generated/OreitationLockerNativeModule.cpp +0 -31
  100. package/harmony/entry/src/main/cpp/generated/OreitationLockerNativeModule.h +0 -21
  101. package/harmony/entry/src/main/cpp/generated/RNCCameraRoll.cpp +0 -28
  102. package/harmony/entry/src/main/cpp/generated/RNCCameraRoll.h +0 -21
  103. package/harmony/entry/src/main/cpp/generated/RNCCameraRollPermission.cpp +0 -26
  104. package/harmony/entry/src/main/cpp/generated/RNCCameraRollPermission.h +0 -21
  105. package/harmony/entry/src/main/cpp/generated/RNCWebViewJSIBinder.h +0 -120
  106. package/harmony/entry/src/main/cpp/generated/RNDeviceInfo.cpp +0 -164
  107. package/harmony/entry/src/main/cpp/generated/RNDeviceInfo.h +0 -21
  108. package/harmony/entry/src/main/cpp/generated/RNDocumentPicker.cpp +0 -24
  109. package/harmony/entry/src/main/cpp/generated/RNDocumentPicker.h +0 -21
  110. package/harmony/entry/src/main/cpp/generated/RNGestureHandlerButtonComponentDescriptor.h +0 -43
  111. package/harmony/entry/src/main/cpp/generated/RNGestureHandlerButtonJSIBinder.h +0 -38
  112. package/harmony/entry/src/main/cpp/generated/RNGestureHandlerModule.cpp +0 -28
  113. package/harmony/entry/src/main/cpp/generated/RNGestureHandlerModule.h +0 -21
  114. package/harmony/entry/src/main/cpp/generated/RNGestureHandlerRootViewComponentDescriptor.h +0 -43
  115. package/harmony/entry/src/main/cpp/generated/RNGestureHandlerRootViewJSIBinder.h +0 -31
  116. package/harmony/entry/src/main/cpp/generated/RNLocalize.cpp +0 -31
  117. package/harmony/entry/src/main/cpp/generated/ReactNativeBlobUtil.cpp +0 -51
  118. package/harmony/entry/src/main/cpp/generated/ReactNativeBlobUtil.h +0 -21
  119. package/harmony/entry/src/main/cpp/generated/XRNAssetLoaderModule.cpp +0 -21
  120. package/harmony/entry/src/main/cpp/generated/XRNAssetLoaderModule.h +0 -21
  121. package/harmony/entry/src/main/cpp/generated/XRNFileModule.cpp +0 -22
  122. package/harmony/entry/src/main/cpp/generated/XRNFileModule.h +0 -21
  123. package/ios/BundleEventCenter/bundleController/XTBundleViewController.m +0 -35
  124. package/ios/BundleEventCenter/bundleController/XTMainBundleViewController.m +0 -41
@@ -0,0 +1,1341 @@
1
+ //
2
+ // IQKeyboardManager+Position.swift
3
+ // https://github.com/hackiftekhar/IQKeyboardManager
4
+ // Copyright (c) 2013-20 Iftekhar Qurashi.
5
+ //
6
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ // of this software and associated documentation files (the "Software"), to deal
8
+ // in the Software without restriction, including without limitation the rights
9
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ // copies of the Software, and to permit persons to whom the Software is
11
+ // furnished to do so, subject to the following conditions:
12
+ //
13
+ // The above copyright notice and this permission notice shall be included in
14
+ // all copies or substantial portions of the Software.
15
+ //
16
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ // THE SOFTWARE.
23
+
24
+ // import Foundation - UIKit contains Foundation
25
+ import UIKit
26
+
27
+ @available(iOSApplicationExtension, unavailable)
28
+ public extension IQKeyboardManager {
29
+
30
+ private struct AssociatedKeys {
31
+ static var movedDistance: Int = 0
32
+ static var movedDistanceChanged: Int = 0
33
+ static var lastScrollView: Int = 0
34
+ static var startingContentOffset: Int = 0
35
+ static var startingScrollIndicatorInsets: Int = 0
36
+ static var startingContentInsets: Int = 0
37
+ static var startingTextViewContentInsets: Int = 0
38
+ static var startingTextViewScrollIndicatorInsets: Int = 0
39
+ static var isTextViewContentInsetChanged: Int = 0
40
+ }
41
+
42
+ /**
43
+ moved distance to the top used to maintain distance between keyboard and textField. Most of the time this will be a positive value.
44
+ */
45
+ @objc private(set) var movedDistance: CGFloat {
46
+ get {
47
+ return objc_getAssociatedObject(self, &AssociatedKeys.movedDistance) as? CGFloat ?? 0.0
48
+ }
49
+ set(newValue) {
50
+ objc_setAssociatedObject(self, &AssociatedKeys.movedDistance, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
51
+ movedDistanceChanged?(movedDistance)
52
+ }
53
+ }
54
+
55
+ /**
56
+ Will be called then movedDistance will be changed
57
+ */
58
+ @objc var movedDistanceChanged: ((CGFloat) -> Void)? {
59
+ get {
60
+ return objc_getAssociatedObject(self, &AssociatedKeys.movedDistanceChanged) as? ((CGFloat) -> Void)
61
+ }
62
+ set(newValue) {
63
+ objc_setAssociatedObject(self, &AssociatedKeys.movedDistanceChanged, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
64
+ movedDistanceChanged?(movedDistance)
65
+ }
66
+ }
67
+
68
+ /** Variable to save lastScrollView that was scrolled. */
69
+ internal weak var lastScrollView: UIScrollView? {
70
+ get {
71
+ return (objc_getAssociatedObject(self, &AssociatedKeys.lastScrollView) as? WeakObjectContainer)?.object as? UIScrollView
72
+ }
73
+ set(newValue) {
74
+ objc_setAssociatedObject(self, &AssociatedKeys.lastScrollView, WeakObjectContainer(object: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
75
+ }
76
+ }
77
+
78
+ /** LastScrollView's initial contentOffset. */
79
+ internal var startingContentOffset: CGPoint {
80
+ get {
81
+ return objc_getAssociatedObject(self, &AssociatedKeys.startingContentOffset) as? CGPoint ?? IQKeyboardManager.kIQCGPointInvalid
82
+ }
83
+ set(newValue) {
84
+ objc_setAssociatedObject(self, &AssociatedKeys.startingContentOffset, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
85
+ }
86
+ }
87
+
88
+ /** LastScrollView's initial scrollIndicatorInsets. */
89
+ internal var startingScrollIndicatorInsets: UIEdgeInsets {
90
+ get {
91
+ return objc_getAssociatedObject(self, &AssociatedKeys.startingScrollIndicatorInsets) as? UIEdgeInsets ?? .init()
92
+ }
93
+ set(newValue) {
94
+ objc_setAssociatedObject(self, &AssociatedKeys.startingScrollIndicatorInsets, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
95
+ }
96
+ }
97
+
98
+ /** LastScrollView's initial contentInsets. */
99
+ internal var startingContentInsets: UIEdgeInsets {
100
+ get {
101
+ return objc_getAssociatedObject(self, &AssociatedKeys.startingContentInsets) as? UIEdgeInsets ?? .init()
102
+ }
103
+ set(newValue) {
104
+ objc_setAssociatedObject(self, &AssociatedKeys.startingContentInsets, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
105
+ }
106
+ }
107
+
108
+ /** used to adjust contentInset of UITextView. */
109
+ internal var startingTextViewContentInsets: UIEdgeInsets {
110
+ get {
111
+ return objc_getAssociatedObject(self, &AssociatedKeys.startingTextViewContentInsets) as? UIEdgeInsets ?? .init()
112
+ }
113
+ set(newValue) {
114
+ objc_setAssociatedObject(self, &AssociatedKeys.startingTextViewContentInsets, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
115
+ }
116
+ }
117
+
118
+ /** used to adjust scrollIndicatorInsets of UITextView. */
119
+ internal var startingTextViewScrollIndicatorInsets: UIEdgeInsets {
120
+ get {
121
+ return objc_getAssociatedObject(self, &AssociatedKeys.startingTextViewScrollIndicatorInsets) as? UIEdgeInsets ?? .init()
122
+ }
123
+ set(newValue) {
124
+ objc_setAssociatedObject(self, &AssociatedKeys.startingTextViewScrollIndicatorInsets, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
125
+ }
126
+ }
127
+
128
+ /** used with textView to detect a textFieldView contentInset is changed or not. (Bug ID: #92)*/
129
+ internal var isTextViewContentInsetChanged: Bool {
130
+ get {
131
+ return objc_getAssociatedObject(self, &AssociatedKeys.isTextViewContentInsetChanged) as? Bool ?? false
132
+ }
133
+ set(newValue) {
134
+ objc_setAssociatedObject(self, &AssociatedKeys.isTextViewContentInsetChanged, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
135
+ }
136
+ }
137
+
138
+ @objc internal func applicationDidBecomeActive(_ notificatin: Notification) {
139
+
140
+ guard privateIsEnabled(),
141
+ keyboardShowing,
142
+ topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false, let textFieldView = textFieldView,
143
+ textFieldView.isAlertViewTextField() == false else {
144
+ return
145
+ }
146
+ self.adjustPosition()
147
+ }
148
+
149
+ internal func adjustPosition() {
150
+ guard UIApplication.shared.applicationState == .active,
151
+ let textFieldView = textFieldView,
152
+ let rootController = textFieldView.parentContainerViewController(),
153
+ let _ = keyWindow() else {
154
+ return
155
+ }
156
+ let isModalClass = checkRootVCClassIsModal(rootVC: rootController)
157
+ softInputMode = isModalClass ? self.modalSoftInputMode : self.viewSoftInputMode
158
+ if softInputMode.softInputMode == "pan" {
159
+ self.adjustPositionPan()
160
+ } else if softInputMode.softInputMode == "auto" {
161
+ self.adjustPositionAuto()
162
+ } else {
163
+ self.adjustPositionDefault()
164
+ }
165
+ }
166
+
167
+ func checkRootVCClassIsModal(rootVC: UIViewController) -> Bool{
168
+ var isModalClass = false
169
+ if let modalClass = NSClassFromString("RCTFabricModalHostViewController"), rootVC.isKind(of: modalClass) {
170
+ isModalClass = true
171
+ if let modalHostView = rootVC.value(forKey: "delegate") as? UIView {
172
+ if let softInputMode = Self.getIvarValue(modalHostView, ivarName: "_softInputMode") as? String {
173
+ self.modalSoftInputMode.softInputMode = softInputMode
174
+ }
175
+ if let space = Self.getIvarValue(modalHostView, ivarName: "_space") as? NSNumber {
176
+ self.modalSoftInputMode.space = space
177
+ }
178
+ if let modalHeaderSpace = Self.getIvarValue(modalHostView, ivarName: "_modalHeaderSpace") as? NSNumber {
179
+ self.modalSoftInputMode.modalHeaderSpace = modalHeaderSpace
180
+ }
181
+ }
182
+ }
183
+ return isModalClass
184
+ }
185
+
186
+ private static func getIvarValue(_ object: AnyObject, ivarName: String) -> Any? {
187
+ guard let ivar = class_getInstanceVariable(type(of: object), ivarName) else { return nil }
188
+ return object_getIvar(object, ivar)
189
+ }
190
+
191
+ // swiftlint:disable function_body_length
192
+ /* Adjusting RootViewController's frame according to interface orientation. */
193
+ internal func adjustPositionPan() {
194
+ // We are unable to get textField object while keyboard showing on WKWebView's textField. (Bug ID: #11)
195
+ guard UIApplication.shared.applicationState == .active,
196
+ let textFieldView = textFieldView,
197
+ let rootController = textFieldView.parentContainerViewController(),
198
+ let window = keyWindow() else {
199
+ return
200
+ }
201
+
202
+ let startTime = CACurrentMediaTime()
203
+ showLog(">>>>> \(#function) started >>>>>", indentation: 1)
204
+
205
+ // Getting RootViewOrigin.
206
+ var rootViewOrigin = rootController.view.frame.origin
207
+ let textFieldViewRectInRootSuperview = textFieldView.convert(textFieldView.bounds, to: window)
208
+ let newKeyboardDistanceFromTextField = textFieldViewRectInRootSuperview.maxY + CGFloat(truncating: softInputMode.space) > window.frame.height ? -0 : CGFloat(truncating: softInputMode.space)
209
+ let kbSize: CGSize
210
+ do {
211
+ var kbFrame = keyboardFrame
212
+
213
+ kbFrame.origin.y -= newKeyboardDistanceFromTextField
214
+ kbFrame.size.height += newKeyboardDistanceFromTextField
215
+
216
+ let intersectRect = kbFrame.intersection(window.frame)
217
+ if intersectRect.isNull {
218
+ kbSize = CGSize(width: kbFrame.size.width, height: 0)
219
+ } else {
220
+ kbSize = intersectRect.size
221
+ }
222
+ }
223
+
224
+ let isOffsetWindowY = rootController.view.frame.size.height - textFieldViewRectInRootSuperview.maxY
225
+ let moveUp: CGFloat = kbSize.height - (isOffsetWindowY > 0 ? isOffsetWindowY : 0)
226
+ showLog("Need to move: \(moveUp), will be moving \(moveUp < 0 ? "down" : "up")")
227
+
228
+ // +Positive or zero.
229
+ if moveUp >= 0 {
230
+ rootViewOrigin.y -= moveUp
231
+ if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
232
+ showLog("Moving Upward")
233
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
234
+ var rect = rootController.view.frame
235
+ rect.origin = rootViewOrigin
236
+ rootController.view.frame = rect
237
+ // Animating content if needed (Bug ID: #204)
238
+ if self.layoutIfNeededOnUpdate {
239
+ // Animating content (Bug ID: #160)
240
+ rootController.view.setNeedsLayout()
241
+ rootController.view.layoutIfNeeded()
242
+ }
243
+ self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")
244
+ })
245
+ }
246
+ movedDistance = (topViewBeginOrigin.y-rootViewOrigin.y)
247
+ }
248
+
249
+ let elapsedTime = CACurrentMediaTime() - startTime
250
+ showLog("<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
251
+ }
252
+
253
+ /// RN:Paper 为 `nativeID`,Fabric 为 `nativeId`。不能对无该属性的 UIView 使用 `value(forKey:)`,否则会触发 `valueForUndefinedKey:` 崩溃。
254
+ private func reactNativeIdentifier(from view: UIView) -> String? {
255
+ let selNativeId = NSSelectorFromString("nativeId")
256
+ let selNativeID = NSSelectorFromString("nativeID")
257
+ if view.responds(to: selNativeId), let result = view.perform(selNativeId)?.takeUnretainedValue() as? NSString {
258
+ return result as String
259
+ }
260
+ if view.responds(to: selNativeID), let result = view.perform(selNativeID)?.takeUnretainedValue() as? NSString {
261
+ return result as String
262
+ }
263
+ return nil
264
+ }
265
+
266
+ // swiftlint:enable function_body_length
267
+
268
+ // swiftlint:disable function_body_length
269
+ /* Adjusting RootViewController's frame according to interface orientation. */
270
+ internal func adjustPositionAuto() {
271
+ // We are unable to get textField object while keyboard showing on WKWebView's textField. (Bug ID: #11)
272
+ guard UIApplication.shared.applicationState == .active,
273
+ let textFieldView = textFieldView,
274
+ let rootController = textFieldView.parentContainerViewController(),
275
+ let window = keyWindow() else {
276
+ return
277
+ }
278
+ let textFieldViewRectInRootSuperview = textFieldView.convert(textFieldView.frame, to: window)
279
+ var popupViewBeginY: CGFloat = 0.0
280
+ var contentView = textFieldView.superview
281
+ var hasKeyboardContent = false
282
+ while contentView != nil && popupViewBeginY == 0 {
283
+ if let tmpContentView = contentView,
284
+ let nativeIdStr = reactNativeIdentifier(from: tmpContentView),
285
+ nativeIdStr == "keyboard_content_native_id",
286
+ let rootView = rootController.view,
287
+ let superview = rootView.superview {
288
+ hasKeyboardContent = true
289
+ let frameInSuperview = tmpContentView.convert(tmpContentView.bounds, to: superview)
290
+ popupViewBeginY = frameInSuperview.minY
291
+ }
292
+ contentView = contentView?.superview
293
+ }
294
+
295
+ let startTime = CACurrentMediaTime()
296
+ showLog(">>>>> \(#function) started >>>>>", indentation: 1)
297
+
298
+ // Getting RootViewOrigin.
299
+ var rootViewOrigin = rootController.view.frame.origin
300
+
301
+ let newKeyboardDistanceFromTextField = CGFloat(truncating: softInputMode.space)
302
+ let kbSize: CGSize
303
+ let originalKbSize: CGSize
304
+
305
+ // Calculating actual keyboard covered size respect to window, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
306
+ do {
307
+ var kbFrame = keyboardFrame
308
+
309
+ kbFrame.origin.y -= newKeyboardDistanceFromTextField
310
+ kbFrame.size.height += newKeyboardDistanceFromTextField
311
+
312
+ let intersectRect = kbFrame.intersection(window.frame)
313
+ if intersectRect.isNull {
314
+ kbSize = CGSize(width: kbFrame.size.width, height: 0)
315
+ } else {
316
+ kbSize = intersectRect.size
317
+ }
318
+ }
319
+
320
+ do {
321
+ let intersectRect = keyboardFrame.intersection(window.frame)
322
+ if intersectRect.isNull {
323
+ originalKbSize = CGSize(width: keyboardFrame.size.width, height: 0)
324
+ } else {
325
+ originalKbSize = intersectRect.size
326
+ }
327
+ }
328
+
329
+ let statusBarHeight: CGFloat
330
+
331
+ let navigationBarAreaHeight: CGFloat
332
+ if let navigationController = rootController.navigationController {
333
+ navigationBarAreaHeight = navigationController.navigationBar.frame.maxY
334
+ } else {
335
+ #if swift(>=5.1)
336
+ if #available(iOS 13, *) {
337
+ statusBarHeight = window.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
338
+ } else {
339
+ statusBarHeight = UIApplication.shared.statusBarFrame.height
340
+ }
341
+ #else
342
+ statusBarHeight = UIApplication.shared.statusBarFrame.height
343
+ #endif
344
+ navigationBarAreaHeight = statusBarHeight
345
+ }
346
+
347
+ let layoutAreaHeight: CGFloat = rootController.view.directionalLayoutMargins.top
348
+
349
+ let isScrollableTextView: Bool
350
+
351
+ if let textView = textFieldView as? UIScrollView, textFieldView.responds(to: #selector(getter: UITextView.isEditable)) {
352
+ isScrollableTextView = textView.isScrollEnabled
353
+ } else {
354
+ isScrollableTextView = false
355
+ }
356
+
357
+ let topLayoutGuide: CGFloat = max(navigationBarAreaHeight, layoutAreaHeight)
358
+
359
+ let maxMoveup = popupViewBeginY - topLayoutGuide - CGFloat(truncating: softInputMode.modalHeaderSpace)
360
+ var moveUp: CGFloat = kbSize.height - (rootController.view.frame.size.height - textFieldViewRectInRootSuperview.maxY)
361
+
362
+ showLog("Need to move: \(moveUp), will be moving \(moveUp < 0 ? "down" : "up")")
363
+
364
+ var superScrollView: UIScrollView?
365
+ var superView = textFieldView.superviewOfClassType(UIScrollView.self) as? UIScrollView
366
+
367
+ // Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
368
+ while let view = superView {
369
+ if view.isScrollEnabled, !view.shouldIgnoreScrollingAdjustment {
370
+ superScrollView = view
371
+ break
372
+ } else {
373
+ // Getting it's superScrollView. // (Enhancement ID: #21, #24)
374
+ superView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView
375
+ }
376
+ }
377
+
378
+ // If there was a lastScrollView. // (Bug ID: #34)
379
+ if let lastScrollView = lastScrollView {
380
+ // If we can't find current superScrollView, then setting lastScrollView to it's original form.
381
+ if superScrollView == nil {
382
+ if lastScrollView.contentInset != self.startingContentInsets {
383
+ showLog("Restoring contentInset to: \(startingContentInsets)")
384
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
385
+ lastScrollView.contentInset = self.startingContentInsets
386
+ lastScrollView.scrollIndicatorInsets = self.startingScrollIndicatorInsets
387
+ })
388
+ }
389
+
390
+ if lastScrollView.shouldRestoreScrollViewContentOffset, !lastScrollView.contentOffset.equalTo(startingContentOffset) {
391
+ showLog("Restoring contentOffset to: \(startingContentOffset)")
392
+
393
+ let animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil // (Bug ID: #1365, #1508, #1541)
394
+
395
+ if animatedContentOffset {
396
+ lastScrollView.setContentOffset(startingContentOffset, animated: UIView.areAnimationsEnabled)
397
+ } else {
398
+ lastScrollView.contentOffset = startingContentOffset
399
+ }
400
+ }
401
+
402
+ startingContentInsets = .zero
403
+ startingScrollIndicatorInsets = .zero
404
+ startingContentOffset = .zero
405
+ self.lastScrollView = nil
406
+ } else if superScrollView != lastScrollView { // If both scrollView's are different, then reset lastScrollView to it's original frame and setting current scrollView as last scrollView.
407
+
408
+ if lastScrollView.contentInset != self.startingContentInsets {
409
+ showLog("Restoring contentInset to: \(startingContentInsets)")
410
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
411
+
412
+ lastScrollView.contentInset = self.startingContentInsets
413
+ lastScrollView.scrollIndicatorInsets = self.startingScrollIndicatorInsets
414
+ })
415
+ }
416
+
417
+ if lastScrollView.shouldRestoreScrollViewContentOffset, !lastScrollView.contentOffset.equalTo(startingContentOffset) {
418
+ showLog("Restoring contentOffset to: \(startingContentOffset)")
419
+
420
+ let animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil // (Bug ID: #1365, #1508, #1541)
421
+
422
+ if animatedContentOffset {
423
+ lastScrollView.setContentOffset(startingContentOffset, animated: UIView.areAnimationsEnabled)
424
+ } else {
425
+ lastScrollView.contentOffset = startingContentOffset
426
+ }
427
+ }
428
+
429
+ self.lastScrollView = superScrollView
430
+ if let scrollView = superScrollView {
431
+ startingContentInsets = scrollView.contentInset
432
+ startingContentOffset = scrollView.contentOffset
433
+
434
+ #if swift(>=5.1)
435
+ if #available(iOS 11.1, *) {
436
+ startingScrollIndicatorInsets = scrollView.verticalScrollIndicatorInsets
437
+ } else {
438
+ startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
439
+ }
440
+ #else
441
+ startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
442
+ #endif
443
+ }
444
+
445
+ showLog("Saving ScrollView New contentInset: \(startingContentInsets) and contentOffset: \(startingContentOffset)")
446
+ }
447
+ // Else the case where superScrollView == lastScrollView means we are on same scrollView after switching to different textField. So doing nothing, going ahead
448
+ } else if let unwrappedSuperScrollView = superScrollView { // If there was no lastScrollView and we found a current scrollView. then setting it as lastScrollView.
449
+ lastScrollView = unwrappedSuperScrollView
450
+ startingContentInsets = unwrappedSuperScrollView.contentInset
451
+ startingContentOffset = unwrappedSuperScrollView.contentOffset
452
+
453
+ #if swift(>=5.1)
454
+ if #available(iOS 11.1, *) {
455
+ startingScrollIndicatorInsets = unwrappedSuperScrollView.verticalScrollIndicatorInsets
456
+ } else {
457
+ startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets
458
+ }
459
+ #else
460
+ startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets
461
+ #endif
462
+
463
+ showLog("Saving ScrollView contentInset: \(startingContentInsets) and contentOffset: \(startingContentOffset)")
464
+ }
465
+
466
+ var hasScrollView = false
467
+ if let _ = lastScrollView {
468
+ hasScrollView = true
469
+ }
470
+
471
+ var isModalClass = false
472
+ if let modalClass = NSClassFromString("RCTFabricModalHostViewController"), rootController.isKind(of: modalClass) {
473
+ isModalClass = true
474
+ if !hasKeyboardContent || !hasScrollView {
475
+ self.adjustPositionPan()
476
+ return
477
+ }
478
+ } else {
479
+ if !hasScrollView {
480
+ self.adjustPositionPan()
481
+ return
482
+ }
483
+ }
484
+
485
+
486
+
487
+ // +Positive or zero.
488
+ if (isModalClass) {
489
+ if moveUp >= 0 {
490
+ if hasScrollView {
491
+ if maxMoveup > 0 {
492
+ let currentY = rootViewOrigin.y;
493
+ rootViewOrigin.y = max(rootViewOrigin.y - moveUp, min(0, -originalKbSize.height))
494
+ rootViewOrigin.y = max(rootViewOrigin.y, -maxMoveup)
495
+ if currentY != rootViewOrigin.y {
496
+ moveUp += (rootViewOrigin.y - currentY);
497
+ }
498
+ } else {
499
+ rootViewOrigin.y = 0
500
+ }
501
+ } else {
502
+ rootViewOrigin.y = max(rootViewOrigin.y - moveUp, min(0, -originalKbSize.height))
503
+ }
504
+ if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
505
+ showLog("Moving Upward")
506
+
507
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
508
+
509
+ var rect = rootController.view.frame
510
+ rect.origin = rootViewOrigin
511
+ rootController.view.frame = rect
512
+
513
+ // Animating content if needed (Bug ID: #204)
514
+ if self.layoutIfNeededOnUpdate {
515
+ // Animating content (Bug ID: #160)
516
+ rootController.view.setNeedsLayout()
517
+ rootController.view.layoutIfNeeded()
518
+ }
519
+
520
+ self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")
521
+ })
522
+ }
523
+
524
+ movedDistance = (topViewBeginOrigin.y-rootViewOrigin.y)
525
+ } else { // -Negative
526
+ var disturbDistance: CGFloat = rootViewOrigin.y-topViewBeginOrigin.y
527
+
528
+ if scrollViewOffsetY + moveUp > 0 || scrollViewOffsetY == 0 {
529
+ disturbDistance = 0
530
+ } else {
531
+ disturbDistance = scrollViewOffsetY + moveUp
532
+ }
533
+ // disturbDistance Negative = frame disturbed.
534
+ // disturbDistance positive = frame not disturbed.
535
+ if disturbDistance <= 0 {
536
+
537
+ rootViewOrigin.y -= max(moveUp, disturbDistance)
538
+
539
+ if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
540
+ showLog("Moving Downward")
541
+ // Setting adjusted rootViewRect
542
+ // Setting adjusted rootViewRect
543
+
544
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
545
+
546
+ var rect = rootController.view.frame
547
+ rect.origin = rootViewOrigin
548
+ rootController.view.frame = rect
549
+
550
+ // Animating content if needed (Bug ID: #204)
551
+ if self.layoutIfNeededOnUpdate {
552
+ // Animating content (Bug ID: #160)
553
+ rootController.view.setNeedsLayout()
554
+ rootController.view.layoutIfNeeded()
555
+ }
556
+
557
+ self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")
558
+ })
559
+ }
560
+
561
+ movedDistance = (topViewBeginOrigin.y-rootViewOrigin.y)
562
+ }
563
+ }
564
+ }
565
+
566
+
567
+ // Special case for ScrollView.
568
+ // If we found lastScrollView then setting it's contentOffset to show textField.
569
+ if let lastScrollView = lastScrollView {
570
+ // Saving
571
+ var lastView = textFieldView
572
+ var superScrollView = self.lastScrollView
573
+
574
+ while let scrollView = superScrollView {
575
+ var shouldContinue = false
576
+
577
+ if moveUp > 0 {
578
+ shouldContinue = moveUp > (-scrollView.contentOffset.y - scrollView.contentInset.top)
579
+ } else if let tableView = scrollView.superviewOfClassType(UITableView.self) as? UITableView {
580
+ // Special treatment for UITableView due to their cell reusing logic
581
+ shouldContinue = scrollView.contentOffset.y > 0
582
+
583
+ if shouldContinue, let tableCell = textFieldView.superviewOfClassType(UITableViewCell.self) as? UITableViewCell, let indexPath = tableView.indexPath(for: tableCell), let previousIndexPath = tableView.previousIndexPath(of: indexPath) {
584
+
585
+ let previousCellRect = tableView.rectForRow(at: previousIndexPath)
586
+ if !previousCellRect.isEmpty {
587
+ let previousCellRectInRootSuperview = tableView.convert(previousCellRect, to: rootController.view.superview)
588
+
589
+ moveUp = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
590
+ }
591
+ }
592
+ } else if let collectionView = scrollView.superviewOfClassType(UICollectionView.self) as? UICollectionView {
593
+ // Special treatment for UITableView due to their cell reusing logic
594
+ shouldContinue = scrollView.contentOffset.y > 0
595
+
596
+ if shouldContinue, let collectionCell = textFieldView.superviewOfClassType(UICollectionViewCell.self) as? UICollectionViewCell, let indexPath = collectionView.indexPath(for: collectionCell), let previousIndexPath = collectionView.previousIndexPath(of: indexPath), let attributes = collectionView.layoutAttributesForItem(at: previousIndexPath) {
597
+
598
+ let previousCellRect = attributes.frame
599
+ if !previousCellRect.isEmpty {
600
+ let previousCellRectInRootSuperview = collectionView.convert(previousCellRect, to: rootController.view.superview)
601
+
602
+ moveUp = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
603
+ }
604
+ }
605
+ } else {
606
+ shouldContinue = true
607
+ }
608
+
609
+ // Looping in upper hierarchy until we don't found any scrollView in it's upper hirarchy till UIWindow object.
610
+ if shouldContinue {
611
+ var tempScrollView = scrollView.superviewOfClassType(UIScrollView.self) as? UIScrollView
612
+ var nextScrollView: UIScrollView?
613
+ while let view = tempScrollView {
614
+
615
+ if view.isScrollEnabled, !view.shouldIgnoreScrollingAdjustment {
616
+ nextScrollView = view
617
+ break
618
+ } else {
619
+ tempScrollView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView
620
+ }
621
+ }
622
+
623
+ // Getting lastViewRect.
624
+ if let lastViewRect = lastView.superview?.convert(lastView.frame, to: scrollView) {
625
+
626
+ // Calculating the expected Y offset from move and scrollView's contentOffset.
627
+ var shouldOffsetY = scrollView.contentOffset.y - min(scrollView.contentOffset.y, -moveUp)
628
+
629
+ // Rearranging the expected Y offset according to the view.
630
+ shouldOffsetY = min(shouldOffsetY, lastViewRect.minY)
631
+
632
+ // [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
633
+ // nextScrollView == nil If processing scrollView is last scrollView in upper hierarchy (there is no other scrollView upper hierrchy.)
634
+ // [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
635
+ // shouldOffsetY >= 0 shouldOffsetY must be greater than in order to keep distance from navigationBar (Bug ID: #92)
636
+ if isScrollableTextView,
637
+ nextScrollView == nil,
638
+ shouldOffsetY >= 0 {
639
+
640
+ // Converting Rectangle according to window bounds.
641
+ if let currentTextFieldViewRect = textFieldView.superview?.convert(textFieldView.frame, to: window) {
642
+
643
+ // Calculating expected fix distance which needs to be managed from navigation bar
644
+ let expectedFixDistance: CGFloat = currentTextFieldViewRect.minY - topLayoutGuide
645
+
646
+ // Now if expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance) is lower than current shouldOffsetY, which means we're in a position where navigationBar up and hide, then reducing shouldOffsetY with expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance)
647
+ shouldOffsetY = min(shouldOffsetY, scrollView.contentOffset.y + expectedFixDistance)
648
+
649
+ // Setting move to 0 because now we don't want to move any view anymore (All will be managed by our contentInset logic.
650
+ moveUp = 0
651
+ } else {
652
+ // Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
653
+ moveUp -= (shouldOffsetY-scrollView.contentOffset.y)
654
+ }
655
+ } else {
656
+ // Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
657
+ moveUp -= (shouldOffsetY-scrollView.contentOffset.y)
658
+ }
659
+
660
+ let newContentOffset = CGPoint(x: scrollView.contentOffset.x, y: shouldOffsetY)
661
+ scrollViewOffsetY = shouldOffsetY
662
+ if scrollView.contentOffset.equalTo(newContentOffset) == false {
663
+
664
+ showLog("old contentOffset: \(scrollView.contentOffset) new contentOffset: \(newContentOffset)")
665
+ self.showLog("Remaining Move: \(moveUp)")
666
+
667
+ // Getting problem while using `setContentOffset:animated:`, So I used animation API.
668
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
669
+
670
+ let animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: scrollView) != nil // (Bug ID: #1365, #1508, #1541)
671
+
672
+ if animatedContentOffset {
673
+ scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
674
+ } else {
675
+ scrollView.contentOffset = newContentOffset
676
+ }
677
+ }, completion: { _ in
678
+
679
+ if scrollView is UITableView || scrollView is UICollectionView {
680
+ // This will update the next/previous states
681
+ self.addToolbarIfRequired()
682
+ }
683
+ })
684
+ }
685
+ }
686
+
687
+ // Getting next lastView & superScrollView.
688
+ lastView = scrollView
689
+ superScrollView = nextScrollView
690
+ } else {
691
+ moveUp = 0
692
+ break
693
+ }
694
+ }
695
+
696
+ // Updating contentInset
697
+ if let lastScrollViewRect = lastScrollView.superview?.convert(lastScrollView.frame, to: window),
698
+ lastScrollView.shouldIgnoreContentInsetAdjustment == false {
699
+
700
+ var bottomInset: CGFloat = (kbSize.height)-(window.frame.height-lastScrollViewRect.maxY)
701
+ var bottomScrollIndicatorInset = bottomInset - newKeyboardDistanceFromTextField - topViewBeginSafeAreaInsets.bottom
702
+
703
+ // Update the insets so that the scroll vew doesn't shift incorrectly when the offset is near the bottom of the scroll view.
704
+ bottomInset = max(startingContentInsets.bottom, bottomInset)
705
+ bottomScrollIndicatorInset = max(startingScrollIndicatorInsets.bottom, bottomScrollIndicatorInset)
706
+
707
+ bottomInset -= lastScrollView.safeAreaInsets.bottom
708
+ bottomScrollIndicatorInset -= lastScrollView.safeAreaInsets.bottom
709
+
710
+ var movedInsets = lastScrollView.contentInset
711
+ movedInsets.bottom = bottomInset
712
+ movedInsets.bottom = 0
713
+
714
+ if lastScrollView.contentInset != movedInsets {
715
+ showLog("old ContentInset: \(lastScrollView.contentInset) new ContentInset: \(movedInsets)")
716
+
717
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
718
+ lastScrollView.contentInset = movedInsets
719
+
720
+ var newScrollIndicatorInset: UIEdgeInsets
721
+
722
+ #if swift(>=5.1)
723
+ if #available(iOS 11.1, *) {
724
+ newScrollIndicatorInset = lastScrollView.verticalScrollIndicatorInsets
725
+ } else {
726
+ newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets
727
+ }
728
+ #else
729
+ newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets
730
+ #endif
731
+
732
+ newScrollIndicatorInset.bottom = bottomScrollIndicatorInset
733
+ lastScrollView.scrollIndicatorInsets = newScrollIndicatorInset
734
+ })
735
+ }
736
+ }
737
+ }
738
+ // Going ahead. No else if.
739
+
740
+ // Special case for UITextView(Readjusting textView.contentInset when textView hight is too big to fit on screen)
741
+ // _lastScrollView If not having inside any scrollView, (now contentInset manages the full screen textView.
742
+ // [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
743
+ if isScrollableTextView, let textView = textFieldView as? UIScrollView {
744
+
745
+ let keyboardYPosition = window.frame.height - originalKbSize.height
746
+ var rootSuperViewFrameInWindow = window.frame
747
+ if let rootSuperview = rootController.view.superview {
748
+ rootSuperViewFrameInWindow = rootSuperview.convert(rootSuperview.bounds, to: window)
749
+ }
750
+
751
+ let keyboardOverlapping = rootSuperViewFrameInWindow.maxY - keyboardYPosition
752
+
753
+ let textViewHeight = min(textView.frame.height, rootSuperViewFrameInWindow.height-topLayoutGuide-keyboardOverlapping)
754
+
755
+ if textView.frame.size.height-textView.contentInset.bottom>textViewHeight {
756
+ // _isTextViewContentInsetChanged, If frame is not change by library in past, then saving user textView properties (Bug ID: #92)
757
+ if !self.isTextViewContentInsetChanged {
758
+ self.startingTextViewContentInsets = textView.contentInset
759
+
760
+ #if swift(>=5.1)
761
+ if #available(iOS 11.1, *) {
762
+ self.startingTextViewScrollIndicatorInsets = textView.verticalScrollIndicatorInsets
763
+ } else {
764
+ self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets
765
+ }
766
+ #else
767
+ self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets
768
+ #endif
769
+ }
770
+
771
+ self.isTextViewContentInsetChanged = true
772
+
773
+ var newContentInset = textView.contentInset
774
+ newContentInset.bottom = textView.frame.size.height-textViewHeight
775
+ newContentInset.bottom -= textView.safeAreaInsets.bottom
776
+
777
+ if textView.contentInset != newContentInset {
778
+ self.showLog("\(textFieldView) Old UITextView.contentInset: \(textView.contentInset) New UITextView.contentInset: \(newContentInset)")
779
+
780
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
781
+
782
+ textView.contentInset = newContentInset
783
+ textView.scrollIndicatorInsets = newContentInset
784
+ }, completion: { (_) -> Void in })
785
+ }
786
+ }
787
+ }
788
+
789
+
790
+
791
+ let elapsedTime = CACurrentMediaTime() - startTime
792
+ showLog("<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
793
+ }
794
+ // swiftlint:enable function_body_length
795
+
796
+
797
+ // swiftlint:disable function_body_length
798
+ /* Adjusting RootViewController's frame according to interface orientation. */
799
+ internal func adjustPositionDefault() {
800
+ // We are unable to get textField object while keyboard showing on WKWebView's textField. (Bug ID: #11)
801
+ guard UIApplication.shared.applicationState == .active,
802
+ let textFieldView = textFieldView,
803
+ let rootController = textFieldView.parentContainerViewController(),
804
+ let window = keyWindow(),
805
+ let textFieldViewRectInWindow = textFieldView.superview?.convert(textFieldView.frame, to: window),
806
+ let textFieldViewRectInRootSuperview = textFieldView.superview?.convert(textFieldView.frame, to: rootController.view?.superview) else {
807
+ return
808
+ }
809
+
810
+ let startTime = CACurrentMediaTime()
811
+ showLog(">>>>> \(#function) started >>>>>", indentation: 1)
812
+
813
+ // Getting RootViewOrigin.
814
+ var rootViewOrigin = rootController.view.frame.origin
815
+
816
+ // Maintain keyboardDistanceFromTextField
817
+ let specialKeyboardDistanceFromTextField: CGFloat
818
+
819
+ if let searchBar = textFieldView.textFieldSearchBar() {
820
+ specialKeyboardDistanceFromTextField = searchBar.keyboardDistanceFromTextField
821
+ } else {
822
+ specialKeyboardDistanceFromTextField = textFieldView.keyboardDistanceFromTextField
823
+ }
824
+
825
+ let newKeyboardDistanceFromTextField = (specialKeyboardDistanceFromTextField == kIQUseDefaultKeyboardDistance) ? keyboardDistanceFromTextField : specialKeyboardDistanceFromTextField
826
+
827
+ let kbSize: CGSize
828
+ let originalKbSize: CGSize
829
+
830
+ // Calculating actual keyboard covered size respect to window, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
831
+ do {
832
+ var kbFrame = keyboardFrame
833
+
834
+ kbFrame.origin.y -= newKeyboardDistanceFromTextField
835
+ kbFrame.size.height += newKeyboardDistanceFromTextField
836
+
837
+ kbFrame.origin.y -= topViewBeginSafeAreaInsets.bottom
838
+ kbFrame.size.height += topViewBeginSafeAreaInsets.bottom
839
+
840
+ let intersectRect = kbFrame.intersection(window.frame)
841
+ if intersectRect.isNull {
842
+ kbSize = CGSize(width: kbFrame.size.width, height: 0)
843
+ } else {
844
+ kbSize = intersectRect.size
845
+ }
846
+ }
847
+
848
+ do {
849
+ let intersectRect = keyboardFrame.intersection(window.frame)
850
+ if intersectRect.isNull {
851
+ originalKbSize = CGSize(width: keyboardFrame.size.width, height: 0)
852
+ } else {
853
+ originalKbSize = intersectRect.size
854
+ }
855
+ }
856
+
857
+ let statusBarHeight: CGFloat
858
+
859
+ let navigationBarAreaHeight: CGFloat
860
+ if let navigationController = rootController.navigationController {
861
+ navigationBarAreaHeight = navigationController.navigationBar.frame.maxY
862
+ } else {
863
+ #if swift(>=5.1)
864
+ if #available(iOS 13, *) {
865
+ statusBarHeight = window.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
866
+ } else {
867
+ statusBarHeight = UIApplication.shared.statusBarFrame.height
868
+ }
869
+ #else
870
+ statusBarHeight = UIApplication.shared.statusBarFrame.height
871
+ #endif
872
+ navigationBarAreaHeight = statusBarHeight
873
+ }
874
+
875
+ let layoutAreaHeight: CGFloat = rootController.view.directionalLayoutMargins.top
876
+
877
+ let isScrollableTextView: Bool
878
+
879
+ if let textView = textFieldView as? UIScrollView, textFieldView.responds(to: #selector(getter: UITextView.isEditable)) {
880
+ isScrollableTextView = textView.isScrollEnabled
881
+ } else {
882
+ isScrollableTextView = false
883
+ }
884
+
885
+ let topLayoutGuide: CGFloat = max(navigationBarAreaHeight, layoutAreaHeight)
886
+
887
+ // Validation of textView for case where there is a tab bar at the bottom or running on iPhone X and textView is at the bottom.
888
+ let bottomLayoutGuide: CGFloat = isScrollableTextView ? 0 : rootController.view.directionalLayoutMargins.bottom
889
+
890
+ // Move positive = textField is hidden.
891
+ // Move negative = textField is showing.
892
+ // Calculating move position.
893
+ var moveUp: CGFloat
894
+
895
+ do {
896
+ let visibleHeight: CGFloat = window.frame.height-kbSize.height
897
+
898
+ let topMovement: CGFloat = textFieldViewRectInRootSuperview.minY-topLayoutGuide
899
+ let bottomMovement: CGFloat = textFieldViewRectInWindow.maxY - visibleHeight + bottomLayoutGuide
900
+ moveUp = min(topMovement, bottomMovement)
901
+ }
902
+
903
+ showLog("Need to move: \(moveUp), will be moving \(moveUp < 0 ? "down" : "up")")
904
+
905
+ var superScrollView: UIScrollView?
906
+ var superView = textFieldView.superviewOfClassType(UIScrollView.self) as? UIScrollView
907
+
908
+ // Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
909
+ while let view = superView {
910
+
911
+ if view.isScrollEnabled, !view.shouldIgnoreScrollingAdjustment {
912
+ superScrollView = view
913
+ break
914
+ } else {
915
+ // Getting it's superScrollView. // (Enhancement ID: #21, #24)
916
+ superView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView
917
+ }
918
+ }
919
+
920
+ // If there was a lastScrollView. // (Bug ID: #34)
921
+ if let lastScrollView = lastScrollView {
922
+ // If we can't find current superScrollView, then setting lastScrollView to it's original form.
923
+ if superScrollView == nil {
924
+
925
+ if lastScrollView.contentInset != self.startingContentInsets {
926
+ showLog("Restoring contentInset to: \(startingContentInsets)")
927
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
928
+
929
+ lastScrollView.contentInset = self.startingContentInsets
930
+ lastScrollView.scrollIndicatorInsets = self.startingScrollIndicatorInsets
931
+ })
932
+ }
933
+
934
+ if lastScrollView.shouldRestoreScrollViewContentOffset, !lastScrollView.contentOffset.equalTo(startingContentOffset) {
935
+ showLog("Restoring contentOffset to: \(startingContentOffset)")
936
+
937
+ let animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil // (Bug ID: #1365, #1508, #1541)
938
+
939
+ if animatedContentOffset {
940
+ lastScrollView.setContentOffset(startingContentOffset, animated: UIView.areAnimationsEnabled)
941
+ } else {
942
+ lastScrollView.contentOffset = startingContentOffset
943
+ }
944
+ }
945
+
946
+ startingContentInsets = .zero
947
+ startingScrollIndicatorInsets = .zero
948
+ startingContentOffset = .zero
949
+ self.lastScrollView = nil
950
+ } else if superScrollView != lastScrollView { // If both scrollView's are different, then reset lastScrollView to it's original frame and setting current scrollView as last scrollView.
951
+
952
+ if lastScrollView.contentInset != self.startingContentInsets {
953
+ showLog("Restoring contentInset to: \(startingContentInsets)")
954
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
955
+
956
+ lastScrollView.contentInset = self.startingContentInsets
957
+ lastScrollView.scrollIndicatorInsets = self.startingScrollIndicatorInsets
958
+ })
959
+ }
960
+
961
+ if lastScrollView.shouldRestoreScrollViewContentOffset, !lastScrollView.contentOffset.equalTo(startingContentOffset) {
962
+ showLog("Restoring contentOffset to: \(startingContentOffset)")
963
+
964
+ let animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil // (Bug ID: #1365, #1508, #1541)
965
+
966
+ if animatedContentOffset {
967
+ lastScrollView.setContentOffset(startingContentOffset, animated: UIView.areAnimationsEnabled)
968
+ } else {
969
+ lastScrollView.contentOffset = startingContentOffset
970
+ }
971
+ }
972
+
973
+ self.lastScrollView = superScrollView
974
+ if let scrollView = superScrollView {
975
+ startingContentInsets = scrollView.contentInset
976
+ startingContentOffset = scrollView.contentOffset
977
+
978
+ #if swift(>=5.1)
979
+ if #available(iOS 11.1, *) {
980
+ startingScrollIndicatorInsets = scrollView.verticalScrollIndicatorInsets
981
+ } else {
982
+ startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
983
+ }
984
+ #else
985
+ startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
986
+ #endif
987
+ }
988
+
989
+ showLog("Saving ScrollView New contentInset: \(startingContentInsets) and contentOffset: \(startingContentOffset)")
990
+ }
991
+ // Else the case where superScrollView == lastScrollView means we are on same scrollView after switching to different textField. So doing nothing, going ahead
992
+ } else if let unwrappedSuperScrollView = superScrollView { // If there was no lastScrollView and we found a current scrollView. then setting it as lastScrollView.
993
+ lastScrollView = unwrappedSuperScrollView
994
+ startingContentInsets = unwrappedSuperScrollView.contentInset
995
+ startingContentOffset = unwrappedSuperScrollView.contentOffset
996
+
997
+ #if swift(>=5.1)
998
+ if #available(iOS 11.1, *) {
999
+ startingScrollIndicatorInsets = unwrappedSuperScrollView.verticalScrollIndicatorInsets
1000
+ } else {
1001
+ startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets
1002
+ }
1003
+ #else
1004
+ startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets
1005
+ #endif
1006
+
1007
+ showLog("Saving ScrollView contentInset: \(startingContentInsets) and contentOffset: \(startingContentOffset)")
1008
+ }
1009
+
1010
+ // Special case for ScrollView.
1011
+ // If we found lastScrollView then setting it's contentOffset to show textField.
1012
+ if let lastScrollView = lastScrollView {
1013
+ // Saving
1014
+ var lastView = textFieldView
1015
+ var superScrollView = self.lastScrollView
1016
+
1017
+ while let scrollView = superScrollView {
1018
+
1019
+ var shouldContinue = false
1020
+
1021
+ if moveUp > 0 {
1022
+ shouldContinue = moveUp > (-scrollView.contentOffset.y - scrollView.contentInset.top)
1023
+
1024
+ } else if let tableView = scrollView.superviewOfClassType(UITableView.self) as? UITableView {
1025
+ // Special treatment for UITableView due to their cell reusing logic
1026
+ shouldContinue = scrollView.contentOffset.y > 0
1027
+
1028
+ if shouldContinue, let tableCell = textFieldView.superviewOfClassType(UITableViewCell.self) as? UITableViewCell, let indexPath = tableView.indexPath(for: tableCell), let previousIndexPath = tableView.previousIndexPath(of: indexPath) {
1029
+
1030
+ let previousCellRect = tableView.rectForRow(at: previousIndexPath)
1031
+ if !previousCellRect.isEmpty {
1032
+ let previousCellRectInRootSuperview = tableView.convert(previousCellRect, to: rootController.view.superview)
1033
+
1034
+ moveUp = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
1035
+ }
1036
+ }
1037
+ } else if let collectionView = scrollView.superviewOfClassType(UICollectionView.self) as? UICollectionView {
1038
+ // Special treatment for UITableView due to their cell reusing logic
1039
+ shouldContinue = scrollView.contentOffset.y > 0
1040
+
1041
+ if shouldContinue, let collectionCell = textFieldView.superviewOfClassType(UICollectionViewCell.self) as? UICollectionViewCell, let indexPath = collectionView.indexPath(for: collectionCell), let previousIndexPath = collectionView.previousIndexPath(of: indexPath), let attributes = collectionView.layoutAttributesForItem(at: previousIndexPath) {
1042
+
1043
+ let previousCellRect = attributes.frame
1044
+ if !previousCellRect.isEmpty {
1045
+ let previousCellRectInRootSuperview = collectionView.convert(previousCellRect, to: rootController.view.superview)
1046
+
1047
+ moveUp = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
1048
+ }
1049
+ }
1050
+ } else {
1051
+
1052
+ shouldContinue = textFieldViewRectInRootSuperview.minY < topLayoutGuide
1053
+
1054
+ if shouldContinue {
1055
+ moveUp = min(0, textFieldViewRectInRootSuperview.minY - topLayoutGuide)
1056
+ }
1057
+ }
1058
+
1059
+ // Looping in upper hierarchy until we don't found any scrollView in it's upper hirarchy till UIWindow object.
1060
+ if shouldContinue {
1061
+
1062
+ var tempScrollView = scrollView.superviewOfClassType(UIScrollView.self) as? UIScrollView
1063
+ var nextScrollView: UIScrollView?
1064
+ while let view = tempScrollView {
1065
+
1066
+ if view.isScrollEnabled, !view.shouldIgnoreScrollingAdjustment {
1067
+ nextScrollView = view
1068
+ break
1069
+ } else {
1070
+ tempScrollView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView
1071
+ }
1072
+ }
1073
+
1074
+ // Getting lastViewRect.
1075
+ if let lastViewRect = lastView.superview?.convert(lastView.frame, to: scrollView) {
1076
+
1077
+ // Calculating the expected Y offset from move and scrollView's contentOffset.
1078
+ var shouldOffsetY = scrollView.contentOffset.y - min(scrollView.contentOffset.y, -moveUp)
1079
+
1080
+ // Rearranging the expected Y offset according to the view.
1081
+ shouldOffsetY = min(shouldOffsetY, lastViewRect.minY)
1082
+
1083
+ // [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
1084
+ // nextScrollView == nil If processing scrollView is last scrollView in upper hierarchy (there is no other scrollView upper hierrchy.)
1085
+ // [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
1086
+ // shouldOffsetY >= 0 shouldOffsetY must be greater than in order to keep distance from navigationBar (Bug ID: #92)
1087
+ if isScrollableTextView,
1088
+ nextScrollView == nil,
1089
+ shouldOffsetY >= 0 {
1090
+
1091
+ // Converting Rectangle according to window bounds.
1092
+ if let currentTextFieldViewRect = textFieldView.superview?.convert(textFieldView.frame, to: window) {
1093
+
1094
+ // Calculating expected fix distance which needs to be managed from navigation bar
1095
+ let expectedFixDistance: CGFloat = currentTextFieldViewRect.minY - topLayoutGuide
1096
+
1097
+ // Now if expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance) is lower than current shouldOffsetY, which means we're in a position where navigationBar up and hide, then reducing shouldOffsetY with expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance)
1098
+ shouldOffsetY = min(shouldOffsetY, scrollView.contentOffset.y + expectedFixDistance)
1099
+
1100
+ // Setting move to 0 because now we don't want to move any view anymore (All will be managed by our contentInset logic.
1101
+ moveUp = 0
1102
+ } else {
1103
+ // Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
1104
+ moveUp -= (shouldOffsetY-scrollView.contentOffset.y)
1105
+ }
1106
+ } else {
1107
+ // Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
1108
+ moveUp -= (shouldOffsetY-scrollView.contentOffset.y)
1109
+ }
1110
+
1111
+ let newContentOffset = CGPoint(x: scrollView.contentOffset.x, y: shouldOffsetY)
1112
+
1113
+ if scrollView.contentOffset.equalTo(newContentOffset) == false {
1114
+
1115
+ showLog("old contentOffset: \(scrollView.contentOffset) new contentOffset: \(newContentOffset)")
1116
+ self.showLog("Remaining Move: \(moveUp)")
1117
+
1118
+ // Getting problem while using `setContentOffset:animated:`, So I used animation API.
1119
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
1120
+
1121
+ let animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: scrollView) != nil // (Bug ID: #1365, #1508, #1541)
1122
+
1123
+ if animatedContentOffset {
1124
+ scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
1125
+ } else {
1126
+ scrollView.contentOffset = newContentOffset
1127
+ }
1128
+ }, completion: { _ in
1129
+
1130
+ if scrollView is UITableView || scrollView is UICollectionView {
1131
+ // This will update the next/previous states
1132
+ self.addToolbarIfRequired()
1133
+ }
1134
+ })
1135
+ }
1136
+ }
1137
+
1138
+ // Getting next lastView & superScrollView.
1139
+ lastView = scrollView
1140
+ superScrollView = nextScrollView
1141
+ } else {
1142
+ moveUp = 0
1143
+ break
1144
+ }
1145
+ }
1146
+
1147
+ // Updating contentInset
1148
+ if let lastScrollViewRect = lastScrollView.superview?.convert(lastScrollView.frame, to: window),
1149
+ lastScrollView.shouldIgnoreContentInsetAdjustment == false {
1150
+
1151
+ var bottomInset: CGFloat = (kbSize.height)-(window.frame.height-lastScrollViewRect.maxY)
1152
+ var bottomScrollIndicatorInset = bottomInset - newKeyboardDistanceFromTextField - topViewBeginSafeAreaInsets.bottom
1153
+
1154
+ // Update the insets so that the scroll vew doesn't shift incorrectly when the offset is near the bottom of the scroll view.
1155
+ bottomInset = max(startingContentInsets.bottom, bottomInset)
1156
+ bottomScrollIndicatorInset = max(startingScrollIndicatorInsets.bottom, bottomScrollIndicatorInset)
1157
+
1158
+ bottomInset -= lastScrollView.safeAreaInsets.bottom
1159
+ bottomScrollIndicatorInset -= lastScrollView.safeAreaInsets.bottom
1160
+
1161
+ var movedInsets = lastScrollView.contentInset
1162
+ movedInsets.bottom = bottomInset
1163
+
1164
+ if lastScrollView.contentInset != movedInsets {
1165
+ showLog("old ContentInset: \(lastScrollView.contentInset) new ContentInset: \(movedInsets)")
1166
+
1167
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
1168
+ lastScrollView.contentInset = movedInsets
1169
+
1170
+ var newScrollIndicatorInset: UIEdgeInsets
1171
+
1172
+ #if swift(>=5.1)
1173
+ if #available(iOS 11.1, *) {
1174
+ newScrollIndicatorInset = lastScrollView.verticalScrollIndicatorInsets
1175
+ } else {
1176
+ newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets
1177
+ }
1178
+ #else
1179
+ newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets
1180
+ #endif
1181
+
1182
+ newScrollIndicatorInset.bottom = bottomScrollIndicatorInset
1183
+ lastScrollView.scrollIndicatorInsets = newScrollIndicatorInset
1184
+ })
1185
+ }
1186
+ }
1187
+ }
1188
+ // Going ahead. No else if.
1189
+
1190
+ // Special case for UITextView(Readjusting textView.contentInset when textView hight is too big to fit on screen)
1191
+ // _lastScrollView If not having inside any scrollView, (now contentInset manages the full screen textView.
1192
+ // [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
1193
+ if isScrollableTextView, let textView = textFieldView as? UIScrollView {
1194
+
1195
+ let keyboardYPosition = window.frame.height - originalKbSize.height
1196
+ var rootSuperViewFrameInWindow = window.frame
1197
+ if let rootSuperview = rootController.view.superview {
1198
+ rootSuperViewFrameInWindow = rootSuperview.convert(rootSuperview.bounds, to: window)
1199
+ }
1200
+
1201
+ let keyboardOverlapping = rootSuperViewFrameInWindow.maxY - keyboardYPosition
1202
+
1203
+ let textViewHeight = min(textView.frame.height, rootSuperViewFrameInWindow.height-topLayoutGuide-keyboardOverlapping)
1204
+
1205
+ if textView.frame.size.height-textView.contentInset.bottom>textViewHeight {
1206
+ // _isTextViewContentInsetChanged, If frame is not change by library in past, then saving user textView properties (Bug ID: #92)
1207
+ if !self.isTextViewContentInsetChanged {
1208
+ self.startingTextViewContentInsets = textView.contentInset
1209
+
1210
+ #if swift(>=5.1)
1211
+ if #available(iOS 11.1, *) {
1212
+ self.startingTextViewScrollIndicatorInsets = textView.verticalScrollIndicatorInsets
1213
+ } else {
1214
+ self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets
1215
+ }
1216
+ #else
1217
+ self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets
1218
+ #endif
1219
+ }
1220
+
1221
+ self.isTextViewContentInsetChanged = true
1222
+
1223
+ var newContentInset = textView.contentInset
1224
+ newContentInset.bottom = textView.frame.size.height-textViewHeight
1225
+ newContentInset.bottom -= textView.safeAreaInsets.bottom
1226
+
1227
+ if textView.contentInset != newContentInset {
1228
+ self.showLog("\(textFieldView) Old UITextView.contentInset: \(textView.contentInset) New UITextView.contentInset: \(newContentInset)")
1229
+
1230
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
1231
+
1232
+ textView.contentInset = newContentInset
1233
+ textView.scrollIndicatorInsets = newContentInset
1234
+ }, completion: { (_) -> Void in })
1235
+ }
1236
+ }
1237
+ }
1238
+
1239
+ // +Positive or zero.
1240
+ if moveUp >= 0 {
1241
+
1242
+ rootViewOrigin.y = max(rootViewOrigin.y - moveUp, min(0, -originalKbSize.height))
1243
+
1244
+ if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
1245
+ showLog("Moving Upward")
1246
+
1247
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
1248
+
1249
+ var rect = rootController.view.frame
1250
+ rect.origin = rootViewOrigin
1251
+ rootController.view.frame = rect
1252
+
1253
+ // Animating content if needed (Bug ID: #204)
1254
+ if self.layoutIfNeededOnUpdate {
1255
+ // Animating content (Bug ID: #160)
1256
+ rootController.view.setNeedsLayout()
1257
+ rootController.view.layoutIfNeeded()
1258
+ }
1259
+
1260
+ self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")
1261
+ })
1262
+ }
1263
+
1264
+ movedDistance = (topViewBeginOrigin.y-rootViewOrigin.y)
1265
+ } else { // -Negative
1266
+ let disturbDistance: CGFloat = rootViewOrigin.y-topViewBeginOrigin.y
1267
+
1268
+ // disturbDistance Negative = frame disturbed.
1269
+ // disturbDistance positive = frame not disturbed.
1270
+ if disturbDistance <= 0 {
1271
+
1272
+ rootViewOrigin.y -= max(moveUp, disturbDistance)
1273
+
1274
+ if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
1275
+ showLog("Moving Downward")
1276
+ // Setting adjusted rootViewRect
1277
+ // Setting adjusted rootViewRect
1278
+
1279
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
1280
+
1281
+ var rect = rootController.view.frame
1282
+ rect.origin = rootViewOrigin
1283
+ rootController.view.frame = rect
1284
+
1285
+ // Animating content if needed (Bug ID: #204)
1286
+ if self.layoutIfNeededOnUpdate {
1287
+ // Animating content (Bug ID: #160)
1288
+ rootController.view.setNeedsLayout()
1289
+ rootController.view.layoutIfNeeded()
1290
+ }
1291
+
1292
+ self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")
1293
+ })
1294
+ }
1295
+
1296
+ movedDistance = (topViewBeginOrigin.y-rootViewOrigin.y)
1297
+ }
1298
+ }
1299
+
1300
+ let elapsedTime = CACurrentMediaTime() - startTime
1301
+ showLog("<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
1302
+ }
1303
+ // swiftlint:enable function_body_length
1304
+
1305
+ internal func restorePosition() {
1306
+
1307
+ // Setting rootViewController frame to it's original position. // (Bug ID: #18)
1308
+ guard topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false, let rootViewController = rootViewController else {
1309
+ return
1310
+ }
1311
+
1312
+ if rootViewController.view.frame.origin.equalTo(self.topViewBeginOrigin) == false {
1313
+ // Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
1314
+ UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in
1315
+
1316
+ // Setting it's new frame
1317
+ var rect = rootViewController.view.frame
1318
+ rect.origin = self.topViewBeginOrigin
1319
+ rootViewController.view.frame = rect
1320
+
1321
+ // Animating content if needed (Bug ID: #204)
1322
+ if self.layoutIfNeededOnUpdate {
1323
+ // Animating content (Bug ID: #160)
1324
+ rootViewController.view.setNeedsLayout()
1325
+ rootViewController.view.layoutIfNeeded()
1326
+ }
1327
+
1328
+ self.showLog("Restoring \(rootViewController) origin to: \(self.topViewBeginOrigin)")
1329
+ })
1330
+ }
1331
+
1332
+ self.movedDistance = 0
1333
+
1334
+ if rootViewController.navigationController?.interactivePopGestureRecognizer?.state == .began {
1335
+ self.rootViewControllerWhilePopGestureRecognizerActive = rootViewController
1336
+ self.topViewBeginOriginWhilePopGestureRecognizerActive = self.topViewBeginOrigin
1337
+ }
1338
+
1339
+ self.rootViewController = nil
1340
+ }
1341
+ }