From 3e3eb9087408b5333075bb2720563ff2547f0c3d Mon Sep 17 00:00:00 2001 From: Cay Zhang <13341339+Cay-Zhang@users.noreply.github.com> Date: Mon, 17 Jul 2023 20:01:07 -0500 Subject: [PATCH] Scroll focused query item view into view when keyboard shows --- Shared/ContentView.swift | 62 +++++++++++++++++++++------------------- Shared/QueryEditor.swift | 21 ++++++++++++-- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/Shared/ContentView.swift b/Shared/ContentView.swift index f44ae47a..53777a41 100644 --- a/Shared/ContentView.swift +++ b/Shared/ContentView.swift @@ -23,30 +23,32 @@ struct ContentView: View { var body: some View { HStack(spacing: 0) { NavigationView { - ScrollView { - LazyVStack(spacing: 16) { - if isOnboarding { - OnboardingView(isRuleManagerPresented: $isRuleManagerPresented) - } else if let error = viewModel.error { - ErrorView(error: error, editOriginalURL: { withAnimation { viewModel.bottomBarViewModel.isEditing = true } }) - } else if viewModel.originalURL == nil { - #if !ACTION_EXTENSION - StartView(isRuleManagerPresented: $isRuleManagerPresented) - #endif - } else { - pageFeeds - - rsshubFeeds - - if (viewModel.rssFeeds?.isEmpty ?? false) && (viewModel.rsshubFeeds?.isEmpty ?? false) && !viewModel.isProcessing { - NothingFoundView(url: viewModel.originalURL, isRuleManagerPresented: $isRuleManagerPresented) - } - - if horizontalSizeClass != .regular { - rsshubParameters + ScrollViewReader { proxy in + ScrollView { + LazyVStack(spacing: 16) { + if isOnboarding { + OnboardingView(isRuleManagerPresented: $isRuleManagerPresented) + } else if let error = viewModel.error { + ErrorView(error: error, editOriginalURL: { withAnimation { viewModel.bottomBarViewModel.isEditing = true } }) + } else if viewModel.originalURL == nil { + #if !ACTION_EXTENSION + StartView(isRuleManagerPresented: $isRuleManagerPresented) + #endif + } else { + pageFeeds + + rsshubFeeds + + if (viewModel.rssFeeds?.isEmpty ?? false) && (viewModel.rsshubFeeds?.isEmpty ?? false) && !viewModel.isProcessing { + NothingFoundView(url: viewModel.originalURL, isRuleManagerPresented: $isRuleManagerPresented) + } + + if horizontalSizeClass != .regular { + rsshubParameters(scrollViewProxy: proxy) + } } - } - }.padding(16) + }.padding(16) + } }.navigationTitle("RSSBud") .toolbar(content: toolbarContent) .environment(\.isEnabled, !viewModel.isFocusedOnBottomBar) @@ -64,10 +66,12 @@ struct ContentView: View { Divider().ignoresSafeArea(.keyboard, edges: .vertical) NavigationView { - ScrollView { - rsshubParameters - .padding(16) - .navigationTitle(Text("Parameters")) + ScrollViewReader { proxy in + ScrollView { + rsshubParameters(scrollViewProxy: proxy) + .padding(16) + .navigationTitle(Text("Parameters")) + } } } } @@ -121,10 +125,10 @@ struct ContentView: View { } } - @ViewBuilder var rsshubParameters: some View { + @ViewBuilder func rsshubParameters(scrollViewProxy: ScrollViewProxy) -> some View { if let feeds = viewModel.rsshubFeeds, !feeds.isEmpty { ExpandableSection(viewModel: viewModel.rsshubParameterSectionViewModel) { - QueryEditor(queryItems: $viewModel.queryItems) + QueryEditor(queryItems: $viewModel.queryItems, scrollViewProxy: scrollViewProxy) } label: { Text("Content Section RSSHub Parameters") } diff --git a/Shared/QueryEditor.swift b/Shared/QueryEditor.swift index 3e49bf29..8fc39c56 100644 --- a/Shared/QueryEditor.swift +++ b/Shared/QueryEditor.swift @@ -8,9 +8,10 @@ import SwiftUI struct QueryEditor: View { - @Binding var queryItems: [URLQueryItem] - + var scrollViewProxy: ScrollViewProxy? = nil + @FocusState var focusedQueryItemName: String? + @Environment(\.customOpenURLAction) var openURL var body: some View { @@ -33,12 +34,22 @@ struct QueryEditor: View { } } ) { groupBoxContent(forQueryItem: item) } + .id(item.name) .contextMenu { Button(action: removeQueryItemAction(name: item.name)) { Label("Delete", systemImage: "trash.fill") } } } + }.onChange(of: focusedQueryItemName) { name in + if let name { + Task { @MainActor in + try await Task.sleep(nanoseconds: 150_000_000) // 0.15s + withAnimation(BottomBar.transitionAnimation.speed(2)) { + scrollViewProxy?.scrollTo(name) + } + } + } } } @@ -80,6 +91,7 @@ struct QueryEditor: View { case "filter_time": TextField("Time Interval", text: queryItemValueBinding) .keyboardType(.decimalPad) + .focused($focusedQueryItemName, equals: item.name) case "mode": Toggle("Enabled", isOn: Binding(get: { queryItemValueBinding.wrappedValue == "fulltext" @@ -88,6 +100,7 @@ struct QueryEditor: View { })) case "opencc": TextField("OpenCC Configuration", text: queryItemValueBinding) + .focused($focusedQueryItemName, equals: item.name) case "filter_case_sensitive": Toggle("Sensitive", isOn: Binding(get: { queryItemValueBinding.wrappedValue != "false" @@ -97,10 +110,12 @@ struct QueryEditor: View { case "limit": TextField("Max Entry Count", text: queryItemValueBinding) .keyboardType(.numberPad) + .focused($focusedQueryItemName, equals: item.name) case "tgiv": TextField("Template Hash", text: queryItemValueBinding) .disableAutocorrection(true) .autocapitalization(.none) + .focused($focusedQueryItemName, equals: item.name) case "scihub": Toggle("Enabled", isOn: Binding(get: { !queryItemValueBinding.wrappedValue.isEmpty @@ -109,8 +124,10 @@ struct QueryEditor: View { })) case _ where item.name.starts(with: "filter"): TextField("Regular Expression", text: queryItemValueBinding) + .focused($focusedQueryItemName, equals: item.name) default: TextField("Value", text: queryItemValueBinding) + .focused($focusedQueryItemName, equals: item.name) } }