Skip to content

Commit

Permalink
Add flag to disable EpoxySwiftUIHostingController keyboard avoidance
Browse files Browse the repository at this point in the history
  • Loading branch information
sammygutierrez committed Jul 23, 2024
1 parent 158193d commit a82e6b9
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Made new layout-based SwiftUI cell rendering option the default.
- Fixed interaction of SwiftUI bars on visionOS
- Added flag for forcing layout on a hosted SwiftUI view after layout margins change
- Updated `EpoxySwiftUIHostingController` with a flag to disable its keyboard avoidance behavior

## [0.10.0](https://github.com/airbnb/epoxy-ios/compare/0.9.0...0.10.0) - 2023-06-29

Expand Down
52 changes: 51 additions & 1 deletion Sources/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ open class EpoxySwiftUIHostingController<Content: View>: UIHostingController<Con

/// Creates a `UIHostingController` that optionally ignores the `safeAreaInsets` when laying out
/// its contained `RootView`.
public convenience init(rootView: Content, ignoreSafeArea: Bool) {
public convenience init(rootView: Content, ignoreSafeArea: Bool, ignoreKeyboardAvoidance: Bool) {
self.init(rootView: rootView)

// We unfortunately need to call a private API to disable the safe area. We can also accomplish
Expand All @@ -28,6 +28,10 @@ open class EpoxySwiftUIHostingController<Content: View>: UIHostingController<Con
// available in this file in the `2d28b3181cca50b89618b54836f7a9b6e36ea78e` commit if this API
// no longer functions in future SwiftUI versions.
_disableSafeArea = ignoreSafeArea

if ignoreKeyboardAvoidance {
disableKeyboardAvoidance()
}
}

// MARK: Open
Expand All @@ -41,5 +45,51 @@ open class EpoxySwiftUIHostingController<Content: View>: UIHostingController<Con
// below, e.g. to draw highlight states in a `CollectionView`.
view.backgroundColor = .clear
}

/// Creates a dynamic subclass of this hosting controller's view that disables its keyboard
/// avoidance behavior.
/// Setting `safeAreaRegions` to `.container` also works but cannot be used since it's
/// supported on 16.4+ and we need to support older versions.
/// See [here](https://steipete.com/posts/disabling-keyboard-avoidance-in-swiftui-uihostingcontroller/) for more info.
private func disableKeyboardAvoidance() {
guard let viewClass = object_getClass(view) else {
EpoxyLogger.shared.assertionFailure("Unable to determine class of \(String(describing: view))")
return
}

let viewClassName = class_getName(viewClass)
let viewSubclassName = String(cString: viewClassName).appending("_IgnoresKeyboard")

// If subclass already exists, just set the class of `view` and return.
if let subclass = NSClassFromString(viewSubclassName) {
object_setClass(view, subclass)
return
}

guard let viewSubclassNameUTF8 = (viewSubclassName as NSString).utf8String else {
EpoxyLogger.shared.assertionFailure("Unable to get utf8String of \(viewSubclassName)")
return
}
guard let viewSubclass = objc_allocateClassPair(viewClass, viewSubclassNameUTF8, 0) else {
EpoxyLogger.shared.assertionFailure(
"Unable to subclass \(viewClass) with \(viewSubclassNameUTF8)")
return
}

let selector = NSSelectorFromString("keyboardWillShowWithNotification:")
guard let method = class_getInstanceMethod(viewClass, selector) else {
EpoxyLogger.shared.assertionFailure("Unable to locate method \(selector) on \(viewClass)")
objc_disposeClassPair(viewSubclass)
return
}

let keyboardWillShowOverride: @convention(block) (AnyObject, AnyObject) -> Void = { _, _ in }
let implementation = imp_implementationWithBlock(keyboardWillShowOverride)
let typeEncoding = method_getTypeEncoding(method)
class_addMethod(viewSubclass, selector, implementation, typeEncoding)

objc_registerClassPair(viewSubclass)
object_setClass(view, viewSubclass)
}
}
#endif
3 changes: 2 additions & 1 deletion Sources/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public final class EpoxySwiftUIHostingView<RootView: View>: UIView, EpoxyableVie
epoxyContent = EpoxyHostingContent(rootView: style.initialContent.rootView)
viewController = EpoxySwiftUIHostingController(
rootView: .init(content: epoxyContent, environment: epoxyEnvironment),
ignoreSafeArea: true)
ignoreSafeArea: true,
ignoreKeyboardAvoidance: true)

dataID = style.initialContent.dataID ?? DefaultDataID.noneProvided as AnyHashable
forceLayoutOnLayoutMarginsChange = style.forceLayoutOnLayoutMarginsChange
Expand Down

0 comments on commit a82e6b9

Please sign in to comment.