Created
October 25, 2025 16:58
-
-
Save zachgibson/ac8591934cdb37b05c281e0ecf023e50 to your computer and use it in GitHub Desktop.
This is mimics a keyboard toolbar, but uses safeAreaBar instead. This allows the bar to have custom layout properties (like bottom padding). iOS 26.0.1 has a bug where toolbar items with keyboard placement sit directly on the keyboard with no bottom padding whatsoever.
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 Combine | |
| import SwiftUI | |
| struct ContentView: View { | |
| @State var text = "" | |
| @State var keyboardObserver = KeyboardObserver() | |
| @Namespace var union | |
| var body: some View { | |
| VStack { | |
| TextField("Search", text: $text) | |
| .textFieldStyle(.roundedBorder) | |
| .padding() | |
| Spacer() | |
| } | |
| .safeAreaBar(edge: .bottom) { | |
| HStack { | |
| GlassEffectContainer { | |
| HStack(spacing: 0) { | |
| Button { | |
| print("bold pressed") | |
| } label: { | |
| Image(systemName: "bold") | |
| .font(.system(size: 22)) | |
| .padding(4) | |
| } | |
| .buttonStyle(.glass) | |
| .glassEffectUnion(id: "union", namespace: union) | |
| Button { | |
| print("italic pressed") | |
| } label: { | |
| Image(systemName: "italic") | |
| .font(.system(size: 22)) | |
| .padding(4) | |
| } | |
| .buttonStyle(.glass) | |
| .glassEffectUnion(id: "union", namespace: union) | |
| Button { | |
| print("underline pressed") | |
| } label: { | |
| Image(systemName: "underline") | |
| .font(.system(size: 22)) | |
| .padding(4) | |
| } | |
| .buttonStyle(.glass) | |
| .glassEffectUnion(id: "union", namespace: union) | |
| } | |
| } | |
| Spacer() | |
| Button { | |
| UIApplication.shared.sendAction( | |
| #selector(UIResponder.resignFirstResponder), | |
| to: nil, | |
| from: nil, | |
| for: nil | |
| ) | |
| } label: { | |
| Image(systemName: "keyboard.chevron.compact.down") | |
| .font(.system(size: 22)) | |
| .padding(4) | |
| } | |
| .buttonStyle(.glass) | |
| } | |
| .padding() | |
| .opacity(keyboardObserver.isVisible ? 1 : 0) | |
| } | |
| } | |
| } | |
| @Observable | |
| class KeyboardObserver { | |
| var isVisible: Bool = false | |
| var height: CGFloat = 0 | |
| private var cancellables = Set<AnyCancellable>() | |
| init() { | |
| NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification) | |
| .map { notif -> (Bool, CGFloat) in | |
| let endFrame = (notif.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect) ?? .zero | |
| return (true, endFrame.height) | |
| } | |
| .merge( | |
| with: NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification) | |
| .map { _ -> (Bool, CGFloat) in (false, 0) } | |
| ) | |
| .receive(on: RunLoop.main) | |
| .sink { [weak self] (visible, height) in | |
| self?.isVisible = visible | |
| self?.height = height | |
| } | |
| .store(in: &cancellables) | |
| } | |
| } | |
| #Preview { | |
| ContentView() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment