解决搜索页面的焦点获取问题

This commit is contained in:
anlicheng 2025-06-30 16:48:41 +08:00
parent 6323b1d2c9
commit b11362b912
3 changed files with 231 additions and 129 deletions

View File

@ -0,0 +1,137 @@
//
// SearchBar.swift
// dimensionhub
//
// Created by on 2025/6/30.
//
import SwiftUI
struct SearchBar: View {
//
@Binding var isFirstResponder: Bool
@Binding var searchText: String
var doSearch: () -> Void
var body: some View {
HStack(spacing: 8) {
ControlledTextField(text: $searchText, placeholder: "搜索..", isFirstResponder: $isFirstResponder, onCommit: doSearch)
.textFieldStyle(PlainTextFieldStyle())
.padding(8)
.background(Color(.systemGray6))
.cornerRadius(8)
.submitLabel(.search)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.keyboardType(.default)
Button {
if !searchText.isEmpty {
doSearch()
isFirstResponder = false
print("isFirstResponder is set false")
}
} label: {
Text("搜索")
.font(.system(size: 18))
.foregroundColor(.gray)
}
.padding(.trailing, 10)
.disabled(searchText.isEmpty)
}
}
}
struct ControlledTextField: UIViewRepresentable {
@Binding var text: String
var placeholder: String
@Binding var isFirstResponder: Bool
var onCommit: () -> Void
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.placeholder = placeholder
textField.delegate = context.coordinator
textField.returnKeyType = .search
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.clearButtonMode = .whileEditing
textField.borderStyle = .none
textField.keyboardType = .default
textField.font = UIFont.systemFont(ofSize: 17)
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
// 1.
if uiView.text != text {
context.coordinator.isUpdatingFromBinding = true
uiView.text = text
context.coordinator.isUpdatingFromBinding = false
}
print("call me update UIView: \(isFirstResponder)")
// 2.
if isFirstResponder {
if !uiView.isFirstResponder {
DispatchQueue.main.async {
uiView.becomeFirstResponder()
}
}
} else {
if uiView.isFirstResponder {
DispatchQueue.main.async {
uiView.resignFirstResponder()
}
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(text: $text, isFirstResponder: $isFirstResponder, onCommit: onCommit)
}
class Coordinator: NSObject, UITextFieldDelegate {
@Binding var text: String
@Binding var isFirstResponder: Bool
var onCommit: () -> Void
var isUpdatingFromBinding = false
init(text: Binding<String>, isFirstResponder: Binding<Bool>, onCommit: @escaping () -> Void) {
self._text = text
self._isFirstResponder = isFirstResponder
self.onCommit = onCommit
}
//
func textFieldDidBeginEditing(_ textField: UITextField) {
self.isFirstResponder = true
}
//
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
self.isFirstResponder = false
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
onCommit()
self.isFirstResponder = false
return true
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return true
}
func textFieldDidChangeSelection(_ textField: UITextField) {
//
if !isUpdatingFromBinding {
text = textField.text ?? ""
}
}
}
}

View File

@ -0,0 +1,70 @@
//
// SearchHistoryView1.swift
// dimensionhub
//
// Created by on 2025/6/30.
//
import SwiftUI
import SwiftData
struct SearchHistoryView: View {
@Query(sort: \SearchHistory.timestamp, order: .reverse) private var historyItems: [SearchHistory]
var body: some View {
VStack {
if historyItems.count > 5 {
ForEach(Array(0..<5), id: \.self) { idx in
SearchHistoryItemView(history: historyItems[idx]) {
print("click me")
}
}
Button {
//self.showHistoryNum = 5
} label: {
Text("查看更多历史")
.font(.system(size: 16))
.foregroundColor(.gray)
}
.buttonStyle(.plain)
} else {
ForEach(historyItems) { history in
SearchHistoryItemView(history: history) {
print("click me")
}
}
}
}
}
}
//
struct SearchHistoryItemView: View {
let history: SearchHistory
var onClick: () -> Void
var body: some View {
HStack(alignment: .center, spacing: 20) {
Image("lost_network")
.resizable()
.clipShape(Circle())
.clipped()
.frame(width: 25, height: 25)
Text(history.keyword)
.font(.system(size: 16))
.lineLimit(1)
.frame(width: 280, alignment: .leading)
Button(action: {
onClick()
}) {
Image(systemName: "xmark")
.font(.system(size: 14))
.foregroundColor(.gray)
}
}
}
}

View File

@ -6,7 +6,6 @@
// //
import SwiftUI import SwiftUI
import SwiftData
struct SearchView: View { struct SearchView: View {
@Environment(\.modelContext) var modelContext @Environment(\.modelContext) var modelContext
@ -16,7 +15,6 @@ struct SearchView: View {
@State var searchText: String = "" @State var searchText: String = ""
@State var searchModel = SearchModel() @State var searchModel = SearchModel()
@FocusState private var isFocused: Bool
@State private var isSearching = false @State private var isSearching = false
// //
@ -25,61 +23,13 @@ struct SearchView: View {
// //
@State private var showKeyboard: Bool = true @State private var showKeyboard: Bool = true
@State private var toolbarID = UUID()
//
@State private var isFocused: Bool = true
var body: some View { var body: some View {
VStack(spacing: 12) { VStack(spacing: 12) {
HStack(spacing: 12) {
Button(action: { dismiss() }) {
Image(systemName: "chevron.left")
.font(.system(size: 20, weight: .medium))
.foregroundColor(.black)
}
HStack(spacing: 8) {
TextField("搜索...", text: $searchText)
.focused($isFocused)
.textFieldStyle(PlainTextFieldStyle())
.padding(8)
.background(Color(.systemGray6))
.cornerRadius(8)
.submitLabel(.search)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.keyboardType(.default)
.onSubmit {
doSearch()
}
.overlay(alignment: .trailing) {
Button(action: {
searchText = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
.opacity(isSearching ? 1 : 0)
.disabled(!isSearching)
}
Button {
if !searchText.isEmpty {
doSearch()
isFocused = false //
}
} label: {
Text("搜索")
.font(.system(size: 18))
.foregroundColor(.gray)
}
.padding(.trailing, 10)
.disabled(searchText.isEmpty)
}
.onChange(of: searchText) {
isSearching = !searchText.trimmingCharacters(in: .whitespaces).isEmpty
}
}
.frame(height: 50)
.padding(.top, 12)
if showSearchResult { if showSearchResult {
if searchModel.dramaGroups.isEmpty { if searchModel.dramaGroups.isEmpty {
Spacer() Spacer()
@ -111,17 +61,25 @@ struct SearchView: View {
} }
} }
.padding(.top, 10)
.background(Color(.systemBackground))
.navigationTitle("") .navigationTitle("")
.navigationBarBackButtonHidden(true) .toolbar {
ToolbarItem(placement: .principal) {
SearchBar(isFirstResponder: $isFocused, searchText: $searchText) {
doSearch()
}
.id(toolbarID)
.onAppear {
print("searchBar appear")
}
}
}
.padding(.horizontal, 12) .padding(.horizontal, 12)
.ignoresSafeArea(.keyboard, edges: .bottom) // .ignoresSafeArea(.keyboard, edges: .bottom) //
.onAppear { .onAppear {
// print("main appear")
if showKeyboard { toolbarID = UUID()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isFocused = true
}
}
} }
} }
@ -182,70 +140,7 @@ struct SearchDramaGroupView: View {
} }
} }
extension SearchView { //
//#Preview {
struct SearchHistoryView1: View { // SearchView()
@Query(sort: \SearchHistory.timestamp, order: .reverse) private var historyItems: [SearchHistory] //}
var body: some View {
VStack {
if historyItems.count > 5 {
ForEach(Array(0..<5), id: \.self) { idx in
SearchHistoryItemView(history: historyItems[idx]) {
print("click me")
}
}
Button {
//self.showHistoryNum = 5
} label: {
Text("查看更多历史")
.font(.system(size: 16))
.foregroundColor(.gray)
}
.buttonStyle(.plain)
} else {
ForEach(historyItems) { history in
SearchHistoryItemView(history: history) {
print("click me")
}
}
}
}
}
}
//
struct SearchHistoryItemView: View {
let history: SearchHistory
var onClick: () -> Void
var body: some View {
HStack(alignment: .center, spacing: 20) {
Image("lost_network")
.resizable()
.clipShape(Circle())
.clipped()
.frame(width: 25, height: 25)
Text(history.keyword)
.font(.system(size: 16))
.lineLimit(1)
.frame(width: 280, alignment: .leading)
Button(action: {
onClick()
}) {
Image(systemName: "xmark")
.font(.system(size: 14))
.foregroundColor(.gray)
}
}
}
}
}
#Preview {
SearchView()
}