diff --git a/dimensionhub/Views/Search/SearchBar.swift b/dimensionhub/Views/Search/SearchBar.swift new file mode 100644 index 0000000..ceb520c --- /dev/null +++ b/dimensionhub/Views/Search/SearchBar.swift @@ -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, isFirstResponder: Binding, 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 ?? "" + } + } + } + +} diff --git a/dimensionhub/Views/Search/SearchHistoryView.swift b/dimensionhub/Views/Search/SearchHistoryView.swift new file mode 100644 index 0000000..8f9537d --- /dev/null +++ b/dimensionhub/Views/Search/SearchHistoryView.swift @@ -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) + } + } + } +} diff --git a/dimensionhub/Views/Search/SearchView.swift b/dimensionhub/Views/Search/SearchView.swift index 913c91e..d0f2198 100644 --- a/dimensionhub/Views/Search/SearchView.swift +++ b/dimensionhub/Views/Search/SearchView.swift @@ -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() +//}