struct LiquidGlassSearchBar: View { @Stateprivatevar searchText = "" @Stateprivatevar isSearching = false @FocusStateprivatevar isFocused: Bool var body: some View { HStack { Image(systemName: "magnifyingglass") .foregroundColor(.secondary) TextField("搜索...", text: $searchText) .focused($isFocused) .onTapGesture { withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { isSearching = true } } } .padding(.horizontal, 15) .padding(.vertical, 12) .background( RoundedRectangle(cornerRadius: isSearching ? 25 : 15) .fill(.ultraThinMaterial) .glassEffect(.regular.tint(.blue.opacity(0.1))) ) .scaleEffect(isSearching ? 1.05 : 1.0) .animation(.spring(response: 0.4, dampingFraction: 0.7), value: isSearching) .onChange(of: isFocused) { focused in withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { isSearching = focused } } } }
struct SearchResult: Identifiable, Equatable { let id: UUID let title: String let subtitle: String let imageURL: URL } struct LiquidGlassSearchResultsView: View { @Stateprivatevar searchResults: [SearchResult] = [] @Namespaceprivatevar namespace init(searchResults: [SearchResult] = []) { self._searchResults = State(initialValue: searchResults) } var body: some View { GlassEffectContainer(spacing: 8) { ForEach(searchResults) { result in SearchResultCard(result: result) .glassEffect(.regular.tint(.white.opacity(0.1)).interactive()) .glassEffectID(result.id, in: namespace) .transition(.asymmetric( insertion: .move(edge: .trailing).combined(with: .opacity), removal: .move(edge: .leading).combined(with: .opacity) )) } } .animation(.spring(response: 0.6, dampingFraction: 0.8), value: searchResults) } } struct SearchResultCard: View { let result: SearchResult var body: some View { HStack { AsyncImage(url: result.imageURL) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { RoundedRectangle(cornerRadius: 8) .fill(.secondary.opacity(0.3)) } .frame(width: 50, height: 50) .clipped() .cornerRadius(8) VStack(alignment: .leading, spacing: 4) { Text(result.title) .font(.headline) Text(result.subtitle) .font(.subheadline) .foregroundColor(.secondary) .lineLimit(2) } Spacer() } .padding(.horizontal, 16) .padding(.vertical, 12) } }
struct LiquidGlassSearchSuggestions: View { @Stateprivatevar suggestions: [String] @Stateprivatevar selectedSuggestion: String? init(suggestions: [String], selectedSuggestion: String? = nil) { self.suggestions = suggestions self.selectedSuggestion = selectedSuggestion } var body: some View { LazyVStack(spacing: 0) { ForEach(suggestions, id: \.self) { suggestion in Button(action: { withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { selectedSuggestion = suggestion } }) { HStack { Image(systemName: "magnifyingglass") .foregroundColor(.secondary) Text(suggestion) .font(.body) Spacer() Image(systemName: "arrow.up.left") .foregroundColor(.secondary) .font(.caption) } .padding(.horizontal, 16) .padding(.vertical, 12) } } } .background( RoundedRectangle(cornerRadius: 16) .fill(.regularMaterial) .glassEffect(.regular.tint(.white.opacity(0.1))) ) } }
struct HistoryItemView: View { let text: String init(text: String) { self.text = text } var body: some View { HStack { Image(systemName: "clock") .foregroundColor(.secondary) Text(text) .font(.body) .lineLimit(1) Spacer() Image(systemName: "xmark.circle") .foregroundColor(.red) .onTapGesture { // Handle delete action print("Delete history item: \(text)") } } .padding(.horizontal, 16) .padding(.vertical, 10) } } struct LiquidGlassSearchHistory: View { @Stateprivatevar searchHistory: [String] = [ "SwiftUI 26", "Liquid Glass 26", "iOS 18", ] @Namespaceprivatevar historyNamespace var body: some View { VStack(alignment: .leading, spacing: 12) { HStack { Text("搜索历史") .font(.headline) Spacer() Button("清空") { withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) { searchHistory.removeAll() } } .foregroundColor(.blue) } GlassEffectContainer(spacing: 6) { ForEach(searchHistory, id: \.self) { historyItem in HistoryItemView(text: historyItem) .glassEffect(.regular.tint(.gray.opacity(0.1)).interactive()) .glassEffectID(historyItem, in: historyNamespace) } } .animation(.spring(response: 0.5, dampingFraction: 0.8), value: searchHistory) } .padding() .background( RoundedRectangle(cornerRadius: 20) .fill(.ultraThinMaterial) .glassEffect(.regular.tint(.white.opacity(0.05))) ) } }
struct LiquidGlassSearchView: View { @Stateprivatevar searchText = "" @Stateprivatevar searchResults: [SearchResult] = [] @Stateprivatevar searchSuggestions: [String] = ["SwiftUI", "Liquid Glass", "Swift", "iOS", "macOS", "Xcode", "WWDC", "SwiftUI 26", "Liquid Glass 26"] @FocusStateprivatevar isSearchFieldFocused: Bool var body: some View { NavigationView { VStack { // 搜索栏 searchBar .padding() // 根据状态显示不同内容 if isSearchFieldFocused && !searchSuggestions.isEmpty { LiquidGlassSearchSuggestions(suggestions: searchSuggestions) .padding(.horizontal, 16) } elseif !searchResults.isEmpty { LiquidGlassSearchResultsView(searchResults: searchResults) .padding(.horizontal, 16) } else { LiquidGlassSearchHistory() .padding(.horizontal, 16) } Spacer() } .background( LinearGradient( colors: [.blue.opacity(0.1), .purple.opacity(0.1)], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .navigationTitle("搜索") .background(.yellow) } } privatevar searchBar: some View { HStack { Image(systemName: "magnifyingglass") .foregroundColor(.secondary) TextField("搜索内容...", text: $searchText) .focused($isSearchFieldFocused) .onSubmit { performSearch() } if isSearchFieldFocused && !searchText.isEmpty { Button("取消") { searchText = "" isSearchFieldFocused = false } .foregroundColor(.blue) } } .padding() .background( RoundedRectangle(cornerRadius: isSearchFieldFocused ? 25 : 15) .fill(.ultraThinMaterial) .glassEffect(.regular.tint(.blue.opacity(0.1))) ) .scaleEffect(isSearchFieldFocused ? 1.02 : 1.0) .animation(.spring(response: 0.4, dampingFraction: 0.7), value: isSearchFieldFocused) } privatefunc performSearch() { // 执行搜索逻辑 isSearchFieldFocused = false // 模拟搜索结果 searchResults = [ SearchResult(id: UUID(), title: "SwiftUI 26", subtitle: "新特性和改进", imageURL: URL(string: "https://example.com/swiftui26.png")!), SearchResult(id: UUID(), title: "Liquid Glass 26", subtitle: "探索液态玻璃效果", imageURL: URL(string: "https://example.com/liquidglass26.png")!) ] } }主要就是把各个组件按需显示,重点是保持动画的流畅性。
// 这样做比较好 GlassEffectContainer(spacing: 8) { ForEach(items) { item in ItemView(item: item) .glassEffect(.regular.tint(.white.opacity(0.1))) } }2. 动画要用 spring
.animation(.spring(response: 0.5, dampingFraction: 0.8), value: isSearching)3. 兼容性问题
@available(iOS 26.0, *) private var liquidGlassSearchBar: some View { // Liquid Glass 实现 } private var fallbackSearchBar: some View { // 传统搜索框 }适用场景
.笔记、文档类 App
.spring 动画 - 让过渡更自然,像液体一样流畅