@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.
- package/$package.json +30 -36
- package/android/app/build.gradle +56 -73
- package/android/app/src/debug/java/com/xrn/template/ReactNativeFlipper.java +23 -44
- package/android/app/src/main/assets/xrn-manifest.json +1 -1
- package/android/app/src/main/java/com/xrn/template/MainApplication.kt +54 -65
- package/android/app/src/main/java/com/xrn/template/multibundle/CodePushBundleActivity.kt +65 -0
- package/android/app/src/main/java/com/xrn/template/multibundle/CodePushJSBundleLoader.kt +18 -0
- package/android/app/src/main/java/com/xrn/template/multibundle/CodePushUtils.kt +263 -0
- package/android/app/src/main/java/com/xrn/template/multibundle/XBundleTool.kt +9 -0
- package/android/app/src/main/java/com/xrn/template/multibundle/XRNReactHostDelegate.kt +56 -0
- package/android/app/src/main/java/com/xrn/template/navigation/XBundleActivity.kt +10 -7
- package/android/app/src/main/res/raw/bundle_config.json +2 -6
- package/android/build.gradle +40 -39
- package/android/buildSrc/build.gradle.kts +15 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +3 -1
- package/android/gradle.properties +17 -10
- package/android/settings.gradle +40 -12
- package/harmony/.clang-tidy +1 -1
- package/harmony/.clangd +3 -1
- package/harmony/AppScope/app.json5 +1 -1
- package/harmony/build-profile.json5 +8 -8
- package/harmony/entry/oh-package-lock.json5 +283 -262
- package/harmony/entry/oh-package.json5 +35 -36
- package/harmony/entry/src/main/cpp/CMakeLists.txt +37 -17
- package/harmony/entry/src/main/cpp/PackageProvider.cpp +20 -2
- package/harmony/entry/src/main/cpp/generated/BundleNavigation.cpp +3 -0
- package/harmony/entry/src/main/cpp/generated/RNOHGeneratedPackage.h +31 -87
- package/harmony/entry/src/main/cpp/generated/RTNCodePush.cpp +7 -1
- package/harmony/entry/src/main/cpp/generated/XRNBundleModule.cpp +9 -0
- package/harmony/entry/src/main/cpp/generated/XRNDebugToolsModule.cpp +10 -0
- package/harmony/entry/src/main/cpp/generated/{ImageResizer.cpp → XRNImageView.cpp} +6 -3
- package/harmony/entry/src/main/cpp/generated/{ImageResizer.h → XRNImageView.h} +2 -2
- package/harmony/entry/src/main/cpp/generated/{RNCWebViewComponentDescriptor.h → XRNImageViewComponentDescriptor.h} +11 -10
- package/harmony/entry/src/main/cpp/generated/XRNImageViewJSIBinder.h +58 -0
- package/harmony/entry/src/main/cpp/generated/XRNKeyboard.cpp +23 -0
- package/harmony/entry/src/main/cpp/generated/{RNLocalize.h → XRNKeyboard.h} +2 -2
- package/harmony/entry/src/main/cpp/generated/XRNNavigation.cpp +5 -0
- package/harmony/entry/src/main/cpp/generated/{RNCWebViewModule.cpp → XRNNetworkModule.cpp} +2 -4
- package/harmony/entry/src/main/cpp/generated/{RNCWebViewModule.h → XRNNetworkModule.h} +2 -2
- package/harmony/entry/src/main/ets/BundleHelper.ets +41 -45
- package/harmony/entry/src/main/ets/MainAbilityStage.ets +9 -4
- package/harmony/entry/src/main/ets/entryability/EntryAbility.ets +1 -1
- package/harmony/entry/src/main/ets/navDestination/BizModuleContainer.ets +2 -2
- package/harmony/entry/src/main/ets/navDestination/MainModuleContainer.ets +2 -2
- package/harmony/entry/src/main/ets/navDestination/Splash.ets +1 -2
- package/harmony/entry/src/main/ets/pages/Index.ets +4 -4
- package/harmony/entry/src/main/resources/rawfile/bundle_config.json5 +2 -6
- package/harmony/entry/src/main/resources/rawfile/xrn-manifest.json +1 -0
- package/harmony/oh-package-lock.json5 +2 -1
- package/harmony/oh-package.json5 +12 -4
- package/index.ts +0 -0
- package/ios/BundleEventCenter/Hooks/XRNCustomKeyborad+XTHooks.h +16 -0
- package/ios/BundleEventCenter/Hooks/XRNCustomKeyborad+XTHooks.mm +40 -0
- package/ios/BundleEventCenter/Hooks/XTIQKeyboardManagerBridge.h +15 -0
- package/ios/BundleEventCenter/Hooks/XTIQKeyboardManagerBridge.m +22 -0
- package/ios/BundleEventCenter/XTBundleProvider.m +98 -72
- package/ios/BundleEventCenter/XTBundleViewControllerFactory.m +4 -15
- package/ios/BundleEventCenter/bundleController/XTBundleViewController.mm +43 -0
- package/ios/BundleEventCenter/bundleController/XTMainBundleViewController.mm +48 -0
- package/ios/BundleEventCenter/bundleController/xtBundles.plist +1 -1
- package/ios/Podfile +234 -151
- package/ios/Podfile.lock +2810 -594
- package/ios/RNDebug/DebugPlugin/XTPluginManage.m +5 -5
- package/ios/RNDebug/MotroTools/XTMetroAutoConnector.h +21 -0
- package/ios/RNDebug/MotroTools/XTMetroAutoConnector.m +124 -0
- package/ios/RNDebug/MotroTools/XTReloadCommandHelper.h +30 -0
- package/ios/RNDebug/MotroTools/XTReloadCommandHelper.mm +128 -0
- package/ios/RNDebug/Views/XTScanQRPlugin.swift +9 -4
- package/ios/XRNTemplate/AppDelegate.h +3 -3
- package/ios/XRNTemplate/AppDelegate.mm +40 -102
- package/ios/XRNTemplate/Info.plist +4 -4
- package/ios/XRNTemplate/PrivacyInfo.xcprivacy +48 -0
- package/ios/XRNTemplate-Bridging-Header.h +2 -1
- package/ios/XRNTemplate.xcodeproj/project.pbxproj +68 -77
- package/ios/ios_patches/fixed_glog_config.h +195 -0
- package/ios/ios_patches/xt_IQKeyboardManager+Position.swift +1341 -0
- package/ios/ios_patches/xt_IQKeyboardManager.swift +454 -0
- package/ios/subBundles/.gitkeep +0 -0
- package/main-bundle/babel.config.js +2 -2
- package/main-bundle/package.json +11 -20
- package/main-bundle/yarn.lock +14655 -9651
- package/metro.config.js +21 -0
- package/package.json +2 -2
- package/react-native.config.js +3 -45
- package/src/.gitkeep +0 -0
- package/sub-bundle/babel.config.js +2 -2
- package/sub-bundle/package.json +11 -20
- package/sub-bundle/react-native.config.js +4 -0
- package/sub-bundle/yarn.lock +14655 -9651
- package/xrn.config.json +12 -12
- package/android/app/src/main/java/com/xrn/template/multibundle/XDevSupportParams.kt +0 -6
- package/android/app/src/main/java/com/xrn/template/multibundle/XRNHostParams.kt +0 -82
- package/harmony/entry/src/main/cpp/generated/ConfigNativeModule.cpp +0 -21
- package/harmony/entry/src/main/cpp/generated/ConfigNativeModule.h +0 -21
- package/harmony/entry/src/main/cpp/generated/GetRandomValuesNativeModule.cpp +0 -21
- package/harmony/entry/src/main/cpp/generated/GetRandomValuesNativeModule.h +0 -21
- package/harmony/entry/src/main/cpp/generated/NativeHarmonyTouchId.cpp +0 -22
- package/harmony/entry/src/main/cpp/generated/NativeHarmonyTouchId.h +0 -21
- package/harmony/entry/src/main/cpp/generated/OreitationLockerNativeModule.cpp +0 -31
- package/harmony/entry/src/main/cpp/generated/OreitationLockerNativeModule.h +0 -21
- package/harmony/entry/src/main/cpp/generated/RNCCameraRoll.cpp +0 -28
- package/harmony/entry/src/main/cpp/generated/RNCCameraRoll.h +0 -21
- package/harmony/entry/src/main/cpp/generated/RNCCameraRollPermission.cpp +0 -26
- package/harmony/entry/src/main/cpp/generated/RNCCameraRollPermission.h +0 -21
- package/harmony/entry/src/main/cpp/generated/RNCWebViewJSIBinder.h +0 -120
- package/harmony/entry/src/main/cpp/generated/RNDeviceInfo.cpp +0 -164
- package/harmony/entry/src/main/cpp/generated/RNDeviceInfo.h +0 -21
- package/harmony/entry/src/main/cpp/generated/RNDocumentPicker.cpp +0 -24
- package/harmony/entry/src/main/cpp/generated/RNDocumentPicker.h +0 -21
- package/harmony/entry/src/main/cpp/generated/RNGestureHandlerButtonComponentDescriptor.h +0 -43
- package/harmony/entry/src/main/cpp/generated/RNGestureHandlerButtonJSIBinder.h +0 -38
- package/harmony/entry/src/main/cpp/generated/RNGestureHandlerModule.cpp +0 -28
- package/harmony/entry/src/main/cpp/generated/RNGestureHandlerModule.h +0 -21
- package/harmony/entry/src/main/cpp/generated/RNGestureHandlerRootViewComponentDescriptor.h +0 -43
- package/harmony/entry/src/main/cpp/generated/RNGestureHandlerRootViewJSIBinder.h +0 -31
- package/harmony/entry/src/main/cpp/generated/RNLocalize.cpp +0 -31
- package/harmony/entry/src/main/cpp/generated/ReactNativeBlobUtil.cpp +0 -51
- package/harmony/entry/src/main/cpp/generated/ReactNativeBlobUtil.h +0 -21
- package/harmony/entry/src/main/cpp/generated/XRNAssetLoaderModule.cpp +0 -21
- package/harmony/entry/src/main/cpp/generated/XRNAssetLoaderModule.h +0 -21
- package/harmony/entry/src/main/cpp/generated/XRNFileModule.cpp +0 -22
- package/harmony/entry/src/main/cpp/generated/XRNFileModule.h +0 -21
- package/ios/BundleEventCenter/bundleController/XTBundleViewController.m +0 -35
- 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
|
+
}
|