解决搜索页面的焦点获取问题
This commit is contained in:
parent
6323b1d2c9
commit
b11362b912
137
dimensionhub/Views/Search/SearchBar.swift
Normal file
137
dimensionhub/Views/Search/SearchBar.swift
Normal 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 ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
70
dimensionhub/Views/Search/SearchHistoryView.swift
Normal file
70
dimensionhub/Views/Search/SearchHistoryView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,6 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct SearchView: View {
|
||||
@Environment(\.modelContext) var modelContext
|
||||
@ -16,7 +15,6 @@ struct SearchView: View {
|
||||
@State var searchText: String = ""
|
||||
@State var searchModel = SearchModel()
|
||||
|
||||
@FocusState private var isFocused: Bool
|
||||
@State private var isSearching = false
|
||||
|
||||
// 是否显示搜索结果
|
||||
@ -25,61 +23,13 @@ struct SearchView: View {
|
||||
// 控制是否需要显示键盘
|
||||
@State private var showKeyboard: Bool = true
|
||||
|
||||
@State private var toolbarID = UUID()
|
||||
|
||||
// 键盘焦点的控制
|
||||
@State private var isFocused: Bool = true
|
||||
|
||||
var body: some View {
|
||||
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 searchModel.dramaGroups.isEmpty {
|
||||
Spacer()
|
||||
@ -111,17 +61,25 @@ struct SearchView: View {
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.background(Color(.systemBackground))
|
||||
.navigationTitle("")
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .principal) {
|
||||
SearchBar(isFirstResponder: $isFocused, searchText: $searchText) {
|
||||
doSearch()
|
||||
}
|
||||
.id(toolbarID)
|
||||
.onAppear {
|
||||
print("searchBar appear")
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.ignoresSafeArea(.keyboard, edges: .bottom) // 避免键盘遮挡
|
||||
.onAppear {
|
||||
// 避免过早触发系统输入错误
|
||||
if showKeyboard {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
isFocused = true
|
||||
}
|
||||
}
|
||||
print("main appear")
|
||||
toolbarID = UUID()
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,70 +140,7 @@ struct SearchDramaGroupView: View {
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchView {
|
||||
|
||||
struct SearchHistoryView1: 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SearchView()
|
||||
}
|
||||
//
|
||||
//#Preview {
|
||||
// SearchView()
|
||||
//}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user