From 73d95814499b9745c0543cb82d26b20e530d4a77 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sun, 9 Nov 2025 14:05:07 +0100 Subject: [PATCH] Improve search field layout and responsiveness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SearchTextField improvements: - Add flexible width constraints (minWidth: 250, maxWidth: .infinity) for macOS - Restructure iOS layout to use HStack instead of ZStack for better alignment - Add invisible spacer to maintain consistent width when clear button is hidden - Adjust padding for more balanced appearance - Remove fixed width from fieldBorder to support flexible sizing SearchView improvements: - Wrap in GeometryReader to calculate available width dynamically - Add searchFieldWidth() helper to compute optimal search field width - Account for navigation buttons and internal padding - Apply dynamic width to both FocusableSearchTextField and SearchTextField 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Shared/Search/SearchTextField.swift | 74 +++++++++++++++-------------- Shared/Search/SearchView.swift | 62 ++++++++++++++++-------- 2 files changed, 81 insertions(+), 55 deletions(-) diff --git a/Shared/Search/SearchTextField.swift b/Shared/Search/SearchTextField.swift index 0b40cdec..ce07afb5 100644 --- a/Shared/Search/SearchTextField.swift +++ b/Shared/Search/SearchTextField.swift @@ -40,48 +40,50 @@ struct SearchTextField: View { } } } + .frame(minWidth: 250, idealWidth: 300, maxWidth: .infinity) .transaction { t in t.animation = nil } } #else var body: some View { - ZStack { - HStack { - HStack(spacing: 0) { - Image(systemName: "magnifyingglass") - .foregroundColor(.gray) - .padding(.leading, 5) - .padding(.trailing, 5) - .imageScale(.medium) + HStack(spacing: 0) { + Image(systemName: "magnifyingglass") + .foregroundColor(.gray) + .padding(.leading, 8) + .padding(.trailing, 5) + .imageScale(.medium) - TextField("Search...", text: $state.queryText) { - state.changeQuery { query in - query.query = state.queryText - navigation.hideKeyboard() - } - RecentsModel.shared.addQuery(state.queryText) - } - .disableAutocorrection(true) - .textFieldStyle(.plain) - .padding(.vertical, 7) - - if !state.queryText.isEmpty { - clearButton - .padding(.leading, 5) - .padding(.trailing, 5) - } + TextField("Search...", text: $state.queryText) { + state.changeQuery { query in + query.query = state.queryText + navigation.hideKeyboard() } - .background( - RoundedRectangle(cornerRadius: 8) - .fill(Color("SearchTextFieldBackground")) - ) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(Color(UIColor.secondarySystemBackground), lineWidth: 1) - ) - .frame(maxWidth: .infinity, alignment: .leading) + RecentsModel.shared.addQuery(state.queryText) + } + .disableAutocorrection(true) + .textFieldStyle(.plain) + .padding(.vertical, 7) + + if !state.queryText.isEmpty { + clearButton + .padding(.leading, 5) + .padding(.trailing, 8) + } else { + // Invisible spacer to maintain consistent width + clearButton + .opacity(0) + .padding(.leading, 5) + .padding(.trailing, 8) } - .padding(.horizontal, 0) } + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color("SearchTextFieldBackground")) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color(UIColor.secondarySystemBackground), lineWidth: 1) + ) + .padding(.horizontal, 16) .transaction { t in t.animation = nil } } #endif @@ -89,11 +91,11 @@ struct SearchTextField: View { private var fieldBorder: some View { RoundedRectangle(cornerRadius: 5, style: .continuous) .fill(Color.background) - .frame(width: 250, height: 27) + .frame(height: 27) .overlay( RoundedRectangle(cornerRadius: 5, style: .continuous) .stroke(Color.gray.opacity(0.4), lineWidth: 1) - .frame(width: 250, height: 27) + .frame(height: 27) ) } diff --git a/Shared/Search/SearchView.swift b/Shared/Search/SearchView.swift index 06414e30..0796eddf 100644 --- a/Shared/Search/SearchView.swift +++ b/Shared/Search/SearchView.swift @@ -41,29 +41,33 @@ struct SearchView: View { #if os(iOS) var body: some View { - VStack { + GeometryReader { geometry in VStack { - if accounts.app.supportsSearchSuggestions, state.query.query != state.queryText { - SearchSuggestions() - .opacity(state.queryText.isEmpty ? 0 : 1) - } else { - results + VStack { + if accounts.app.supportsSearchSuggestions, state.query.query != state.queryText { + SearchSuggestions() + .opacity(state.queryText.isEmpty ? 0 : 1) + } else { + results + } } + .backport + .scrollDismissesKeyboardInteractively() } - .backport - .scrollDismissesKeyboardInteractively() - } - .environment(\.listingStyle, searchListingStyle) - .toolbar { - ToolbarItem(placement: .principal) { - if #available(iOS 15, *) { - FocusableSearchTextField() - } else { - SearchTextField() + .environment(\.listingStyle, searchListingStyle) + .toolbar { + ToolbarItem(placement: .principal) { + if #available(iOS 15, *) { + FocusableSearchTextField() + .frame(width: searchFieldWidth(geometry.size.width)) + } else { + SearchTextField() + .frame(width: searchFieldWidth(geometry.size.width)) + } + } + ToolbarItem(placement: .navigationBarTrailing) { + searchMenu } - } - ToolbarItem(placement: .navigationBarTrailing) { - searchMenu } } .navigationBarTitleDisplayMode(.inline) @@ -645,6 +649,26 @@ struct SearchView: View { )) } + #if os(iOS) + private func searchFieldWidth(_ viewWidth: CGFloat) -> CGFloat { + // Base padding for internal SearchTextField padding (16pt each side = 32 total) + var totalDeduction: CGFloat = 32 + + // Add space for trailing menu button + totalDeduction += 44 + + // Add space for sidebar toggle button if in sidebar navigation style + if navigationStyle == .sidebar { + totalDeduction += 44 + } + + // Minimum width to ensure usability + let minWidth: CGFloat = 200 + + return max(minWidth, viewWidth - totalDeduction) + } + #endif + var shouldDisplayHeader: Bool { #if os(tvOS) !state.query.isEmpty