Forked from sammcode/KeyboardAnchoredContainer.swift
Created
August 29, 2024 00:27
-
-
Save cyrilzakka/d214998aa16cda7d3a870fcf9444fd85 to your computer and use it in GitHub Desktop.
A wrapper view with an input that sticks to the top of the keyboard during interactive dismissal.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import UIKit | |
struct TextInputView: View { | |
@State private var textFieldText = "" | |
var body: some View { | |
TextField("Type here", text: $textFieldText) | |
.textFieldStyle(RoundedBorderTextFieldStyle()) | |
.padding() | |
} | |
} | |
struct KeyboardAnchoredContainer<Content: View>: UIViewControllerRepresentable { | |
typealias ViewController = KeyboardAnchoredContainerViewController | |
@ViewBuilder var content: () -> Content | |
func makeUIViewController(context: Context) -> ViewController<Content> { | |
ViewController(content: content()) | |
} | |
func updateUIViewController(_ uiViewController: ViewController<Content>, context: Context) {} | |
} | |
class KeyboardAnchoredContainerViewController<Content: View>: UIViewController { | |
var content: Content | |
init(content: Content) { | |
self.content = content | |
super.init(nibName: nil, bundle: nil) | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func viewDidLoad() { | |
setUpContent() | |
setUpInput() | |
} | |
private func setUpContent() { | |
let hostingView = UIHostingController(rootView: content) | |
hostingView.view.translatesAutoresizingMaskIntoConstraints = false | |
view.addSubview(hostingView.view) | |
// Anchor content to top of keyboard layout | |
NSLayoutConstraint.activate([ | |
hostingView.view.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor), | |
hostingView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), | |
hostingView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), | |
hostingView.view.topAnchor.constraint(equalTo: view.topAnchor), | |
]) | |
} | |
private func setUpInput() { | |
let textInputView = TextInputView() | |
let hostingView = UIHostingController(rootView: textInputView) | |
view.addSubview(hostingView.view) | |
hostingView.view.translatesAutoresizingMaskIntoConstraints = false | |
// Anchor input to top of keyboard layout | |
NSLayoutConstraint.activate([ | |
hostingView.view.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor), | |
hostingView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), | |
hostingView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), | |
hostingView.view.heightAnchor.constraint(equalToConstant: 46) | |
]) | |
} | |
} | |
struct ContentView: View { | |
var body: some View { | |
KeyboardAnchoredContainer { | |
VStack { | |
ScrollView { | |
ForEach(0..<20) { index in | |
HStack { | |
Text("Item \(index + 1)") | |
.padding() | |
Spacer() | |
} | |
} | |
} | |
.scrollDismissesKeyboard(.interactively) | |
.scrollClipDisabled() | |
.defaultScrollAnchor(.bottom) | |
// Inset content slightly to avoid overlap with text input. | |
.contentMargins([.bottom], EdgeInsets(top: 0, leading: 0, bottom: 40, trailing: 0)) | |
} | |
} | |
// This is key, otherwise the text input taps into SwiftUI's keyboard safe area avoidance. | |
.ignoresSafeArea() | |
} | |
} | |
#Preview { | |
ContentView() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment