added argument to voice over on the search bar automatically

- also removed the geomtry reader in favour of a `readWidth` function
This commit is contained in:
Mauro Romito
2025-05-08 16:26:33 +02:00
committed by Mauro
parent 489f784640
commit 36242bc271
3 changed files with 52 additions and 28 deletions
@@ -31,4 +31,15 @@ extension View {
height.wrappedValue = newValue height.wrappedValue = newValue
} }
} }
/// Reads the width of the view and stores it in the `width` binding.
/// - Parameters:
/// - width: a `CGFloat` binding
func readWidth(_ width: Binding<CGFloat>) -> some View {
onGeometryChange(for: CGFloat.self) { geometry in
geometry.size.width
} action: { newValue in
width.wrappedValue = newValue
}
}
} }
+15 -4
View File
@@ -28,12 +28,14 @@ extension View {
placeholder: String? = nil, placeholder: String? = nil,
hidesNavigationBar: Bool = false, hidesNavigationBar: Bool = false,
showsCancelButton: Bool = true, showsCancelButton: Bool = true,
disablesInteractiveDismiss: Bool = false) -> some View { disablesInteractiveDismiss: Bool = false,
accessibilityFocusOnStart: Bool = false) -> some View {
modifier(SearchControllerModifier(searchQuery: query, modifier(SearchControllerModifier(searchQuery: query,
placeholder: placeholder, placeholder: placeholder,
hidesNavigationBar: hidesNavigationBar, hidesNavigationBar: hidesNavigationBar,
showsCancelButton: showsCancelButton, showsCancelButton: showsCancelButton,
disablesInteractiveDismiss: disablesInteractiveDismiss)) disablesInteractiveDismiss: disablesInteractiveDismiss,
accessibilityFocusOnStart: accessibilityFocusOnStart))
} }
} }
@@ -44,6 +46,7 @@ private struct SearchControllerModifier: ViewModifier {
let hidesNavigationBar: Bool let hidesNavigationBar: Bool
let showsCancelButton: Bool let showsCancelButton: Bool
let disablesInteractiveDismiss: Bool let disablesInteractiveDismiss: Bool
let accessibilityFocusOnStart: Bool
/// Whether or not the user is currently searching. When ``automaticallyShowsCancelButton`` /// Whether or not the user is currently searching. When ``automaticallyShowsCancelButton``
/// is `false`, checking if this value is `false` is pretty much meaningless. /// is `false`, checking if this value is `false` is pretty much meaningless.
@@ -58,6 +61,7 @@ private struct SearchControllerModifier: ViewModifier {
hidesNavigationBar: hidesNavigationBar, hidesNavigationBar: hidesNavigationBar,
showsCancelButton: showsCancelButton, showsCancelButton: showsCancelButton,
hidesSearchBarWhenScrolling: false, hidesSearchBarWhenScrolling: false,
accessibilityFocusOnStart: accessibilityFocusOnStart,
isSearching: $isSearching) isSearching: $isSearching)
} }
.onDisappear { .onDisappear {
@@ -76,12 +80,19 @@ private struct SearchController: UIViewControllerRepresentable {
let hidesNavigationBar: Bool let hidesNavigationBar: Bool
let showsCancelButton: Bool let showsCancelButton: Bool
let hidesSearchBarWhenScrolling: Bool let hidesSearchBarWhenScrolling: Bool
let accessibilityFocusOnStart: Bool
@Binding var isSearching: Bool @Binding var isSearching: Bool
func makeUIViewController(context: Context) -> SearchInjectionViewController { func makeUIViewController(context: Context) -> SearchInjectionViewController {
SearchInjectionViewController(searchController: context.coordinator.searchController, let controller = SearchInjectionViewController(searchController: context.coordinator.searchController,
hidesSearchBarWhenScrolling: hidesSearchBarWhenScrolling) hidesSearchBarWhenScrolling: hidesSearchBarWhenScrolling)
if accessibilityFocusOnStart {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
UIAccessibility.post(notification: .screenChanged, argument: controller.searchController.searchBar)
}
}
return controller
} }
func updateUIViewController(_ viewController: SearchInjectionViewController, context: Context) { func updateUIViewController(_ viewController: SearchInjectionViewController, context: Context) {
@@ -11,6 +11,8 @@ import SwiftUI
struct InviteUsersScreen: View { struct InviteUsersScreen: View {
@ObservedObject var context: InviteUsersScreenViewModel.Context @ObservedObject var context: InviteUsersScreenViewModel.Context
@State private var formWidth = CGFloat.zero
var showTopSection: Bool { var showTopSection: Bool {
!context.viewState.selectedUsers.isEmpty || context.viewState.isSearching !context.viewState.selectedUsers.isEmpty || context.viewState.isSearching
} }
@@ -25,7 +27,8 @@ struct InviteUsersScreen: View {
.searchController(query: $context.searchQuery, .searchController(query: $context.searchQuery,
placeholder: L10n.commonSearchForSomeone, placeholder: L10n.commonSearchForSomeone,
showsCancelButton: false, showsCancelButton: false,
disablesInteractiveDismiss: true) disablesInteractiveDismiss: true,
accessibilityFocusOnStart: true)
.compoundSearchField() .compoundSearchField()
.alert(item: $context.alertInfo) .alert(item: $context.alertInfo)
} }
@@ -33,34 +36,33 @@ struct InviteUsersScreen: View {
// MARK: - Private // MARK: - Private
private var mainContent: some View { private var mainContent: some View {
GeometryReader { proxy in Form {
Form { if showTopSection {
if showTopSection { // this is a fix for having the carousel not clipped, and inside the form, so when the search is dismissed, it wont break the design
// this is a fix for having the carousel not clipped, and inside the form, so when the search is dismissed, it wont break the design Section {
Section { EmptyView()
EmptyView() } header: {
} header: { VStack(spacing: 16) {
VStack(spacing: 16) { selectedUsersSection
selectedUsersSection .textCase(.none)
.textCase(.none) .frame(width: formWidth)
.frame(width: proxy.size.width)
if context.viewState.isSearching { if context.viewState.isSearching {
ProgressView() ProgressView()
.frame(maxWidth: .infinity, alignment: .center) .frame(maxWidth: .infinity, alignment: .center)
.listRowBackground(Color.clear) .listRowBackground(Color.clear)
}
} }
} }
} }
}
if context.viewState.hasEmptySearchResults { if context.viewState.hasEmptySearchResults {
noResultsContent noResultsContent
} else { } else {
usersSection usersSection
}
} }
} }
.readWidth($formWidth)
} }
private var noResultsContent: some View { private var noResultsContent: some View {