diff --git a/ElementX/Sources/Other/SwiftUI/Layout/ViewFrameReader.swift b/ElementX/Sources/Other/SwiftUI/Layout/ViewFrameReader.swift index f5cffdd98..18509b5fa 100644 --- a/ElementX/Sources/Other/SwiftUI/Layout/ViewFrameReader.swift +++ b/ElementX/Sources/Other/SwiftUI/Layout/ViewFrameReader.swift @@ -31,4 +31,15 @@ extension View { 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) -> some View { + onGeometryChange(for: CGFloat.self) { geometry in + geometry.size.width + } action: { newValue in + width.wrappedValue = newValue + } + } } diff --git a/ElementX/Sources/Other/SwiftUI/Search.swift b/ElementX/Sources/Other/SwiftUI/Search.swift index 9d5f360d4..9d9fa0de9 100644 --- a/ElementX/Sources/Other/SwiftUI/Search.swift +++ b/ElementX/Sources/Other/SwiftUI/Search.swift @@ -28,12 +28,14 @@ extension View { placeholder: String? = nil, hidesNavigationBar: Bool = false, showsCancelButton: Bool = true, - disablesInteractiveDismiss: Bool = false) -> some View { + disablesInteractiveDismiss: Bool = false, + accessibilityFocusOnStart: Bool = false) -> some View { modifier(SearchControllerModifier(searchQuery: query, placeholder: placeholder, hidesNavigationBar: hidesNavigationBar, showsCancelButton: showsCancelButton, - disablesInteractiveDismiss: disablesInteractiveDismiss)) + disablesInteractiveDismiss: disablesInteractiveDismiss, + accessibilityFocusOnStart: accessibilityFocusOnStart)) } } @@ -44,6 +46,7 @@ private struct SearchControllerModifier: ViewModifier { let hidesNavigationBar: Bool let showsCancelButton: Bool let disablesInteractiveDismiss: Bool + let accessibilityFocusOnStart: Bool /// Whether or not the user is currently searching. When ``automaticallyShowsCancelButton`` /// is `false`, checking if this value is `false` is pretty much meaningless. @@ -58,6 +61,7 @@ private struct SearchControllerModifier: ViewModifier { hidesNavigationBar: hidesNavigationBar, showsCancelButton: showsCancelButton, hidesSearchBarWhenScrolling: false, + accessibilityFocusOnStart: accessibilityFocusOnStart, isSearching: $isSearching) } .onDisappear { @@ -76,12 +80,19 @@ private struct SearchController: UIViewControllerRepresentable { let hidesNavigationBar: Bool let showsCancelButton: Bool let hidesSearchBarWhenScrolling: Bool + let accessibilityFocusOnStart: Bool @Binding var isSearching: Bool func makeUIViewController(context: Context) -> SearchInjectionViewController { - SearchInjectionViewController(searchController: context.coordinator.searchController, - hidesSearchBarWhenScrolling: hidesSearchBarWhenScrolling) + let controller = SearchInjectionViewController(searchController: context.coordinator.searchController, + 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) { diff --git a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift index 3dd2baab6..9a0135335 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift @@ -11,6 +11,8 @@ import SwiftUI struct InviteUsersScreen: View { @ObservedObject var context: InviteUsersScreenViewModel.Context + @State private var formWidth = CGFloat.zero + var showTopSection: Bool { !context.viewState.selectedUsers.isEmpty || context.viewState.isSearching } @@ -25,7 +27,8 @@ struct InviteUsersScreen: View { .searchController(query: $context.searchQuery, placeholder: L10n.commonSearchForSomeone, showsCancelButton: false, - disablesInteractiveDismiss: true) + disablesInteractiveDismiss: true, + accessibilityFocusOnStart: true) .compoundSearchField() .alert(item: $context.alertInfo) } @@ -33,34 +36,33 @@ struct InviteUsersScreen: View { // MARK: - Private private var mainContent: some View { - GeometryReader { proxy in - Form { - 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 - Section { - EmptyView() - } header: { - VStack(spacing: 16) { - selectedUsersSection - .textCase(.none) - .frame(width: proxy.size.width) - - if context.viewState.isSearching { - ProgressView() - .frame(maxWidth: .infinity, alignment: .center) - .listRowBackground(Color.clear) - } + Form { + 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 + Section { + EmptyView() + } header: { + VStack(spacing: 16) { + selectedUsersSection + .textCase(.none) + .frame(width: formWidth) + + if context.viewState.isSearching { + ProgressView() + .frame(maxWidth: .infinity, alignment: .center) + .listRowBackground(Color.clear) } } } - - if context.viewState.hasEmptySearchResults { - noResultsContent - } else { - usersSection - } + } + + if context.viewState.hasEmptySearchResults { + noResultsContent + } else { + usersSection } } + .readWidth($formWidth) } private var noResultsContent: some View {